1#!/usr/bin/env python3 2# 3# Copyright (C) 2015 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18"""Compares Host types against Guest types.""" 19 20 21def _is_size_required(atype): 22 if atype['kind'] in ['incomplete', 'const', 'volatile', 'restrict', 'function']: 23 return False 24 return atype['kind'] != 'array' or not bool(atype.get('incomplete', 'false')) 25 26 27def _compare_list_length(guest_list, host_list): 28 if ((guest_list is None) != (host_list is None)): 29 return False, False 30 if (guest_list is None): 31 return True, True 32 if len(guest_list) == len(host_list): 33 return True, False 34 return False, False 35 36 37class APIComparator(object): 38 def __init__(self, guest_types, host_types, verbose=False): 39 self.guest_types = guest_types 40 self.host_types = host_types 41 self.type_references = {} 42 self.incompatible = set() 43 self.compared = set() 44 self.verbose = verbose 45 46 def _add_incompatible(self, name_pair, reason): 47 if (self.verbose): 48 print(reason, name_pair) 49 self.incompatible.add(name_pair) 50 51 def _notice_type_reference(self, from_type_pair, to_type_pair): 52 if (to_type_pair not in self.type_references): 53 self.type_references[to_type_pair] = [] 54 self.type_references[to_type_pair].append(from_type_pair) 55 56 def _compare_referenced_types( 57 self, guest_type_name, host_type_name, ref_from_pair): 58 # If referenced types are incompatible we will notice that 59 # in propagate_incompatible(). 60 self.compare_types( 61 guest_type_name, host_type_name) 62 self._notice_type_reference( 63 ref_from_pair, (guest_type_name, host_type_name)) 64 65 def _compare_size_and_align(self, guest_type, host_type, name_pair): 66 if (guest_type['size'] != host_type['size']): 67 self._add_incompatible(name_pair, "Types diff in size") 68 return False 69 # Objects in the guest memory should have at least the same 70 # alignment as in host to be passed to host functions. 71 # For objects created in host memory we assume that guest code 72 # will never check their alignment. 73 # TODO(b/232598137): Also we can skip alignment check for types which won't 74 # be going to be addressed in memory. E.g. these are types not referenced 75 # by pointers (not even by indirect pointers like pointer to a structure 76 # containing the type). 77 # TODO(b/232598137): DWARF generated by current version of clang does not 78 # always provide information about alignment. Fix the dwarf or if this takes 79 # too long fix nogrod to provide make educated guesses about alignments of 80 # types. See also http://b/77671138 81 if (('align' in guest_type and 'align' not in host_type) or 82 ('align' not in guest_type and 'align' in host_type) or 83 int(guest_type.get('align', '0')) < int(host_type.get('align', '0'))): 84 self._add_incompatible(name_pair, "Types diff in align") 85 return False 86 return True 87 88 def _compare_record_type_attrs(self, guest_type, host_type, name_pair): 89 # In regular case polymorphic objects are created with v_table being in 90 # host memory. When this happens it is safe to use polymorphic objects in 91 # trampolines. 92 # But sometimes polymorphic objects can be created with v_table in guest 93 # memory. E. g. this occurs when client inherits from class defined in 94 # ndk-api and creates a derived object. Such objects require special 95 # translation thus are not subject for automatic trampolines generation. 96 # We don't know whether object has v_table in host or in guest, so we 97 # require custom trampolines for all of them. 98 # Allow 'is_polymorphic' to be absent for backward-compatibility. 99 # TODO(b/232598137): Correct code when all APIs are regenerated. 100 if (guest_type.get('is_polymorphic', False) or 101 host_type.get('is_polymorphic', False)): 102 self._add_incompatible(name_pair, "Types diff due to polymorphism") 103 return False 104 105 is_cmp_ok, are_both_none = _compare_list_length( 106 guest_type['fields'], host_type['fields']) 107 if (not is_cmp_ok): 108 self._add_incompatible(name_pair, "Types diff in fields list lengths") 109 return False 110 if (are_both_none): 111 return True 112 113 for i in range(len(guest_type['fields'])): 114 guest_field = guest_type['fields'][i] 115 host_field = host_type['fields'][i] 116 if (guest_field['offset'] != host_field['offset']): 117 self._add_incompatible(name_pair, "Types diff in field offset") 118 return False 119 self._compare_referenced_types( 120 guest_field['type'], host_field['type'], name_pair) 121 122 return True 123 124 # TODO(b/232598137): Can we generate such trampolines? 125 def _is_type_allowed_in_trampoline(self, type_name): 126 # void is exception from other types of incomplete kind - it is supported. 127 if (type_name == 'void'): 128 return True 129 type_desc = self.guest_types[type_name] 130 if type_desc['kind'] in ['const', 'volatile', 'restrict']: 131 type_desc = self.guest_types[type_desc['base_type']] 132 return type_desc['kind'] not in \ 133 ['class', 'struct', 'union', 'incomplete', 'array'] 134 135 def _compare_trampoline_operand( 136 self, operand_no, guest_name, host_name, name_pair): 137 # We use 'x' trampoline operand type to involve this conversion. 138 # Note: We don't make reference from function to this operand. So that 139 # if these types are marked incompatible in context other than function 140 # parameter, function itself still can be compatible (if everything 141 # else is ok). 142 # TODO(b/232598137): Define such compatible pairs in custom trampolines? 143 if (guest_name == 'fp64' and host_name == 'fp96'): 144 operands = self.guest_types[name_pair[0]].get( 145 'long_double_conversion_operands', []) 146 operands.append(operand_no) 147 self.guest_types[name_pair[0]][ 148 'long_double_conversion_operands'] = operands 149 return True 150 if (not self._is_type_allowed_in_trampoline(guest_name)): 151 self._add_incompatible( 152 name_pair, "Types diff due to unallowed operand type") 153 return False 154 # If we accept pointers to functions we look on the functions themselves. 155 # Note: function pointers embedded into data structures make them 156 # incompatible, but GetTrampolineFunc knows how to wrap simple callbacks. 157 guest_type = self.guest_types[guest_name] 158 host_type = self.host_types[host_name] 159 if (guest_type['kind'] == 'pointer' and 160 self.guest_types[guest_type['pointee_type']]['kind'] == 'function' and 161 host_type['kind'] == 'pointer' and 162 self.host_types[host_type['pointee_type']]['kind'] == 'function'): 163 guest_name = guest_type['pointee_type'] 164 host_name = host_type['pointee_type'] 165 self._compare_referenced_types(guest_name, host_name, name_pair) 166 return True 167 168 def _compare_function_type_attrs(self, guest_type, host_type, name_pair): 169 if (not self._compare_trampoline_operand( 170 0, guest_type['return_type'], host_type['return_type'], name_pair)): 171 return False 172 173 if (guest_type['has_variadic_args'] or host_type['has_variadic_args']): 174 self._add_incompatible(name_pair, "Types diff due to variadic args") 175 return False 176 177 # Allow 'is_virtual_method' to be absent for backward-compatibility. 178 # TODO(b/232598137): Correct code when all APIs are regenerated. 179 if (guest_type.get('is_virtual_method', False) or 180 host_type.get('is_virtual_method', False)): 181 self._add_incompatible(name_pair, "Types diff due to virtual method") 182 return False 183 184 is_cmp_ok, are_both_none = _compare_list_length( 185 guest_type['params'], host_type['params']) 186 if (not is_cmp_ok): 187 self._add_incompatible(name_pair, "Types diff in params lengths") 188 return False 189 if (are_both_none): 190 return True 191 192 for i in range(len(guest_type['params'])): 193 if (not self._compare_trampoline_operand( 194 i + 1, guest_type['params'][i], host_type['params'][i], name_pair)): 195 return False 196 197 return True 198 199 def _compare_array_type_attrs(self, guest_type, host_type, name_pair): 200 if (guest_type.get('incomplete', 'false') != host_type.get('incomplete', 'false')): 201 self._add_incompatible(name_pair, "Types diff in incomleteness") 202 return False 203 self._compare_referenced_types( 204 guest_type['element_type'], host_type['element_type'], name_pair) 205 return True 206 207 def _compare_pointer_type_attrs(self, guest_type, host_type, name_pair): 208 if (self.guest_types[guest_type['pointee_type']]['kind'] == 'function'): 209 self._add_incompatible( 210 name_pair, "Types diff due to pointing to function") 211 return False 212 self._compare_referenced_types( 213 guest_type['pointee_type'], host_type['pointee_type'], name_pair) 214 return True 215 216 def _is_compatibility_forced(self, name_pair): 217 guest_type = self.guest_types[name_pair[0]] 218 # Forcing compatible is only supported for the types with the same name. 219 if (guest_type.get('force_compatible', False) and 220 name_pair[0] == name_pair[1]): 221 return True 222 223 return name_pair[1] in guest_type.get('force_compatible_with', []) 224 225 def _set_useful_force_compatible(self, guest_type_name): 226 guest_type = self.guest_types[guest_type_name] 227 guest_type['useful_force_compatible'] = True 228 229 # Compare types internals and compare referenced types recursively. 230 # References are remembered to propagate incompatibility later. 231 # If types are incompatible internally return immediately as 232 # referenced types are not of interest. 233 # 234 # To save us from loops in references between types we propagate 235 # incompatibility from referenced types afterwards in 236 # propagate_incompatible(). 237 def compare_types( 238 self, guest_type_name, host_type_name): 239 name_pair = (guest_type_name, host_type_name) 240 241 # Prevent infinite recursion. 242 if (name_pair in self.compared): 243 return 244 self.compared.add(name_pair) 245 246 guest_type = self.guest_types[guest_type_name] 247 host_type = self.host_types[host_type_name] 248 249 if (guest_type['kind'] != host_type['kind']): 250 self._add_incompatible(name_pair, "Types diff in kind") 251 return 252 253 kind = guest_type['kind'] 254 255 if (kind == 'dependent'): 256 self._add_incompatible(name_pair, "Types depend on template parameters") 257 return 258 259 if (_is_size_required(guest_type)): 260 if (not self._compare_size_and_align( 261 guest_type, host_type, name_pair)): 262 return 263 264 if (kind in ['class', 'struct', 'union']): 265 self._compare_record_type_attrs( 266 guest_type, host_type, name_pair) 267 elif (kind == 'function'): 268 self._compare_function_type_attrs( 269 guest_type, host_type, name_pair) 270 elif ((kind == 'pointer') or (kind == 'reference') or (kind == 'rvalue_reference')): 271 self._compare_pointer_type_attrs( 272 guest_type, host_type, name_pair) 273 elif (kind in ['const', 'volatile', 'restrict', 'atomic']): 274 self._compare_referenced_types( 275 guest_type['base_type'], host_type['base_type'], name_pair) 276 elif (kind == 'array'): 277 self._compare_array_type_attrs( 278 guest_type, host_type, name_pair) 279 280 # If more checks are added here, check return values of the 281 # functions in previous if-elif block and return in case of 282 # miscomparison. 283 284 # Make sure we did not ignore types we shouldn't have. 285 assert kind in ('array', 286 'atomic', 287 'char', 288 'class', 289 'const', 290 'incomplete', 291 'int', 292 'float', 293 'fp', # TODO: remove - this not used by new json generator. 294 'function', 295 'nullptr_t', 296 'pointer', 297 'reference', 298 'restrict', 299 'rvalue_reference', 300 'struct', 301 'union', 302 'UNSUPPORTED Enum', # TODO: what is this? 303 'UNSUPPORTED Atomic', # TODO: and this... 304 'volatile'), "Unknown type %s kind=%s, couldn't process" % (guest_type_name, kind) 305 306 def _mark_references_as_incompatible(self, ref_to_type_pair): 307 for ref_from_type_pair in self.type_references.get(ref_to_type_pair, []): 308 # Go only through compatible types because incompatible types either are 309 # already propagated or will be propagated in propagate_incompatible (if 310 # they were in initial incompatible set). 311 if (not self.are_types_compatible(ref_from_type_pair[0], ref_from_type_pair[1])): 312 continue 313 if (self._is_compatibility_forced(ref_from_type_pair)): 314 if (self.verbose): 315 print(("Not propagating incompatibility to types %s" + \ 316 " since they are forced to be compatible") % (ref_from_type_pair, )) 317 self._set_useful_force_compatible(ref_from_type_pair[0]) 318 continue 319 self._add_incompatible( 320 ref_from_type_pair, 321 "Incompatible type pair %s is referenced by" % (ref_to_type_pair,)) 322 self._mark_references_as_incompatible(ref_from_type_pair) 323 324 def force_compatibility(self): 325 for atype in self.guest_types: 326 name_pair = (atype, atype) 327 if (atype not in self.host_types): 328 continue 329 if (not self._is_compatibility_forced(name_pair)): 330 continue 331 if (self.are_types_compatible(atype, atype)): 332 if (self.verbose): 333 print(("Forcing compatibility for internally compatible types %s" + \ 334 " (maybe referencing other incompatible types)") % (name_pair, )) 335 continue 336 self._set_useful_force_compatible(atype) 337 if (self.verbose): 338 print("Forcing compatibility for", name_pair) 339 self.incompatible.remove(name_pair) 340 341 342 def propagate_incompatible(self): 343 # Make a copy because we expand initial set. 344 for type_pair in self.incompatible.copy(): 345 self._mark_references_as_incompatible(type_pair) 346 347 def are_types_compatible(self, guest_type_name, host_type_name): 348 return (guest_type_name, host_type_name) not in self.incompatible 349 350 351def mark_incompatible_api_with_comparator( 352 comparator, guest_symbols, host_symbols, verbose=False): 353 for symbol, descr in guest_symbols.items(): 354 if (symbol not in host_symbols): 355 continue 356 comparator.compare_types(descr['type'], host_symbols[symbol]['type']) 357 # Compare all types in case some of them are not referenced by 358 # compatible symbols or other compatible types. We might want to use 359 # the result of the analysis (e.g. check type compatibility expectation). 360 for atype in comparator.guest_types: 361 if (atype not in comparator.host_types): 362 continue 363 comparator.compare_types(atype, atype) 364 365 # Do it before propagate_incompatible so that we don't propagate 366 # those that are forced to be compatible. 367 comparator.force_compatibility() 368 369 comparator.propagate_incompatible() 370 371 for symbol, descr in guest_symbols.items(): 372 if (symbol not in host_symbols): 373 if (verbose): 374 print("Symbol '%s' doesn't present on host" % symbol) 375 descr['is_compatible'] = False 376 continue 377 378 descr['is_compatible'] = \ 379 comparator.are_types_compatible( 380 descr['type'], host_symbols[symbol]['type']) 381 382 for atype, descr in comparator.guest_types.items(): 383 descr['is_compatible'] = comparator.are_types_compatible(atype, atype) 384 385 386def mark_incompatible_api(guest_api, host_api, verbose=False): 387 comparator = APIComparator(guest_api['types'], host_api['types'], verbose) 388 389 mark_incompatible_api_with_comparator( 390 comparator, guest_api['symbols'], host_api['symbols'], verbose) 391 392 393def _override_custom_type_properties(guest_api, custom_api): 394 for custom_type, custom_descr in custom_api.get('types', {}).items(): 395 guest_api['types'][custom_type].update(custom_descr) 396 397 398def _set_call_method_for_symbols(guest_api): 399 for symbol, descr in guest_api['symbols'].items(): 400 type_name = descr['type'] 401 if (guest_api['types'][type_name]['kind'] == 'function'): 402 descr['call_method'] = 'default' 403 else: 404 descr['call_method'] = 'do_not_call' 405 406 407def _override_custom_symbol_properties(guest_api, custom_api): 408 custom_config = custom_api.get('config', {}) 409 410 if (custom_config.get('ignore_variables', False)): 411 for symbol, descr in guest_api['symbols'].items(): 412 type_name = descr['type'] 413 if guest_api['types'][type_name]['kind'] != 'function': 414 descr['call_method'] = 'ignore' 415 416 if (custom_config.get('force_incompatible', False)): 417 for symbol, descr in guest_api['symbols'].items(): 418 descr['is_compatible'] = False 419 420 for custom_symbol, custom_descr in custom_api['symbols'].items(): 421 # Some exported symbols may not present in headers 422 # which are used for guest_api generation. 423 if (custom_symbol not in guest_api['symbols']): 424 guest_api['symbols'][custom_symbol] = custom_descr 425 else: 426 # This may override 'call_method' for function-type symbol. 427 # But should not override 'is_compatible', which is only used 428 # when symbol isn't present in guest_api. 429 assert 'is_compatible' not in custom_descr, ('The symbol %s is already ' 430 'compatible: remove the ' 431 'override') % custom_symbol 432 if ('is_custom_compatible' in custom_descr): 433 custom_descr['is_compatible'] = custom_descr['is_custom_compatible'] 434 guest_api['symbols'][custom_symbol].update(custom_descr) 435 436 if (custom_config.get('ignore_non_custom', False)): 437 for symbol, descr in guest_api['symbols'].items(): 438 if symbol not in custom_api['symbols']: 439 descr['call_method'] = 'ignore' 440 441 442def _check_force_compatibility_was_useful(types): 443 for atype, descr in types.items(): 444 if (('force_compatible' in descr) or ('force_compatible_with' in descr)): 445 if (not descr.get('useful_force_compatible', False)): 446 raise Exception("Forcing compatibility for type '%s' is redundant" % (atype)) 447 448 449def _check_expected_types_compatibility(types): 450 for atype, descr in types.items(): 451 if ('expect_compatible' in descr): 452 if (descr['is_compatible'] != descr['expect_compatible']): 453 raise Exception( 454 ("Compatibility expectation for type '%s' is wrong:" 455 ' is_compatible=%s, expect_compatible=%s') % 456 (atype, descr['is_compatible'], descr['expect_compatible'])) 457 458 459def mark_incompatible_and_custom_api(guest_api, host_api, custom_api, verbose=False): 460 # Type properties are used in api compatibility analysis. 461 # So override them before the analysis. 462 _override_custom_type_properties(guest_api, custom_api) 463 mark_incompatible_api(guest_api, host_api, verbose=verbose) 464 _check_force_compatibility_was_useful(guest_api['types']) 465 466 _set_call_method_for_symbols(guest_api) 467 # Custom symbol properties may override analysis results. 468 _override_custom_symbol_properties(guest_api, custom_api) 469 470 _check_expected_types_compatibility(guest_api['types']) 471