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