| # Copyright (C) 2013 Google Inc. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Resolve interface dependencies, producing a merged IdlDefinitions object. |
| |
| This library computes interface dependencies (partial interfaces and |
| implements), reads the dependency files, and merges them to the IdlDefinitions |
| for the main IDL file, producing an IdlDefinitions object representing the |
| entire interface. |
| |
| Design doc: http://www.chromium.org/developers/design-documents/idl-compiler#TOC-Dependency-resolution |
| """ |
| |
| import os.path |
| from utilities import idl_filename_to_component, is_valid_component_dependency, merge_dict_recursively |
| |
| # The following extended attributes can be applied to a dependency interface, |
| # and are then applied to the individual members when merging. |
| # Note that this moves the extended attribute from the interface to the member, |
| # which changes the semantics and yields different code than the same extended |
| # attribute on the main interface. |
| DEPENDENCY_EXTENDED_ATTRIBUTES = frozenset([ |
| 'OriginTrialEnabled', |
| 'RuntimeEnabled', |
| 'SecureContext', |
| ]) |
| |
| |
| class InterfaceDependencyResolver(object): |
| def __init__(self, interfaces_info, reader): |
| """Initialize dependency resolver. |
| |
| Args: |
| interfaces_info: |
| dict of interfaces information, from compute_dependencies.py |
| reader: |
| IdlReader, used for reading dependency files |
| """ |
| self.interfaces_info = interfaces_info |
| self.reader = reader |
| |
| def resolve_dependencies(self, definitions, component): |
| """Resolve dependencies, merging them into IDL definitions of main file. |
| |
| Dependencies consist of 'partial interface' for the same interface as |
| in the main file, and other interfaces that this interface 'implements'. |
| These are merged into the main IdlInterface, as the main IdlInterface |
| implements all these members. |
| |
| Referenced interfaces are added to IdlDefinitions, but not merged into |
| the main IdlInterface, as these are only referenced (their members are |
| introspected, but not implemented in this interface). |
| |
| Inherited extended attributes are also added to the main IdlInterface. |
| |
| Modifies definitions in place by adding parsed dependencies. |
| |
| Args: |
| definitions: IdlDefinitions object, modified in place |
| component: |
| string, describing where the above definitions are defined, |
| 'core' or 'modules'. See KNOWN_COMPONENTS in utilities.py |
| |
| Returns: |
| A dictionary whose key is component and value is IdlDefinitions |
| object whose dependency is resolved. |
| |
| Raises: |
| Exception: |
| A given IdlDefinitions object doesn't have any interfaces, |
| or a given IdlDefinitions object has incorrect referenced |
| interfaces. |
| """ |
| # FIXME: we need to resolve dependency when we implement partial |
| # dictionary. |
| if not definitions.interfaces: |
| raise Exception('No need to resolve any dependencies of ' |
| 'this definition: %s, because this should ' |
| 'have a dictionary.' % definitions.idl_name) |
| |
| target_interface = next(iter(definitions.interfaces.values())) |
| interface_name = target_interface.name |
| interface_info = self.interfaces_info[interface_name] |
| |
| if 'inherited_extended_attributes' in interface_info: |
| target_interface.extended_attributes.update( |
| interface_info['inherited_extended_attributes']) |
| |
| resolved_definitions = merge_interface_dependencies( |
| definitions, |
| component, |
| target_interface, |
| interface_info['dependencies_full_paths'] + |
| interface_info['dependencies_other_component_full_paths'], |
| self.reader) |
| |
| inherit_unforgeable_attributes(resolved_definitions, self.interfaces_info) |
| |
| for referenced_interface_name in interface_info['referenced_interfaces']: |
| referenced_definitions = self.reader.read_idl_definitions( |
| self.interfaces_info[referenced_interface_name]['full_path']) |
| |
| for referenced_component in referenced_definitions: |
| if not is_valid_component_dependency(component, referenced_component): |
| raise Exception('This definitions: %s is defined in %s ' |
| 'but reference interface:%s is defined ' |
| 'in %s' % (definitions.idl_name, |
| component, |
| referenced_interface_name, |
| referenced_component)) |
| |
| resolved_definitions[component].update(referenced_definitions[component]) |
| |
| return resolved_definitions |
| |
| |
| def merge_interface_dependencies(definitions, component, target_interface, dependency_idl_filenames, reader): |
| """Merge dependencies ('partial interface' and 'implements') in dependency_idl_filenames into target_interface. |
| |
| Args: |
| definitions: IdlDefinitions object, modified in place |
| component: |
| string, describing where the above definitions are defined, |
| 'core' or 'modules'. See KNOWN_COMPONENTS in utilities.py |
| target_interface: IdlInterface object, modified in place |
| dependency_idl_filenames: |
| Idl filenames which depend on the above definitions. |
| reader: IdlReader object. |
| Returns: |
| A dictionary whose key is component and value is IdlDefinitions |
| object whose dependency is resolved. |
| """ |
| resolved_definitions = {component: definitions} |
| # Sort so order consistent, so can compare output from run to run. |
| for dependency_idl_filename in sorted(dependency_idl_filenames): |
| dependency_definitions = reader.read_idl_file(dependency_idl_filename) |
| # For Cobalt, always use the component where |definitions| is located |
| # so interfaces will get merged together. |
| dependency_component = component |
| |
| dependency_interface = next(iter(dependency_definitions.interfaces.values())) |
| dependency_interface_basename, _ = os.path.splitext(os.path.basename(dependency_idl_filename)) |
| |
| transfer_extended_attributes(dependency_interface, |
| dependency_interface_basename) |
| |
| # We need to use different checkdeps here for partial interface and |
| # inheritance. |
| if dependency_interface.is_partial: |
| # Case: dependency_interface is a partial interface of |
| # target_interface. |
| # So, |
| # - A partial interface defined in modules can update |
| # the original interface defined in core. |
| # However, |
| # - A partial interface defined in core cannot update |
| # the original interface defined in modules. |
| if not is_valid_component_dependency(dependency_component, component): |
| raise Exception('The partial interface:%s in %s cannot update ' |
| 'the original interface:%s in %s' % (dependency_interface.name, |
| dependency_component, |
| target_interface.name, |
| component)) |
| |
| if dependency_component in resolved_definitions: |
| # When merging a new partial interfaces, should not overwrite |
| # ImpelemntedAs extended attributes in merged partial |
| # interface. |
| # See also the below "if 'ImplementedAs' not in ... " line's |
| # comment. |
| dependency_interface.extended_attributes.pop('ImplementedAs', None) |
| resolved_definitions[dependency_component].update(dependency_definitions) |
| continue |
| |
| dependency_interface.extended_attributes.update(target_interface.extended_attributes) |
| assert target_interface == definitions.interfaces[dependency_interface.name] |
| # A partial interface should use its original interface's |
| # ImplementedAs. If the original interface doesn't have, |
| # remove ImplementedAs defined in the partial interface. |
| # Because partial interface needs the original interface's |
| # cpp class to obtain partial interface's cpp class. |
| # e.g.. V8WindowPartial.cpp: |
| # DOMWindow* impl = V8Window::toImpl(holder); |
| # DOMWindowQuota* cppValue(DOMWindowQuota::webkitStorageInfo(impl)); |
| # TODO(tasak): remove ImplementedAs extended attributes |
| # from all partial interfaces. Instead, rename all cpp/header |
| # files correctly. ImplementedAs should not be allowed in |
| # partial interfaces. |
| if 'ImplementedAs' not in target_interface.extended_attributes: |
| dependency_interface.extended_attributes.pop('ImplementedAs', None) |
| dependency_interface.original_interface = target_interface |
| target_interface.partial_interfaces.append(dependency_interface) |
| resolved_definitions[dependency_component] = dependency_definitions |
| else: |
| # Case: target_interface implements dependency_interface. |
| # So, |
| # - An interface defined in modules can implement some interface |
| # defined in core. |
| # In this case, we need "NoInterfaceObject" extended attribute. |
| # However, |
| # - An interface defined in core cannot implement any interface |
| # defined in modules. |
| if not is_valid_component_dependency(component, dependency_component): |
| raise Exception('The interface:%s in %s cannot implement ' |
| 'the interface:%s in %s.' % (dependency_interface.name, |
| dependency_component, |
| target_interface.name, |
| component)) |
| |
| if component != dependency_component and 'NoInterfaceObject' not in dependency_interface.extended_attributes: |
| raise Exception('The interface:%s in %s cannot implement ' |
| 'the interface:%s in %s because of ' |
| 'missing NoInterfaceObject.' % (dependency_interface.name, |
| dependency_component, |
| target_interface.name, |
| component)) |
| |
| resolved_definitions[component].update(dependency_definitions) # merges partial interfaces |
| # Implemented interfaces (non-partial dependencies) are also merged |
| # into the target interface, so Code Generator can just iterate |
| # over one list (and not need to handle 'implements' itself). |
| target_interface.merge(dependency_interface) |
| |
| return resolved_definitions |
| |
| |
| def transfer_extended_attributes(dependency_interface, dependency_interface_basename): |
| """Transfer extended attributes from dependency interface onto members. |
| |
| Merging consists of storing certain interface-level data in extended |
| attributes of the *members* (because there is no separate dependency |
| interface post-merging). |
| |
| The data storing consists of: |
| * moving certain extended attributes from the dependency interface |
| to its members (deleting the extended attribute from the interface) |
| * storing the C++ class of the implementation in an internal |
| extended attribute of each member, [PartialInterfaceImplementedAs] |
| |
| No return: modifies dependency_interface in place. |
| """ |
| merged_extended_attributes = {} |
| for key in DEPENDENCY_EXTENDED_ATTRIBUTES: |
| if key not in dependency_interface.extended_attributes: |
| continue |
| |
| merged_extended_attributes[key] = dependency_interface.extended_attributes[key] |
| # Remove the merged attributes from the original dependency interface. |
| # This ensures that if other dependency interfaces are merged onto this |
| # one, its extended_attributes do not leak through |
| # (https://crbug.com/603782). |
| del dependency_interface.extended_attributes[key] |
| |
| # A partial interface's members are implemented as static member functions |
| # in a separate C++ class. This class name is stored in |
| # [PartialInterfaceImplementedAs] which defaults to the basename of |
| # dependency IDL file. |
| # This class name can be overridden by [ImplementedAs] on the partial |
| # interface definition. |
| # |
| # Note that implemented interfaces do *not* need [ImplementedAs], since |
| # they are implemented on the C++ object |impl| itself, just like members of |
| # the main interface definition, so the bindings do not need to know in |
| # which class implemented interfaces are implemented. |
| # |
| # Currently [LegacyTreatAsPartialInterface] can be used to have partial |
| # interface behavior on implemented interfaces, but this is being removed |
| # as legacy cruft: |
| # FIXME: Remove [LegacyTreatAsPartialInterface] |
| # http://crbug.com/360435 |
| # |
| # Note that [ImplementedAs] is used with different meanings on interfaces |
| # and members: |
| # for Blink class name and function name (or constant name), respectively. |
| # Thus we do not want to copy this from the interface to the member, but |
| # instead extract it and handle it separately. |
| if (dependency_interface.is_partial or |
| 'LegacyTreatAsPartialInterface' in dependency_interface.extended_attributes): |
| merged_extended_attributes['PartialInterfaceImplementedAs'] = ( |
| dependency_interface.extended_attributes.pop( |
| 'ImplementedAs', dependency_interface_basename)) |
| |
| def update_attributes(attributes, extras): |
| for key, value in extras.items(): |
| if key not in attributes: |
| attributes[key] = value |
| |
| for attribute in dependency_interface.attributes: |
| update_attributes(attribute.extended_attributes, merged_extended_attributes) |
| for constant in dependency_interface.constants: |
| update_attributes(constant.extended_attributes, merged_extended_attributes) |
| for operation in dependency_interface.operations: |
| update_attributes(operation.extended_attributes, merged_extended_attributes) |
| |
| |
| def inherit_unforgeable_attributes(resolved_definitions, interfaces_info): |
| """Inherits [Unforgeable] attributes and updates the arguments accordingly. |
| |
| For each interface in |resolved_definitions|, collects all [Unforgeable] |
| attributes in ancestor interfaces in the same component and adds them to |
| the interface. 'referenced_interfaces' and 'cpp_includes' in |
| |interfaces_info| are updated accordingly. |
| """ |
| def collect_unforgeable_attributes_in_ancestors(interface_name, component): |
| if not interface_name: |
| # unforgeable_attributes, referenced_interfaces, cpp_includes |
| return [], [], set() |
| interface = interfaces_info[interface_name] |
| unforgeable_attributes, referenced_interfaces, cpp_includes = collect_unforgeable_attributes_in_ancestors(interface.get('parent'), component) |
| this_unforgeable = interface.get('unforgeable_attributes', {}).get(component, []) |
| unforgeable_attributes.extend(this_unforgeable) |
| this_referenced = [attr.idl_type.base_type for attr in this_unforgeable |
| if attr.idl_type.base_type in |
| interface.get('referenced_interfaces', [])] |
| referenced_interfaces.extend(this_referenced) |
| cpp_includes.update(interface.get('cpp_includes', {}).get(component, {})) |
| return unforgeable_attributes, referenced_interfaces, cpp_includes |
| |
| for component, definitions in resolved_definitions.items(): |
| for interface_name, interface in definitions.interfaces.items(): |
| interface_info = interfaces_info[interface_name] |
| inherited_unforgeable_attributes, referenced_interfaces, cpp_includes = collect_unforgeable_attributes_in_ancestors(interface_info.get('parent'), component) |
| # This loop may process the same interface many times, so it's |
| # possible that we're adding the same attributes twice or more. |
| # So check if there is a duplicate. |
| for attr in inherited_unforgeable_attributes: |
| if attr not in interface.attributes: |
| interface.attributes.append(attr) |
| referenced_interfaces.extend(interface_info.get('referenced_interfaces', [])) |
| interface_info['referenced_interfaces'] = sorted(set(referenced_interfaces)) |
| merge_dict_recursively(interface_info, |
| {'cpp_includes': {component: cpp_includes}}) |