diff options
Diffstat (limited to 'utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py')
-rw-r--r-- | utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py | 1635 |
1 files changed, 1635 insertions, 0 deletions
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py new file mode 100644 index 00000000..8547ff64 --- /dev/null +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py @@ -0,0 +1,1635 @@ +# Copyright 2013 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. + +# This module's classes provide an interface to mojo modules. Modules are +# collections of interfaces and structs to be used by mojo ipc clients and +# servers. +# +# A simple interface would be created this way: +# module = mojom.generate.module.Module('Foo') +# interface = module.AddInterface('Bar') +# method = interface.AddMethod('Tat', 0) +# method.AddParameter('baz', 0, mojom.INT32) + +import pickle + +# We use our own version of __repr__ when displaying the AST, as the +# AST currently doesn't capture which nodes are reference (e.g. to +# types) and which nodes are definitions. This allows us to e.g. print +# the definition of a struct when it's defined inside a module, but +# only print its name when it's referenced in e.g. a method parameter. +def Repr(obj, as_ref=True): + """A version of __repr__ that can distinguish references. + + Sometimes we like to print an object's full representation + (e.g. with its fields) and sometimes we just want to reference an + object that was printed in full elsewhere. This function allows us + to make that distinction. + + Args: + obj: The object whose string representation we compute. + as_ref: If True, use the short reference representation. + + Returns: + A str representation of |obj|. + """ + if hasattr(obj, 'Repr'): + return obj.Repr(as_ref=as_ref) + # Since we cannot implement Repr for existing container types, we + # handle them here. + elif isinstance(obj, list): + if not obj: + return '[]' + else: + return ('[\n%s\n]' % (',\n'.join( + ' %s' % Repr(elem, as_ref).replace('\n', '\n ') + for elem in obj))) + elif isinstance(obj, dict): + if not obj: + return '{}' + else: + return ('{\n%s\n}' % (',\n'.join( + ' %s: %s' % (Repr(key, as_ref).replace('\n', '\n '), + Repr(val, as_ref).replace('\n', '\n ')) + for key, val in obj.items()))) + else: + return repr(obj) + + +def GenericRepr(obj, names): + """Compute generic Repr for |obj| based on the attributes in |names|. + + Args: + obj: The object to compute a Repr for. + names: A dict from attribute names to include, to booleans + specifying whether those attributes should be shown as + references or not. + + Returns: + A str representation of |obj|. + """ + + def ReprIndent(name, as_ref): + return ' %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace( + '\n', '\n ')) + + return '%s(\n%s\n)' % (obj.__class__.__name__, ',\n'.join( + ReprIndent(name, as_ref) for (name, as_ref) in names.items())) + + +class Kind(object): + """Kind represents a type (e.g. int8, string). + + Attributes: + spec: A string uniquely identifying the type. May be None. + module: {Module} The defining module. Set to None for built-in types. + parent_kind: The enclosing type. For example, an enum defined + inside an interface has that interface as its parent. May be None. + """ + + def __init__(self, spec=None, module=None): + self.spec = spec + self.module = module + self.parent_kind = None + + def Repr(self, as_ref=True): + # pylint: disable=unused-argument + return '<%s spec=%r>' % (self.__class__.__name__, self.spec) + + def __repr__(self): + # Gives us a decent __repr__ for all kinds. + return self.Repr() + + def __eq__(self, rhs): + # pylint: disable=unidiomatic-typecheck + return (type(self) == type(rhs) + and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind)) + + def __hash__(self): + # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind + # and its subclasses. This is to support existing generator code which uses + # some primitive Kinds as dict keys. The default hash (object identity) + # breaks these dicts when a pickled Module instance is unpickled and used + # during a subsequent run of the parser. + return hash((self.spec, self.parent_kind)) + + +class ReferenceKind(Kind): + """ReferenceKind represents pointer and handle types. + + A type is nullable if null (for pointer types) or invalid handle (for handle + types) is a legal value for the type. + + Attributes: + is_nullable: True if the type is nullable. + """ + + def __init__(self, spec=None, is_nullable=False, module=None): + assert spec is None or is_nullable == spec.startswith('?') + Kind.__init__(self, spec, module) + self.is_nullable = is_nullable + self.shared_definition = {} + + def Repr(self, as_ref=True): + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, + self.is_nullable) + + def MakeNullableKind(self): + assert not self.is_nullable + + if self == STRING: + return NULLABLE_STRING + if self == HANDLE: + return NULLABLE_HANDLE + if self == DCPIPE: + return NULLABLE_DCPIPE + if self == DPPIPE: + return NULLABLE_DPPIPE + if self == MSGPIPE: + return NULLABLE_MSGPIPE + if self == SHAREDBUFFER: + return NULLABLE_SHAREDBUFFER + if self == PLATFORMHANDLE: + return NULLABLE_PLATFORMHANDLE + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = '?' + self.spec + nullable_kind.is_nullable = True + nullable_kind.parent_kind = self.parent_kind + nullable_kind.module = self.module + + return nullable_kind + + @classmethod + def AddSharedProperty(cls, name): + """Adds a property |name| to |cls|, which accesses the corresponding item in + |shared_definition|. + + The reason of adding such indirection is to enable sharing definition + between a reference kind and its nullable variation. For example: + a = Struct('test_struct_1') + b = a.MakeNullableKind() + a.name = 'test_struct_2' + print(b.name) # Outputs 'test_struct_2'. + """ + + def Get(self): + try: + return self.shared_definition[name] + except KeyError: # Must raise AttributeError if property doesn't exist. + raise AttributeError + + def Set(self, value): + self.shared_definition[name] = value + + setattr(cls, name, property(Get, Set)) + + def __eq__(self, rhs): + return (isinstance(rhs, ReferenceKind) + and super(ReferenceKind, self).__eq__(rhs) + and self.is_nullable == rhs.is_nullable) + + def __hash__(self): + return hash((super(ReferenceKind, self).__hash__(), self.is_nullable)) + + +# Initialize the set of primitive types. These can be accessed by clients. +BOOL = Kind('b') +INT8 = Kind('i8') +INT16 = Kind('i16') +INT32 = Kind('i32') +INT64 = Kind('i64') +UINT8 = Kind('u8') +UINT16 = Kind('u16') +UINT32 = Kind('u32') +UINT64 = Kind('u64') +FLOAT = Kind('f') +DOUBLE = Kind('d') +STRING = ReferenceKind('s') +HANDLE = ReferenceKind('h') +DCPIPE = ReferenceKind('h:d:c') +DPPIPE = ReferenceKind('h:d:p') +MSGPIPE = ReferenceKind('h:m') +SHAREDBUFFER = ReferenceKind('h:s') +PLATFORMHANDLE = ReferenceKind('h:p') +NULLABLE_STRING = ReferenceKind('?s', True) +NULLABLE_HANDLE = ReferenceKind('?h', True) +NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True) +NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True) +NULLABLE_MSGPIPE = ReferenceKind('?h:m', True) +NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True) +NULLABLE_PLATFORMHANDLE = ReferenceKind('?h:p', True) + +# Collection of all Primitive types +PRIMITIVES = ( + BOOL, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + FLOAT, + DOUBLE, + STRING, + HANDLE, + DCPIPE, + DPPIPE, + MSGPIPE, + SHAREDBUFFER, + PLATFORMHANDLE, + NULLABLE_STRING, + NULLABLE_HANDLE, + NULLABLE_DCPIPE, + NULLABLE_DPPIPE, + NULLABLE_MSGPIPE, + NULLABLE_SHAREDBUFFER, + NULLABLE_PLATFORMHANDLE, +) + +ATTRIBUTE_MIN_VERSION = 'MinVersion' +ATTRIBUTE_EXTENSIBLE = 'Extensible' +ATTRIBUTE_STABLE = 'Stable' +ATTRIBUTE_SYNC = 'Sync' + + +class NamedValue(object): + def __init__(self, module, parent_kind, mojom_name): + self.module = module + self.parent_kind = parent_kind + self.mojom_name = mojom_name + + def GetSpec(self): + return (self.module.GetNamespacePrefix() + + (self.parent_kind and + (self.parent_kind.mojom_name + '.') or "") + self.mojom_name) + + def __eq__(self, rhs): + return (isinstance(rhs, NamedValue) + and (self.parent_kind, self.mojom_name) == (rhs.parent_kind, + rhs.mojom_name)) + + +class BuiltinValue(object): + def __init__(self, value): + self.value = value + + def __eq__(self, rhs): + return isinstance(rhs, BuiltinValue) and self.value == rhs.value + + +class ConstantValue(NamedValue): + def __init__(self, module, parent_kind, constant): + NamedValue.__init__(self, module, parent_kind, constant.mojom_name) + self.constant = constant + + @property + def name(self): + return self.constant.name + + +class EnumValue(NamedValue): + def __init__(self, module, enum, field): + NamedValue.__init__(self, module, enum.parent_kind, field.mojom_name) + self.field = field + self.enum = enum + + def GetSpec(self): + return (self.module.GetNamespacePrefix() + + (self.parent_kind and (self.parent_kind.mojom_name + '.') or "") + + self.enum.mojom_name + '.' + self.mojom_name) + + @property + def name(self): + return self.field.name + + +class Constant(object): + def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None): + self.mojom_name = mojom_name + self.name = None + self.kind = kind + self.value = value + self.parent_kind = parent_kind + + def Stylize(self, stylizer): + self.name = stylizer.StylizeConstant(self.mojom_name) + + def __eq__(self, rhs): + return (isinstance(rhs, Constant) + and (self.mojom_name, self.kind, self.value, + self.parent_kind) == (rhs.mojom_name, rhs.kind, rhs.value, + rhs.parent_kind)) + + +class Field(object): + def __init__(self, + mojom_name=None, + kind=None, + ordinal=None, + default=None, + attributes=None): + if self.__class__.__name__ == 'Field': + raise Exception() + self.mojom_name = mojom_name + self.name = None + self.kind = kind + self.ordinal = ordinal + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + # pylint: disable=unused-argument + # Fields are only referenced by objects which define them and thus + # they are always displayed as non-references. + return GenericRepr(self, {'mojom_name': False, 'kind': True}) + + def Stylize(self, stylizer): + self.name = stylizer.StylizeField(self.mojom_name) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + def __eq__(self, rhs): + return (isinstance(rhs, Field) + and (self.mojom_name, self.kind, self.ordinal, self.default, + self.attributes) == (rhs.mojom_name, rhs.kind, rhs.ordinal, + rhs.default, rhs.attributes)) + + def __hash__(self): + return hash((self.mojom_name, self.kind, self.ordinal, self.default)) + + +class StructField(Field): + pass + + +class UnionField(Field): + pass + + +def _IsFieldBackwardCompatible(new_field, old_field): + if (new_field.min_version or 0) != (old_field.min_version or 0): + return False + + if isinstance(new_field.kind, (Enum, Struct, Union)): + return new_field.kind.IsBackwardCompatible(old_field.kind) + + return new_field.kind == old_field.kind + + +class Struct(ReferenceKind): + """A struct with typed fields. + + Attributes: + mojom_name: {str} The name of the struct type as defined in mojom. + name: {str} The stylized name. + native_only: {bool} Does the struct have a body (i.e. any fields) or is it + purely a native struct. + custom_serializer: {bool} Should we generate a serializer for the struct or + will one be provided by non-generated code. + fields: {List[StructField]} The members of the struct. + enums: {List[Enum]} The enums defined in the struct scope. + constants: {List[Constant]} The constants defined in the struct scope. + attributes: {dict} Additional information about the struct, such as + if it's a native struct. + """ + + ReferenceKind.AddSharedProperty('mojom_name') + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('native_only') + ReferenceKind.AddSharedProperty('custom_serializer') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('enums') + ReferenceKind.AddSharedProperty('constants') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, mojom_name=None, module=None, attributes=None): + if mojom_name is not None: + spec = 'x:' + mojom_name + else: + spec = None + ReferenceKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.native_only = False + self.custom_serializer = False + self.fields = [] + self.enums = [] + self.constants = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__, + self.mojom_name, + Repr(self.module, as_ref=True)) + else: + return GenericRepr(self, { + 'mojom_name': False, + 'fields': False, + 'module': True + }) + + def AddField(self, + mojom_name, + kind, + ordinal=None, + default=None, + attributes=None): + field = StructField(mojom_name, kind, ordinal, default, attributes) + self.fields.append(field) + return field + + def Stylize(self, stylizer): + self.name = stylizer.StylizeStruct(self.mojom_name) + for field in self.fields: + field.Stylize(stylizer) + for enum in self.enums: + enum.Stylize(stylizer) + for constant in self.constants: + constant.Stylize(stylizer) + + def IsBackwardCompatible(self, older_struct): + """This struct is backward-compatible with older_struct if and only if all + of the following conditions hold: + - Any newly added field is tagged with a [MinVersion] attribute specifying + a version number greater than all previously used [MinVersion] + attributes within the struct. + - All fields present in older_struct remain present in the new struct, + with the same ordinal position, same optional or non-optional status, + same (or backward-compatible) type and where applicable, the same + [MinVersion] attribute value. + - All [MinVersion] attributes must be non-decreasing in ordinal order. + - All reference-typed (string, array, map, struct, or union) fields tagged + with a [MinVersion] greater than zero must be optional. + """ + + def buildOrdinalFieldMap(struct): + fields_by_ordinal = {} + for field in struct.fields: + if field.ordinal in fields_by_ordinal: + raise Exception('Multiple fields with ordinal %s in struct %s.' % + (field.ordinal, struct.mojom_name)) + fields_by_ordinal[field.ordinal] = field + return fields_by_ordinal + + new_fields = buildOrdinalFieldMap(self) + old_fields = buildOrdinalFieldMap(older_struct) + if len(new_fields) < len(old_fields): + # At least one field was removed, which is not OK. + return False + + # If there are N fields, existing ordinal values must exactly cover the + # range from 0 to N-1. + num_old_ordinals = len(old_fields) + max_old_min_version = 0 + for ordinal in range(num_old_ordinals): + new_field = new_fields[ordinal] + old_field = old_fields[ordinal] + if (old_field.min_version or 0) > max_old_min_version: + max_old_min_version = old_field.min_version + if not _IsFieldBackwardCompatible(new_field, old_field): + # Type or min-version mismatch between old and new versions of the same + # ordinal field. + return False + + # At this point we know all old fields are intact in the new struct + # definition. Now verify that all new fields have a high enough min version + # and are appropriately optional where required. + num_new_ordinals = len(new_fields) + last_min_version = max_old_min_version + for ordinal in range(num_old_ordinals, num_new_ordinals): + new_field = new_fields[ordinal] + min_version = new_field.min_version or 0 + if min_version <= max_old_min_version: + # A new field is being added to an existing version, which is not OK. + return False + if min_version < last_min_version: + # The [MinVersion] of a field cannot be lower than the [MinVersion] of + # a field with lower ordinal value. + return False + if IsReferenceKind(new_field.kind) and not IsNullableKind(new_field.kind): + # New fields whose type can be nullable MUST be nullable. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + + def __eq__(self, rhs): + return (isinstance(rhs, Struct) and + (self.mojom_name, self.native_only, self.fields, self.constants, + self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields, + rhs.constants, rhs.attributes)) + + def __hash__(self): + return id(self) + + +class Union(ReferenceKind): + """A union of several kinds. + + Attributes: + mojom_name: {str} The name of the union type as defined in mojom. + name: {str} The stylized name. + fields: {List[UnionField]} The members of the union. + attributes: {dict} Additional information about the union, such as + which Java class name to use to represent it in the generated + bindings. + """ + ReferenceKind.AddSharedProperty('mojom_name') + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, mojom_name=None, module=None, attributes=None): + if mojom_name is not None: + spec = 'x:' + mojom_name + else: + spec = None + ReferenceKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r fields=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, Repr( + self.fields)) + else: + return GenericRepr(self, {'fields': True, 'is_nullable': False}) + + def AddField(self, mojom_name, kind, ordinal=None, attributes=None): + field = UnionField(mojom_name, kind, ordinal, None, attributes) + self.fields.append(field) + return field + + def Stylize(self, stylizer): + self.name = stylizer.StylizeUnion(self.mojom_name) + for field in self.fields: + field.Stylize(stylizer) + + def IsBackwardCompatible(self, older_union): + """This union is backward-compatible with older_union if and only if all + of the following conditions hold: + - Any newly added field is tagged with a [MinVersion] attribute specifying + a version number greater than all previously used [MinVersion] + attributes within the union. + - All fields present in older_union remain present in the new union, + with the same ordinal value, same optional or non-optional status, + same (or backward-compatible) type, and where applicable, the same + [MinVersion] attribute value. + """ + + def buildOrdinalFieldMap(union): + fields_by_ordinal = {} + for field in union.fields: + if field.ordinal in fields_by_ordinal: + raise Exception('Multiple fields with ordinal %s in union %s.' % + (field.ordinal, union.mojom_name)) + fields_by_ordinal[field.ordinal] = field + return fields_by_ordinal + + new_fields = buildOrdinalFieldMap(self) + old_fields = buildOrdinalFieldMap(older_union) + if len(new_fields) < len(old_fields): + # At least one field was removed, which is not OK. + return False + + max_old_min_version = 0 + for ordinal, old_field in old_fields.items(): + new_field = new_fields.get(ordinal) + if not new_field: + # A field was removed, which is not OK. + return False + if not _IsFieldBackwardCompatible(new_field, old_field): + # An field changed its type or MinVersion, which is not OK. + return False + old_min_version = old_field.min_version or 0 + if old_min_version > max_old_min_version: + max_old_min_version = old_min_version + + new_ordinals = set(new_fields.keys()) - set(old_fields.keys()) + for ordinal in new_ordinals: + if (new_fields[ordinal].min_version or 0) <= max_old_min_version: + # New fields must use a MinVersion greater than any old fields. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + + def __eq__(self, rhs): + return (isinstance(rhs, Union) and + (self.mojom_name, self.fields, + self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes)) + + def __hash__(self): + return id(self) + + +class Array(ReferenceKind): + """An array. + + Attributes: + kind: {Kind} The type of the elements. May be None. + length: The number of elements. None if unknown. + """ + + ReferenceKind.AddSharedProperty('kind') + ReferenceKind.AddSharedProperty('length') + + def __init__(self, kind=None, length=None): + if kind is not None: + if length is not None: + spec = 'a%d:%s' % (length, kind.spec) + else: + spec = 'a:%s' % kind.spec + + ReferenceKind.__init__(self, spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + self.length = length + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % ( + self.__class__.__name__, self.spec, self.is_nullable, Repr( + self.kind), self.length) + else: + return GenericRepr(self, { + 'kind': True, + 'length': False, + 'is_nullable': False + }) + + def __eq__(self, rhs): + return (isinstance(rhs, Array) + and (self.kind, self.length) == (rhs.kind, rhs.length)) + + def __hash__(self): + return id(self) + + +class Map(ReferenceKind): + """A map. + + Attributes: + key_kind: {Kind} The type of the keys. May be None. + value_kind: {Kind} The type of the elements. May be None. + """ + ReferenceKind.AddSharedProperty('key_kind') + ReferenceKind.AddSharedProperty('value_kind') + + def __init__(self, key_kind=None, value_kind=None): + if (key_kind is not None and value_kind is not None): + ReferenceKind.__init__( + self, 'm[' + key_kind.spec + '][' + value_kind.spec + ']') + if IsNullableKind(key_kind): + raise Exception("Nullable kinds cannot be keys in maps.") + if IsAnyHandleKind(key_kind): + raise Exception("Handles cannot be keys in maps.") + if IsAnyInterfaceKind(key_kind): + raise Exception("Interfaces cannot be keys in maps.") + if IsArrayKind(key_kind): + raise Exception("Arrays cannot be keys in maps.") + else: + ReferenceKind.__init__(self) + + self.key_kind = key_kind + self.value_kind = value_kind + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, + Repr(self.key_kind), Repr(self.value_kind)) + else: + return GenericRepr(self, {'key_kind': True, 'value_kind': True}) + + def __eq__(self, rhs): + return (isinstance(rhs, Map) and + (self.key_kind, self.value_kind) == (rhs.key_kind, rhs.value_kind)) + + def __hash__(self): + return id(self) + + +class PendingRemote(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + 'pending_remote<T> requires T to be an interface type. Got %r' % + kind.spec) + ReferenceKind.__init__(self, 'rmt:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, PendingRemote) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class PendingReceiver(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + 'pending_receiver<T> requires T to be an interface type. Got %r' % + kind.spec) + ReferenceKind.__init__(self, 'rcv:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, PendingReceiver) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class PendingAssociatedRemote(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + 'pending_associated_remote<T> requires T to be an interface ' + + 'type. Got %r' % kind.spec) + ReferenceKind.__init__(self, 'rma:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, PendingAssociatedRemote) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class PendingAssociatedReceiver(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + 'pending_associated_receiver<T> requires T to be an interface' + + 'type. Got %r' % kind.spec) + ReferenceKind.__init__(self, 'rca:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, PendingAssociatedReceiver) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class InterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Interface request requires %r to be an interface." % kind.spec) + ReferenceKind.__init__(self, 'r:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, InterfaceRequest) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class AssociatedInterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, InterfaceRequest): + raise Exception( + "Associated interface request requires %r to be an interface " + "request." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind.kind if kind is not None else None + + def __eq__(self, rhs): + return isinstance(rhs, AssociatedInterfaceRequest) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class Parameter(object): + def __init__(self, + mojom_name=None, + kind=None, + ordinal=None, + default=None, + attributes=None): + self.mojom_name = mojom_name + self.name = None + self.ordinal = ordinal + self.kind = kind + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + # pylint: disable=unused-argument + return '<%s mojom_name=%r kind=%s>' % ( + self.__class__.__name__, self.mojom_name, self.kind.Repr(as_ref=True)) + + def Stylize(self, stylizer): + self.name = stylizer.StylizeParameter(self.mojom_name) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + def __eq__(self, rhs): + return (isinstance(rhs, Parameter) + and (self.mojom_name, self.ordinal, self.kind, self.default, + self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.kind, + rhs.default, rhs.attributes)) + + +class Method(object): + def __init__(self, interface, mojom_name, ordinal=None, attributes=None): + self.interface = interface + self.mojom_name = mojom_name + self.name = None + self.explicit_ordinal = ordinal + self.ordinal = ordinal + self.parameters = [] + self.param_struct = None + self.response_parameters = None + self.response_param_struct = None + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) + else: + return GenericRepr(self, { + 'mojom_name': False, + 'parameters': True, + 'response_parameters': True + }) + + def AddParameter(self, + mojom_name, + kind, + ordinal=None, + default=None, + attributes=None): + parameter = Parameter(mojom_name, kind, ordinal, default, attributes) + self.parameters.append(parameter) + return parameter + + def AddResponseParameter(self, + mojom_name, + kind, + ordinal=None, + default=None, + attributes=None): + if self.response_parameters == None: + self.response_parameters = [] + parameter = Parameter(mojom_name, kind, ordinal, default, attributes) + self.response_parameters.append(parameter) + return parameter + + def Stylize(self, stylizer): + self.name = stylizer.StylizeMethod(self.mojom_name) + for param in self.parameters: + param.Stylize(stylizer) + if self.response_parameters is not None: + for param in self.response_parameters: + param.Stylize(stylizer) + + if self.param_struct: + self.param_struct.Stylize(stylizer) + if self.response_param_struct: + self.response_param_struct.Stylize(stylizer) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + @property + def sync(self): + return self.attributes.get(ATTRIBUTE_SYNC) \ + if self.attributes else None + + def __eq__(self, rhs): + return (isinstance(rhs, Method) and + (self.mojom_name, self.ordinal, self.parameters, + self.response_parameters, + self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters, + rhs.response_parameters, rhs.attributes)) + + +class Interface(ReferenceKind): + ReferenceKind.AddSharedProperty('mojom_name') + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('methods') + ReferenceKind.AddSharedProperty('enums') + ReferenceKind.AddSharedProperty('constants') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, mojom_name=None, module=None, attributes=None): + if mojom_name is not None: + spec = 'x:' + mojom_name + else: + spec = None + ReferenceKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.methods = [] + self.enums = [] + self.constants = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) + else: + return GenericRepr(self, { + 'mojom_name': False, + 'attributes': False, + 'methods': False + }) + + def AddMethod(self, mojom_name, ordinal=None, attributes=None): + method = Method(self, mojom_name, ordinal, attributes) + self.methods.append(method) + return method + + def Stylize(self, stylizer): + self.name = stylizer.StylizeInterface(self.mojom_name) + for method in self.methods: + method.Stylize(stylizer) + for enum in self.enums: + enum.Stylize(stylizer) + for constant in self.constants: + constant.Stylize(stylizer) + + def IsBackwardCompatible(self, older_interface): + """This interface is backward-compatible with older_interface if and only + if all of the following conditions hold: + - All defined methods in older_interface (when identified by ordinal) have + backward-compatible definitions in this interface. For each method this + means: + - The parameter list is backward-compatible, according to backward- + compatibility rules for structs, where each parameter is essentially + a struct field. + - If the old method definition does not specify a reply message, the + new method definition must not specify a reply message. + - If the old method definition specifies a reply message, the new + method definition must also specify a reply message with a parameter + list that is backward-compatible according to backward-compatibility + rules for structs. + - All newly introduced methods in this interface have a [MinVersion] + attribute specifying a version greater than any method in + older_interface. + """ + + def buildOrdinalMethodMap(interface): + methods_by_ordinal = {} + for method in interface.methods: + if method.ordinal in methods_by_ordinal: + raise Exception('Multiple methods with ordinal %s in interface %s.' % + (method.ordinal, interface.mojom_name)) + methods_by_ordinal[method.ordinal] = method + return methods_by_ordinal + + new_methods = buildOrdinalMethodMap(self) + old_methods = buildOrdinalMethodMap(older_interface) + max_old_min_version = 0 + for ordinal, old_method in old_methods.items(): + new_method = new_methods.get(ordinal) + if not new_method: + # A method was removed, which is not OK. + return False + + if not new_method.param_struct.IsBackwardCompatible( + old_method.param_struct): + # The parameter list is not backward-compatible, which is not OK. + return False + + if old_method.response_param_struct is None: + if new_method.response_param_struct is not None: + # A reply was added to a message which didn't have one before, and + # this is not OK. + return False + else: + if new_method.response_param_struct is None: + # A reply was removed from a message, which is not OK. + return False + if not new_method.response_param_struct.IsBackwardCompatible( + old_method.response_param_struct): + # The new message's reply is not backward-compatible with the old + # message's reply, which is not OK. + return False + + if (old_method.min_version or 0) > max_old_min_version: + max_old_min_version = old_method.min_version + + # All the old methods are compatible with their new counterparts. Now verify + # that newly added methods are properly versioned. + new_ordinals = set(new_methods.keys()) - set(old_methods.keys()) + for ordinal in new_ordinals: + new_method = new_methods[ordinal] + if (new_method.min_version or 0) <= max_old_min_version: + # A method was added to an existing version, which is not OK. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + + def __eq__(self, rhs): + return (isinstance(rhs, Interface) + and (self.mojom_name, self.methods, self.enums, self.constants, + self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums, + rhs.constants, rhs.attributes)) + + def __hash__(self): + return id(self) + + +class AssociatedInterface(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Associated interface requires %r to be an interface." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + def __eq__(self, rhs): + return isinstance(rhs, AssociatedInterface) and self.kind == rhs.kind + + def __hash__(self): + return id(self) + + +class EnumField(object): + def __init__(self, + mojom_name=None, + value=None, + attributes=None, + numeric_value=None): + self.mojom_name = mojom_name + self.name = None + self.value = value + self.attributes = attributes + self.numeric_value = numeric_value + + def Stylize(self, stylizer): + self.name = stylizer.StylizeEnumField(self.mojom_name) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + def __eq__(self, rhs): + return (isinstance(rhs, EnumField) + and (self.mojom_name, self.value, self.attributes, + self.numeric_value) == (rhs.mojom_name, rhs.value, + rhs.attributes, rhs.numeric_value)) + + +class Enum(Kind): + def __init__(self, mojom_name=None, module=None, attributes=None): + self.mojom_name = mojom_name + self.name = None + self.native_only = False + if mojom_name is not None: + spec = 'x:' + mojom_name + else: + spec = None + Kind.__init__(self, spec, module) + self.fields = [] + self.attributes = attributes + self.min_value = None + self.max_value = None + + def Repr(self, as_ref=True): + if as_ref: + return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) + else: + return GenericRepr(self, {'mojom_name': False, 'fields': False}) + + def Stylize(self, stylizer): + self.name = stylizer.StylizeEnum(self.mojom_name) + for field in self.fields: + field.Stylize(stylizer) + + @property + def extensible(self): + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ + if self.attributes else False + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + + def IsBackwardCompatible(self, older_enum): + """This enum is backward-compatible with older_enum if and only if one of + the following conditions holds: + - Neither enum is [Extensible] and both have the exact same set of valid + numeric values. Field names and aliases for the same numeric value do + not affect compatibility. + - older_enum is [Extensible], and for every version defined by + older_enum, this enum has the exact same set of valid numeric values. + """ + + def buildVersionFieldMap(enum): + fields_by_min_version = {} + for field in enum.fields: + if field.min_version not in fields_by_min_version: + fields_by_min_version[field.min_version] = set() + fields_by_min_version[field.min_version].add(field.numeric_value) + return fields_by_min_version + + old_fields = buildVersionFieldMap(older_enum) + new_fields = buildVersionFieldMap(self) + + if new_fields.keys() != old_fields.keys() and not older_enum.extensible: + return False + + for min_version, valid_values in old_fields.items(): + if (min_version not in new_fields + or new_fields[min_version] != valid_values): + return False + + return True + + def __eq__(self, rhs): + return (isinstance(rhs, Enum) and + (self.mojom_name, self.native_only, self.fields, self.attributes, + self.min_value, + self.max_value) == (rhs.mojom_name, rhs.native_only, rhs.fields, + rhs.attributes, rhs.min_value, rhs.max_value)) + + def __hash__(self): + return id(self) + + +class Module(object): + def __init__(self, path=None, mojom_namespace=None, attributes=None): + self.path = path + self.mojom_namespace = mojom_namespace + self.namespace = None + self.structs = [] + self.unions = [] + self.interfaces = [] + self.enums = [] + self.constants = [] + self.kinds = {} + self.attributes = attributes + self.imports = [] + self.imported_kinds = {} + + def __repr__(self): + # Gives us a decent __repr__ for modules. + return self.Repr() + + def __eq__(self, rhs): + return (isinstance(rhs, Module) and + (self.path, self.attributes, self.mojom_namespace, self.imports, + self.constants, self.enums, self.structs, self.unions, + self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace, + rhs.imports, rhs.constants, rhs.enums, + rhs.structs, rhs.unions, rhs.interfaces)) + + def Repr(self, as_ref=True): + if as_ref: + return '<%s path=%r mojom_namespace=%r>' % ( + self.__class__.__name__, self.path, self.mojom_namespace) + else: + return GenericRepr( + self, { + 'path': False, + 'mojom_namespace': False, + 'attributes': False, + 'structs': False, + 'interfaces': False, + 'unions': False + }) + + def GetNamespacePrefix(self): + return '%s.' % self.mojom_namespace if self.mojom_namespace else '' + + def AddInterface(self, mojom_name, attributes=None): + interface = Interface(mojom_name, self, attributes) + self.interfaces.append(interface) + return interface + + def AddStruct(self, mojom_name, attributes=None): + struct = Struct(mojom_name, self, attributes) + self.structs.append(struct) + return struct + + def AddUnion(self, mojom_name, attributes=None): + union = Union(mojom_name, self, attributes) + self.unions.append(union) + return union + + def Stylize(self, stylizer): + self.namespace = stylizer.StylizeModule(self.mojom_namespace) + for struct in self.structs: + struct.Stylize(stylizer) + for union in self.unions: + union.Stylize(stylizer) + for interface in self.interfaces: + interface.Stylize(stylizer) + for enum in self.enums: + enum.Stylize(stylizer) + for constant in self.constants: + constant.Stylize(stylizer) + + for imported_module in self.imports: + imported_module.Stylize(stylizer) + + def Dump(self, f): + pickle.dump(self, f, 2) + + @classmethod + def Load(cls, f): + result = pickle.load(f) + assert isinstance(result, Module) + return result + + +def IsBoolKind(kind): + return kind.spec == BOOL.spec + + +def IsFloatKind(kind): + return kind.spec == FLOAT.spec + + +def IsDoubleKind(kind): + return kind.spec == DOUBLE.spec + + +def IsIntegralKind(kind): + return (kind.spec == BOOL.spec or kind.spec == INT8.spec + or kind.spec == INT16.spec or kind.spec == INT32.spec + or kind.spec == INT64.spec or kind.spec == UINT8.spec + or kind.spec == UINT16.spec or kind.spec == UINT32.spec + or kind.spec == UINT64.spec) + + +def IsStringKind(kind): + return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec + + +def IsGenericHandleKind(kind): + return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec + + +def IsDataPipeConsumerKind(kind): + return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec + + +def IsDataPipeProducerKind(kind): + return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec + + +def IsMessagePipeKind(kind): + return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec + + +def IsSharedBufferKind(kind): + return (kind.spec == SHAREDBUFFER.spec + or kind.spec == NULLABLE_SHAREDBUFFER.spec) + + +def IsPlatformHandleKind(kind): + return (kind.spec == PLATFORMHANDLE.spec + or kind.spec == NULLABLE_PLATFORMHANDLE.spec) + + +def IsStructKind(kind): + return isinstance(kind, Struct) + + +def IsUnionKind(kind): + return isinstance(kind, Union) + + +def IsArrayKind(kind): + return isinstance(kind, Array) + + +def IsInterfaceKind(kind): + return isinstance(kind, Interface) + + +def IsAssociatedInterfaceKind(kind): + return isinstance(kind, AssociatedInterface) + + +def IsInterfaceRequestKind(kind): + return isinstance(kind, InterfaceRequest) + + +def IsAssociatedInterfaceRequestKind(kind): + return isinstance(kind, AssociatedInterfaceRequest) + + +def IsPendingRemoteKind(kind): + return isinstance(kind, PendingRemote) + + +def IsPendingReceiverKind(kind): + return isinstance(kind, PendingReceiver) + + +def IsPendingAssociatedRemoteKind(kind): + return isinstance(kind, PendingAssociatedRemote) + + +def IsPendingAssociatedReceiverKind(kind): + return isinstance(kind, PendingAssociatedReceiver) + + +def IsEnumKind(kind): + return isinstance(kind, Enum) + + +def IsReferenceKind(kind): + return isinstance(kind, ReferenceKind) + + +def IsNullableKind(kind): + return IsReferenceKind(kind) and kind.is_nullable + + +def IsMapKind(kind): + return isinstance(kind, Map) + + +def IsObjectKind(kind): + return IsPointerKind(kind) or IsUnionKind(kind) + + +def IsPointerKind(kind): + return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) + or IsMapKind(kind)) + + +# Please note that it doesn't include any interface kind. +def IsAnyHandleKind(kind): + return (IsGenericHandleKind(kind) or IsDataPipeConsumerKind(kind) + or IsDataPipeProducerKind(kind) or IsMessagePipeKind(kind) + or IsSharedBufferKind(kind) or IsPlatformHandleKind(kind)) + + +def IsAnyInterfaceKind(kind): + return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind) + or IsAssociatedKind(kind) or IsPendingRemoteKind(kind) + or IsPendingReceiverKind(kind)) + + +def IsAnyHandleOrInterfaceKind(kind): + return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind) + + +def IsAssociatedKind(kind): + return (IsAssociatedInterfaceKind(kind) + or IsAssociatedInterfaceRequestKind(kind) + or IsPendingAssociatedRemoteKind(kind) + or IsPendingAssociatedReceiverKind(kind)) + + +def HasCallbacks(interface): + for method in interface.methods: + if method.response_parameters != None: + return True + return False + + +# Finds out whether an interface passes associated interfaces and associated +# interface requests. +def PassesAssociatedKinds(interface): + visited_kinds = set() + for method in interface.methods: + if MethodPassesAssociatedKinds(method, visited_kinds): + return True + return False + + +def _AnyMethodParameterRecursive(method, predicate, visited_kinds=None): + def _HasProperty(kind): + if kind in visited_kinds: + # No need to examine the kind again. + return False + visited_kinds.add(kind) + if predicate(kind): + return True + if IsArrayKind(kind): + return _HasProperty(kind.kind) + if IsStructKind(kind) or IsUnionKind(kind): + for field in kind.fields: + if _HasProperty(field.kind): + return True + if IsMapKind(kind): + if _HasProperty(kind.key_kind) or _HasProperty(kind.value_kind): + return True + return False + + if visited_kinds is None: + visited_kinds = set() + + for param in method.parameters: + if _HasProperty(param.kind): + return True + if method.response_parameters != None: + for param in method.response_parameters: + if _HasProperty(param.kind): + return True + return False + + +# Finds out whether a method passes associated interfaces and associated +# interface requests. +def MethodPassesAssociatedKinds(method, visited_kinds=None): + return _AnyMethodParameterRecursive( + method, IsAssociatedKind, visited_kinds=visited_kinds) + + +# Determines whether a method passes interfaces. +def MethodPassesInterfaces(method): + return _AnyMethodParameterRecursive(method, IsInterfaceKind) + + +def HasSyncMethods(interface): + for method in interface.methods: + if method.sync: + return True + return False + + +def ContainsHandlesOrInterfaces(kind): + """Check if the kind contains any handles. + + This check is recursive so it checks all struct fields, containers elements, + etc. + + Args: + struct: {Kind} The kind to check. + + Returns: + {bool}: True if the kind contains handles. + """ + # We remember the types we already checked to avoid infinite recursion when + # checking recursive (or mutually recursive) types: + checked = set() + + def Check(kind): + if kind.spec in checked: + return False + checked.add(kind.spec) + if IsStructKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsUnionKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsAnyHandleKind(kind): + return True + elif IsAnyInterfaceKind(kind): + return True + elif IsArrayKind(kind): + return Check(kind.kind) + elif IsMapKind(kind): + return Check(kind.key_kind) or Check(kind.value_kind) + else: + return False + + return Check(kind) + + +def ContainsNativeTypes(kind): + """Check if the kind contains any native type (struct or enum). + + This check is recursive so it checks all struct fields, scoped interface + enums, etc. + + Args: + struct: {Kind} The kind to check. + + Returns: + {bool}: True if the kind contains native types. + """ + # We remember the types we already checked to avoid infinite recursion when + # checking recursive (or mutually recursive) types: + checked = set() + + def Check(kind): + if kind.spec in checked: + return False + checked.add(kind.spec) + if IsEnumKind(kind): + return kind.native_only + elif IsStructKind(kind): + if kind.native_only: + return True + if any(enum.native_only for enum in kind.enums): + return True + return any(Check(field.kind) for field in kind.fields) + elif IsUnionKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsInterfaceKind(kind): + return any(enum.native_only for enum in kind.enums) + elif IsArrayKind(kind): + return Check(kind.kind) + elif IsMapKind(kind): + return Check(kind.key_kind) or Check(kind.value_kind) + else: + return False + + return Check(kind) |