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