#!/usr/bin/python
#
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import copy
import unittest

import api_analysis


class APIAnalysisTests(unittest.TestCase):
  def test_guest_symbol_not_present_in_host(self):
    guest_api = \
        {
            "symbols": {"guest_only": {"type": "guest_only"}},
            "types": {"guest_only": {"kind": "incomplete"}}
        }
    host_api = {"symbols": {}, "types": {}}
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['guest_only']['is_compatible'])

  def test_compatible_int(self):
    guest_api = \
        {
            "symbols": {
                "int": {"type": "int32"}
            },
            "types": {
                "int32": {"align": 64,
                          "kind": "int",
                          "size": 32}
            }
        }
    host_api = copy.deepcopy(guest_api)
    # We allow host alignment to be less than guest one.
    # See comments in api_analysis.py for details.
    host_api['types']['int32']['align'] = 32
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertTrue(guest_api['symbols']['int']['is_compatible'])

  def test_compatible_loop_reference(self):
    guest_api = \
        {
            "symbols": {
                "pointer1": {"type": "pointer1"},
                "pointer2": {"type": "pointer2"},
            },
            "types": {
                "pointer1": {"align": 32,
                             "kind": "pointer",
                             "pointee_type": "pointer2",
                             "size": 32},
                "pointer2": {"align": 32,
                             "kind": "pointer",
                             "pointee_type": "pointer1",
                             "size": 32}
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertTrue(guest_api['symbols']['pointer1']['is_compatible'])
    self.assertTrue(guest_api['symbols']['pointer2']['is_compatible'])


  def test_incompatible_kind(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "incomplete"}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_size(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['size'] = 64
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_align(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['align'] = 64
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_fields_num(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "struct",
                            "is_polymorphic": False,
                            "fields": [{"offset": 0,
                                        "type": "t2"}],
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['fields'] = []
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_field_type(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "struct",
                            "is_polymorphic": False,
                            "fields": [{"offset": 0,
                                        "type": "t2"}],
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_field_offset(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "struct",
                            "is_polymorphic": False,
                            "fields": [{"offset": 0,
                                        "type": "t2"}],
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['fields'][0]["offset"] = 32
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_polymorphic(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "struct",
                            "is_polymorphic": True,
                            "fields": [],
                            "size": 32,
                            "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_func_variadic_args(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "align": 32,
                    "has_variadic_args": True,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": [],
                    "return_type": "void",
                },
                "void": {
                    "kind": "incomplete"
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_virtual_method(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "align": 32,
                    "has_variadic_args": True,
                    "is_virtual_method": True,
                    "kind": "function",
                    "params": [],
                    "return_type": "void",
                },
                "void": {
                    "kind": "incomplete"
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_func_params_num(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "align": 32,
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": ["t2"],
                    "return_type": "t2",
                },
                "t2": {
                    "kind": "int",
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['params'] = []
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_func_param_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": ["t2"],
                    "return_type": "void",
                },
                "void": {
                    "kind": "incomplete"
                },
                "t2": {
                    "kind": "int",
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_unallowed_func_param_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "align": 32,
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": ["t2"],
                    "return_type": "void",
                    "size": 0
                },
                "void": {
                    "kind": "incomplete"
                },
                # Structs are not supported in trampolines.
                "t2": {
                    "kind": "struct",
                    "is_polymorphic": False,
                    "fields": [],
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_func_return_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": [],
                    "return_type": "t2",
                },
                "t2": {
                    "kind": "int",
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_unallowed_func_return_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": [],
                    "return_type": "t2",
                },
                # Structs are not supported in trampolines.
                "t2": {
                    "kind": "struct",
                    "is_polymorphic": False,
                    "fields": [],
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_pointee_type(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "pointer",
                            "pointee_type": "t2",
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_volatile_type(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "volatile",
                            "base_type" : "t2",
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'char8'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_restrict_type(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "restrict",
                            "base_type" : "t2",
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'char8'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_const_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "kind": "const",
                    "base_type" : "t2"
                },
                "t2": {
                    "kind": "int",
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'char8'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_pointer_to_function(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "kind": "pointer",
                    "pointee_type": "func",
                    "size": 32,
                    "align": 32
                },
                "func": {
                    "has_variadic_args": False,
                    "is_virtual_method": False,
                    "kind": "function",
                    "params": [],
                    "return_type": "void",
                },
                "void": {
                    "kind": "incomplete"
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_array_element_type(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                }
            },
            "types": {
                "t": {
                    "align": 32,
                    "kind": "array",
                    "element_type": "t2",
                    "incomplete": False,
                    "size": 32
                },
                "t2": {
                    "kind": "int",
                    "size": 32,
                    "align": 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_incompatible_array_incompleteness(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"align": 32,
                            "kind": "array",
                            "element_type": "t2",
                            "incomplete": False,
                            "size": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['incomplete'] = True
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])


  def test_comparison_context(self):
    guest_api = \
        {
            "symbols": {"good_symb": {"type": "good_pointer_type"},
                        "bad_symb": {"type": "bad_pointer_type"}},
            "types": {"good_pointer_type": {"kind": "pointer",
                                            "pointee_type": "int_type",
                                            "size": 32,
                                            "align": 32},
                      "bad_pointer_type": {"kind": "pointer",
                                           "pointee_type": "int_type",
                                           "size": 32,
                                           "align": 32},
                      "int_type": {"kind": "int", "size": 32, "align": 32},
                      "fp_type": {"kind": "fp", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['bad_pointer_type']['pointee_type'] = 'fp_type'
    comparator = api_analysis.APIComparator(
        guest_api['types'], host_api['types'])
    api_analysis.mark_incompatible_api_with_comparator(
        comparator, guest_api['symbols'], host_api['symbols'])
    # Though int_type is compared against incompatible type in case of
    # 'bad_symb' the type itself may still be compatible in other contexts.
    # Thus this doesn't affect 'good_symb' compatibility.
    self.assertTrue(guest_api['symbols']['good_symb']['is_compatible'])
    self.assertFalse(guest_api['symbols']['bad_symb']['is_compatible'])
    self.assertTrue(comparator.are_types_compatible('int_type', 'int_type'))
    self.assertFalse(comparator.are_types_compatible('int_type', 'fp_type'))


  def test_loop_references(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "ref_loop_head"}},
            "types": {"ref_loop_head": {"kind": "struct",
                                        "is_polymorphic": False,
                                        "fields": [{"offset": 0,
                                                    "type": "ref_loop_body"},
                                                   {"offset": 0,
                                                    "type": "ref_post_loop"}],
                                        "size": 32,
                                        "align": 32},
                      "ref_loop_body": {"kind": "pointer",
                                        "pointee_type": "ref_loop_head",
                                        "size": 32,
                                        "align": 32},
                      "ref_post_loop": {"kind": "int",
                                        "size": 32,
                                        "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['ref_post_loop']['kind'] = 'fp'
    comparator = api_analysis.APIComparator(
        guest_api['types'], host_api['types'])
    api_analysis.mark_incompatible_api_with_comparator(
        comparator, guest_api['symbols'], host_api['symbols'])
    # 'ref_loop_body' is incompatible due to referencing 'ref_loop_head' which
    # references 'ref_post_loop' incompatible due to different type kind.
    # This is true even though reference from 'ref_loop_body' is back edge if to
    # consider graph traversal from 'symb'.
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])
    self.assertFalse(
        comparator.are_types_compatible('ref_loop_body', 'ref_loop_body'))


  def test_force_compatibility(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                },
                "symb_p": {
                    "type": "t_p"
                },
                "symb_c_p" : {
                    "type": "t_c_p"
                }
            },
            "types": {
                "t": {
                    "kind": "incomplete",
                    "force_compatible": True
                },
                "t_p" : {
                    "kind" : "pointer",
                    "pointee_type": "t",
                    "size" : 32
                },
                "t_c" : {
                    "kind" : "const",
                    "base_type": "t",
                },
                "t_c_p" : {
                    "kind" : "pointer",
                    "pointee_type": "t_c",
                    "size" : 32
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertTrue(guest_api['symbols']['symb']['is_compatible'])
    self.assertTrue(guest_api['symbols']['symb_p']['is_compatible'])
    self.assertTrue(guest_api['symbols']['symb_c_p']['is_compatible'])
    self.assertTrue(guest_api['types']['t']['useful_force_compatible'])


  def test_force_compatibility_for_referencing_incompatible(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                },
                "symb_p": {
                    "type": "t_p"
                },
                "symb_c_p" : {
                    "type": "t_c_p"
                }
            },
            "types": {
                "t": {
                    "kind": "incomplete"
                },
                "t_p" : {
                    "kind" : "pointer",
                    "pointee_type": "t",
                    "size" : 32,
                    "force_compatible": True
                },
                "t_c" : {
                    "kind" : "const",
                    "base_type": "t",
                },
                "t_c_p" : {
                    "kind" : "pointer",
                    "pointee_type": "t_c",
                    "size" : 32,
                    "force_compatible": True
                }
            }
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])
    self.assertTrue(guest_api['symbols']['symb_p']['is_compatible'])
    self.assertTrue(guest_api['symbols']['symb_c_p']['is_compatible'])
    self.assertTrue(guest_api['types']['t_p']['useful_force_compatible'])
    self.assertTrue(guest_api['types']['t_c_p']['useful_force_compatible'])

  def test_useless_force_compatibility(self):
    guest_api = \
        {
            "symbols": {
                "symb": {
                    "type": "t"
                },
            },
            "types": {
                "t": {
                    "kind": "incomplete",
                    "force_compatible": True
                },
            }
        }
    host_api = copy.deepcopy(guest_api)
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertTrue(guest_api['symbols']['symb']['is_compatible'])
    self.assertFalse(guest_api['types']['t'].get('useful_force_compatible', False))


  def test_incompatible_type_referenced_by_incompatible_type(self):
    guest_api = \
        {
            "symbols": {"symb": {"type": "t"}},
            "types": {"t": {"kind": "struct",
                            "is_polymorphic": False,
                            "fields": [{"offset": 0,
                                        "type": "t2"}],
                            "size": 32,
                            "align": 32},
                      "t2": {"kind": "int", "size": 32, "align": 32}}
        }
    host_api = copy.deepcopy(guest_api)
    host_api['types']['t']['size'] = 64
    host_api['types']['t2']['kind'] = 'fp'
    api_analysis.mark_incompatible_api(guest_api, host_api)
    self.assertFalse(guest_api['symbols']['symb']['is_compatible'])
    self.assertFalse(guest_api['types']['t']['is_compatible'])
    self.assertFalse(guest_api['types']['t2']['is_compatible'])

if __name__ == '__main__':
  unittest.main()
