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