1 # Copyright (C) 2013 Google Inc. All rights reserved. 2 # coding=utf-8 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions are 6 # met: 7 # 8 # * Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # * Redistributions in binary form must reproduce the above 11 # copyright notice, this list of conditions and the following disclaimer 12 # in the documentation and/or other materials provided with the 13 # distribution. 14 # * Neither the name of Google Inc. nor the names of its 15 # contributors may be used to endorse or promote products derived from 16 # this software without specific prior written permission. 17 # 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 """Generate template values for an interface. 31 32 Design doc: http://www.chromium.org/developers/design-documents/idl-compiler 33 """ 34 35 from collections import defaultdict 36 import itertools 37 from operator import itemgetter 38 39 import idl_definitions 40 from idl_definitions import IdlOperation 41 import idl_types 42 from idl_types import IdlType, inherits_interface 43 import v8_attributes 44 from v8_globals import includes 45 import v8_methods 46 import v8_types 47 from v8_types import cpp_ptr_type, cpp_template_type 48 import v8_utilities 49 from v8_utilities import (capitalize, conditional_string, cpp_name, gc_type, 50 has_extended_attribute_value, runtime_enabled_function_name, 51 extended_attribute_value_as_list) 52 53 54 INTERFACE_H_INCLUDES = frozenset([ 55 'bindings/core/v8/ScriptWrappable.h', 56 'bindings/core/v8/V8Binding.h', 57 'bindings/core/v8/V8DOMWrapper.h', 58 'bindings/core/v8/WrapperTypeInfo.h', 59 'platform/heap/Handle.h', 60 ]) 61 INTERFACE_CPP_INCLUDES = frozenset([ 62 'bindings/core/v8/ExceptionState.h', 63 'bindings/core/v8/V8DOMConfiguration.h', 64 'bindings/core/v8/V8HiddenValue.h', 65 'bindings/core/v8/V8ObjectConstructor.h', 66 'core/dom/ContextFeatures.h', 67 'core/dom/Document.h', 68 'platform/RuntimeEnabledFeatures.h', 69 'platform/TraceEvent.h', 70 'wtf/GetPtr.h', 71 'wtf/RefPtr.h', 72 ]) 73 74 75 def interface_context(interface): 76 includes.clear() 77 includes.update(INTERFACE_CPP_INCLUDES) 78 header_includes = set(INTERFACE_H_INCLUDES) 79 80 parent_interface = interface.parent 81 if parent_interface: 82 header_includes.update(v8_types.includes_for_interface(parent_interface)) 83 extended_attributes = interface.extended_attributes 84 85 is_audio_buffer = inherits_interface(interface.name, 'AudioBuffer') 86 if is_audio_buffer: 87 includes.add('modules/webaudio/AudioBuffer.h') 88 89 is_document = inherits_interface(interface.name, 'Document') 90 if is_document: 91 includes.update(['bindings/core/v8/ScriptController.h', 92 'bindings/core/v8/WindowProxy.h', 93 'core/frame/LocalFrame.h']) 94 95 # [ActiveDOMObject] 96 is_active_dom_object = 'ActiveDOMObject' in extended_attributes 97 98 # [CheckSecurity] 99 is_check_security = 'CheckSecurity' in extended_attributes 100 if is_check_security: 101 includes.add('bindings/core/v8/BindingSecurity.h') 102 103 # [DependentLifetime] 104 is_dependent_lifetime = 'DependentLifetime' in extended_attributes 105 106 # [Iterable] 107 iterator_method = None 108 if 'Iterable' in extended_attributes: 109 iterator_operation = IdlOperation(interface.idl_name) 110 iterator_operation.name = 'iterator' 111 iterator_operation.idl_type = IdlType('Iterator') 112 iterator_operation.extended_attributes['RaisesException'] = None 113 iterator_operation.extended_attributes['CallWith'] = 'ScriptState' 114 iterator_method = v8_methods.method_context(interface, 115 iterator_operation) 116 117 # [MeasureAs] 118 is_measure_as = 'MeasureAs' in extended_attributes 119 if is_measure_as: 120 includes.add('core/frame/UseCounter.h') 121 122 # [SetWrapperReferenceFrom] 123 reachable_node_function = extended_attributes.get('SetWrapperReferenceFrom') 124 if reachable_node_function: 125 includes.update(['bindings/core/v8/V8GCController.h', 126 'core/dom/Element.h']) 127 128 # [SetWrapperReferenceTo] 129 set_wrapper_reference_to_list = [{ 130 'name': argument.name, 131 # FIXME: properly should be: 132 # 'cpp_type': argument.idl_type.cpp_type_args(raw_type=True), 133 # (if type is non-wrapper type like NodeFilter, normally RefPtr) 134 # Raw pointers faster though, and NodeFilter hacky anyway. 135 'cpp_type': argument.idl_type.implemented_as + '*', 136 'idl_type': argument.idl_type, 137 'v8_type': v8_types.v8_type(argument.idl_type.name), 138 } for argument in extended_attributes.get('SetWrapperReferenceTo', [])] 139 for set_wrapper_reference_to in set_wrapper_reference_to_list: 140 set_wrapper_reference_to['idl_type'].add_includes_for_type() 141 142 # [NotScriptWrappable] 143 is_script_wrappable = 'NotScriptWrappable' not in extended_attributes 144 145 # [SpecialWrapFor] 146 if 'SpecialWrapFor' in extended_attributes: 147 special_wrap_for = extended_attribute_value_as_list(interface, 'SpecialWrapFor') 148 else: 149 special_wrap_for = [] 150 for special_wrap_interface in special_wrap_for: 151 v8_types.add_includes_for_interface(special_wrap_interface) 152 153 # [Custom=Wrap], [SetWrapperReferenceFrom] 154 has_visit_dom_wrapper = ( 155 has_extended_attribute_value(interface, 'Custom', 'VisitDOMWrapper') or 156 reachable_node_function or 157 set_wrapper_reference_to_list) 158 159 this_gc_type = gc_type(interface) 160 161 wrapper_class_id = ('NodeClassId' if inherits_interface(interface.name, 'Node') else 'ObjectClassId') 162 163 context = { 164 'conditional_string': conditional_string(interface), # [Conditional] 165 'cpp_class': cpp_name(interface), 166 'gc_type': this_gc_type, 167 # FIXME: Remove 'EventTarget' special handling, http://crbug.com/383699 168 'has_access_check_callbacks': (is_check_security and 169 interface.name != 'Window' and 170 interface.name != 'EventTarget'), 171 'has_custom_legacy_call_as_function': has_extended_attribute_value(interface, 'Custom', 'LegacyCallAsFunction'), # [Custom=LegacyCallAsFunction] 172 'has_custom_to_v8': has_extended_attribute_value(interface, 'Custom', 'ToV8'), # [Custom=ToV8] 173 'has_custom_wrap': has_extended_attribute_value(interface, 'Custom', 'Wrap'), # [Custom=Wrap] 174 'has_visit_dom_wrapper': has_visit_dom_wrapper, 175 'header_includes': header_includes, 176 'interface_name': interface.name, 177 'is_active_dom_object': is_active_dom_object, 178 'is_audio_buffer': is_audio_buffer, 179 'is_check_security': is_check_security, 180 'is_dependent_lifetime': is_dependent_lifetime, 181 'is_document': is_document, 182 'is_event_target': inherits_interface(interface.name, 'EventTarget'), 183 'is_exception': interface.is_exception, 184 'is_node': inherits_interface(interface.name, 'Node'), 185 'is_script_wrappable': is_script_wrappable, 186 'iterator_method': iterator_method, 187 'lifetime': 'Dependent' 188 if (has_visit_dom_wrapper or 189 is_active_dom_object or 190 is_dependent_lifetime) 191 else 'Independent', 192 'measure_as': v8_utilities.measure_as(interface), # [MeasureAs] 193 'parent_interface': parent_interface, 194 'pass_cpp_type': cpp_template_type( 195 cpp_ptr_type('PassRefPtr', 'RawPtr', this_gc_type), 196 cpp_name(interface)), 197 'reachable_node_function': reachable_node_function, 198 'runtime_enabled_function': runtime_enabled_function_name(interface), # [RuntimeEnabled] 199 'set_wrapper_reference_to_list': set_wrapper_reference_to_list, 200 'special_wrap_for': special_wrap_for, 201 'v8_class': v8_utilities.v8_class_name(interface), 202 'wrapper_class_id': wrapper_class_id, 203 } 204 205 # Constructors 206 constructors = [constructor_context(interface, constructor) 207 for constructor in interface.constructors 208 # FIXME: shouldn't put named constructors with constructors 209 # (currently needed for Perl compatibility) 210 # Handle named constructors separately 211 if constructor.name == 'Constructor'] 212 if len(constructors) > 1: 213 context['constructor_overloads'] = overloads_context(constructors) 214 215 # [CustomConstructor] 216 custom_constructors = [{ # Only needed for computing interface length 217 'number_of_required_arguments': 218 number_of_required_arguments(constructor), 219 } for constructor in interface.custom_constructors] 220 221 # [EventConstructor] 222 has_event_constructor = 'EventConstructor' in extended_attributes 223 any_type_attributes = [attribute for attribute in interface.attributes 224 if attribute.idl_type.name == 'Any'] 225 if has_event_constructor: 226 includes.add('bindings/core/v8/Dictionary.h') 227 if any_type_attributes: 228 includes.add('bindings/core/v8/SerializedScriptValue.h') 229 230 # [NamedConstructor] 231 named_constructor = named_constructor_context(interface) 232 233 if (constructors or custom_constructors or has_event_constructor or 234 named_constructor): 235 includes.add('bindings/core/v8/V8ObjectConstructor.h') 236 includes.add('core/frame/LocalDOMWindow.h') 237 238 context.update({ 239 'any_type_attributes': any_type_attributes, 240 'constructors': constructors, 241 'has_custom_constructor': bool(custom_constructors), 242 'has_event_constructor': has_event_constructor, 243 'interface_length': 244 interface_length(interface, constructors + custom_constructors), 245 'is_constructor_raises_exception': extended_attributes.get('RaisesException') == 'Constructor', # [RaisesException=Constructor] 246 'named_constructor': named_constructor, 247 }) 248 249 constants = [constant_context(constant) for constant in interface.constants] 250 251 special_getter_constants = [] 252 runtime_enabled_constants = [] 253 constant_configuration_constants = [] 254 255 for constant in constants: 256 if constant['measure_as'] or constant['deprecate_as']: 257 special_getter_constants.append(constant) 258 continue 259 if constant['runtime_enabled_function']: 260 runtime_enabled_constants.append(constant) 261 continue 262 constant_configuration_constants.append(constant) 263 264 # Constants 265 context.update({ 266 'constant_configuration_constants': constant_configuration_constants, 267 'constants': constants, 268 'do_not_check_constants': 'DoNotCheckConstants' in extended_attributes, 269 'has_constant_configuration': any( 270 not constant['runtime_enabled_function'] 271 for constant in constants), 272 'runtime_enabled_constants': runtime_enabled_constants, 273 'special_getter_constants': special_getter_constants, 274 }) 275 276 # Attributes 277 attributes = [v8_attributes.attribute_context(interface, attribute) 278 for attribute in interface.attributes] 279 context.update({ 280 'attributes': attributes, 281 'has_accessors': any(attribute['is_expose_js_accessors'] and attribute['should_be_exposed_to_script'] for attribute in attributes), 282 'has_attribute_configuration': any( 283 not (attribute['is_expose_js_accessors'] or 284 attribute['is_static'] or 285 attribute['runtime_enabled_function'] or 286 attribute['per_context_enabled_function']) 287 and attribute['should_be_exposed_to_script'] 288 for attribute in attributes), 289 'has_conditional_attributes': any(attribute['per_context_enabled_function'] or attribute['exposed_test'] for attribute in attributes), 290 'has_constructor_attributes': any(attribute['constructor_type'] for attribute in attributes), 291 'has_replaceable_attributes': any(attribute['is_replaceable'] for attribute in attributes), 292 }) 293 294 # Methods 295 methods = [v8_methods.method_context(interface, method) 296 for method in interface.operations 297 if method.name] # Skip anonymous special operations (methods) 298 compute_method_overloads_context(methods) 299 300 # Stringifier 301 if interface.stringifier: 302 stringifier = interface.stringifier 303 method = IdlOperation(interface.idl_name) 304 method.name = 'toString' 305 method.idl_type = IdlType('DOMString') 306 method.extended_attributes.update(stringifier.extended_attributes) 307 if stringifier.attribute: 308 method.extended_attributes['ImplementedAs'] = stringifier.attribute.name 309 elif stringifier.operation: 310 method.extended_attributes['ImplementedAs'] = stringifier.operation.name 311 methods.append(v8_methods.method_context(interface, method)) 312 313 conditionally_enabled_methods = [] 314 custom_registration_methods = [] 315 method_configuration_methods = [] 316 317 for method in methods: 318 # Skip all but one method in each set of overloaded methods. 319 if 'overload_index' in method and 'overloads' not in method: 320 continue 321 322 if 'overloads' in method: 323 overloads = method['overloads'] 324 per_context_enabled_function = overloads['per_context_enabled_function_all'] 325 conditionally_exposed_function = overloads['exposed_test_all'] 326 runtime_enabled_function = overloads['runtime_enabled_function_all'] 327 has_custom_registration = overloads['has_custom_registration_all'] 328 else: 329 per_context_enabled_function = method['per_context_enabled_function'] 330 conditionally_exposed_function = method['exposed_test'] 331 runtime_enabled_function = method['runtime_enabled_function'] 332 has_custom_registration = method['has_custom_registration'] 333 334 if per_context_enabled_function or conditionally_exposed_function: 335 conditionally_enabled_methods.append(method) 336 continue 337 if runtime_enabled_function or has_custom_registration: 338 custom_registration_methods.append(method) 339 continue 340 if method['should_be_exposed_to_script']: 341 method_configuration_methods.append(method) 342 343 for method in methods: 344 # The value of the Function objects length property is a Number 345 # determined as follows: 346 # 1. Let S be the effective overload set for regular operations (if the 347 # operation is a regular operation) or for static operations (if the 348 # operation is a static operation) with identifier id on interface I and 349 # with argument count 0. 350 # 2. Return the length of the shortest argument list of the entries in S. 351 # FIXME: This calculation doesn't take into account whether runtime 352 # enabled overloads are actually enabled, so length may be incorrect. 353 # E.g., [RuntimeEnabled=Foo] void f(); void f(long x); 354 # should have length 1 if Foo is not enabled, but length 0 if it is. 355 method['length'] = (method['overloads']['minarg'] if 'overloads' in method else 356 method['number_of_required_arguments']) 357 358 context.update({ 359 'conditionally_enabled_methods': conditionally_enabled_methods, 360 'custom_registration_methods': custom_registration_methods, 361 'has_origin_safe_method_setter': any( 362 method['is_check_security_for_frame'] and not method['is_read_only'] 363 for method in methods), 364 'has_private_script': any(attribute['is_implemented_in_private_script'] for attribute in attributes) or 365 any(method['is_implemented_in_private_script'] for method in methods), 366 'method_configuration_methods': method_configuration_methods, 367 'methods': methods, 368 }) 369 370 context.update({ 371 'indexed_property_getter': indexed_property_getter(interface), 372 'indexed_property_setter': indexed_property_setter(interface), 373 'indexed_property_deleter': indexed_property_deleter(interface), 374 'is_override_builtins': 'OverrideBuiltins' in extended_attributes, 375 'named_property_getter': named_property_getter(interface), 376 'named_property_setter': named_property_setter(interface), 377 'named_property_deleter': named_property_deleter(interface), 378 }) 379 380 return context 381 382 383 # [DeprecateAs], [Reflect], [RuntimeEnabled] 384 def constant_context(constant): 385 extended_attributes = constant.extended_attributes 386 return { 387 'cpp_class': extended_attributes.get('PartialInterfaceImplementedAs'), 388 'deprecate_as': v8_utilities.deprecate_as(constant), # [DeprecateAs] 389 'idl_type': constant.idl_type.name, 390 'measure_as': v8_utilities.measure_as(constant), # [MeasureAs] 391 'name': constant.name, 392 # FIXME: use 'reflected_name' as correct 'name' 393 'reflected_name': extended_attributes.get('Reflect', constant.name), 394 'runtime_enabled_function': runtime_enabled_function_name(constant), 395 'value': constant.value, 396 } 397 398 399 ################################################################################ 400 # Overloads 401 ################################################################################ 402 403 def compute_method_overloads_context(methods): 404 # Regular methods 405 compute_method_overloads_context_by_type([method for method in methods 406 if not method['is_static']]) 407 # Static methods 408 compute_method_overloads_context_by_type([method for method in methods 409 if method['is_static']]) 410 411 412 def compute_method_overloads_context_by_type(methods): 413 """Computes |method.overload*| template values. 414 415 Called separately for static and non-static (regular) methods, 416 as these are overloaded separately. 417 Modifies |method| in place for |method| in |methods|. 418 Doesn't change the |methods| list itself (only the values, i.e. individual 419 methods), so ok to treat these separately. 420 """ 421 # Add overload information only to overloaded methods, so template code can 422 # easily verify if a function is overloaded 423 for name, overloads in method_overloads_by_name(methods): 424 # Resolution function is generated after last overloaded function; 425 # package necessary information into |method.overloads| for that method. 426 overloads[-1]['overloads'] = overloads_context(overloads) 427 overloads[-1]['overloads']['name'] = name 428 429 430 def method_overloads_by_name(methods): 431 """Returns generator of overloaded methods by name: [name, [method]]""" 432 # Filter to only methods that are actually overloaded 433 method_counts = Counter(method['name'] for method in methods) 434 overloaded_method_names = set(name 435 for name, count in method_counts.iteritems() 436 if count > 1) 437 overloaded_methods = [method for method in methods 438 if method['name'] in overloaded_method_names] 439 440 # Group by name (generally will be defined together, but not necessarily) 441 return sort_and_groupby(overloaded_methods, itemgetter('name')) 442 443 444 def overloads_context(overloads): 445 """Returns |overloads| template values for a single name. 446 447 Sets |method.overload_index| in place for |method| in |overloads| 448 and returns dict of overall overload template values. 449 """ 450 assert len(overloads) > 1 # only apply to overloaded names 451 for index, method in enumerate(overloads, 1): 452 method['overload_index'] = index 453 454 effective_overloads_by_length = effective_overload_set_by_length(overloads) 455 lengths = [length for length, _ in effective_overloads_by_length] 456 name = overloads[0].get('name', '<constructor>') 457 458 # Check and fail if all overloads with the shortest acceptable arguments 459 # list are runtime enabled, since we would otherwise set 'length' on the 460 # function object to an incorrect value when none of those overloads were 461 # actually enabled at runtime. The exception is if all overloads are 462 # controlled by the same runtime enabled feature, in which case there would 463 # be no function object at all if it is not enabled. 464 shortest_overloads = effective_overloads_by_length[0][1] 465 if (all(method.get('runtime_enabled_function') 466 for method, _, _ in shortest_overloads) and 467 not common_value(overloads, 'runtime_enabled_function')): 468 raise ValueError('Function.length of %s depends on runtime enabled features' % name) 469 470 # Check and fail if overloads disagree on any of the extended attributes 471 # that affect how the method should be registered. 472 # Skip the check for overloaded constructors, since they don't support any 473 # of the extended attributes in question. 474 if not overloads[0].get('is_constructor'): 475 overload_extended_attributes = [ 476 method['custom_registration_extended_attributes'] 477 for method in overloads] 478 for extended_attribute in v8_methods.CUSTOM_REGISTRATION_EXTENDED_ATTRIBUTES: 479 if common_key(overload_extended_attributes, extended_attribute) is None: 480 raise ValueError('Overloads of %s have conflicting extended attribute %s' 481 % (name, extended_attribute)) 482 483 # Check and fail if overloads disagree about whether the return type 484 # is a Promise or not. 485 promise_overload_count = sum(1 for method in overloads if method.get('idl_type') == 'Promise') 486 if promise_overload_count not in (0, len(overloads)): 487 raise ValueError('Overloads of %s have conflicting Promise/non-Promise types' 488 % (name)) 489 490 return { 491 'deprecate_all_as': common_value(overloads, 'deprecate_as'), # [DeprecateAs] 492 'exposed_test_all': common_value(overloads, 'exposed_test'), # [Exposed] 493 'has_custom_registration_all': common_value(overloads, 'has_custom_registration'), 494 'length_tests_methods': length_tests_methods(effective_overloads_by_length), 495 # 1. Let maxarg be the length of the longest type list of the 496 # entries in S. 497 'maxarg': lengths[-1], 498 'measure_all_as': common_value(overloads, 'measure_as'), # [MeasureAs] 499 'minarg': lengths[0], 500 'per_context_enabled_function_all': common_value(overloads, 'per_context_enabled_function'), # [PerContextEnabled] 501 'runtime_enabled_function_all': common_value(overloads, 'runtime_enabled_function'), # [RuntimeEnabled] 502 'valid_arities': lengths 503 # Only need to report valid arities if there is a gap in the 504 # sequence of possible lengths, otherwise invalid length means 505 # "not enough arguments". 506 if lengths[-1] - lengths[0] != len(lengths) - 1 else None, 507 } 508 509 510 def effective_overload_set(F): 511 """Returns the effective overload set of an overloaded function. 512 513 An effective overload set is the set of overloaded functions + signatures 514 (type list of arguments, with optional and variadic arguments included or 515 not), and is used in the overload resolution algorithm. 516 517 For example, given input [f1(optional long x), f2(DOMString s)], the output 518 is informally [f1(), f1(long), f2(DOMString)], and formally 519 [(f1, [], []), (f1, [long], [optional]), (f2, [DOMString], [required])]. 520 521 Currently the optionality list is a list of |is_optional| booleans (True 522 means optional, False means required); to support variadics this needs to 523 be tri-valued as required, optional, or variadic. 524 525 Formally: 526 An effective overload set represents the allowable invocations for a 527 particular operation, constructor (specified with [Constructor] or 528 [NamedConstructor]), legacy caller or callback function. 529 530 An additional argument N (argument count) is needed when overloading 531 variadics, but we don't use that currently. 532 533 Spec: http://heycam.github.io/webidl/#dfn-effective-overload-set 534 535 Formally the input and output lists are sets, but methods are stored 536 internally as dicts, which can't be stored in a set because they are not 537 hashable, so we use lists instead. 538 539 Arguments: 540 F: list of overloads for a given callable name. 541 542 Returns: 543 S: list of tuples of the form (callable, type list, optionality list). 544 """ 545 # Code closely follows the algorithm in the spec, for clarity and 546 # correctness, and hence is not very Pythonic. 547 548 # 1. Initialize S to . 549 # (We use a list because we can't use a set, as noted above.) 550 S = [] 551 552 # 2. Let F be a set with elements as follows, according to the kind of 553 # effective overload set: 554 # (Passed as argument, nothing to do.) 555 556 # 3. & 4. (maxarg, m) are only needed for variadics, not used. 557 558 # 5. For each operation, extended attribute or callback function X in F: 559 for X in F: # X is the "callable", F is the overloads. 560 arguments = X['arguments'] 561 # 1. Let n be the number of arguments X is declared to take. 562 n = len(arguments) 563 # 2. Let t0..n1 be a list of types, where ti is the type of Xs 564 # argument at index i. 565 # (type list) 566 t = tuple(argument['idl_type_object'] for argument in arguments) 567 # 3. Let o0..n1 be a list of optionality values, where oi is variadic 568 # if Xs argument at index i is a final, variadic argument, optional 569 # if the argument is optional, and required otherwise. 570 # (optionality list) 571 # (Were just using a boolean for optional vs. required.) 572 o = tuple(argument['is_optional'] for argument in arguments) 573 # 4. Add to S the tuple <X, t0..n1, o0..n1>. 574 S.append((X, t, o)) 575 # 5. If X is declared to be variadic, then: 576 # (Not used, so not implemented.) 577 # 6. Initialize i to n1. 578 i = n - 1 579 # 7. While i 0: 580 # Spec bug (fencepost error); should be While i > 0: 581 # https://www.w3.org/Bugs/Public/show_bug.cgi?id=25590 582 while i > 0: 583 # 1. If argument i of X is not optional, then break this loop. 584 if not o[i]: 585 break 586 # 2. Otherwise, add to S the tuple <X, t0..i1, o0..i1>. 587 S.append((X, t[:i], o[:i])) 588 # 3. Set i to i1. 589 i = i - 1 590 # 8. If n > 0 and all arguments of X are optional, then add to S the 591 # tuple <X, (), ()> (where () represents the empty list). 592 if n > 0 and all(oi for oi in o): 593 S.append((X, [], [])) 594 # 6. The effective overload set is S. 595 return S 596 597 598 def effective_overload_set_by_length(overloads): 599 def type_list_length(entry): 600 # Entries in the effective overload set are 3-tuples: 601 # (callable, type list, optionality list) 602 return len(entry[1]) 603 604 effective_overloads = effective_overload_set(overloads) 605 return list(sort_and_groupby(effective_overloads, type_list_length)) 606 607 608 def distinguishing_argument_index(entries): 609 """Returns the distinguishing argument index for a sequence of entries. 610 611 Entries are elements of the effective overload set with the same number 612 of arguments (formally, same type list length), each a 3-tuple of the form 613 (callable, type list, optionality list). 614 615 Spec: http://heycam.github.io/webidl/#dfn-distinguishing-argument-index 616 617 If there is more than one entry in an effective overload set that has a 618 given type list length, then for those entries there must be an index i 619 such that for each pair of entries the types at index i are 620 distinguishable. 621 The lowest such index is termed the distinguishing argument index for the 622 entries of the effective overload set with the given type list length. 623 """ 624 # Only applicable If there is more than one entry 625 assert len(entries) > 1 626 type_lists = [tuple(idl_type.name for idl_type in entry[1]) 627 for entry in entries] 628 type_list_length = len(type_lists[0]) 629 # Only applicable for entries that [have] a given type list length 630 assert all(len(type_list) == type_list_length for type_list in type_lists) 631 name = entries[0][0].get('name', 'Constructor') # for error reporting 632 633 # The spec defines the distinguishing argument index by conditions it must 634 # satisfy, but does not give an algorithm. 635 # 636 # We compute the distinguishing argument index by first computing the 637 # minimum index where not all types are the same, and then checking that 638 # all types in this position are distinguishable (and the optionality lists 639 # up to this point are identical), since "minimum index where not all types 640 # are the same" is a *necessary* condition, and more direct to check than 641 # distinguishability. 642 types_by_index = (set(types) for types in zip(*type_lists)) 643 try: 644 # In addition, for each index j, where j is less than the 645 # distinguishing argument index for a given type list length, the types 646 # at index j in all of the entries type lists must be the same 647 index = next(i for i, types in enumerate(types_by_index) 648 if len(types) > 1) 649 except StopIteration: 650 raise ValueError('No distinguishing index found for %s, length %s:\n' 651 'All entries have the same type list:\n' 652 '%s' % (name, type_list_length, type_lists[0])) 653 # Check optionality 654 # and the booleans in the corresponding list indicating argument 655 # optionality must be the same. 656 # FIXME: spec typo: optionality value is no longer a boolean 657 # https://www.w3.org/Bugs/Public/show_bug.cgi?id=25628 658 initial_optionality_lists = set(entry[2][:index] for entry in entries) 659 if len(initial_optionality_lists) > 1: 660 raise ValueError( 661 'Invalid optionality lists for %s, length %s:\n' 662 'Optionality lists differ below distinguishing argument index %s:\n' 663 '%s' 664 % (name, type_list_length, index, set(initial_optionality_lists))) 665 666 # Check distinguishability 667 # http://heycam.github.io/webidl/#dfn-distinguishable 668 # Use names to check for distinct types, since objects are distinct 669 # FIXME: check distinguishability more precisely, for validation 670 distinguishing_argument_type_names = [type_list[index] 671 for type_list in type_lists] 672 if (len(set(distinguishing_argument_type_names)) != 673 len(distinguishing_argument_type_names)): 674 raise ValueError('Types in distinguishing argument are not distinct:\n' 675 '%s' % distinguishing_argument_type_names) 676 677 return index 678 679 680 def length_tests_methods(effective_overloads_by_length): 681 """Returns sorted list of resolution tests and associated methods, by length. 682 683 This builds the main data structure for the overload resolution loop. 684 For a given argument length, bindings test argument at distinguishing 685 argument index, in order given by spec: if it is compatible with 686 (optionality or) type required by an overloaded method, resolve to that 687 method. 688 689 Returns: 690 [(length, [(test, method)])] 691 """ 692 return [(length, list(resolution_tests_methods(effective_overloads))) 693 for length, effective_overloads in effective_overloads_by_length] 694 695 696 def resolution_tests_methods(effective_overloads): 697 """Yields resolution test and associated method, in resolution order, for effective overloads of a given length. 698 699 This is the heart of the resolution algorithm. 700 http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm 701 702 Note that a given method can be listed multiple times, with different tests! 703 This is to handle implicit type conversion. 704 705 Returns: 706 [(test, method)] 707 """ 708 methods = [effective_overload[0] 709 for effective_overload in effective_overloads] 710 if len(methods) == 1: 711 # If only one method with a given length, no test needed 712 yield 'true', methods[0] 713 return 714 715 # 6. If there is more than one entry in S, then set d to be the 716 # distinguishing argument index for the entries of S. 717 index = distinguishing_argument_index(effective_overloads) 718 # (7-9 are for handling |undefined| values for optional arguments before 719 # the distinguishing argument (as missing), so you can specify only some 720 # optional arguments. We dont support this, so we skip these steps.) 721 # 10. If i = d, then: 722 # (d is the distinguishing argument index) 723 # 1. Let V be argi. 724 # Note: This is the argument that will be used to resolve which 725 # overload is selected. 726 cpp_value = 'info[%s]' % index 727 728 # Extract argument and IDL type to simplify accessing these in each loop. 729 arguments = [method['arguments'][index] for method in methods] 730 arguments_methods = zip(arguments, methods) 731 idl_types = [argument['idl_type_object'] for argument in arguments] 732 idl_types_methods = zip(idl_types, methods) 733 734 # We cant do a single loop through all methods or simply sort them, because 735 # a method may be listed in multiple steps of the resolution algorithm, and 736 # which test to apply differs depending on the step. 737 # 738 # Instead, we need to go through all methods at each step, either finding 739 # first match (if only one test is allowed) or filtering to matches (if 740 # multiple tests are allowed), and generating an appropriate tests. 741 742 # 2. If V is undefined, and there is an entry in S whose list of 743 # optionality values has optional at index i, then remove from S all 744 # other entries. 745 try: 746 method = next(method for argument, method in arguments_methods 747 if argument['is_optional']) 748 test = '%s->IsUndefined()' % cpp_value 749 yield test, method 750 except StopIteration: 751 pass 752 753 # 3. Otherwise: if V is null or undefined, and there is an entry in S that 754 # has one of the following types at position i of its type list, 755 # a nullable type 756 try: 757 method = next(method for idl_type, method in idl_types_methods 758 if idl_type.is_nullable) 759 test = 'isUndefinedOrNull(%s)' % cpp_value 760 yield test, method 761 except StopIteration: 762 pass 763 764 # 4. Otherwise: if V is a platform object but not a platform array 765 # object and there is an entry in S that has one of the following 766 # types at position i of its type list, 767 # an interface type that V implements 768 # (Unlike most of these tests, this can return multiple methods, since we 769 # test if it implements an interface. Thus we need a for loop, not a next.) 770 # (We distinguish wrapper types from built-in interface types.) 771 for idl_type, method in ((idl_type, method) 772 for idl_type, method in idl_types_methods 773 if idl_type.is_wrapper_type): 774 test = 'V8{idl_type}::hasInstance({cpp_value}, info.GetIsolate())'.format(idl_type=idl_type.base_type, cpp_value=cpp_value) 775 yield test, method 776 777 # 8. Otherwise: if V is any kind of object except for a native Date object, 778 # a native RegExp object, and there is an entry in S that has one of the 779 # following types at position i of its type list, 780 # an array type 781 # a sequence type 782 # ... 783 # a dictionary 784 # 785 # FIXME: 786 # We don't strictly follow the algorithm here. The algorithm says "remove 787 # all other entries" if there is "one entry" matching, but we yield all 788 # entries to support following constructors: 789 # [constructor(sequence<DOMString> arg), constructor(Dictionary arg)] 790 # interface I { ... } 791 # (Need to check array types before objects because an array is an object) 792 for idl_type, method in idl_types_methods: 793 if idl_type.native_array_element_type: 794 # (We test for Array instead of generic Object to type-check.) 795 # FIXME: test for Object during resolution, then have type check for 796 # Array in overloaded method: http://crbug.com/262383 797 yield '%s->IsArray()' % cpp_value, method 798 for idl_type, method in idl_types_methods: 799 if idl_type.is_dictionary or idl_type.name == 'Dictionary': 800 # FIXME: should be '{1}->IsObject() && !{1}->IsDate() && !{1}->IsRegExp()'.format(cpp_value) 801 # FIXME: the IsDate and IsRegExp checks can be skipped if we've 802 # already generated tests for them. 803 yield '%s->IsObject()' % cpp_value, method 804 805 # (Check for exact type matches before performing automatic type conversion; 806 # only needed if distinguishing between primitive types.) 807 if len([idl_type.is_primitive_type for idl_type in idl_types]) > 1: 808 # (Only needed if match in step 11, otherwise redundant.) 809 if any(idl_type.is_string_type or idl_type.is_enum 810 for idl_type in idl_types): 811 # 10. Otherwise: if V is a Number value, and there is an entry in S 812 # that has one of the following types at position i of its type 813 # list, 814 # a numeric type 815 try: 816 method = next(method for idl_type, method in idl_types_methods 817 if idl_type.is_numeric_type) 818 test = '%s->IsNumber()' % cpp_value 819 yield test, method 820 except StopIteration: 821 pass 822 823 # (Perform automatic type conversion, in order. If any of these match, 824 # thats the end, and no other tests are needed.) To keep this code simple, 825 # we rely on the C++ compiler's dead code elimination to deal with the 826 # redundancy if both cases below trigger. 827 828 # 11. Otherwise: if there is an entry in S that has one of the following 829 # types at position i of its type list, 830 # DOMString 831 # ByteString 832 # ScalarValueString [a DOMString typedef, per definition.] 833 # an enumeration type 834 try: 835 method = next(method for idl_type, method in idl_types_methods 836 if idl_type.is_string_type or idl_type.is_enum) 837 yield 'true', method 838 except StopIteration: 839 pass 840 841 # 12. Otherwise: if there is an entry in S that has one of the following 842 # types at position i of its type list, 843 # a numeric type 844 try: 845 method = next(method for idl_type, method in idl_types_methods 846 if idl_type.is_numeric_type) 847 yield 'true', method 848 except StopIteration: 849 pass 850 851 852 ################################################################################ 853 # Utility functions 854 ################################################################################ 855 856 def Counter(iterable): 857 # Once using Python 2.7, using collections.Counter 858 counter = defaultdict(lambda: 0) 859 for item in iterable: 860 counter[item] += 1 861 return counter 862 863 864 def common(dicts, f): 865 """Returns common result of f across an iterable of dicts, or None. 866 867 Call f for each dict and return its result if the same across all dicts. 868 """ 869 values = (f(d) for d in dicts) 870 first_value = next(values) 871 if all(value == first_value for value in values): 872 return first_value 873 return None 874 875 876 def common_key(dicts, key): 877 """Returns common presence of a key across an iterable of dicts, or None. 878 879 True if all dicts have the key, False if none of the dicts have the key, 880 and None if some but not all dicts have the key. 881 """ 882 return common(dicts, lambda d: key in d) 883 884 885 def common_value(dicts, key): 886 """Returns common value of a key across an iterable of dicts, or None. 887 888 Auxiliary function for overloads, so can consolidate an extended attribute 889 that appears with the same value on all items in an overload set. 890 """ 891 return common(dicts, lambda d: d.get(key)) 892 893 894 def sort_and_groupby(l, key=None): 895 """Returns a generator of (key, list), sorting and grouping list by key.""" 896 l.sort(key=key) 897 return ((k, list(g)) for k, g in itertools.groupby(l, key)) 898 899 900 ################################################################################ 901 # Constructors 902 ################################################################################ 903 904 # [Constructor] 905 def constructor_context(interface, constructor): 906 # [RaisesException=Constructor] 907 is_constructor_raises_exception = \ 908 interface.extended_attributes.get('RaisesException') == 'Constructor' 909 910 return { 911 'arguments': [v8_methods.argument_context(interface, constructor, argument, index) 912 for index, argument in enumerate(constructor.arguments)], 913 'cpp_type': cpp_template_type( 914 cpp_ptr_type('RefPtr', 'RawPtr', gc_type(interface)), 915 cpp_name(interface)), 916 'cpp_value': v8_methods.cpp_value( 917 interface, constructor, len(constructor.arguments)), 918 'has_exception_state': 919 is_constructor_raises_exception or 920 any(argument for argument in constructor.arguments 921 if argument.idl_type.name == 'SerializedScriptValue' or 922 argument.idl_type.v8_conversion_needs_exception_state), 923 'is_call_with_document': 924 # [ConstructorCallWith=Document] 925 has_extended_attribute_value(interface, 926 'ConstructorCallWith', 'Document'), 927 'is_call_with_execution_context': 928 # [ConstructorCallWith=ExecutionContext] 929 has_extended_attribute_value(interface, 930 'ConstructorCallWith', 'ExecutionContext'), 931 'is_constructor': True, 932 'is_named_constructor': False, 933 'is_raises_exception': is_constructor_raises_exception, 934 'number_of_required_arguments': 935 number_of_required_arguments(constructor), 936 } 937 938 939 # [NamedConstructor] 940 def named_constructor_context(interface): 941 extended_attributes = interface.extended_attributes 942 if 'NamedConstructor' not in extended_attributes: 943 return None 944 # FIXME: parser should return named constructor separately; 945 # included in constructors (and only name stored in extended attribute) 946 # for Perl compatibility 947 idl_constructor = interface.constructors[-1] 948 assert idl_constructor.name == 'NamedConstructor' 949 context = constructor_context(interface, idl_constructor) 950 context.update({ 951 'name': extended_attributes['NamedConstructor'], 952 'is_named_constructor': True, 953 }) 954 return context 955 956 957 def number_of_required_arguments(constructor): 958 return len([argument for argument in constructor.arguments 959 if not argument.is_optional]) 960 961 962 def interface_length(interface, constructors): 963 # Docs: http://heycam.github.io/webidl/#es-interface-call 964 if 'EventConstructor' in interface.extended_attributes: 965 return 1 966 if not constructors: 967 return 0 968 return min(constructor['number_of_required_arguments'] 969 for constructor in constructors) 970 971 972 ################################################################################ 973 # Special operations (methods) 974 # http://heycam.github.io/webidl/#idl-special-operations 975 ################################################################################ 976 977 def property_getter(getter, cpp_arguments): 978 def is_null_expression(idl_type): 979 if idl_type.is_union_type: 980 notnull = ' || '.join([ 981 member_argument['null_check_value'] 982 for member_argument in idl_type.union_arguments]) 983 return '!(%s)' % notnull 984 if idl_type.name == 'String': 985 return 'result.isNull()' 986 if idl_type.is_interface_type: 987 return '!result' 988 return '' 989 990 idl_type = getter.idl_type 991 extended_attributes = getter.extended_attributes 992 is_raises_exception = 'RaisesException' in extended_attributes 993 994 # FIXME: make more generic, so can use v8_methods.cpp_value 995 cpp_method_name = 'impl->%s' % cpp_name(getter) 996 997 if is_raises_exception: 998 cpp_arguments.append('exceptionState') 999 union_arguments = idl_type.union_arguments 1000 if union_arguments: 1001 cpp_arguments.extend([member_argument['cpp_value'] 1002 for member_argument in union_arguments]) 1003 1004 cpp_value = '%s(%s)' % (cpp_method_name, ', '.join(cpp_arguments)) 1005 1006 return { 1007 'cpp_type': idl_type.cpp_type, 1008 'cpp_value': cpp_value, 1009 'is_custom': 1010 'Custom' in extended_attributes and 1011 (not extended_attributes['Custom'] or 1012 has_extended_attribute_value(getter, 'Custom', 'PropertyGetter')), 1013 'is_custom_property_enumerator': has_extended_attribute_value( 1014 getter, 'Custom', 'PropertyEnumerator'), 1015 'is_custom_property_query': has_extended_attribute_value( 1016 getter, 'Custom', 'PropertyQuery'), 1017 'is_enumerable': 'NotEnumerable' not in extended_attributes, 1018 'is_null_expression': is_null_expression(idl_type), 1019 'is_raises_exception': is_raises_exception, 1020 'name': cpp_name(getter), 1021 'union_arguments': union_arguments, 1022 'v8_set_return_value': idl_type.v8_set_return_value('result', extended_attributes=extended_attributes, script_wrappable='impl', release=idl_type.release), 1023 } 1024 1025 1026 def property_setter(setter): 1027 idl_type = setter.arguments[1].idl_type 1028 extended_attributes = setter.extended_attributes 1029 is_raises_exception = 'RaisesException' in extended_attributes 1030 return { 1031 'has_type_checking_interface': 1032 has_extended_attribute_value(setter, 'TypeChecking', 'Interface') and 1033 idl_type.is_wrapper_type, 1034 'idl_type': idl_type.base_type, 1035 'is_custom': 'Custom' in extended_attributes, 1036 'has_exception_state': (is_raises_exception or 1037 idl_type.v8_conversion_needs_exception_state), 1038 'is_raises_exception': is_raises_exception, 1039 'name': cpp_name(setter), 1040 'v8_value_to_local_cpp_value': idl_type.v8_value_to_local_cpp_value( 1041 extended_attributes, 'v8Value', 'propertyValue'), 1042 } 1043 1044 1045 def property_deleter(deleter): 1046 idl_type = deleter.idl_type 1047 if str(idl_type) != 'boolean': 1048 raise Exception( 1049 'Only deleters with boolean type are allowed, but type is "%s"' % 1050 idl_type) 1051 extended_attributes = deleter.extended_attributes 1052 return { 1053 'is_custom': 'Custom' in extended_attributes, 1054 'is_raises_exception': 'RaisesException' in extended_attributes, 1055 'name': cpp_name(deleter), 1056 } 1057 1058 1059 ################################################################################ 1060 # Indexed properties 1061 # http://heycam.github.io/webidl/#idl-indexed-properties 1062 ################################################################################ 1063 1064 def indexed_property_getter(interface): 1065 try: 1066 # Find indexed property getter, if present; has form: 1067 # getter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1) 1068 getter = next( 1069 method 1070 for method in interface.operations 1071 if ('getter' in method.specials and 1072 len(method.arguments) == 1 and 1073 str(method.arguments[0].idl_type) == 'unsigned long')) 1074 except StopIteration: 1075 return None 1076 1077 return property_getter(getter, ['index']) 1078 1079 1080 def indexed_property_setter(interface): 1081 try: 1082 # Find indexed property setter, if present; has form: 1083 # setter RETURN_TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1, ARG_TYPE ARG2) 1084 setter = next( 1085 method 1086 for method in interface.operations 1087 if ('setter' in method.specials and 1088 len(method.arguments) == 2 and 1089 str(method.arguments[0].idl_type) == 'unsigned long')) 1090 except StopIteration: 1091 return None 1092 1093 return property_setter(setter) 1094 1095 1096 def indexed_property_deleter(interface): 1097 try: 1098 # Find indexed property deleter, if present; has form: 1099 # deleter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG) 1100 deleter = next( 1101 method 1102 for method in interface.operations 1103 if ('deleter' in method.specials and 1104 len(method.arguments) == 1 and 1105 str(method.arguments[0].idl_type) == 'unsigned long')) 1106 except StopIteration: 1107 return None 1108 1109 return property_deleter(deleter) 1110 1111 1112 ################################################################################ 1113 # Named properties 1114 # http://heycam.github.io/webidl/#idl-named-properties 1115 ################################################################################ 1116 1117 def named_property_getter(interface): 1118 try: 1119 # Find named property getter, if present; has form: 1120 # getter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1) 1121 getter = next( 1122 method 1123 for method in interface.operations 1124 if ('getter' in method.specials and 1125 len(method.arguments) == 1 and 1126 str(method.arguments[0].idl_type) == 'DOMString')) 1127 except StopIteration: 1128 return None 1129 1130 getter.name = getter.name or 'anonymousNamedGetter' 1131 return property_getter(getter, ['propertyName']) 1132 1133 1134 def named_property_setter(interface): 1135 try: 1136 # Find named property setter, if present; has form: 1137 # setter RETURN_TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1, ARG_TYPE ARG2) 1138 setter = next( 1139 method 1140 for method in interface.operations 1141 if ('setter' in method.specials and 1142 len(method.arguments) == 2 and 1143 str(method.arguments[0].idl_type) == 'DOMString')) 1144 except StopIteration: 1145 return None 1146 1147 return property_setter(setter) 1148 1149 1150 def named_property_deleter(interface): 1151 try: 1152 # Find named property deleter, if present; has form: 1153 # deleter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG) 1154 deleter = next( 1155 method 1156 for method in interface.operations 1157 if ('deleter' in method.specials and 1158 len(method.arguments) == 1 and 1159 str(method.arguments[0].idl_type) == 'DOMString')) 1160 except StopIteration: 1161 return None 1162 1163 return property_deleter(deleter) 1164