def collect_interfaces_info(idl_path_list): info_collector = InterfaceInfoCollector() for idl_path in idl_path_list: info_collector.collect_info(idl_path) info = info_collector.get_info_as_dict() # TestDictionary.{h,cpp} are placed under # Source/bindings/tests/idls/core. However, IdlCompiler generates # TestDictionary.{h,cpp} by using relative_dir. # So the files will be generated under # output_dir/core/bindings/tests/idls/core. # To avoid this issue, we need to clear relative_dir here. for value in info['interfaces_info'].itervalues(): value['relative_dir'] = '' component_info = info_collector.get_component_info_as_dict() return info, component_info
def collect_interfaces_info(idl_path_list): info_collector = InterfaceInfoCollector() for idl_path in idl_path_list: if os.path.basename(idl_path) in NON_BLINK_IDL_FILES: continue info_collector.collect_info(idl_path) info = info_collector.get_info_as_dict() # TestDictionary.{h,cpp} are placed under # Source/bindings/tests/idls/core. However, IdlCompiler generates # TestDictionary.{h,cpp} by using relative_dir. # So the files will be generated under # output_dir/core/bindings/tests/idls/core. # To avoid this issue, we need to clear relative_dir here. for value in info['interfaces_info'].itervalues(): value['relative_dir'] = '' component_info = info_collector.get_component_info_as_dict() return info, component_info
class DatabaseBuilder(object): def __init__(self, database): """DatabaseBuilder is used for importing and merging interfaces into the Database""" self._info_collector = InterfaceInfoCollector() self._database = database self._imported_interfaces = [] self._impl_stmts = [] self.conditionals_met = set() # Spin up the new IDL parser. self.build = Build(self) # Global typedef to mapping. self.global_type_defs = monitored.Dict('databasebuilder.global_type_defs', { 'Transferable' : 'MessagePort', }) # TODO(terry): Consider keeping richer type information (e.g., # IdlArrayOrSequenceType from the Blink parser) instead of just # a type name. def _resolve_type_defs(self, idl_file): for type_node in idl_file.all(IDLType): resolved = False type_name = type_node.id for typedef in self.global_type_defs: seq_name_typedef = 'sequence<%s>' % typedef if type_name == typedef: type_node.id = self.global_type_defs[typedef] resolved = True elif type_name == seq_name_typedef: type_node.id = 'sequence<%s>' % self.global_type_defs[typedef] resolved = True if not(resolved): for typedef in idl_file.typeDefs: if type_name == typedef.id: type_node.id = typedef.type.id resolved = True def _strip_ext_attributes(self, idl_file): """Strips unuseful extended attributes.""" for ext_attrs in idl_file.all(IDLExtAttrs): # TODO: Decide which attributes are uninteresting. pass def _rename_types(self, idl_file, import_options): """Rename interface and type names with names provided in the options. Also clears scopes from scoped names""" strip_modules = lambda name: name.split('::')[-1] def rename_node(idl_node): idl_node.reset_id(strip_modules(idl_node.id)) def rename_ext_attrs(ext_attrs_node): for type_valued_attribute_name in ['DartSupplemental']: if type_valued_attribute_name in ext_attrs_node: value = ext_attrs_node[type_valued_attribute_name] if isinstance(value, str): ext_attrs_node[type_valued_attribute_name] = strip_modules(value) map(rename_node, idl_file.all(IDLInterface)) map(rename_node, idl_file.all(IDLType)) map(rename_ext_attrs, idl_file.all(IDLExtAttrs)) def _annotate(self, interface, import_options): """Adds @ annotations based on the source and source_attributes members of import_options.""" source = import_options.source if not source: return def add_source_annotation(idl_node): annotation = IDLAnnotation( copy.deepcopy(import_options.source_attributes)) idl_node.annotations[source] = annotation if ((isinstance(idl_node, IDLInterface) or isinstance(idl_node, IDLMember)) and idl_node.is_fc_suppressed): annotation['suppressed'] = None add_source_annotation(interface) map(add_source_annotation, interface.parents) map(add_source_annotation, interface.constants) map(add_source_annotation, interface.attributes) map(add_source_annotation, interface.operations) def _sign(self, node): """Computes a unique signature for the node, for merging purposed, by concatenating types and names in the declaration.""" if isinstance(node, IDLType): res = node.id if res.startswith('unsigned '): res = res[len('unsigned '):] return res res = [] if isinstance(node, IDLInterface): res = ['interface', node.id] elif isinstance(node, IDLParentInterface): res = ['parent', self._sign(node.type)] elif isinstance(node, IDLOperation): res = ['op'] for special in node.specials: res.append(special) if node.id is not None: res.append(node.id) for arg in node.arguments: res.append(self._sign(arg.type)) res.append(self._sign(node.type)) elif isinstance(node, IDLAttribute): res = [] if node.is_read_only: res.append('readonly') res.append(node.id) res.append(self._sign(node.type)) elif isinstance(node, IDLConstant): res = [] res.append('const') res.append(node.id) res.append(node.value) res.append(self._sign(node.type)) else: raise TypeError("Can't sign input of type %s" % type(node)) return ':'.join(res) def _build_signatures_map(self, idl_node_list): """Creates a hash table mapping signatures to idl_nodes for the given list of nodes""" res = {} for idl_node in idl_node_list: sig = self._sign(idl_node) if sig is None: continue if sig in res: op = res[sig] # Only report if the operations that match are either both suppressed # or both not suppressed. Optional args aren't part of type signature # for this routine. Suppressing a non-optional type and supplementing # with an optional type appear the same. if idl_node.is_fc_suppressed == op.is_fc_suppressed: raise RuntimeError('Warning: Multiple members have the same ' ' signature: "%s"' % sig) res[sig] = idl_node return res def _get_parent_interfaces(self, interface): """Return a list of all the parent interfaces of a given interface""" res = [] def recurse(current_interface): if current_interface in res: return res.append(current_interface) for parent in current_interface.parents: parent_name = parent.type.id if self._database.HasInterface(parent_name): recurse(self._database.GetInterface(parent_name)) recurse(interface) return res[1:] def _merge_ext_attrs(self, old_attrs, new_attrs): """Merges two sets of extended attributes. Returns: True if old_attrs has changed. """ changed = False for (name, value) in new_attrs.items(): if name in old_attrs and old_attrs[name] == value: pass # Identical else: if name == 'ImplementedAs' and name in old_attrs: continue old_attrs[name] = value changed = True return changed def _merge_nodes(self, old_list, new_list, import_options): """Merges two lists of nodes. Annotates nodes with the source of each node. Returns: True if the old_list has changed. Args: old_list -- the list to merge into. new_list -- list containing more nodes. import_options -- controls how merging is done. """ changed = False source = import_options.source old_signatures_map = self._build_signatures_map(old_list) new_signatures_map = self._build_signatures_map(new_list) # Merge new items for (sig, new_node) in new_signatures_map.items(): if sig not in old_signatures_map: # New node: old_list.append(new_node) changed = True else: # Merge old and new nodes: old_node = old_signatures_map[sig] if (source not in old_node.annotations and source in new_node.annotations): old_node.annotations[source] = new_node.annotations[source] changed = True # Maybe rename arguments: if isinstance(old_node, IDLOperation): for i in range(0, len(old_node.arguments)): old_arg = old_node.arguments[i] new_arg = new_node.arguments[i] old_arg_name = old_arg.id new_arg_name = new_arg.id if (old_arg_name != new_arg_name and (old_arg_name == 'arg' or old_arg_name.endswith('Arg') or import_options.rename_operation_arguments_on_merge)): old_node.arguments[i].id = new_arg_name changed = True if self._merge_ext_attrs(old_arg.ext_attrs, new_arg.ext_attrs): changed = True # Merge in [Default=Undefined] and DOMString a = null handling in # IDL. The IDL model (IDLArgument) coalesces these two different # default value syntaxes into the default_value* models. old_default_value = old_arg.default_value new_default_value = new_arg.default_value old_default_value_is_null = old_arg.default_value_is_null new_default_value_is_null = new_arg.default_value_is_null if old_default_value != new_default_value: old_arg.default_value = new_default_value changed = True if old_default_value_is_null != new_default_value_is_null: old_arg.default_value_is_null = new_default_value_is_null changed = True # Merge in any optional argument differences. old_optional = old_arg.optional new_optional = new_arg.optional if old_optional != new_optional: old_arg.optional = new_optional changed = True # Maybe merge annotations: if (isinstance(old_node, IDLAttribute) or isinstance(old_node, IDLOperation)): if self._merge_ext_attrs(old_node.ext_attrs, new_node.ext_attrs): changed = True # Remove annotations on obsolete items from the same source if import_options.obsolete_old_declarations: for (sig, old_node) in old_signatures_map.items(): if (source in old_node.annotations and sig not in new_signatures_map): _logger.warn('%s not available in %s anymore' % (sig, source)) del old_node.annotations[source] changed = True return changed def _merge_interfaces(self, old_interface, new_interface, import_options): """Merges the new_interface into the old_interface, annotating the interface with the sources of each change.""" changed = False source = import_options.source if (source and source not in old_interface.annotations and source in new_interface.annotations and not new_interface.is_supplemental): old_interface.annotations[source] = new_interface.annotations[source] changed = True def merge_list(what): old_list = old_interface.__dict__[what] new_list = new_interface.__dict__[what] if what != 'parents' and old_interface.id != new_interface.id: for node in new_list: node.doc_js_interface_name = old_interface.id node.ext_attrs['ImplementedBy'] = new_interface.id changed = self._merge_nodes(old_list, new_list, import_options) # Delete list items with zero remaining annotations. if changed and import_options.obsolete_old_declarations: def has_annotations(idl_node): return len(idl_node.annotations) old_interface.__dict__[what] = filter(has_annotations, old_list) return changed # Smartly merge various declarations: if merge_list('parents'): changed = True if merge_list('constants'): changed = True if merge_list('attributes'): changed = True if merge_list('operations'): changed = True if self._merge_ext_attrs(old_interface.ext_attrs, new_interface.ext_attrs): changed = True _logger.info('merged interface %s (changed=%s, supplemental=%s)' % (old_interface.id, changed, new_interface.is_supplemental)) return changed def _merge_impl_stmt(self, impl_stmt, import_options): """Applies "X implements Y" statemetns on the proper places in the database""" implementor_name = impl_stmt.implementor.id implemented_name = impl_stmt.implemented.id _logger.info('merging impl stmt %s implements %s' % (implementor_name, implemented_name)) source = import_options.source if self._database.HasInterface(implementor_name): interface = self._database.GetInterface(implementor_name) if interface.parents is None: interface.parents = [] for parent in interface.parents: if parent.type.id == implemented_name: if source and source not in parent.annotations: parent.annotations[source] = IDLAnnotation( import_options.source_attributes) return # not found, so add new one parent = IDLParentInterface(None) parent.type = IDLType(implemented_name) if source: parent.annotations[source] = IDLAnnotation( import_options.source_attributes) interface.parents.append(parent) def merge_imported_interfaces(self): """Merges all imported interfaces and loads them into the DB.""" imported_interfaces = self._imported_interfaces # Step 1: Pre process imported interfaces # for interface, import_options in imported_interfaces.iteritems(): for interface, import_options in imported_interfaces: self._annotate(interface, import_options) # Step 2: Add all new interfaces and merge overlapping ones for interface, import_options in imported_interfaces: if not interface.is_supplemental: if self._database.HasInterface(interface.id): old_interface = self._database.GetInterface(interface.id) self._merge_interfaces(old_interface, interface, import_options) else: if import_options.add_new_interfaces: self._database.AddInterface(interface) # Step 3: Merge in supplemental interfaces for interface, import_options in imported_interfaces: if interface.is_supplemental: target = interface.id if self._database.HasInterface(target): old_interface = self._database.GetInterface(target) self._merge_interfaces(old_interface, interface, import_options) else: _logger.warning("Supplemental target '%s' not found", target) # Step 4: Resolve 'implements' statements for impl_stmt, import_options in self._impl_stmts: self._merge_impl_stmt(impl_stmt, import_options) self._impl_stmts = [] self._imported_interfaces = [] def _compute_dart_idl_implements(self, idl_filename): full_path = os.path.realpath(idl_filename) with open(full_path) as f: idl_file_contents = f.read() implements_re = (r'^\s*' r'(\w+)\s+' r'implements\s+' r'(\w+)\s*' r';') implements_matches = re.finditer(implements_re, idl_file_contents, re.MULTILINE) return [match.groups() for match in implements_matches] # Compile the IDL file with the Blink compiler and remember each AST for the # IDL. def _blink_compile_idl_files(self, file_paths, import_options, is_dart_idl): if not(is_dart_idl): start_time = time.time() # Compute information for individual files # Information is stored in global variables interfaces_info and # partial_interface_files. for file_path in file_paths: self._info_collector.collect_info(file_path) end_time = time.time() print 'Compute dependencies %s seconds' % round((end_time - start_time), 2) else: # Compute the interface_info for dart.idl for implements defined. This # file is special in that more than one interface can exist in this file. implement_pairs = self._compute_dart_idl_implements(file_paths[0]) self._info_collector.interfaces_info['__dart_idl___'] = { 'implement_pairs': implement_pairs, } # Parse the IDL files serially. start_time = time.time() for file_path in file_paths: file_path = os.path.normpath(file_path) ast = _compile_idl_file(self.build, file_path, import_options) self._process_ast(os.path.splitext(os.path.basename(file_path))[0], ast) end_time = time.time() print 'Compiled %s IDL files in %s seconds' % (len(file_paths), round((end_time - start_time), 2)) def _process_ast(self, filename, ast): if len(ast) == 1: ast = ast.values()[0] else: print 'ERROR: Processing AST: ' + os.path.basename(file_name) new_asts[filename] = ast def import_idl_files(self, file_paths, import_options, is_dart_idl): self._blink_compile_idl_files(file_paths, import_options, is_dart_idl) start_time = time.time() # Parse the IDL files in serial. for file_path in file_paths: file_path = os.path.normpath(file_path) idl_file = _load_idl_file(self.build, file_path, import_options) _logger.info('Processing %s' % os.path.splitext(os.path.basename(file_path))[0]) self._process_idl_file(idl_file, import_options, is_dart_idl) end_time = time.time() for warning in report_unions_to_any(): _logger.warning(warning) print 'Total %s files %sprocessed in databasebuilder in %s seconds' % \ (len(file_paths), '', round((end_time - start_time), 2)) def _process_idl_file(self, idl_file, import_options, dart_idl = False): # TODO(terry): strip_ext_attributes on an idl_file does nothing. #self._strip_ext_attributes(idl_file) self._resolve_type_defs(idl_file) self._rename_types(idl_file, import_options) def enabled(idl_node): return self._is_node_enabled(idl_node, import_options.idl_defines) for interface in idl_file.interfaces: if not self._is_node_enabled(interface, import_options.idl_defines): _logger.info('skipping interface %s (source=%s)' % (interface.id, import_options.source)) continue _logger.info('importing interface %s (source=%s file=%s)' % (interface.id, import_options.source, os.path.basename(idl_file.filename))) interface.attributes = filter(enabled, interface.attributes) interface.operations = filter(enabled, interface.operations) self._imported_interfaces.append((interface, import_options)) # If an IDL dictionary then there is no implementsStatements. if hasattr(idl_file, 'implementsStatements'): for implStmt in idl_file.implementsStatements: self._impl_stmts.append((implStmt, import_options)) for enum in idl_file.enums: self._database.AddEnum(enum) for dictionary in idl_file.dictionaries: self._database.AddDictionary(dictionary) # TODO(terry): Hack to remember all typedef unions they're mapped to any # - no type. for typedef in idl_file.typeDefs: self._database.AddTypeDef(typedef) def _is_node_enabled(self, node, idl_defines): if not 'Conditional' in node.ext_attrs: return True def enabled(condition): return 'ENABLE_%s' % condition in idl_defines conditional = node.ext_attrs['Conditional'] if conditional.find('&') != -1: for condition in conditional.split('&'): condition = condition.strip() self.conditionals_met.add(condition) if not enabled(condition): return False return True for condition in conditional.split('|'): condition = condition.strip() self.conditionals_met.add(condition) if enabled(condition): return True return False def fix_displacements(self, source): """E.g. In W3C, something is declared on HTMLDocument but in WebKit its on Document, so we need to mark that something in HTMLDocument with @WebKit(via=Document). The 'via' attribute specifies the parent interface that has the declaration.""" for interface in self._database.GetInterfaces(): changed = False _logger.info('fixing displacements in %s' % interface.id) for parent_interface in self._get_parent_interfaces(interface): _logger.info('scanning parent %s of %s' % (parent_interface.id, interface.id)) def fix_nodes(local_list, parent_list): changed = False parent_signatures_map = self._build_signatures_map( parent_list) for idl_node in local_list: sig = self._sign(idl_node) if sig in parent_signatures_map: parent_member = parent_signatures_map[sig] if (source in parent_member.annotations and source not in idl_node.annotations and _VIA_ANNOTATION_ATTR_NAME not in parent_member.annotations[source]): idl_node.annotations[source] = IDLAnnotation( {_VIA_ANNOTATION_ATTR_NAME: parent_interface.id}) changed = True return changed changed = fix_nodes(interface.constants, parent_interface.constants) or changed changed = fix_nodes(interface.attributes, parent_interface.attributes) or changed changed = fix_nodes(interface.operations, parent_interface.operations) or changed if changed: _logger.info('fixed displaced declarations in %s' % interface.id) def normalize_annotations(self, sources): """Makes the IDLs less verbose by removing annotation attributes that are identical to the ones defined at the interface level. Args: sources -- list of source names to normalize.""" for interface in self._database.GetInterfaces(): _logger.debug('normalizing annotations for %s' % interface.id) for source in sources: if (source not in interface.annotations or not interface.annotations[source]): continue top_level_annotation = interface.annotations[source] def normalize(idl_node): if (source in idl_node.annotations and idl_node.annotations[source]): annotation = idl_node.annotations[source] for name, value in annotation.items(): if (name in top_level_annotation and value == top_level_annotation[name]): del annotation[name] map(normalize, interface.parents) map(normalize, interface.constants) map(normalize, interface.attributes) map(normalize, interface.operations) def map_dictionaries(self): """Changes the type of operations/constructors arguments from an IDL dictionary to a Dictionary. The IDL dictionary is just an enums of strings which are checked at run-time.""" def dictionary_to_map(type_node): if self._database.HasDictionary(type_node.id): type_node.dictionary = type_node.id type_node.id = 'Dictionary' def all_types(node): map(dictionary_to_map, node.all(IDLType)) for interface in self._database.GetInterfaces(): map(all_types, interface.all(IDLExtAttrFunctionValue)) map(all_types, interface.attributes) map(all_types, interface.operations) def fetch_constructor_data(self, options): window_interface = self._database.GetInterface('Window') for attr in window_interface.attributes: type = attr.type.id if not type.endswith('Constructor'): continue type = re.sub('(Constructor)+$', '', type) # TODO(antonm): Ideally we'd like to have pristine copy of WebKit IDLs and fetch # this information directly from it. Unfortunately right now database is massaged # a lot so it's difficult to maintain necessary information on Window itself. interface = self._database.GetInterface(type) if 'V8EnabledPerContext' in attr.ext_attrs: interface.ext_attrs['synthesizedV8EnabledPerContext'] = \ attr.ext_attrs['V8EnabledPerContext'] if 'V8EnabledAtRuntime' in attr.ext_attrs: interface.ext_attrs['synthesizedV8EnabledAtRuntime'] = \ attr.ext_attrs['V8EnabledAtRuntime'] or attr.id # Iterate of the database looking for relationships between dictionaries and # interfaces marked with NoInterfaceObject. This mechanism can be used for # other IDL analysis. def examine_database(self): # Contains list of dictionary structure: {'dictionary': dictionary, 'usages': []} self._diag_dictionaries = []; self._dictionaries_used_types = []; # Record any dictionary. for dictionary in self._database.GetDictionaries(): self._diag_dictionaries.append({'dictionary': dictionary, 'usages': []}); # Contains list of NoInterfaceObject structures: {'no_interface_object': dictionary, 'usages': []} self._diag_no_interfaces = []; self._no_interfaces_used_types = []; # Record any interface with Blink IDL Extended Attribute 'NoInterfaceObject'. for interface in self._database.GetInterfaces(): if interface.is_no_interface_object: self._diag_no_interfaces.append({'no_interface_object': interface, 'usages': []}); for interface in self._database.GetInterfaces(): self._constructors(interface) self._constructors(interface, check_dictionaries=False) for attribute in interface.attributes: self._attribute_operation(interface, attribute) self._attribute_operation(interface, attribute, check_dictionaries=False) for operation in interface.operations: self._attribute_operation(interface, operation) self._attribute_operation(interface, operation, check_dictionaries=False) # Report all dictionaries and their usage. self._output_examination() # Report all interface marked with NoInterfaceObject and their usage. self._output_examination(check_dictionaries=False) print '\nKey:' print ' (READ-ONLY) - read-only attribute has relationship' print ' (GET/SET) - attribute has relationship' print ' RETURN - operation\'s returned value has relationship' print ' (ARGUMENT) - operation\'s argument(s) has relationship' print '' print ' (New) - After dictionary name if constructor(s) exist' print ' (Ops,Props,New) after a NoInterfaceObject name is defined as:' print ' Ops - number of operations for a NoInterfaceObject' print ' Props - number of properties for a NoInterfaceObject' print ' New - T(#) number constructors for a NoInterfaceObject' print ' F no constructors for a NoInterfaceObject' print ' e.g., an interface 5 operations, 3 properties and 2' print ' constructors would display (5,3,T(2))' print '\n\nExamination Complete\n' def _output_examination(self, check_dictionaries=True): # Output diagnostics. First columns is Dictionary or NoInterfaceObject e.g., # | Dictionary | Used In Interface | Usage Operation/Attribute | print '\n\n' title_bar = ['Dictionary', 'Used In Interface', 'Usage Operation/Attribute'] if check_dictionaries \ else ['NoInterfaceObject (Ops,Props,New)', 'Used In Interface', 'Usage Operation/Attribute'] self._tabulate_title(title_bar) diags = self._diag_dictionaries if check_dictionaries else self._diag_no_interfaces for diag in diags: if not(check_dictionaries): interface = diag['no_interface_object'] ops_count = len(interface.operations) properties_count = len(interface.attributes) any_constructors = 'Constructor' in interface.ext_attrs constructors = 'T(%s)' % len(interface.ext_attrs['Constructor']) if any_constructors else 'F' interface_detail = '%s (%s,%s,%s)' % \ (diag['no_interface_object'].id, ops_count, properties_count, constructors) self._tabulate([interface_detail, '', '']) else: dictionary = diag['dictionary'] any_constructors = 'Constructor' in dictionary.ext_attrs self._tabulate(['%s%s' % (dictionary.id, ' (New)' if any_constructors else ''), '', '']) for usage in diag['usages']: detail = '' if 'attribute' in usage: attribute_type = 'READ-ONLY' if not usage['argument'] else 'GET/SET' detail = '(%s) %s' % (attribute_type, usage['attribute']) elif 'operation' in usage: detail = '%s %s%s' % ('RETURN' if usage['result'] else '', usage['operation'], '(ARGUMENT)' if usage['argument'] else '') self._tabulate([None, usage['interface'], detail]) self._tabulate_break() # operation_or_attribute either IDLOperation or IDLAttribute if None then # its a constructor (IDLExtAttrFunctionValue). def _mark_usage(self, interface, operation_or_attribute = None, check_dictionaries=True): for diag in self._diag_dictionaries if check_dictionaries else self._diag_no_interfaces: for usage in diag['usages']: if not usage['interface']: usage['interface'] = interface.id if isinstance(operation_or_attribute, IDLOperation): usage['operation'] = operation_or_attribute.id if check_dictionaries: usage['result'] = hasattr(operation_or_attribute.type, 'dictionary') and \ operation_or_attribute.type.dictionary == diag['dictionary'].id else: usage['result'] = operation_or_attribute.type.id == diag['no_interface_object'].id usage['argument'] = False for argument in operation_or_attribute.arguments: if check_dictionaries: arg = hasattr(argument.type, 'dictionary') and argument.type.dictionary == diag['dictionary'].id else: arg = argument.type.id == diag['no_interface_object'].id if arg: usage['argument'] = arg elif isinstance(operation_or_attribute, IDLAttribute): usage['attribute'] = operation_or_attribute.id usage['result'] = True usage['argument'] = not operation_or_attribute.is_read_only elif not operation_or_attribute: # Its a constructor only argument is dictionary or interface with NoInterfaceObject. usage['operation'] = 'constructor' usage['result'] = False usage['argument'] = True def _remember_usage(self, node, check_dictionaries=True): if check_dictionaries: used_types = self._dictionaries_used_types diag_list = self._diag_dictionaries diag_name = 'dictionary' else: used_types = self._no_interfaces_used_types diag_list = self._diag_no_interfaces diag_name = 'no_interface_object' if len(used_types) > 0: normalized_used = list(set(used_types)) for recorded_id in normalized_used: for diag in diag_list: if diag[diag_name].id == recorded_id: diag['usages'].append({'interface': None, 'node': node}) # Iterator function to look for any IDLType that is a dictionary then remember # that dictionary. def _dictionary_used(self, type_node): if hasattr(type_node, 'dictionary'): dictionary_id = type_node.dictionary if self._database.HasDictionary(dictionary_id): for diag_dictionary in self._diag_dictionaries: if diag_dictionary['dictionary'].id == dictionary_id: # Record the dictionary that was referenced. self._dictionaries_used_types.append(dictionary_id) return # If we get to this point, the IDL dictionary was never defined ... oops. print 'DIAGNOSE_ERROR: IDL Dictionary %s doesn\'t exist.' % dictionary_id # Iterator function to look for any IDLType that is an interface marked with # NoInterfaceObject then remember that interface. def _no_interface_used(self, type_node): if hasattr(type_node, 'id'): no_interface_id = type_node.id if self._database.HasInterface(no_interface_id): no_interface = self._database.GetInterface(no_interface_id) if no_interface.is_no_interface_object: for diag_no_interface in self._diag_no_interfaces: if diag_no_interface['no_interface_object'].id == no_interface_id: # Record the interface marked with NoInterfaceObject. self._no_interfaces_used_types.append(no_interface_id) return def _constructors(self, interface, check_dictionaries=True): if check_dictionaries: self._dictionaries_used_types = [] constructor_function = self._dictionary_constructor_types else: self._no_interfaces_used_types = []; constructor_function = self._no_interface_constructor_types map(constructor_function, interface.all(IDLExtAttrFunctionValue)) self._mark_usage(interface, check_dictionaries=check_dictionaries) # Scan an attribute or operation for a dictionary or interface with NoInterfaceObject # reference. def _attribute_operation(self, interface, operation_attribute, check_dictionaries=True): if check_dictionaries: self._dictionaries_used_types = [] used = self._dictionary_used else: self._no_interfaces_used_types = []; used = self._no_interface_used map(used, operation_attribute.all(IDLType)) self._remember_usage(operation_attribute, check_dictionaries=check_dictionaries) self._mark_usage(interface, operation_attribute, check_dictionaries=check_dictionaries) # Iterator function for map to iterate over all constructor types # (IDLExtAttrFunctionValue) that have a dictionary reference. def _dictionary_constructor_types(self, node): self._dictionaries_used_types = [] map(self._dictionary_used, node.all(IDLType)) self._remember_usage(node) # Iterator function for map to iterate over all constructor types # (IDLExtAttrFunctionValue) that reference an interface with NoInterfaceObject. def _no_interface_constructor_types(self, node): self._no_interfaces_used_types = []; map(self._no_interface_used, node.all(IDLType)) self._remember_usage(node, check_dictionaries=False) # Maximum width of each column. def _TABULATE_WIDTH(self): return 45 def _tabulate_title(self, row_title): title_separator = "=" * self._TABULATE_WIDTH() self._tabulate([title_separator, title_separator, title_separator]) self._tabulate(row_title) self._tabulate([title_separator, title_separator, title_separator]) def _tabulate_break(self): break_separator = "-" * self._TABULATE_WIDTH() self._tabulate([break_separator, break_separator, break_separator]) def _tabulate(self, columns): """Tabulate a list of columns for a row. Each item in columns is a column value each column will be padded up to _TABULATE_WIDTH. Each column starts/ends with a vertical bar '|' the format a row: | columns[0] | columns[1] | columns[2] | ... | """ if len(columns) > 0: for column in columns: value = '' if not column else column sys.stdout.write('|{0:^{1}}'.format(value, self._TABULATE_WIDTH())) else: sys.stdout.write('|{0:^{1}}'.format('', self._TABULATE_WIDTH())) sys.stdout.write('|\n')
class DatabaseBuilder(object): def __init__(self, database): """DatabaseBuilder is used for importing and merging interfaces into the Database""" self._info_collector = InterfaceInfoCollector() self._database = database self._imported_interfaces = [] self._impl_stmts = [] self.conditionals_met = set() # Spin up the new IDL parser. self.build = Build(self) # Global typedef to mapping. self.global_type_defs = monitored.Dict( 'databasebuilder.global_type_defs', { 'Transferable': 'MessagePort', }) # TODO(terry): Consider keeping richer type information (e.g., # IdlArrayOrSequenceType from the Blink parser) instead of just # a type name. def _resolve_type_defs(self, idl_file): for type_node in idl_file.all(IDLType): resolved = False type_name = type_node.id for typedef in self.global_type_defs: seq_name_typedef = 'sequence<%s>' % typedef if type_name == typedef: type_node.id = self.global_type_defs[typedef] resolved = True elif type_name == seq_name_typedef: type_node.id = 'sequence<%s>' % self.global_type_defs[ typedef] resolved = True if not (resolved): for typedef in idl_file.typeDefs: if type_name == typedef.id: type_node.id = typedef.type.id resolved = True def _strip_ext_attributes(self, idl_file): """Strips unuseful extended attributes.""" for ext_attrs in idl_file.all(IDLExtAttrs): # TODO: Decide which attributes are uninteresting. pass def _rename_types(self, idl_file, import_options): """Rename interface and type names with names provided in the options. Also clears scopes from scoped names""" strip_modules = lambda name: name.split('::')[-1] def rename_node(idl_node): idl_node.reset_id(strip_modules(idl_node.id)) def rename_ext_attrs(ext_attrs_node): for type_valued_attribute_name in ['DartSupplemental']: if type_valued_attribute_name in ext_attrs_node: value = ext_attrs_node[type_valued_attribute_name] if isinstance(value, str): ext_attrs_node[ type_valued_attribute_name] = strip_modules(value) map(rename_node, idl_file.all(IDLInterface)) map(rename_node, idl_file.all(IDLType)) map(rename_ext_attrs, idl_file.all(IDLExtAttrs)) def _annotate(self, interface, import_options): """Adds @ annotations based on the source and source_attributes members of import_options.""" source = import_options.source if not source: return def add_source_annotation(idl_node): annotation = IDLAnnotation( copy.deepcopy(import_options.source_attributes)) idl_node.annotations[source] = annotation if ((isinstance(idl_node, IDLInterface) or isinstance(idl_node, IDLMember)) and idl_node.is_fc_suppressed): annotation['suppressed'] = None add_source_annotation(interface) map(add_source_annotation, interface.parents) map(add_source_annotation, interface.constants) map(add_source_annotation, interface.attributes) map(add_source_annotation, interface.operations) def _sign(self, node): """Computes a unique signature for the node, for merging purposed, by concatenating types and names in the declaration.""" if isinstance(node, IDLType): res = node.id if res.startswith('unsigned '): res = res[len('unsigned '):] if hasattr(node, 'nullable') and node.nullable: res += '?' return res res = [] if isinstance(node, IDLInterface): res = ['interface', node.id] elif isinstance(node, IDLParentInterface): res = ['parent', self._sign(node.type)] elif isinstance(node, IDLOperation): res = ['op'] for special in node.specials: res.append(special) if node.id is not None: res.append(node.id) for arg in node.arguments: res.append(self._sign(arg.type)) res.append(self._sign(node.type)) elif isinstance(node, IDLAttribute): res = [] if node.is_read_only: res.append('readonly') res.append(node.id) res.append(self._sign(node.type)) elif isinstance(node, IDLConstant): res = [] res.append('const') res.append(node.id) res.append(node.value) res.append(self._sign(node.type)) else: raise TypeError("Can't sign input of type %s" % type(node)) return ':'.join(res) def _build_signatures_map(self, idl_node_list): """Creates a hash table mapping signatures to idl_nodes for the given list of nodes""" res = {} for idl_node in idl_node_list: sig = self._sign(idl_node) if sig is None: continue if sig in res: op = res[sig] # Only report if the operations that match are either both suppressed # or both not suppressed. Optional args aren't part of type signature # for this routine. Suppressing a non-optional type and supplementing # with an optional type appear the same. if idl_node.is_fc_suppressed == op.is_fc_suppressed: raise RuntimeError( 'Warning: Multiple members have the same ' ' signature: "%s"' % sig) res[sig] = idl_node return res def _get_parent_interfaces(self, interface): """Return a list of all the parent interfaces of a given interface""" res = [] def recurse(current_interface): if current_interface in res: return res.append(current_interface) for parent in current_interface.parents: parent_name = parent.type.id if self._database.HasInterface(parent_name): recurse(self._database.GetInterface(parent_name)) recurse(interface) return res[1:] def _merge_ext_attrs(self, old_attrs, new_attrs): """Merges two sets of extended attributes. Returns: True if old_attrs has changed. """ changed = False for (name, value) in new_attrs.items(): if name in old_attrs and old_attrs[name] == value: pass # Identical else: if name == 'ImplementedAs' and name in old_attrs: continue old_attrs[name] = value changed = True return changed def _merge_nodes(self, old_list, new_list, import_options): """Merges two lists of nodes. Annotates nodes with the source of each node. Returns: True if the old_list has changed. Args: old_list -- the list to merge into. new_list -- list containing more nodes. import_options -- controls how merging is done. """ changed = False source = import_options.source old_signatures_map = self._build_signatures_map(old_list) new_signatures_map = self._build_signatures_map(new_list) # Merge new items for (sig, new_node) in new_signatures_map.items(): if sig not in old_signatures_map: # New node: old_list.append(new_node) changed = True else: # Merge old and new nodes: old_node = old_signatures_map[sig] if (source not in old_node.annotations and source in new_node.annotations): old_node.annotations[source] = new_node.annotations[source] changed = True # Maybe rename arguments: if isinstance(old_node, IDLOperation): for i in range(0, len(old_node.arguments)): old_arg = old_node.arguments[i] new_arg = new_node.arguments[i] old_arg_name = old_arg.id new_arg_name = new_arg.id if (old_arg_name != new_arg_name and (old_arg_name == 'arg' or old_arg_name.endswith('Arg') or import_options.rename_operation_arguments_on_merge )): old_node.arguments[i].id = new_arg_name changed = True if self._merge_ext_attrs(old_arg.ext_attrs, new_arg.ext_attrs): changed = True # Merge in [Default=Undefined] and DOMString a = null handling in # IDL. The IDL model (IDLArgument) coalesces these two different # default value syntaxes into the default_value* models. old_default_value = old_arg.default_value new_default_value = new_arg.default_value old_default_value_is_null = old_arg.default_value_is_null new_default_value_is_null = new_arg.default_value_is_null if old_default_value != new_default_value: old_arg.default_value = new_default_value changed = True if old_default_value_is_null != new_default_value_is_null: old_arg.default_value_is_null = new_default_value_is_null changed = True # Merge in any optional argument differences. old_optional = old_arg.optional new_optional = new_arg.optional if old_optional != new_optional: old_arg.optional = new_optional changed = True # Maybe merge annotations: if (isinstance(old_node, IDLAttribute) or isinstance(old_node, IDLOperation)): if self._merge_ext_attrs(old_node.ext_attrs, new_node.ext_attrs): changed = True # Remove annotations on obsolete items from the same source if import_options.obsolete_old_declarations: for (sig, old_node) in old_signatures_map.items(): if (source in old_node.annotations and sig not in new_signatures_map): _logger.warn('%s not available in %s anymore' % (sig, source)) del old_node.annotations[source] changed = True return changed def _merge_interfaces(self, old_interface, new_interface, import_options): """Merges the new_interface into the old_interface, annotating the interface with the sources of each change.""" changed = False source = import_options.source if (source and source not in old_interface.annotations and source in new_interface.annotations and not new_interface.is_supplemental): old_interface.annotations[source] = new_interface.annotations[ source] changed = True def merge_list(what): old_list = old_interface.__dict__[what] new_list = new_interface.__dict__[what] if what != 'parents' and old_interface.id != new_interface.id: for node in new_list: node.doc_js_interface_name = old_interface.id node.ext_attrs['ImplementedBy'] = new_interface.id changed = self._merge_nodes(old_list, new_list, import_options) # Delete list items with zero remaining annotations. if changed and import_options.obsolete_old_declarations: def has_annotations(idl_node): return len(idl_node.annotations) old_interface.__dict__[what] = filter(has_annotations, old_list) return changed # Smartly merge various declarations: if merge_list('parents'): changed = True if merge_list('constants'): changed = True if merge_list('attributes'): changed = True if merge_list('operations'): changed = True if self._merge_ext_attrs(old_interface.ext_attrs, new_interface.ext_attrs): changed = True _logger.info( 'merged interface %s (changed=%s, supplemental=%s)' % (old_interface.id, changed, new_interface.is_supplemental)) return changed def _merge_impl_stmt(self, impl_stmt, import_options): """Applies "X implements Y" statemetns on the proper places in the database""" implementor_name = impl_stmt.implementor.id implemented_name = impl_stmt.implemented.id _logger.info('merging impl stmt %s implements %s' % (implementor_name, implemented_name)) source = import_options.source if self._database.HasInterface(implementor_name): interface = self._database.GetInterface(implementor_name) if interface.parents is None: interface.parents = [] for parent in interface.parents: if parent.type.id == implemented_name: if source and source not in parent.annotations: parent.annotations[source] = IDLAnnotation( import_options.source_attributes) return # not found, so add new one parent = IDLParentInterface(None) parent.type = IDLType(implemented_name) if source: parent.annotations[source] = IDLAnnotation( import_options.source_attributes) interface.parents.append(parent) def merge_imported_interfaces(self): """Merges all imported interfaces and loads them into the DB.""" imported_interfaces = self._imported_interfaces # Step 1: Pre process imported interfaces for interface, import_options in imported_interfaces: self._annotate(interface, import_options) # Step 2: Add all new interfaces and merge overlapping ones for interface, import_options in imported_interfaces: if not interface.is_supplemental: if self._database.HasInterface(interface.id): old_interface = self._database.GetInterface(interface.id) self._merge_interfaces(old_interface, interface, import_options) else: if import_options.add_new_interfaces: self._database.AddInterface(interface) # Step 3: Merge in supplemental interfaces for interface, import_options in imported_interfaces: if interface.is_supplemental: target = interface.id if self._database.HasInterface(target): old_interface = self._database.GetInterface(target) self._merge_interfaces(old_interface, interface, import_options) else: _logger.warning("Supplemental target '%s' not found", target) # Step 4: Resolve 'implements' statements for impl_stmt, import_options in self._impl_stmts: self._merge_impl_stmt(impl_stmt, import_options) self._impl_stmts = [] self._imported_interfaces = [] def _compute_dart_idl_implements(self, idl_filename): full_path = os.path.realpath(idl_filename) with open(full_path) as f: idl_file_contents = f.read() implements_re = (r'^\s*' r'(\w+)\s+' r'implements\s+' r'(\w+)\s*' r';') implements_matches = re.finditer(implements_re, idl_file_contents, re.MULTILINE) return [match.groups() for match in implements_matches] # Compile the IDL file with the Blink compiler and remember each AST for the # IDL. def _blink_compile_idl_files(self, file_paths, import_options, is_dart_idl): if not (is_dart_idl): start_time = time.time() # Compute information for individual files # Information is stored in global variables interfaces_info and # partial_interface_files. for file_path in file_paths: self._info_collector.collect_info(file_path) end_time = time.time() print('Compute dependencies %s seconds' % round( (end_time - start_time), 2)) else: # Compute the interface_info for dart.idl for implements defined. This # file is special in that more than one interface can exist in this file. implement_pairs = self._compute_dart_idl_implements(file_paths[0]) self._info_collector.interfaces_info['__dart_idl___'] = { 'implement_pairs': implement_pairs, } # Parse the IDL files serially. start_time = time.time() for file_path in file_paths: file_path = os.path.normpath(file_path) ast = _compile_idl_file(self.build, file_path, import_options) self._process_ast( os.path.splitext(os.path.basename(file_path))[0], ast) end_time = time.time() print('Compiled %s IDL files in %s seconds' % (len(file_paths), round((end_time - start_time), 2))) def _process_ast(self, filename, ast): if len(ast) == 1: ast = ast.values()[0] else: print('ERROR: Processing AST: ' + os.path.basename(file_name)) new_asts[filename] = ast def import_idl_files(self, file_paths, import_options, is_dart_idl): self._blink_compile_idl_files(file_paths, import_options, is_dart_idl) start_time = time.time() # Parse the IDL files in serial. for file_path in file_paths: file_path = os.path.normpath(file_path) idl_file = _load_idl_file(self.build, file_path, import_options) _logger.info('Processing %s' % os.path.splitext(os.path.basename(file_path))[0]) self._process_idl_file(idl_file, import_options, is_dart_idl) end_time = time.time() for warning in report_unions_to_any(): _logger.warning(warning) print('Total %s files %sprocessed in databasebuilder in %s seconds' % \ (len(file_paths), '', round((end_time - start_time), 2))) def _process_idl_file(self, idl_file, import_options, dart_idl=False): # TODO(terry): strip_ext_attributes on an idl_file does nothing. #self._strip_ext_attributes(idl_file) self._resolve_type_defs(idl_file) self._rename_types(idl_file, import_options) def enabled(idl_node): return self._is_node_enabled(idl_node, import_options.idl_defines) for interface in idl_file.interfaces: if not self._is_node_enabled(interface, import_options.idl_defines): _logger.info('skipping interface %s (source=%s)' % (interface.id, import_options.source)) continue _logger.info('importing interface %s (source=%s file=%s)' % (interface.id, import_options.source, os.path.basename(idl_file.filename))) interface.attributes = filter(enabled, interface.attributes) interface.operations = filter(enabled, interface.operations) self._imported_interfaces.append((interface, import_options)) # If an IDL dictionary then there is no implementsStatements. if hasattr(idl_file, 'implementsStatements'): for implStmt in idl_file.implementsStatements: self._impl_stmts.append((implStmt, import_options)) for enum in idl_file.enums: self._database.AddEnum(enum) for dictionary in idl_file.dictionaries: self._database.AddDictionary(dictionary) # TODO(terry): Hack to remember all typedef unions they're mapped to any # - no type. for typedef in idl_file.typeDefs: self._database.AddTypeDef(typedef) def _is_node_enabled(self, node, idl_defines): if not 'Conditional' in node.ext_attrs: return True def enabled(condition): return 'ENABLE_%s' % condition in idl_defines conditional = node.ext_attrs['Conditional'] if conditional.find('&') != -1: for condition in conditional.split('&'): condition = condition.strip() self.conditionals_met.add(condition) if not enabled(condition): return False return True for condition in conditional.split('|'): condition = condition.strip() self.conditionals_met.add(condition) if enabled(condition): return True return False def fix_displacements(self, source): """E.g. In W3C, something is declared on HTMLDocument but in WebKit its on Document, so we need to mark that something in HTMLDocument with @WebKit(via=Document). The 'via' attribute specifies the parent interface that has the declaration.""" for interface in self._database.GetInterfaces(): changed = False _logger.info('fixing displacements in %s' % interface.id) for parent_interface in self._get_parent_interfaces(interface): _logger.info('scanning parent %s of %s' % (parent_interface.id, interface.id)) def fix_nodes(local_list, parent_list): changed = False parent_signatures_map = self._build_signatures_map( parent_list) for idl_node in local_list: sig = self._sign(idl_node) if sig in parent_signatures_map: parent_member = parent_signatures_map[sig] if (source in parent_member.annotations and source not in idl_node.annotations and _VIA_ANNOTATION_ATTR_NAME not in parent_member.annotations[source]): idl_node.annotations[source] = IDLAnnotation({ _VIA_ANNOTATION_ATTR_NAME: parent_interface.id }) changed = True return changed changed = fix_nodes(interface.constants, parent_interface.constants) or changed changed = fix_nodes(interface.attributes, parent_interface.attributes) or changed changed = fix_nodes(interface.operations, parent_interface.operations) or changed if changed: _logger.info('fixed displaced declarations in %s' % interface.id) def normalize_annotations(self, sources): """Makes the IDLs less verbose by removing annotation attributes that are identical to the ones defined at the interface level. Args: sources -- list of source names to normalize.""" for interface in self._database.GetInterfaces(): _logger.debug('normalizing annotations for %s' % interface.id) for source in sources: if (source not in interface.annotations or not interface.annotations[source]): continue top_level_annotation = interface.annotations[source] def normalize(idl_node): if (source in idl_node.annotations and idl_node.annotations[source]): annotation = idl_node.annotations[source] for name, value in annotation.items(): if (name in top_level_annotation and value == top_level_annotation[name]): del annotation[name] map(normalize, interface.parents) map(normalize, interface.constants) map(normalize, interface.attributes) map(normalize, interface.operations) def map_dictionaries(self): """Changes the type of operations/constructors arguments from an IDL dictionary to a Dictionary. The IDL dictionary is just an enums of strings which are checked at run-time.""" def dictionary_to_map(type_node): if self._database.HasDictionary(type_node.id): type_node.dictionary = type_node.id type_node.id = 'Dictionary' def all_types(node): map(dictionary_to_map, node.all(IDLType)) for interface in self._database.GetInterfaces(): map(all_types, interface.all(IDLExtAttrFunctionValue)) map(all_types, interface.attributes) map(all_types, interface.operations) def fetch_constructor_data(self, options): window_interface = self._database.GetInterface('Window') for attr in window_interface.attributes: type = attr.type.id if not type.endswith('Constructor'): continue type = re.sub('(Constructor)+$', '', type) # TODO(antonm): Ideally we'd like to have pristine copy of WebKit IDLs and fetch # this information directly from it. Unfortunately right now database is massaged # a lot so it's difficult to maintain necessary information on Window itself. interface = self._database.GetInterface(type) if 'V8EnabledPerContext' in attr.ext_attrs: interface.ext_attrs['synthesizedV8EnabledPerContext'] = \ attr.ext_attrs['V8EnabledPerContext'] if 'V8EnabledAtRuntime' in attr.ext_attrs: interface.ext_attrs['synthesizedV8EnabledAtRuntime'] = \ attr.ext_attrs['V8EnabledAtRuntime'] or attr.id # Iterate of the database looking for relationships between dictionaries and # interfaces marked with NoInterfaceObject. This mechanism can be used for # other IDL analysis. def examine_database(self): # Contains list of dictionary structure: {'dictionary': dictionary, 'usages': []} self._diag_dictionaries = [] self._dictionaries_used_types = [] # Record any dictionary. for dictionary in self._database.GetDictionaries(): self._diag_dictionaries.append({ 'dictionary': dictionary, 'usages': [] }) # Contains list of NoInterfaceObject structures: {'no_interface_object': dictionary, 'usages': []} self._diag_no_interfaces = [] self._no_interfaces_used_types = [] # Record any interface with Blink IDL Extended Attribute 'NoInterfaceObject'. for interface in self._database.GetInterfaces(): if interface.is_no_interface_object: self._diag_no_interfaces.append({ 'no_interface_object': interface, 'usages': [] }) for interface in self._database.GetInterfaces(): self._constructors(interface) self._constructors(interface, check_dictionaries=False) for attribute in interface.attributes: self._attribute_operation(interface, attribute) self._attribute_operation(interface, attribute, check_dictionaries=False) for operation in interface.operations: self._attribute_operation(interface, operation) self._attribute_operation(interface, operation, check_dictionaries=False) # Report all dictionaries and their usage. self._output_examination() # Report all interface marked with NoInterfaceObject and their usage. self._output_examination(check_dictionaries=False) print(''' Key: (READ-ONLY) - read-only attribute has relationship (GET/SET) - attribute has relationship RETURN - operation\'s returned value has relationship (ARGUMENT) - operation\'s argument(s) has relationship (New) - After dictionary name if constructor(s) exist (Ops,Props,New) after a NoInterfaceObject name is defined as: Ops - number of operations for a NoInterfaceObject Props - number of properties for a NoInterfaceObject New - T(#) number constructors for a NoInterfaceObject F no constructors for a NoInterfaceObject e.g., an interface 5 operations, 3 properties and 2 constructors would display (5,3,T(2)) Examination Complete ''') def _output_examination(self, check_dictionaries=True): # Output diagnostics. First columns is Dictionary or NoInterfaceObject e.g., # | Dictionary | Used In Interface | Usage Operation/Attribute | print('\n\n') title_bar = ['Dictionary', 'Used In Interface', 'Usage Operation/Attribute'] if check_dictionaries \ else ['NoInterfaceObject (Ops,Props,New)', 'Used In Interface', 'Usage Operation/Attribute'] self._tabulate_title(title_bar) diags = self._diag_dictionaries if check_dictionaries else self._diag_no_interfaces for diag in diags: if not (check_dictionaries): interface = diag['no_interface_object'] ops_count = len(interface.operations) properties_count = len(interface.attributes) any_constructors = 'Constructor' in interface.ext_attrs constructors = 'T(%s)' % len(interface.ext_attrs['Constructor'] ) if any_constructors else 'F' interface_detail = '%s (%s,%s,%s)' % \ (diag['no_interface_object'].id, ops_count, properties_count, constructors) self._tabulate([interface_detail, '', '']) else: dictionary = diag['dictionary'] any_constructors = 'Constructor' in dictionary.ext_attrs self._tabulate([ '%s%s' % (dictionary.id, ' (New)' if any_constructors else ''), '', '' ]) for usage in diag['usages']: detail = '' if 'attribute' in usage: attribute_type = 'READ-ONLY' if not usage[ 'argument'] else 'GET/SET' detail = '(%s) %s' % (attribute_type, usage['attribute']) elif 'operation' in usage: detail = '%s %s%s' % ('RETURN' if usage['result'] else '', usage['operation'], '(ARGUMENT)' if usage['argument'] else '') self._tabulate([None, usage['interface'], detail]) self._tabulate_break() # operation_or_attribute either IDLOperation or IDLAttribute if None then # its a constructor (IDLExtAttrFunctionValue). def _mark_usage(self, interface, operation_or_attribute=None, check_dictionaries=True): for diag in self._diag_dictionaries if check_dictionaries else self._diag_no_interfaces: for usage in diag['usages']: if not usage['interface']: usage['interface'] = interface.id if isinstance(operation_or_attribute, IDLOperation): usage['operation'] = operation_or_attribute.id if check_dictionaries: usage['result'] = hasattr(operation_or_attribute.type, 'dictionary') and \ operation_or_attribute.type.dictionary == diag['dictionary'].id else: usage[ 'result'] = operation_or_attribute.type.id == diag[ 'no_interface_object'].id usage['argument'] = False for argument in operation_or_attribute.arguments: if check_dictionaries: arg = hasattr( argument.type, 'dictionary' ) and argument.type.dictionary == diag[ 'dictionary'].id else: arg = argument.type.id == diag[ 'no_interface_object'].id if arg: usage['argument'] = arg elif isinstance(operation_or_attribute, IDLAttribute): usage['attribute'] = operation_or_attribute.id usage['result'] = True usage[ 'argument'] = not operation_or_attribute.is_read_only elif not operation_or_attribute: # Its a constructor only argument is dictionary or interface with NoInterfaceObject. usage['operation'] = 'constructor' usage['result'] = False usage['argument'] = True def _remember_usage(self, node, check_dictionaries=True): if check_dictionaries: used_types = self._dictionaries_used_types diag_list = self._diag_dictionaries diag_name = 'dictionary' else: used_types = self._no_interfaces_used_types diag_list = self._diag_no_interfaces diag_name = 'no_interface_object' if len(used_types) > 0: normalized_used = list(set(used_types)) for recorded_id in normalized_used: for diag in diag_list: if diag[diag_name].id == recorded_id: diag['usages'].append({ 'interface': None, 'node': node }) # Iterator function to look for any IDLType that is a dictionary then remember # that dictionary. def _dictionary_used(self, type_node): if hasattr(type_node, 'dictionary'): dictionary_id = type_node.dictionary if self._database.HasDictionary(dictionary_id): for diag_dictionary in self._diag_dictionaries: if diag_dictionary['dictionary'].id == dictionary_id: # Record the dictionary that was referenced. self._dictionaries_used_types.append(dictionary_id) return # If we get to this point, the IDL dictionary was never defined ... oops. print('DIAGNOSE_ERROR: IDL Dictionary %s doesn\'t exist.' % dictionary_id) # Iterator function to look for any IDLType that is an interface marked with # NoInterfaceObject then remember that interface. def _no_interface_used(self, type_node): if hasattr(type_node, 'id'): no_interface_id = type_node.id if self._database.HasInterface(no_interface_id): no_interface = self._database.GetInterface(no_interface_id) if no_interface.is_no_interface_object: for diag_no_interface in self._diag_no_interfaces: if diag_no_interface[ 'no_interface_object'].id == no_interface_id: # Record the interface marked with NoInterfaceObject. self._no_interfaces_used_types.append( no_interface_id) return def _constructors(self, interface, check_dictionaries=True): if check_dictionaries: self._dictionaries_used_types = [] constructor_function = self._dictionary_constructor_types else: self._no_interfaces_used_types = [] constructor_function = self._no_interface_constructor_types map(constructor_function, interface.all(IDLExtAttrFunctionValue)) self._mark_usage(interface, check_dictionaries=check_dictionaries) # Scan an attribute or operation for a dictionary or interface with NoInterfaceObject # reference. def _attribute_operation(self, interface, operation_attribute, check_dictionaries=True): if check_dictionaries: self._dictionaries_used_types = [] used = self._dictionary_used else: self._no_interfaces_used_types = [] used = self._no_interface_used map(used, operation_attribute.all(IDLType)) self._remember_usage(operation_attribute, check_dictionaries=check_dictionaries) self._mark_usage(interface, operation_attribute, check_dictionaries=check_dictionaries) # Iterator function for map to iterate over all constructor types # (IDLExtAttrFunctionValue) that have a dictionary reference. def _dictionary_constructor_types(self, node): self._dictionaries_used_types = [] map(self._dictionary_used, node.all(IDLType)) self._remember_usage(node) # Iterator function for map to iterate over all constructor types # (IDLExtAttrFunctionValue) that reference an interface with NoInterfaceObject. def _no_interface_constructor_types(self, node): self._no_interfaces_used_types = [] map(self._no_interface_used, node.all(IDLType)) self._remember_usage(node, check_dictionaries=False) # Maximum width of each column. def _TABULATE_WIDTH(self): return 45 def _tabulate_title(self, row_title): title_separator = "=" * self._TABULATE_WIDTH() self._tabulate([title_separator, title_separator, title_separator]) self._tabulate(row_title) self._tabulate([title_separator, title_separator, title_separator]) def _tabulate_break(self): break_separator = "-" * self._TABULATE_WIDTH() self._tabulate([break_separator, break_separator, break_separator]) def _tabulate(self, columns): """Tabulate a list of columns for a row. Each item in columns is a column value each column will be padded up to _TABULATE_WIDTH. Each column starts/ends with a vertical bar '|' the format a row: | columns[0] | columns[1] | columns[2] | ... | """ if len(columns) > 0: for column in columns: value = '' if not column else column sys.stdout.write('|{0:^{1}}'.format(value, self._TABULATE_WIDTH())) else: sys.stdout.write('|{0:^{1}}'.format('', self._TABULATE_WIDTH())) sys.stdout.write('|\n')