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

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
// 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
# found in the LICENSE file.
@ -10,7 +10,11 @@ group("mojo_python_unittests") {
"run_all_python_unittests.py",
"//testing/scripts/run_isolated_script_test.py",
]
deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
deps = [
"//mojo/public/tools/bindings:tests",
"//mojo/public/tools/mojom:tests",
"//mojo/public/tools/mojom/mojom:tests",
]
data_deps = [
"//testing:test_scripts_shared",
"//third_party/catapult/third_party/typ/",

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
# found in the LICENSE file.
import("//build/config/python.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//third_party/jinja2/jinja2.gni")
# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
python2_action("precompile_templates") {
action("precompile_templates") {
sources = mojom_generator_sources
sources += [
"$mojom_generator_root/generators/cpp_templates/cpp_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/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_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_proxy_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_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-import-headers.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.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
@ -65,9 +67,6 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/java_templates/struct.java.tmpl",
"$mojom_generator_root/generators/java_templates/union.java.tmpl",
"$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
"$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
@ -93,8 +92,11 @@ python2_action("precompile_templates") {
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/mojom.tmpl",
"$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
]
script = mojom_generator_script
@ -102,8 +104,8 @@ python2_action("precompile_templates") {
outputs = [
"$target_gen_dir/cpp_templates.zip",
"$target_gen_dir/java_templates.zip",
"$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/js_templates.zip",
"$target_gen_dir/mojolpm_templates.zip",
"$target_gen_dir/ts_templates.zip",
]
args = [
@ -113,3 +115,17 @@ python2_action("precompile_templates") {
"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.
| `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.
| `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<message_pipe>` | Generic message pipe handle.
| `handle<shared_buffer>` | Shared buffer handle.
@ -188,8 +188,8 @@ struct StringPair {
};
enum AnEnum {
YES,
NO
kYes,
kNo
};
interface SampleInterface {
@ -209,7 +209,7 @@ struct AllTheThings {
uint64 unsigned_64bit_value;
float float_value_32bit;
double float_value_64bit;
AnEnum enum_value = AnEnum.YES;
AnEnum enum_value = AnEnum.kYes;
// Strings may be nullable.
string? maybe_a_string_maybe_not;
@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:
module business.mojom;
enum Department {
SALES = 0,
DEV,
kSales = 0,
kDev,
};
struct Employee {
enum Type {
FULL_TIME,
PART_TIME,
kFullTime,
kPartTime,
};
Type type;
@ -315,6 +315,9 @@ struct Employee {
};
```
C++ constant-style enum value names are preferred as specified in the
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
Similar to C-style enums, individual values may be explicitly assigned within an
enum definition. By default, values are based at zero and increment by
1 sequentially.
@ -336,8 +339,8 @@ struct Employee {
const uint64 kInvalidId = 0;
enum Type {
FULL_TIME,
PART_TIME,
kFullTime,
kPartTime,
};
uint64 id = kInvalidId;
@ -348,6 +351,37 @@ struct Employee {
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).
### 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
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.
* **`[Default]`**:
The `Default` attribute may be used to specify an enumerator value that
will be used if an `Extensible` enumeration does not deserialize to a known
value on the receiver side, i.e. the sender is using a newer version of the
enum. This allows unknown values to be mapped to a well-defined value that can
be appropriately handled.
The `Default` attribute may be used to specify an enumerator value or union
field that will be used if an `Extensible` enumeration or union does not
deserialize to a known value on the receiver side, i.e. the sender is using a
newer version of the enum or union. This allows unknown values to be mapped to
a well-defined value that can be appropriately handled.
Note: The `Default` field for a union must be of nullable or integral type.
When a union is defaulted to this field, the field takes on the default value
for its type: null for nullable types, and zero/false for integral types.
* **`[Extensible]`**:
The `Extensible` attribute may be specified for any enum definition. This
essentially disables builtin range validation when receiving values of the
enum type in a message, allowing older bindings to tolerate unrecognized
values from newer versions of the enum.
The `Extensible` attribute may be specified for any enum or union definition.
For enums, this essentially disables builtin range validation when receiving
values of the enum type in a message, allowing older bindings to tolerate
unrecognized values from newer versions of the enum.
Note: in the future, an `Extensible` enumeration will require that a `Default`
enumerator value also be specified.
If an enum value within an extensible enum definition is affixed with the
`Default` attribute, out-of-range values for the enum will deserialize to that
default value. Only one enum value may be designated as the `Default`.
Similarly, a union marked `Extensible` will deserialize to its `Default` field
when an unrecognized field is received. Extensible unions MUST specify exactly
one `Default` field, and the field must be of nullable or integral type. When
defaulted to this field, the value is always null/zero/false as appropriate.
An `Extensible` enumeration REQUIRES that a `Default` value be specified,
so all new extensible enums should specify one.
* **`[Native]`**:
The `Native` attribute may be specified for an empty struct declaration to
@ -422,7 +469,10 @@ interesting attributes supported today.
* **`[MinVersion=N]`**:
The `MinVersion` attribute is used to specify the version at which a given
field, enum value, interface method, or method parameter was introduced.
See [Versioning](#Versioning) for more details.
See [Versioning](#Versioning) for more details. `MinVersion` does not apply
to interfaces, structs or enums, but to the fields of those types.
`MinVersion` is not a module-global value, but it is ok to pretend it is by
skipping versions when adding fields or parameters.
* **`[Stable]`**:
The `Stable` attribute specifies that a given mojom type or interface
@ -442,13 +492,73 @@ interesting attributes supported today.
string representation as specified by RFC 4122. New UUIDs can be generated
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]`**:
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
matching `value` in the list of `enabled_features`, the definition will be
disabled. This is useful for mojom definitions that only make sense on one
platform. Note that the `EnableIf` attribute can only be set once per
definition.
definition and cannot be set at the same time as `EnableIfNot`. Also be aware
that only one condition can be tested, `EnableIf=value,xyz` introduces a new
`xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
only on the feature `value`. Complex conditions can be introduced via
enabled_features in `build.gn` files.
* **`[EnableIfNot=value]`**:
The `EnableIfNot` attribute is used to conditionally enable definitions when
the mojom is parsed. If the `mojom` target in the GN file includes the
matching `value` in the list of `enabled_features`, the definition will be
disabled. This is useful for mojom definitions that only make sense on all but
one platform. Note that the `EnableIfNot` attribute can only be set once per
definition and cannot be set at the same time as `EnableIf`.
* **`[ServiceSandbox=value]`**:
The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
service hosting an implementation of interface will be launched in. This only
applies to `C++` bindings. `value` should match a constant defined in an
imported `sandbox.mojom.Sandbox` enum (for Chromium this is
`//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
* **`[RequireContext=enum]`**:
The `RequireContext` attribute is used in Chromium to tag interfaces that
should be passed (as remotes or receivers) only to privileged process
contexts. The process context must be an enum that is imported into the
mojom that defines the tagged interface. `RequireContext` may be used in
future to DCHECK or CHECK if remotes are made available in contexts that
conflict with the one provided in the interface definition. Process contexts
are not the same as the sandbox a process is running in, but will reflect
the set of capabilities provided to the service.
* **`[AllowedContext=enum]`**:
The `AllowedContext` attribute is used in Chromium to tag methods that pass
remotes or receivers of interfaces that are marked with a `RequireContext`
attribute. The enum provided on the method must be equal or better (lower
numerically) than the one required on the interface being passed. At present
failing to specify an adequate `AllowedContext` value will cause mojom
generation to fail at compile time. In future DCHECKs or CHECKs might be
added to enforce that method is only called from a process context that meets
the given `AllowedContext` value. The enum must of the same type as that
specified in the interface's `RequireContext` attribute. Adding an
`AllowedContext` attribute to a method is a strong indication that you need
a detailed security review of your design - please reach out to the security
team.
* **`[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
@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:
``` cpp
enum AdvancedBoolean {
TRUE = 0,
FALSE = 1,
FILE_NOT_FOUND = 2,
kTrue = 0,
kFalse = 1,
kFileNotFound = 2,
};
```
@ -550,10 +660,16 @@ See the documentation for
*** note
**NOTE:** You don't need to worry about versioning if you don't care about
backwards compatibility. Specifically, all parts of Chrome are updated
atomically today and there is not yet any possibility of any two Chrome
processes communicating with two different versions of any given Mojom
interface.
backwards compatibility. Today, all parts of the Chrome browser are
updated atomically and there is not yet any possibility of any two
Chrome processes communicating with two different versions of any given Mojom
interface. On Chrome OS, there are several places where versioning is required.
For example,
[ARC++](https://developer.android.com/chrome-os/intro)
uses versioned mojo to send IPC to the Android container.
Likewise, the
[Lacros](/docs/lacros.md)
browser uses versioned mojo to talk to the ash system UI.
***
Services extend their interfaces to support new features over time, and clients
@ -593,8 +709,8 @@ struct Employee {
*** note
**NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
optional (nullable). See [Primitive Types](#Primitive-Types) for details on
nullable values.
optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
details on nullable values.
***
By default, fields belong to version 0. New fields must be appended to the
@ -624,10 +740,10 @@ the following hard constraints:
* For any given struct or interface, if any field or method explicitly specifies
an ordinal value, all fields or methods must explicitly specify an ordinal
value.
* For an *N*-field struct or *N*-method interface, the set of explicitly
assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
should include placeholder methods to fill the ordinal positions of removed
methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc).
* For an *N*-field struct, the set of explicitly assigned ordinal values must be
limited to the range *[0, N-1]*. Structs should include placeholder fields
to fill the ordinal positions of removed fields (for example "Unused_Field"
or "RemovedField", etc).
You may reorder fields, but you must ensure that the ordinal values of existing
fields remain unchanged. For example, the following struct remains
@ -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
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
expect a response is a not a backwards-compatible change.
@ -664,17 +798,12 @@ For example:
``` cpp
// Old version:
interface HumanResourceDatabase {
AddEmployee(Employee employee) => (bool success);
QueryEmployee(uint64 id) => (Employee? employee);
};
// New version:
interface HumanResourceDatabase {
AddEmployee(Employee employee) => (bool success);
QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
=> (Employee? employee,
[MinVersion=1] array<uint8>? finger_print);
QueryEmployee(uint64 id) => (Employee? employee);
[MinVersion=1]
AttachFingerPrint(uint64 id, array<uint8> finger_print)
@ -682,10 +811,7 @@ interface HumanResourceDatabase {
};
```
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. However, if the method
call itself is not recognized, it is considered a validation error and the
If a method call 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
version 1 of the above interface sends an `AttachFingerPrint` request to an
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
[Extensible]
enum Department {
SALES,
DEV,
kSales,
kDev,
};
```
@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:
``` cpp
[Extensible]
enum Department {
SALES,
DEV,
[MinVersion=1] RESEARCH,
kSales,
kDev,
[MinVersion=1] kResearch,
};
```
@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition
ModuleStatement = AttributeSection "module" Identifier ";"
ImportStatement = "import" StringLiteral ";"
Definition = Struct Union Interface Enum Const
Definition = Struct Union Interface Enum Feature Const
AttributeSection = <empty> | "[" AttributeList "]"
AttributeList = <empty> | NonEmptyAttributeList
@ -809,7 +935,7 @@ InterfaceBody = <empty>
| InterfaceBody Const
| InterfaceBody Enum
| InterfaceBody Method
Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";"
ParameterList = <empty> | NonEmptyParameterList
NonEmptyParameterList = Parameter
| Parameter "," NonEmptyParameterList
@ -847,6 +973,13 @@ EnumValue = AttributeSection Name
| AttributeSection Name "=" Integer
| 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 ";"
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
# Copyright 2019 The Chromium Authors. All rights reserved.
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
@ -15,6 +15,7 @@
from __future__ import print_function
import optparse
import sys
def Concatenate(filenames):
@ -47,7 +48,7 @@ def main():
parser.set_usage("""Concatenate several files into one.
Equivalent to: cat file1 ... > target.""")
(_options, args) = parser.parse_args()
exit(0 if Concatenate(args) else 1)
sys.exit(0 if Concatenate(args) else 1)
if __name__ == "__main__":

View file

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

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
# found in the LICENSE file.
"""Generates a list of all files in a directory.

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors. All rights reserved.
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a JSON typemap from its command-line arguments and dependencies.
@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):
for entry in config['types']:
configs[entry['mojom']] = {
'typename': entry['cpp'],
'forward_declaration': entry.get('forward_declaration', None),
'public_headers': config.get('traits_headers', []),
'traits_headers': config.get('traits_private_headers', []),
'copyable_pass_by_value': entry.get('copyable_pass_by_value',
False),
'default_constructible': entry.get('default_constructible', True),
'force_serialize': entry.get('force_serialize', False),
'hashable': entry.get('hashable', 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
# Copyright 2013 The Chromium Authors. All rights reserved.
# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {
"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):
if not generators_string:
return [] # No generators.
return {} # No generators.
generators = {}
for generator_name in [s.strip() for s in generators_string.split(",")]:
@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
return generators
def LoadChecks(checks_string):
if not checks_string:
return {} # No checks.
checks = {}
for check_name in [s.strip() for s in checks_string.split(",")]:
check = check_name.lower()
if check not in _BUILTIN_CHECKS:
print("Unknown check name %s" % check_name)
sys.exit(1)
check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
checks[check] = check_module
return checks
def MakeImportStackMessage(imported_filename_stack):
"""Make a (human-readable) message listing a chain of imports. (Returned
string begins with a newline (if nonempty) and does not end with one.)"""
@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
zip(imported_filename_stack[1:], imported_filename_stack)]))
class RelativePath(object):
class RelativePath:
"""Represents a path relative to the source tree or generated output dir."""
def __init__(self, path, source_root, output_dir):
@ -142,7 +164,7 @@ def ReadFileContents(filename):
return f.read()
class MojomProcessor(object):
class MojomProcessor:
"""Takes parsed mojom modules and generates language bindings from them.
Attributes:
@ -169,8 +191,8 @@ class MojomProcessor(object):
if 'c++' in self._typemap:
self._typemap['mojolpm'] = self._typemap['c++']
def _GenerateModule(self, args, remaining_args, generator_modules,
rel_filename, imported_filename_stack):
def _GenerateModule(self, args, remaining_args, check_modules,
generator_modules, rel_filename, imported_filename_stack):
# Return the already-generated module.
if rel_filename.path in self._processed_files:
return self._processed_files[rel_filename.path]
@ -190,12 +212,16 @@ class MojomProcessor(object):
ScrambleMethodOrdinals(module.interfaces, salt)
if self._should_generate(rel_filename.path):
# Run checks on module first.
for check_module in check_modules.values():
checker = check_module.Check(module)
checker.CheckModule()
# Then run generation.
for language, generator_module in generator_modules.items():
generator = generator_module.Generator(
module, args.output_dir, typemap=self._typemap.get(language, {}),
variant=args.variant, bytecode_path=args.bytecode_path,
for_blink=args.for_blink,
js_bindings_mode=args.js_bindings_mode,
js_generate_struct_deserializers=\
args.js_generate_struct_deserializers,
export_attribute=args.export_attribute,
@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
args.import_directories[idx] = RelativePath(tokens[0], args.depth,
args.output_dir)
generator_modules = LoadGenerators(args.generators_string)
check_modules = LoadChecks(args.checks_string)
fileutil.EnsureDirectoryExists(args.output_dir)
@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
for filename in args.filename:
processor._GenerateModule(
args, remaining_args, generator_modules,
args, remaining_args, check_modules, generator_modules,
RelativePath(filename, args.depth, args.output_dir), [])
return 0
@ -286,6 +313,12 @@ def main():
metavar="GENERATORS",
default="c++,javascript,java,mojolpm",
help="comma-separated list of generators")
generate_parser.add_argument("-c",
"--checks",
dest="checks_string",
metavar="CHECKS",
default=",".join(_BUILTIN_CHECKS.keys()),
help="comma-separated list of checks")
generate_parser.add_argument(
"--gen_dir", dest="gen_directories", action="append", metavar="directory",
default=[], help="add a directory to be searched for the syntax trees.")
@ -308,11 +341,6 @@ def main():
generate_parser.add_argument("--for_blink", action="store_true",
help="Use WTF types as generated types for mojo "
"string/array/map.")
generate_parser.add_argument(
"--js_bindings_mode", choices=["new", "old"], default="old",
help="This option only affects the JavaScript bindings. The value could "
"be \"new\" to generate new-style lite JS bindings in addition to the "
"old, or \"old\" to only generate old bindings.")
generate_parser.add_argument(
"--js_generate_struct_deserializers", action="store_true",
help="Generate javascript deserialize methods for structs in "
@ -387,4 +415,10 @@ def main():
if __name__ == "__main__":
with crbug_1001171.DumpStateOnLookupError():
sys.exit(main())
ret = main()
# Exit without running GC, which can save multiple seconds due to the large
# number of object created. But flush is necessary as os._exit doesn't do
# that.
sys.stdout.flush()
sys.stderr.flush()
os._exit(ret)

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
# found in the LICENSE file.
@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
from mojom_bindings_generator import ScrambleMethodOrdinals
class FakeIface(object):
class FakeIface:
def __init__(self):
self.mojom_name = None
self.methods = None
class FakeMethod(object):
class FakeMethod:
def __init__(self, explicit_ordinal=None):
self.explicit_ordinal = explicit_ordinal
self.ordinal = explicit_ordinal

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
# Copyright 2020 The Chromium Authors. All rights reserved.
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
])
_SUPPORTED_TYPE_KEYS = set([
'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
'move_only', 'nullable_is_same_type'
'move_only', 'nullable_is_same_type', 'forward_declaration',
'default_constructible'
])
with open(config_filename, 'r') as 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
# Copyright 2020 The Chromium Authors. All rights reserved.
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Verifies backward-compatibility of mojom type changes.
@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making
breaking changes to stable mojoms."""
import argparse
import errno
import io
import json
import os
import os.path
import shutil
import six
import sys
import tempfile
from mojom.generate import module
from mojom.generate import translate
from mojom.parse import parser
# pylint: disable=raise-missing-from
class ParseError(Exception):
pass
@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
transitive closure of a mojom's input dependencies all at once.
"""
translate.is_running_backwards_compatibility_check_hack = True
# First build a map of all files covered by the delta
affected_files = set()
old_files = {}
@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):
try:
ast = parser.Parse(contents, mojom)
except Exception as e:
six.reraise(
ParseError,
'encountered exception {0} while parsing {1}'.format(e, mojom),
sys.exc_info()[2])
raise ParseError('encountered exception {0} while parsing {1}'.format(
e, mojom))
# Files which are generated at compile time can't be checked by this script
# (at the moment) since they may not exist in the output directory.
generated_files_to_skip = {
('third_party/blink/public/mojom/runtime_feature_state/'
'runtime_feature.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:
if (not file_overrides.get(imp.import_filename)
and not os.path.exists(os.path.join(root, imp.import_filename))):
# Speculatively construct a path prefix to locate the import_filename
mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
test_prefix = ''
for path_component in mojom_path:
test_prefix = os.path.join(test_prefix, path_component)
test_import_filename = os.path.join(test_prefix, imp.import_filename)
if os.path.exists(os.path.join(root, test_import_filename)):
imp.import_filename = test_import_filename
break
parseMojom(imp.import_filename, file_overrides, override_modules)
# Now that the transitive set of dependencies has been imported and parsed
@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):
modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
old_modules = {}
for mojom in old_files.keys():
for mojom in old_files:
parseMojom(mojom, old_files, old_modules)
new_modules = {}
for mojom in new_files.keys():
for mojom in new_files:
parseMojom(mojom, new_files, new_modules)
# At this point we have a complete set of translated Modules from both the
@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):
'can be deleted by a subsequent change.' % qualified_name)
checker = module.BackwardCompatibilityChecker()
try:
if not checker.IsBackwardCompatible(new_types[new_name], kind):
raise Exception('Stable type %s appears to have changed in a way which '
raise Exception(
'Stable type %s appears to have changed in a way which '
'breaks backward-compatibility. Please fix!\n\nIf you '
'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):

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
from mojom.generate import module
class Change(object):
class Change:
"""Helper to clearly define a mojom file delta to be analyzed."""
def __init__(self, filename, old=None, new=None):
@ -28,7 +28,7 @@ class Change(object):
class UnchangedFile(Change):
def __init__(self, filename, contents):
super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
super().__init__(filename, old=contents, new=contents)
class CheckStableMojomCompatibilityTest(unittest.TestCase):
@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
[Stable] struct T { foo.S s; int32 x; };
""")
])
def testWithPartialImport(self):
"""The compatibility checking tool correctly parses imports with partial
paths."""
self.assertBackwardCompatible([
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
Change('foo/bar.mojom',
old="""\
module bar;
import "foo/foo.mojom";
[Stable] struct T { foo.S s; };
""",
new="""\
module bar;
import "foo.mojom";
[Stable] struct T { foo.S s; };
""")
])
self.assertBackwardCompatible([
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
Change('foo/bar.mojom',
old="""\
module bar;
import "foo.mojom";
[Stable] struct T { foo.S s; };
""",
new="""\
module bar;
import "foo/foo.mojom";
[Stable] struct T { foo.S s; };
""")
])
self.assertNotBackwardCompatible([
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
Change('bar/bar.mojom',
old="""\
module bar;
import "foo/foo.mojom";
[Stable] struct T { foo.S s; };
""",
new="""\
module bar;
import "foo.mojom";
[Stable] struct T { foo.S s; };
""")
])
self.assertNotBackwardCompatible([
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
Change('bar/bar.mojom',
old="""\
module bar;
import "foo.mojom";
[Stable] struct T { foo.S s; };
""",
new="""\
module bar;
import "foo/foo.mojom";
[Stable] struct T { foo.S s; };
""")
])
def testNewEnumDefault(self):
# Should be backwards compatible since it does not affect the wire format.
# This specific case also checks that the backwards compatibility checker
# does not throw an error due to the older version of the enum not
# specifying [Default].
self.assertBackwardCompatible([
Change('foo/foo.mojom',
old='[Extensible] enum E { One };',
new='[Extensible] enum E { [Default] One };')
])
self.assertBackwardCompatible([
Change('foo/foo.mojom',
old='[Extensible] enum E { [Default] One, Two, };',
new='[Extensible] enum E { One, [Default] Two, };')
])

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
# 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
# found in the LICENSE file.
@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
self.assertEqual('F', b.enums[0].mojom_name)
self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
self.assertEqual(37, b.enums[0].fields[0].numeric_value)
def testEnumAttributesAreEnums(self):
"""Verifies that enum values in attributes are really enum types."""
a_mojom = 'a.mojom'
self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
b_mojom = 'b.mojom'
self.WriteFile(
b_mojom, 'module b;'
'import "a.mojom";'
'[MooCow=a.E.kFoo]'
'interface Foo { Foo(); };')
self.ParseMojoms([a_mojom, b_mojom])
b = self.LoadModule(b_mojom)
self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
def testConstantAttributes(self):
"""Verifies that constants as attributes are translated to the constant."""
a_mojom = 'a.mojom'
self.WriteFile(
a_mojom, 'module a;'
'enum E { kFoo, kBar };'
'const E kB = E.kFoo;'
'[Attr=kB] interface Hello { Foo(); };')
self.ParseMojoms([a_mojom])
a = self.LoadModule(a_mojom)
self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
'kFoo')

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
# found in the LICENSE file.
@ -8,6 +8,7 @@ group("mojom") {
"error.py",
"fileutil.py",
"generate/__init__.py",
"generate/check.py",
"generate/generator.py",
"generate/module.py",
"generate/pack.py",

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
# 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
# found in the LICENSE file.
import errno
import imp
import os.path
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
# found in the LICENSE file.
import imp
import os.path
import shutil
import sys
import tempfile
import unittest
from mojom import fileutil
class FileUtilTest(unittest.TestCase):
def testEnsureDirectoryExists(self):
"""Test that EnsureDirectoryExists fuctions correctly."""
"""Test that EnsureDirectoryExists functions correctly."""
temp_dir = tempfile.mkdtemp()
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
# found in the LICENSE file.
"""Code shared by the various language-specific code generators."""
@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
return _ToSnakeCase(identifier, upper=False)
class Stylizer(object):
class Stylizer:
"""Stylizers specify naming rules to map mojom names to names in generated
code. For example, if you would like method_name in mojom to be mapped to
MethodName in the generated code, you need to define a subclass of Stylizer
@ -130,6 +130,9 @@ class Stylizer(object):
def StylizeEnum(self, mojom_name):
return mojom_name
def StylizeFeature(self, mojom_name):
return mojom_name
def StylizeModule(self, mojom_namespace):
return mojom_namespace
@ -233,7 +236,7 @@ def AddComputedData(module):
_AddInterfaceComputedData(interface)
class Generator(object):
class Generator:
# Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
# files to stdout.
def __init__(self,
@ -243,7 +246,6 @@ class Generator(object):
variant=None,
bytecode_path=None,
for_blink=False,
js_bindings_mode="new",
js_generate_struct_deserializers=False,
export_attribute=None,
export_header=None,
@ -262,7 +264,6 @@ class Generator(object):
self.variant = variant
self.bytecode_path = bytecode_path
self.for_blink = for_blink
self.js_bindings_mode = js_bindings_mode
self.js_generate_struct_deserializers = js_generate_struct_deserializers
self.export_attribute = export_attribute
self.export_header = export_header

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
# found in the LICENSE file.
import imp
import importlib.util
import os.path
import sys
import unittest
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
try:
imp.find_module("mojom")
importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
from mojom.generate import generator
class StringManipulationTest(unittest.TestCase):
"""generator contains some string utilities, this tests only those."""
@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
self.assertEquals("SNAKE_D3D11_CASE",
generator.ToUpperSnakeCase("snakeD3d11Case"))
if __name__ == "__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
# 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
# found in the LICENSE file.
import copy
from mojom.generate import module as mojom
# This module provides a mechanism for determining the packed order and offsets
@ -15,7 +16,7 @@ from mojom.generate import module as mojom
HEADER_SIZE = 8
class PackedField(object):
class PackedField:
kind_to_size = {
mojom.BOOL: 1,
mojom.INT8: 1,
@ -75,18 +76,55 @@ class PackedField(object):
return 8
return cls.GetSizeForKind(kind)
def __init__(self, field, index, ordinal):
def __init__(self,
field,
index,
ordinal,
original_field=None,
sub_ordinal=None,
linked_value_packed_field=None):
"""
Args:
field: the original field.
index: the position of the original field in the struct.
ordinal: the ordinal of the field for serialization.
original_field: See below.
sub_ordinal: See below.
linked_value_packed_field: See below.
original_field, sub_ordinal, and linked_value_packed_field are used to
support nullable ValueKind fields. For legacy reasons, nullable ValueKind
fields actually generate two PackedFields. This allows:
- backwards compatibility prior to Mojo support for nullable ValueKinds.
- correct packing of fields for the aforementioned backwards compatibility.
When translating Fields to PackedFields, the original field is turned into
two PackedFields: the first PackedField always has type mojom.BOOL, while
the second PackedField has the non-nullable version of the field's kind.
When constructing these PackedFields, original_field references the field
as defined in the mojom; the name as defined in the mojom will be used for
all layers above the wire/data layer.
sub_ordinal is used to sort the two PackedFields correctly with respect to
each other: the first mojom.BOOL field always has sub_ordinal 0, while the
second field always has sub_ordinal 1.
Finally, linked_value_packed_field is used by the serialization and
deserialization helpers, which generally just iterate over a PackedStruct's
PackedField's in ordinal order. This allows the helpers to easily reference
any related PackedFields rather than having to lookup related PackedFields
by index while iterating.
"""
self.field = field
self.index = index
self.ordinal = ordinal
self.size = self.GetSizeForKind(field.kind)
self.alignment = self.GetAlignmentForKind(field.kind)
self.original_field = original_field
self.sub_ordinal = sub_ordinal
self.linked_value_packed_field = linked_value_packed_field
self.size = self.GetSizeForKind(self.field.kind)
self.alignment = self.GetAlignmentForKind(self.field.kind)
self.offset = None
self.bit = None
self.min_version = None
@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
return offset + pad
class PackedStruct(object):
def IsNullableValueKindPackedField(field):
"""Returns true if `field` is derived from a nullable ValueKind field.
Nullable ValueKind fields often require special handling in the bindings due
to the way the implementation is constrained for wire compatibility.
"""
assert isinstance(field, PackedField)
return field.sub_ordinal is not None
def IsPrimaryNullableValueKindPackedField(field):
"""Returns true if `field` is derived from a nullable ValueKind mojom field
and is the "primary" field.
The primary field is a bool PackedField that controls if the field should be
considered as present or not; it will have a reference to the PackedField that
holds the actual value representation if considered present.
Bindings code that translates between the wire protocol and the higher layers
can use this to simplify mapping multiple PackedFields to the single field
that is logically exposed to bindings consumers.
"""
assert isinstance(field, PackedField)
return field.linked_value_packed_field is not None
class PackedStruct:
def __init__(self, struct):
self.struct = struct
# |packed_fields| contains all the fields, in increasing offset order.
@ -139,9 +203,41 @@ class PackedStruct(object):
for index, field in enumerate(struct.fields):
if field.ordinal is not None:
ordinal = field.ordinal
# Nullable value types are a bit weird: they generate two PackedFields
# despite being a single ValueKind. This is for wire compatibility to
# ease the transition from legacy mojom syntax where nullable value types
# were not supported.
if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
# The suffixes intentionally use Unicode codepoints which are considered
# valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
# actual user code.
has_value_field = copy.copy(field)
has_value_field.name = f'{field.mojom_name}_$flag'
has_value_field.kind = mojom.BOOL
value_field = copy.copy(field)
value_field.name = f'{field.mojom_name}_$value'
value_field.kind = field.kind.MakeUnnullableKind()
value_packed_field = PackedField(value_field,
index,
ordinal,
original_field=field,
sub_ordinal=1,
linked_value_packed_field=None)
has_value_packed_field = PackedField(
has_value_field,
index,
ordinal,
original_field=field,
sub_ordinal=0,
linked_value_packed_field=value_packed_field)
src_fields.append(has_value_packed_field)
src_fields.append(value_packed_field)
else:
src_fields.append(PackedField(field, index, ordinal))
ordinal += 1
src_fields.sort(key=lambda field: field.ordinal)
src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
# Set |min_version| for each field.
next_min_version = 0
@ -156,8 +252,9 @@ class PackedStruct(object):
if (packed_field.min_version != 0
and mojom.IsReferenceKind(packed_field.field.kind)
and not packed_field.field.kind.is_nullable):
raise Exception("Non-nullable fields are only allowed in version 0 of "
"a struct. %s.%s is defined with [MinVersion=%d]." %
raise Exception(
"Non-nullable reference fields are only allowed in version 0 of a "
"struct. %s.%s is defined with [MinVersion=%d]." %
(self.struct.name, packed_field.field.name,
packed_field.min_version))
@ -186,7 +283,7 @@ class PackedStruct(object):
dst_fields.append(src_field)
class ByteInfo(object):
class ByteInfo:
def __init__(self):
self.is_padding = False
self.packed_fields = []
@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):
return byte_info
class VersionInfo(object):
def __init__(self, version, num_fields, num_bytes):
class VersionInfo:
def __init__(self, version, num_fields, num_packed_fields, num_bytes):
self.version = version
self.num_fields = num_fields
self.num_packed_fields = num_packed_fields
self.num_bytes = num_bytes
@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):
versions = []
last_version = 0
last_num_fields = 0
last_num_packed_fields = 0
last_payload_size = 0
for packed_field in packed_struct.packed_fields_in_ordinal_order:
if packed_field.min_version != last_version:
versions.append(
VersionInfo(last_version, last_num_fields,
VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE))
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
# 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.
last_payload_size = max(
GetPayloadSizeUpToField(packed_field), last_payload_size)
last_payload_size = max(GetPayloadSizeUpToField(packed_field),
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(
VersionInfo(last_version, last_num_fields,
VersionInfo(last_version, last_num_fields, last_num_packed_fields,
last_payload_size + HEADER_SIZE))
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
# found in the LICENSE file.
@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):
self.assertEqual(4, versions[2].num_fields)
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):
"""Tests that interfaces are aligned on 4-byte boundaries, although the size
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
# 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
# found in the LICENSE file.
"""Convert parse tree to AST.
@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.
import itertools
import os
import re
import sys
from collections import OrderedDict
from mojom.generate import generator
from mojom.generate import module as mojom
from mojom.parse import ast
def _IsStrOrUnicode(x):
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str))
return isinstance(x, str)
is_running_backwards_compatibility_check_hack = False
### DO NOT ADD ENTRIES TO THIS LIST. ###
_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
'x:arc.keymaster.mojom.Algorithm',
'x:arc.keymaster.mojom.Digest',
'x:arc.keymaster.mojom.SignatureResult',
'x:arc.mojom.AccessibilityActionType',
'x:arc.mojom.AccessibilityBooleanProperty',
'x:arc.mojom.AccessibilityEventIntListProperty',
'x:arc.mojom.AccessibilityEventIntProperty',
'x:arc.mojom.AccessibilityEventStringProperty',
'x:arc.mojom.AccessibilityEventType',
'x:arc.mojom.AccessibilityFilterType',
'x:arc.mojom.AccessibilityIntListProperty',
'x:arc.mojom.AccessibilityIntProperty',
'x:arc.mojom.AccessibilityLiveRegionType',
'x:arc.mojom.AccessibilityNotificationStateType',
'x:arc.mojom.AccessibilityRangeType',
'x:arc.mojom.AccessibilitySelectionMode',
'x:arc.mojom.AccessibilityStringListProperty',
'x:arc.mojom.AccessibilityStringProperty',
'x:arc.mojom.AccessibilityWindowBooleanProperty',
'x:arc.mojom.AccessibilityWindowIntListProperty',
'x:arc.mojom.AccessibilityWindowIntProperty',
'x:arc.mojom.AccessibilityWindowStringProperty',
'x:arc.mojom.AccessibilityWindowType',
'x:arc.mojom.AccountCheckStatus',
'x:arc.mojom.AccountUpdateType',
'x:arc.mojom.ActionType',
'x:arc.mojom.Algorithm',
'x:arc.mojom.AndroidIdSource',
'x:arc.mojom.AnrSource',
'x:arc.mojom.AnrType',
'x:arc.mojom.AppDiscoveryRequestState',
'x:arc.mojom.AppKillType',
'x:arc.mojom.AppPermission',
'x:arc.mojom.AppPermissionGroup',
'x:arc.mojom.AppReinstallState',
'x:arc.mojom.AppShortcutItemType',
'x:arc.mojom.ArcAuthCodeStatus',
'x:arc.mojom.ArcClipboardDragDropEvent',
'x:arc.mojom.ArcCorePriAbiMigEvent',
'x:arc.mojom.ArcDnsQuery',
'x:arc.mojom.ArcImageCopyPasteCompatAction',
'x:arc.mojom.ArcNetworkError',
'x:arc.mojom.ArcNetworkEvent',
'x:arc.mojom.ArcNotificationEvent',
'x:arc.mojom.ArcNotificationExpandState',
'x:arc.mojom.ArcNotificationPriority',
'x:arc.mojom.ArcNotificationRemoteInputState',
'x:arc.mojom.ArcNotificationShownContents',
'x:arc.mojom.ArcNotificationStyle',
'x:arc.mojom.ArcNotificationType',
'x:arc.mojom.ArcPipEvent',
'x:arc.mojom.ArcResizeLockState',
'x:arc.mojom.ArcSignInSuccess',
'x:arc.mojom.ArcTimerResult',
'x:arc.mojom.AudioSwitch',
'x:arc.mojom.BluetoothAclState',
'x:arc.mojom.BluetoothAdapterState',
'x:arc.mojom.BluetoothAdvertisingDataType',
'x:arc.mojom.BluetoothBondState',
'x:arc.mojom.BluetoothDeviceType',
'x:arc.mojom.BluetoothDiscoveryState',
'x:arc.mojom.BluetoothGattDBAttributeType',
'x:arc.mojom.BluetoothGattStatus',
'x:arc.mojom.BluetoothPropertyType',
'x:arc.mojom.BluetoothScanMode',
'x:arc.mojom.BluetoothSdpAttributeType',
'x:arc.mojom.BluetoothSocketType',
'x:arc.mojom.BluetoothStatus',
'x:arc.mojom.BootType',
'x:arc.mojom.CaptionTextShadowType',
'x:arc.mojom.ChangeType',
'x:arc.mojom.ChromeAccountType',
'x:arc.mojom.ChromeApp',
'x:arc.mojom.ChromePage',
'x:arc.mojom.ClockId',
'x:arc.mojom.CloudProvisionFlowError',
'x:arc.mojom.CommandResultType',
'x:arc.mojom.CompanionLibApiId',
'x:arc.mojom.ConnectionStateType',
'x:arc.mojom.ContentChangeType',
'x:arc.mojom.CpuRestrictionState',
'x:arc.mojom.CursorCoordinateSpace',
'x:arc.mojom.DataRestoreStatus',
'x:arc.mojom.DecoderStatus',
'x:arc.mojom.DeviceType',
'x:arc.mojom.Digest',
'x:arc.mojom.DisplayWakeLockType',
'x:arc.mojom.EapMethod',
'x:arc.mojom.EapPhase2Method',
'x:arc.mojom.FileSelectorEventType',
'x:arc.mojom.GMSCheckInError',
'x:arc.mojom.GMSSignInError',
'x:arc.mojom.GeneralSignInError',
'x:arc.mojom.GetNetworksRequestType',
'x:arc.mojom.HalPixelFormat',
'x:arc.mojom.IPAddressType',
'x:arc.mojom.InstallErrorReason',
'x:arc.mojom.KeyFormat',
'x:arc.mojom.KeyManagement',
'x:arc.mojom.KeyPurpose',
'x:arc.mojom.KeymasterError',
'x:arc.mojom.MainAccountHashMigrationStatus',
'x:arc.mojom.MainAccountResolutionStatus',
'x:arc.mojom.ManagementChangeStatus',
'x:arc.mojom.ManagementState',
'x:arc.mojom.MessageCenterVisibility',
'x:arc.mojom.MetricsType',
'x:arc.mojom.MountEvent',
'x:arc.mojom.NativeBridgeType',
'x:arc.mojom.NetworkResult',
'x:arc.mojom.NetworkType',
'x:arc.mojom.OemCryptoAlgorithm',
'x:arc.mojom.OemCryptoCipherMode',
'x:arc.mojom.OemCryptoHdcpCapability',
'x:arc.mojom.OemCryptoLicenseType',
'x:arc.mojom.OemCryptoPrivateKey',
'x:arc.mojom.OemCryptoProvisioningMethod',
'x:arc.mojom.OemCryptoResult',
'x:arc.mojom.OemCryptoRsaPaddingScheme',
'x:arc.mojom.OemCryptoUsageEntryStatus',
'x:arc.mojom.Padding',
'x:arc.mojom.PaiFlowState',
'x:arc.mojom.PatternType',
'x:arc.mojom.PressureLevel',
'x:arc.mojom.PrintColorMode',
'x:arc.mojom.PrintContentType',
'x:arc.mojom.PrintDuplexMode',
'x:arc.mojom.PrinterStatus',
'x:arc.mojom.ProcessState',
'x:arc.mojom.PurchaseState',
'x:arc.mojom.ReauthReason',
'x:arc.mojom.ScaleFactor',
'x:arc.mojom.SecurityType',
'x:arc.mojom.SegmentStyle',
'x:arc.mojom.SelectFilesActionType',
'x:arc.mojom.SetNativeChromeVoxResponse',
'x:arc.mojom.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):
@ -98,12 +375,6 @@ def _MapKind(kind):
}
if kind.endswith('?'):
base_kind = _MapKind(kind[0:-1])
# NOTE: This doesn't rule out enum types. Those will be detected later, when
# cross-reference is established.
reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
'rma', 'rca')
if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
return '?' + base_kind
if kind.endswith('}'):
lbracket = kind.rfind('{')
@ -113,8 +384,6 @@ def _MapKind(kind):
lbracket = kind.rfind('[')
typename = kind[0:lbracket]
return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
if kind.endswith('&'):
return 'r:' + _MapKind(kind[0:-1])
if kind.startswith('asso<'):
assert kind.endswith('>')
return 'asso:' + _MapKind(kind[5:-1])
@ -135,13 +404,45 @@ def _MapKind(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:
return None
assert isinstance(attribute_list, ast.AttributeList)
# TODO(vtl): Check for duplicate keys here.
return dict(
[(attribute.key, attribute.value) for attribute in attribute_list])
attributes = dict()
for attribute in attribute_list:
if attribute.key in attributes:
raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
attributes[attribute.key] = _MapAttributeValue(module, kind,
attribute.value)
return attributes
builtin_values = frozenset([
@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):
return kind
if spec.startswith('?'):
kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
kind = _Kind(kinds, spec[1:], scope)
kind = kind.MakeNullableKind()
elif spec.startswith('a:'):
kind = mojom.Array(_Kind(kinds, spec[2:], scope))
elif spec.startswith('asso:'):
@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):
def _Import(module, import_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():
if (isinstance(kind, importable_kinds)
and kind.module.path == import_module.path):
@ -316,6 +619,32 @@ def _Import(module, 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):
"""
Args:
@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):
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
# declarations more explicit.
@ -377,7 +707,8 @@ def _Union(module, parsed_union):
union.fields_data = []
_ProcessElements(parsed_union.mojom_name, parsed_union.body,
{ast.UnionField: union.fields_data.append})
union.attributes = _AttributeListToDict(parsed_union.attribute_list)
union.attributes = _AttributeListToDict(module, union,
parsed_union.attribute_list)
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.default = _LookupValue(module, struct, field.kind,
parsed_field.default_value)
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
field.attributes = _AttributeListToDict(module, field,
parsed_field.attribute_list)
return field
@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):
"""
field = mojom.UnionField()
field.mojom_name = parsed_field.mojom_name
# Disallow unions from being self-recursive.
parsed_typename = parsed_field.typename
if parsed_typename.endswith('?'):
parsed_typename = parsed_typename[:-1]
assert parsed_typename != union.mojom_name
field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
(module.mojom_namespace, union.mojom_name))
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = None
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
field.attributes = _AttributeListToDict(module, 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
@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):
parameter.ordinal = (parsed_param.ordinal.value
if parsed_param.ordinal else None)
parameter.default = None # TODO(tibell): We never have these. Remove field?
parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
parameter.attributes = _AttributeListToDict(module, parameter,
parsed_param.attribute_list)
return parameter
@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):
method.response_parameters = list(
map(lambda parameter: _Parameter(module, parameter, interface),
parsed_method.response_parameter_list))
method.attributes = _AttributeListToDict(parsed_method.attribute_list)
method.attributes = _AttributeListToDict(module, method,
parsed_method.attribute_list)
# Enforce that only methods with response can have a [Sync] attribute.
if method.sync and method.response_parameters is None:
@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):
interface.mojom_name = parsed_iface.mojom_name
interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
module.kinds[interface.spec] = interface
interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
interface.attributes = _AttributeListToDict(module, interface,
parsed_iface.attribute_list)
interface.enums = []
interface.constants = []
interface.methods_data = []
@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):
field = mojom.EnumField()
field.mojom_name = parsed_field.mojom_name
field.value = _LookupValue(module, enum, None, parsed_field.value)
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
field.attributes = _AttributeListToDict(module, field,
parsed_field.attribute_list)
value = mojom.EnumValue(module, enum, field)
module.values[value.GetSpec()] = value
return field
@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):
prev_value += 1
# Integral value (e.g: BEGIN = -0x1).
elif _IsStrOrUnicode(field.value):
elif isinstance(field.value, str):
prev_value = int(field.value, 0)
# Reference to a previous enum value (e.g: INIT = BEGIN).
@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):
else:
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
if min_value is None or prev_value < min_value:
min_value = prev_value
@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):
mojom_name = parent_kind.mojom_name + '.' + mojom_name
enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
enum.parent_kind = parent_kind
enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
enum.attributes = _AttributeListToDict(module, enum,
parsed_enum.attribute_list)
if not enum.native_only:
enum.fields = list(
@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):
for field in enum.fields:
if field.default:
if not enum.extensible:
raise Exception('Non-extensible enums may not specify a default')
if enum.default_field is not None:
raise Exception(
'Only one enumerator value may be specified as the default')
f'Non-extensible enum {enum.spec} may not specify a default')
if enum.default_field is not None:
raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
enum.default_field = field
# While running the backwards compatibility check, ignore errors because the
# old version of the enum might not specify [Default].
if (enum.extensible and enum.default_field is None
and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
and not is_running_backwards_compatibility_check_hack):
raise Exception(
f'Extensible enum {enum.spec} must specify a [Default] enumerator')
module.kinds[enum.spec] = enum
@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
for referenced_kind in extract_referenced_user_kinds(param.kind):
sanitized_kind = sanitize_kind(referenced_kind)
referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
# Consts can reference imported enums.
for const in module.constants:
if not const.kind in mojom.PRIMITIVES:
sanitized_kind = sanitize_kind(const.kind)
referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
return referenced_user_kinds
@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):
assertDependencyIsStable(response_param.kind)
def _AssertStructIsValid(kind):
expected_ordinals = set(range(0, len(kind.fields)))
ordinals = set(map(lambda field: field.ordinal, kind.fields))
if ordinals != expected_ordinals:
raise Exception(
'Structs must use contiguous ordinals starting from 0. ' +
'{} is missing the following ordinals: {}.'.format(
kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
def _Module(tree, path, imports):
"""
Args:
@ -778,6 +1151,8 @@ def _Module(tree, path, imports):
module.structs = []
module.unions = []
module.interfaces = []
module.features = []
_ProcessElements(
filename, tree.definition_list, {
ast.Const:
@ -791,6 +1166,8 @@ def _Module(tree, path, imports):
ast.Interface:
lambda interface: module.interfaces.append(
_Interface(module, interface)),
ast.Feature:
lambda feature: module.features.append(_Feature(module, feature)),
})
# 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:
all_defined_kinds[enum.spec] = enum
for feature in module.features:
all_defined_kinds[feature.spec] = feature
for union in module.unions:
union.fields = list(
map(lambda field: _UnionField(module, field, union), union.fields_data))
_AssignDefaultOrdinals(union.fields)
for field in union.fields:
if field.is_default:
if union.default_field is not None:
raise Exception('Multiple [Default] fields in union %s.' %
union.mojom_name)
union.default_field = field
del union.fields_data
all_defined_kinds[union.spec] = union
if union.extensible and union.default_field is None:
raise Exception('Extensible union %s must specify a [Default] field' %
union.mojom_name)
for interface in module.interfaces:
interface.methods = list(
@ -829,8 +1218,8 @@ def _Module(tree, path, imports):
all_defined_kinds.values())
imported_kind_specs = set(all_referenced_kinds.keys()).difference(
set(all_defined_kinds.keys()))
module.imported_kinds = dict(
(spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
for spec in sorted(imported_kind_specs))
generator.AddComputedData(module)
for iface in module.interfaces:
@ -847,6 +1236,9 @@ def _Module(tree, path, imports):
if kind.stable:
_AssertTypeIsStable(kind)
for kind in module.structs:
_AssertStructIsValid(kind)
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
# found in the LICENSE file.
import imp
import os.path
import sys
import unittest
from mojom.generate import module as mojom
from mojom.generate import translate
from mojom.parse import ast
class TranslateTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):
# pylint: disable=W0212
self.assertEquals(
translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
self.assertEquals(
translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
self.assertEquals(translate._MapKind("rca<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
# found in the LICENSE file.
"""Node classes for the AST for a Mojo IDL file."""
@ -8,17 +8,14 @@
# and lineno). You may also define __repr__() to help with analyzing test
# failures, especially for more complex types.
import sys
import os.path
def _IsStrOrUnicode(x):
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str))
return isinstance(x, str)
# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
# pylint: disable=no-member
class NodeBase(object):
class NodeBase:
"""Base class for nodes in the AST."""
def __init__(self, filename=None, lineno=None):
@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
classes, in a tuple) of the members of the list.)"""
def __init__(self, item_or_items=None, **kwargs):
super(NodeListBase, self).__init__(**kwargs)
super().__init__(**kwargs)
self.items = []
if item_or_items is None:
pass
@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
return self.items.__iter__()
def __eq__(self, other):
return super(NodeListBase, self).__eq__(other) and \
return super().__eq__(other) and \
self.items == other.items
# Implement this so that on failure, we get slightly more sensible output.
@ -96,7 +93,7 @@ class Definition(NodeBase):
include parameter definitions.) This class is meant to be subclassed."""
def __init__(self, mojom_name, **kwargs):
assert _IsStrOrUnicode(mojom_name)
assert isinstance(mojom_name, str)
NodeBase.__init__(self, **kwargs)
self.mojom_name = mojom_name
@ -108,13 +105,13 @@ class Attribute(NodeBase):
"""Represents an attribute."""
def __init__(self, key, value, **kwargs):
assert _IsStrOrUnicode(key)
super(Attribute, self).__init__(**kwargs)
assert isinstance(key, str)
super().__init__(**kwargs)
self.key = key
self.value = value
def __eq__(self, other):
return super(Attribute, self).__eq__(other) and \
return super().__eq__(other) and \
self.key == other.key and \
self.value == other.value
@ -131,17 +128,17 @@ class Const(Definition):
def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
# The typename is currently passed through as a string.
assert _IsStrOrUnicode(typename)
assert isinstance(typename, str)
# The value is either a literal (currently passed through as a string) or a
# "wrapped identifier".
assert _IsStrOrUnicode or isinstance(value, tuple)
super(Const, self).__init__(mojom_name, **kwargs)
assert isinstance(value, (tuple, str))
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.typename = typename
self.value = value
def __eq__(self, other):
return super(Const, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.typename == other.typename and \
self.value == other.value
@ -153,12 +150,12 @@ class Enum(Definition):
def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
super(Enum, self).__init__(mojom_name, **kwargs)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.enum_value_list = enum_value_list
def __eq__(self, other):
return super(Enum, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.enum_value_list == other.enum_value_list
@ -170,13 +167,13 @@ class EnumValue(Definition):
# The optional value is either an int (which is current a string) or a
# "wrapped identifier".
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
super(EnumValue, self).__init__(mojom_name, **kwargs)
assert value is None or isinstance(value, (tuple, str))
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.value = value
def __eq__(self, other):
return super(EnumValue, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.value == other.value
@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):
_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):
"""Represents an import statement."""
def __init__(self, attribute_list, import_filename, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert _IsStrOrUnicode(import_filename)
super(Import, self).__init__(**kwargs)
assert isinstance(import_filename, str)
super().__init__(**kwargs)
self.attribute_list = attribute_list
self.import_filename = import_filename
# TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
def __eq__(self, other):
return super(Import, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.import_filename == other.import_filename
@ -216,12 +242,12 @@ class Interface(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, InterfaceBody)
super(Interface, self).__init__(mojom_name, **kwargs)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
return super(Interface, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
@ -236,14 +262,14 @@ class Method(Definition):
assert isinstance(parameter_list, ParameterList)
assert response_parameter_list is None or \
isinstance(response_parameter_list, ParameterList)
super(Method, self).__init__(mojom_name, **kwargs)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.parameter_list = parameter_list
self.response_parameter_list = response_parameter_list
def __eq__(self, other):
return super(Method, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.parameter_list == other.parameter_list and \
@ -264,12 +290,12 @@ class Module(NodeBase):
# |mojom_namespace| is either none or a "wrapped identifier".
assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
super(Module, self).__init__(**kwargs)
super().__init__(**kwargs)
self.mojom_namespace = mojom_namespace
self.attribute_list = attribute_list
def __eq__(self, other):
return super(Module, self).__eq__(other) and \
return super().__eq__(other) and \
self.mojom_namespace == other.mojom_namespace and \
self.attribute_list == other.attribute_list
@ -281,13 +307,13 @@ class Mojom(NodeBase):
assert module is None or isinstance(module, Module)
assert isinstance(import_list, ImportList)
assert isinstance(definition_list, list)
super(Mojom, self).__init__(**kwargs)
super().__init__(**kwargs)
self.module = module
self.import_list = import_list
self.definition_list = definition_list
def __eq__(self, other):
return super(Mojom, self).__eq__(other) and \
return super().__eq__(other) and \
self.module == other.module and \
self.import_list == other.import_list and \
self.definition_list == other.definition_list
@ -302,11 +328,11 @@ class Ordinal(NodeBase):
def __init__(self, value, **kwargs):
assert isinstance(value, int)
super(Ordinal, self).__init__(**kwargs)
super().__init__(**kwargs)
self.value = value
def __eq__(self, other):
return super(Ordinal, self).__eq__(other) and \
return super().__eq__(other) and \
self.value == other.value
@ -314,18 +340,18 @@ class Parameter(NodeBase):
"""Represents a method request or response parameter."""
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
assert _IsStrOrUnicode(mojom_name)
assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert _IsStrOrUnicode(typename)
super(Parameter, self).__init__(**kwargs)
assert isinstance(typename, str)
super().__init__(**kwargs)
self.mojom_name = mojom_name
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
def __eq__(self, other):
return super(Parameter, self).__eq__(other) and \
return super().__eq__(other) and \
self.mojom_name == other.mojom_name and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
@ -344,42 +370,51 @@ class Struct(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, StructBody) or body is None
super(Struct, self).__init__(mojom_name, **kwargs)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
return super(Struct, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
def __repr__(self):
return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
self.mojom_name, self.attribute_list, self.body)
class StructField(Definition):
"""Represents a struct field definition."""
def __init__(self, mojom_name, attribute_list, ordinal, typename,
default_value, **kwargs):
assert _IsStrOrUnicode(mojom_name)
assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert _IsStrOrUnicode(typename)
assert isinstance(typename, str)
# The optional default value is currently either a value as a string or a
# "wrapped identifier".
assert default_value is None or _IsStrOrUnicode(default_value) or \
isinstance(default_value, tuple)
super(StructField, self).__init__(mojom_name, **kwargs)
assert default_value is None or isinstance(default_value, (str, tuple))
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
self.default_value = default_value
def __eq__(self, other):
return super(StructField, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.typename == other.typename and \
self.default_value == other.default_value
def __repr__(self):
return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
"typename = %s, default_value = %s") % (
self.mojom_name, self.attribute_list, self.ordinal,
self.typename, self.default_value)
# This needs to be declared after |StructField|.
class StructBody(NodeListBase):
@ -394,29 +429,29 @@ class Union(Definition):
def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, UnionBody)
super(Union, self).__init__(mojom_name, **kwargs)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
def __eq__(self, other):
return super(Union, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.body == other.body
class UnionField(Definition):
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
assert _IsStrOrUnicode(mojom_name)
assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert _IsStrOrUnicode(typename)
super(UnionField, self).__init__(mojom_name, **kwargs)
assert isinstance(typename, str)
super().__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
def __eq__(self, other):
return super(UnionField, self).__eq__(other) and \
return super().__eq__(other) and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.typename == other.typename

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
# found in the LICENSE file.
import imp
import os.path
import sys
import unittest
from mojom.parse import ast
class _TestNode(ast.NodeBase):
"""Node type for tests."""
def __init__(self, value, **kwargs):
super(_TestNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.value = value
def __eq__(self, other):
return super(_TestNode, self).__eq__(other) and self.value == other.value
return super().__eq__(other) and self.value == other.value
class _TestNodeList(ast.NodeListBase):
"""Node list type for tests."""
_list_item_type = _TestNode
class ASTTest(unittest.TestCase):
"""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
# found in the LICENSE file.
"""Helpers for processing conditionally enabled features in a mojom."""
@ -17,8 +17,10 @@ class EnableIfError(Error):
def _IsEnabled(definition, enabled_features):
"""Returns true if a definition is enabled.
A definition is enabled if it has no EnableIf attribute, or if the value of
the EnableIf attribute is in enabled_features.
A definition is enabled if it has no EnableIf/EnableIfNot attribute.
It is retained if it has an EnableIf attribute and the attribute is in
enabled_features. It is retained if it has an EnableIfNot attribute and the
attribute is not in enabled features.
"""
if not hasattr(definition, "attribute_list"):
return True
@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
already_defined = False
for a in definition.attribute_list:
if a.key == 'EnableIf':
if a.key == 'EnableIf' or a.key == 'EnableIfNot':
if already_defined:
raise EnableIfError(
definition.filename,
"EnableIf attribute may only be defined once per field.",
"EnableIf/EnableIfNot attribute may only be set once per field.",
definition.lineno)
already_defined = True
for attribute in definition.attribute_list:
if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
return False
if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
return False
return True
@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):
"""Filters definitions with a body."""
if isinstance(definition, ast.Enum):
_FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
elif isinstance(definition, ast.Interface):
_FilterDisabledFromNodeList(definition.body, enabled_features)
elif isinstance(definition, ast.Method):
_FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
_FilterDisabledFromNodeList(definition.response_parameter_list,
enabled_features)
elif isinstance(definition, ast.Struct):
_FilterDisabledFromNodeList(definition.body, enabled_features)
elif isinstance(definition, ast.Union):
elif isinstance(definition,
(ast.Interface, ast.Struct, ast.Union, ast.Feature)):
_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
# found in the LICENSE file.
import imp
import importlib.util
import os
import sys
import unittest
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
try:
imp.find_module('mojom')
importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
import mojom.parse.ast as ast
@ -29,7 +27,6 @@ import mojom.parse.parser as parser
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
class ConditionalFeaturesTest(unittest.TestCase):
"""Tests |mojom.parse.conditional_features|."""
@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(const_source, expected_source)
def testFilterIfNotConst(self):
"""Test that Consts are correctly filtered."""
const_source = """
[EnableIfNot=blue]
const int kMyConst1 = 1;
[EnableIfNot=orange]
const double kMyConst2 = 2;
[EnableIf=blue]
const int kMyConst3 = 3;
[EnableIfNot=blue]
const int kMyConst4 = 4;
[EnableIfNot=purple]
const int kMyConst5 = 5;
"""
expected_source = """
[EnableIfNot=orange]
const double kMyConst2 = 2;
[EnableIf=blue]
const int kMyConst3 = 3;
[EnableIfNot=purple]
const int kMyConst5 = 5;
"""
self.parseAndAssertEqual(const_source, expected_source)
def testFilterIfNotMultipleConst(self):
"""Test that Consts are correctly filtered."""
const_source = """
[EnableIfNot=blue]
const int kMyConst1 = 1;
[EnableIfNot=orange]
const double kMyConst2 = 2;
[EnableIfNot=orange]
const int kMyConst3 = 3;
"""
expected_source = """
[EnableIfNot=orange]
const double kMyConst2 = 2;
[EnableIfNot=orange]
const int kMyConst3 = 3;
"""
self.parseAndAssertEqual(const_source, expected_source)
def testFilterEnum(self):
"""Test that EnumValues are correctly filtered from an Enum."""
enum_source = """
@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(import_source, expected_source)
def testFilterIfNotImport(self):
"""Test that imports are correctly filtered from a Mojom."""
import_source = """
[EnableIf=blue]
import "foo.mojom";
[EnableIfNot=purple]
import "bar.mojom";
[EnableIfNot=green]
import "baz.mojom";
"""
expected_source = """
[EnableIf=blue]
import "foo.mojom";
[EnableIfNot=purple]
import "bar.mojom";
"""
self.parseAndAssertEqual(import_source, expected_source)
def testFilterInterface(self):
"""Test that definitions are correctly filtered from an Interface."""
interface_source = """
@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
self.parseAndAssertEqual(struct_source, expected_source)
def testFilterIfNotStruct(self):
"""Test that definitions are correctly filtered from a Struct."""
struct_source = """
struct MyStruct {
[EnableIf=blue]
enum MyEnum {
VALUE1,
[EnableIfNot=red]
VALUE2,
};
[EnableIfNot=yellow]
const double kMyConst = 1.23;
[EnableIf=green]
int32 a;
double b;
[EnableIfNot=purple]
int32 c;
[EnableIf=blue]
double d;
int32 e;
[EnableIfNot=red]
double f;
};
"""
expected_source = """
struct MyStruct {
[EnableIf=blue]
enum MyEnum {
VALUE1,
};
[EnableIfNot=yellow]
const double kMyConst = 1.23;
[EnableIf=green]
int32 a;
double b;
[EnableIfNot=purple]
int32 c;
[EnableIf=blue]
double d;
int32 e;
};
"""
self.parseAndAssertEqual(struct_source, expected_source)
def testFilterUnion(self):
"""Test that UnionFields are correctly filtered from a Union."""
union_source = """
@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):
"""
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):
source = """
enum Foo {
@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):
conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES)
def testMultipleEnableIfs(self):
source = """
enum Foo {
[EnableIf=red,EnableIfNot=yellow]
kBarValue = 5,
};
"""
definition = parser.Parse(source, "my_file.mojom")
self.assertRaises(conditional_features.EnableIfError,
conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES)
def testMultipleEnableIfs(self):
source = """
enum Foo {
[EnableIfNot=red,EnableIfNot=yellow]
kBarValue = 5,
};
"""
definition = parser.Parse(source, "my_file.mojom")
self.assertRaises(conditional_features.EnableIfError,
conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES)
if __name__ == '__main__':
unittest.main()

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

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
# found in the LICENSE file.
import imp
import importlib.util
import os.path
import sys
import unittest
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex
try:
imp.find_module("mojom")
importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.lexer
# 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
# for object identity.)
@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):
self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
self.assertEquals(
self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
self.assertEquals(
self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
self.assertEquals(

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
# found in the LICENSE 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 sys
@ -33,7 +36,7 @@ class ParseError(Error):
# We have methods which look like they could be functions:
# pylint: disable=R0201
class Parser(object):
class Parser:
def __init__(self, lexer, source, filename):
self.tokens = lexer.tokens
self.source = source
@ -111,7 +114,8 @@ class Parser(object):
| union
| interface
| enum
| const"""
| const
| feature"""
p[0] = p[1]
def p_attribute_section_1(self, p):
@ -140,12 +144,19 @@ class Parser(object):
p[0].Append(p[3])
def p_attribute_1(self, p):
"""attribute : NAME EQUALS evaled_literal
| NAME EQUALS NAME"""
p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
"""attribute : name_wrapped EQUALS identifier_wrapped"""
p[0] = ast.Attribute(p[1],
p[3][1],
filename=self.filename,
lineno=p.lineno(1))
def p_attribute_2(self, p):
"""attribute : NAME"""
"""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))
def p_evaled_literal(self, p):
@ -161,11 +172,11 @@ class Parser(object):
p[0] = eval(p[1])
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])
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)
def p_struct_body_1(self, p):
@ -180,11 +191,24 @@ class Parser(object):
p[0].Append(p[2])
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])
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):
"""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])
def p_union_body_1(self, p):
@ -197,7 +221,7 @@ class Parser(object):
p[1].Append(p[2])
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])
def p_default_1(self, p):
@ -209,8 +233,7 @@ class Parser(object):
p[0] = p[2]
def p_interface(self, p):
"""interface : attribute_section INTERFACE NAME LBRACE interface_body \
RBRACE SEMI"""
"""interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI"""
p[0] = ast.Interface(p[3], p[1], p[5])
def p_interface_body_1(self, p):
@ -233,8 +256,7 @@ class Parser(object):
p[0] = p[3]
def p_method(self, p):
"""method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \
response SEMI"""
"""method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI"""
p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
def p_parameter_list_1(self, p):
@ -255,7 +277,7 @@ class Parser(object):
p[0].Append(p[3])
def p_parameter(self, p):
"""parameter : attribute_section typename NAME ordinal"""
"""parameter : attribute_section typename name_wrapped ordinal"""
p[0] = ast.Parameter(
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
| array
| fixed_array
| associative_array
| interfacerequest"""
| associative_array"""
p[0] = p[1]
def p_basictypename(self, p):
@ -297,18 +318,16 @@ class Parser(object):
p[0] = "rcv<%s>" % p[3]
def p_associatedremotetype(self, p):
"""associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \
RANGLE"""
"""associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE"""
p[0] = "rma<%s>" % p[3]
def p_associatedreceivertype(self, p):
"""associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \
RANGLE"""
"""associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE"""
p[0] = "rca<%s>" % p[3]
def p_handletype(self, p):
"""handletype : HANDLE
| HANDLE LANGLE NAME RANGLE"""
| HANDLE LANGLE name_wrapped RANGLE"""
if len(p) == 2:
p[0] = p[1]
else:
@ -342,14 +361,6 @@ class Parser(object):
"""associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
p[0] = p[5] + "{" + p[3] + "}"
def p_interfacerequest(self, p):
"""interfacerequest : identifier AMP
| ASSOCIATED identifier AMP"""
if len(p) == 3:
p[0] = p[1] + "&"
else:
p[0] = "asso<" + p[2] + "&>"
def p_ordinal_1(self, p):
"""ordinal : """
p[0] = None
@ -366,15 +377,14 @@ class Parser(object):
p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
def p_enum_1(self, p):
"""enum : attribute_section ENUM NAME LBRACE enum_value_list \
RBRACE SEMI
| attribute_section ENUM NAME LBRACE nonempty_enum_value_list \
COMMA RBRACE SEMI"""
"""enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI
| attribute_section ENUM name_wrapped LBRACE \
nonempty_enum_value_list COMMA RBRACE SEMI"""
p[0] = ast.Enum(
p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))
def p_enum_2(self, p):
"""enum : attribute_section ENUM NAME SEMI"""
"""enum : attribute_section ENUM name_wrapped SEMI"""
p[0] = ast.Enum(
p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))
@ -396,9 +406,9 @@ class Parser(object):
p[0].Append(p[3])
def p_enum_value(self, p):
"""enum_value : attribute_section NAME
| attribute_section NAME EQUALS int
| attribute_section NAME EQUALS identifier_wrapped"""
"""enum_value : attribute_section name_wrapped
| attribute_section name_wrapped EQUALS int
| attribute_section name_wrapped EQUALS identifier_wrapped"""
p[0] = ast.EnumValue(
p[2],
p[1],
@ -407,7 +417,7 @@ class Parser(object):
lineno=p.lineno(2))
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])
def p_constant(self, p):
@ -422,10 +432,16 @@ class Parser(object):
# TODO(vtl): Make this produce a "wrapped" identifier (probably as an
# |ast.Identifier|, to be added) and get rid of identifier_wrapped.
def p_identifier(self, p):
"""identifier : NAME
| NAME DOT identifier"""
"""identifier : name_wrapped
| name_wrapped DOT identifier"""
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):
"""literal : int
| float
@ -458,6 +474,12 @@ class Parser(object):
# TODO(vtl): Can we figure out what's missing?
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(
self.filename,
"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
# found in the LICENSE file.
import imp
import os.path
import sys
import unittest
from mojom.parse import ast
from mojom.parse import lexer
from mojom.parse import parser
class ParserTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):
handle<data_pipe_producer>? k;
handle<message_pipe>? l;
handle<shared_buffer>? m;
some_interface&? n;
pending_receiver<some_interface>? n;
handle<platform>? o;
};
"""
@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):
ast.StructField('l', None, None, 'handle<message_pipe>?', None),
ast.StructField('m', None, None, 'handle<shared_buffer>?',
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)
]))
])
@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):
r" *handle\?<data_pipe_consumer> a;$"):
parser.Parse(source2, "my_file.mojom")
source3 = """\
struct MyStruct {
some_interface?& a;
};
"""
with self.assertRaisesRegexp(
parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
r" *some_interface\?& a;$"):
parser.Parse(source3, "my_file.mojom")
def testSimpleUnion(self):
"""Tests a simple .mojom source that just defines a union."""
source = """\
@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):
source1 = """\
struct MyStruct {
associated MyInterface a;
associated MyInterface& b;
pending_associated_receiver<MyInterface> b;
associated MyInterface? c;
associated MyInterface&? d;
pending_associated_receiver<MyInterface>? d;
};
"""
expected1 = ast.Mojom(None, ast.ImportList(), [
@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):
'MyStruct', None,
ast.StructBody([
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('d', None, None, 'asso<MyInterface&>?', None)
ast.StructField('d', None, None, 'rca<MyInterface>?', None)
]))
])
self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
source2 = """\
interface MyInterface {
MyMethod(associated A a) =>(associated B& b);
MyMethod(associated A a) =>(pending_associated_receiver<B> b);
};"""
expected2 = ast.Mojom(None, ast.ImportList(), [
ast.Interface(
@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):
ast.InterfaceBody(
ast.Method(
'MyMethod', None, None,
ast.ParameterList(
ast.Parameter('a', None, None, 'asso<A>')),
ast.ParameterList(
ast.Parameter('b', None, None, 'asso<B&>')))))
ast.ParameterList(ast.Parameter('a', None, None,
'asso<A>')),
ast.ParameterList(ast.Parameter('b', None, None,
'rca<B>')))))
])
self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):
r" *associated\? MyInterface& a;$"):
parser.Parse(source3, "my_file.mojom")
if __name__ == "__main__":
unittest.main()

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Parses mojom IDL files.
@ -11,6 +11,7 @@ generate usable language bindings.
"""
import argparse
import builtins
import codecs
import errno
import json
@ -19,6 +20,7 @@ import multiprocessing
import os
import os.path
import sys
import traceback
from collections import defaultdict
from mojom.generate import module
@ -28,16 +30,12 @@ from mojom.parse import conditional_features
# Disable this for easier debugging.
# In Python 2, subprocesses just hang when exceptions are thrown :(.
_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
_ENABLE_MULTIPROCESSING = True
if sys.version_info < (3, 4):
_MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
else:
# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
if __name__ == '__main__' and sys.platform == 'darwin':
# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
if __name__ == '__main__' and sys.platform == 'darwin':
multiprocessing.set_start_method('fork')
_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
def _ResolveRelativeImportPath(path, roots):
@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
raise ValueError('"%s" does not exist in any of %s' % (path, roots))
def _RebaseAbsolutePath(path, roots):
def RebaseAbsolutePath(path, roots):
"""Rewrites an absolute file path as relative to an absolute directory path in
roots.
@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
# Already done.
return
for dep_abspath, dep_path in dependencies[mojom_abspath]:
for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
if dep_abspath not in loaded_modules:
_EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
loaded_modules, module_metadata)
@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
def collect(metadata_filename):
processed_deps.add(metadata_filename)
# Paths in the metadata file are relative to the metadata file's dir.
metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
def to_abs(s):
return os.path.normpath(os.path.join(metadata_dir, s))
with open(metadata_filename) as f:
metadata = json.load(f)
allowed_imports.update(
map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
[os.path.normcase(to_abs(s)) for s in metadata['sources']])
for dep_metadata in metadata['deps']:
dep_metadata = to_abs(dep_metadata)
if dep_metadata not in processed_deps:
collect(dep_metadata)
@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
# multiprocessing helper.
def _ParseAstHelper(args):
mojom_abspath, enabled_features = args
def _ParseAstHelper(mojom_abspath, enabled_features):
with codecs.open(mojom_abspath, encoding='utf-8') as f:
ast = parser.Parse(f.read(), mojom_abspath)
conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
@ -181,8 +186,7 @@ def _ParseAstHelper(args):
# multiprocessing helper.
def _SerializeHelper(args):
mojom_abspath, mojom_path = args
def _SerializeHelper(mojom_abspath, mojom_path):
module_path = os.path.join(_SerializeHelper.output_root_path,
_GetModuleFilename(mojom_path))
module_dir = os.path.dirname(module_path)
@ -199,12 +203,33 @@ def _SerializeHelper(args):
_SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
def _Shard(target_func, args, processes=None):
args = list(args)
class _ExceptionWrapper:
def __init__(self):
# Do not capture exception object to ensure pickling works.
self.formatted_trace = traceback.format_exc()
class _FuncWrapper:
"""Marshals exceptions and spreads args."""
def __init__(self, func):
self._func = func
def __call__(self, args):
# multiprocessing does not gracefully handle excptions.
# https://crbug.com/1219044
try:
return self._func(*args)
except: # pylint: disable=bare-except
return _ExceptionWrapper()
def _Shard(target_func, arg_list, processes=None):
arg_list = list(arg_list)
if processes is None:
processes = multiprocessing.cpu_count()
# Seems optimal to have each process perform at least 2 tasks.
processes = min(processes, len(args) // 2)
processes = min(processes, len(arg_list) // 2)
if sys.platform == 'win32':
# TODO(crbug.com/1190269) - we can't use more than 56
@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
# Don't spin up processes unless there is enough work to merit doing so.
if not _ENABLE_MULTIPROCESSING or processes < 2:
for result in map(target_func, args):
yield result
for arg_tuple in arg_list:
yield target_func(*arg_tuple)
return
pool = multiprocessing.Pool(processes=processes)
try:
for result in pool.imap_unordered(target_func, args):
wrapped_func = _FuncWrapper(target_func)
for result in pool.imap_unordered(wrapped_func, arg_list):
if isinstance(result, _ExceptionWrapper):
sys.stderr.write(result.formatted_trace)
sys.exit(1)
yield result
finally:
pool.close()
@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
def _ParseMojoms(mojom_files,
input_root_paths,
output_root_path,
module_root_paths,
enabled_features,
module_metadata,
allowed_imports=None):
@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
are based on the mojom's relative path, rebased onto this path.
Additionally, the script expects this root to contain already-generated
modules for any transitive dependencies not listed in mojom_files.
module_root_paths: A list of absolute filesystem paths which contain
already-generated modules for any non-transitive dependencies.
enabled_features: A list of enabled feature names, controlling which AST
nodes are filtered by [EnableIf] attributes.
nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
module_metadata: A list of 2-tuples representing metadata key-value pairs to
attach to each compiled module output.
@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
loaded_modules = {}
input_dependencies = defaultdict(set)
mojom_files_to_parse = dict((os.path.normcase(abs_path),
_RebaseAbsolutePath(abs_path, input_root_paths))
RebaseAbsolutePath(abs_path, input_root_paths))
for abs_path in mojom_files)
abs_paths = dict(
(path, abs_path) for abs_path, path in mojom_files_to_parse.items())
@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
loaded_mojom_asts[mojom_abspath] = ast
logging.info('Processing dependencies')
for mojom_abspath, ast in loaded_mojom_asts.items():
for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
invalid_imports = []
for imp in ast.import_list:
import_abspath = _ResolveRelativeImportPath(imp.import_filename,
@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
# be parsed and have a module file sitting in a corresponding output
# location.
module_path = _GetModuleFilename(imp.import_filename)
module_abspath = _ResolveRelativeImportPath(module_path,
[output_root_path])
module_abspath = _ResolveRelativeImportPath(
module_path, module_root_paths + [output_root_path])
with open(module_abspath, 'rb') as module_file:
loaded_modules[import_abspath] = module.Module.Load(module_file)
@ -370,6 +402,15 @@ already present in the provided output root.""")
'based on the relative input path, rebased onto this root. Note that '
'ROOT is also searched for existing modules of any transitive imports '
'which were not included in the set of inputs.')
arg_parser.add_argument(
'--module-root',
default=[],
action='append',
metavar='ROOT',
dest='module_root_paths',
help='Adds ROOT to the set of root paths to search for existing modules '
'of non-transitive imports. Provided root paths are always searched in '
'order from longest absolute path to shortest.')
arg_parser.add_argument(
'--mojoms',
nargs='+',
@ -396,9 +437,9 @@ already present in the provided output root.""")
help='Enables a named feature when parsing the given mojoms. Features '
'are identified by arbitrary string values. Specifying this flag with a '
'given FEATURE name will cause the parser to process any syntax elements '
'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
'provided for a given FEATURE, such tagged elements are discarded by the '
'parser and will not be present in the compiled output.')
'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
'flag is not provided for a given FEATURE, such tagged elements are '
'discarded by the parser and will not be present in the compiled output.')
arg_parser.add_argument(
'--check-imports',
dest='build_metadata_filename',
@ -436,6 +477,7 @@ already present in the provided output root.""")
mojom_files = list(map(os.path.abspath, args.mojom_files))
input_roots = list(map(os.path.abspath, args.input_root_paths))
output_root = os.path.abspath(args.output_root_path)
module_roots = list(map(os.path.abspath, args.module_root_paths))
if args.build_metadata_filename:
allowed_imports = _CollectAllowedImportsFromBuildMetadata(
@ -445,13 +487,16 @@ already present in the provided output root.""")
module_metadata = list(
map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
_ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
module_metadata, allowed_imports)
_ParseMojoms(mojom_files, input_roots, output_root, module_roots,
args.enabled_features, module_metadata, allowed_imports)
logging.info('Finished')
# Exit without running GC, which can save multiple seconds due the large
# number of object created.
os._exit(0)
if __name__ == '__main__':
Run(sys.argv[1:])
# Exit without running GC, which can save multiple seconds due to the large
# number of object created. But flush is necessary as os._exit doesn't do
# that.
sys.stdout.flush()
sys.stderr.flush()
os._exit(0)

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
# found in the LICENSE file.
@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
resolution, and module serialization and deserialization."""
def __init__(self, method_name):
super(MojomParserTestCase, self).__init__(method_name)
super().__init__(method_name)
self._temp_dir = None
def setUp(self):
@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):
self.ParseMojoms([filename])
m = self.LoadModule(filename)
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:
definitions[kind.mojom_name] = kind
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
# found in the LICENSE file.
import json
from mojom_parser_test_case import MojomParserTestCase
@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
c = 'c.mojom'
c_metadata = 'out/c.build_metadata'
self.WriteFile(a_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
json.dumps({
"sources": [self.GetPath(a)],
"deps": []
}))
self.WriteFile(
b_metadata,
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
self.GetPath(a_metadata)))
json.dumps({
"sources": [self.GetPath(b)],
"deps": [self.GetPath(a_metadata)]
}))
self.WriteFile(
c_metadata,
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
self.GetPath(b_metadata)))
json.dumps({
"sources": [self.GetPath(c)],
"deps": [self.GetPath(b_metadata)]
}))
self.WriteFile(a, """\
module a;
struct Bar {};""")
@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
b = 'b.mojom'
b_metadata = 'out/b.build_metadata'
self.WriteFile(a_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
json.dumps({
"sources": [self.GetPath(a)],
"deps": []
}))
self.WriteFile(b_metadata,
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
json.dumps({
"sources": [self.GetPath(b)],
"deps": []
}))
self.WriteFile(a, """\
module a;
struct Bar {};""")

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

View file

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

View file

@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
Files in this directory are imported from 9c138d992bfc of Chromium. Do not
Files in this directory are imported from 9be4263648d7 of Chromium. Do not
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
# found in the LICENSE file.