"""
Test completing types using information from other shared libraries.
"""

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class LimitDebugInfoTestCase(TestBase):
    SHARED_BUILD_TESTCASE = False

    def _check_type(self, target, name):
        exe = target.FindModule(lldb.SBFileSpec("a.out"))
        type_ = exe.FindFirstType(name)
        self.trace("type_: %s" % type_)
        self.assertTrue(type_)
        self.assertTrue(type_.IsTypeComplete())
        base = type_.GetDirectBaseClassAtIndex(0).GetType()
        self.trace("base:%s" % base)
        self.assertTrue(base)
        self.assertEqual(base.GetNumberOfFields(), 0)
        self.assertFalse(base.IsTypeComplete())

    def _check_debug_info_is_limited(self, target):
        # Without other shared libraries we should only see the member declared
        # in the derived class. This serves as a sanity check that we are truly
        # building with limited debug info.
        self._check_type(target, "InheritsFromOne")
        self._check_type(target, "InheritsFromTwo")

        # Check that the statistics show that we had incomplete debug info.
        stats = self.get_stats()
        # Find the a.out module info in the stats and verify it has the
        # "debugInfoHadIncompleteTypes" key value pair set to True
        exe_module_found = False
        for module in stats["modules"]:
            if module["path"].endswith("a.out"):
                self.assertTrue(module["debugInfoHadIncompleteTypes"])
                exe_module_found = True
                break
        self.assertTrue(exe_module_found)
        # Verify that "totalModuleCountWithIncompleteTypes" at the top level
        # is greater than zero which shows we had incomplete debug info in a
        # module
        self.assertGreater(stats["totalModuleCountWithIncompleteTypes"], 0)

    def _check_incomplete_frame_variable_output(self):
        # Check that the display of the "frame variable" output identifies the
        # incomplete types. Currently the expression parser will find the real
        # definition for a type when running an expression for any forcefully
        # completed types, but "frame variable" won't. I hope to fix this with
        # a follow up patch, but if we don't find the actual definition we
        # should clearly show this to the user by showing which types were
        # incomplete. So this will test verifies the expected output for such
        # types. We also need to verify the standard "frame variable" output
        # which will inline all of the members on one line, versus the full
        # output from "frame variable --raw" and a few other options.
        # self.expect("frame variable two_as_member", error=True,
        #     substrs=["no member named 'one' in 'InheritsFromOne'"])

        command_expect_pairs = [
            # Test standard "frame variable" output for types to make sure
            # "<incomplete type>" shows up where we expect it to
            [
                "var two_as_member",
                [
                    "(TwoAsMember) ::two_as_member = (two = <incomplete type>, member = 47)"
                ],
            ],
            [
                "var inherits_from_one",
                [
                    "(InheritsFromOne) ::inherits_from_one = (One = <incomplete type>, member = 47)"
                ],
            ],
            [
                "var inherits_from_two",
                [
                    "(InheritsFromTwo) ::inherits_from_two = (Two = <incomplete type>, member = 47)"
                ],
            ],
            [
                "var one_as_member",
                [
                    "(OneAsMember) ::one_as_member = (one = <incomplete type>, member = 47)"
                ],
            ],
            [
                "var two_as_member",
                [
                    "(TwoAsMember) ::two_as_member = (two = <incomplete type>, member = 47)"
                ],
            ],
            [
                "var array_of_one",
                [
                    "(array::One[3]) ::array_of_one = ([0] = <incomplete type>, [1] = <incomplete type>, [2] = <incomplete type>)"
                ],
            ],
            [
                "var array_of_two",
                [
                    "(array::Two[3]) ::array_of_two = ([0] = <incomplete type>, [1] = <incomplete type>, [2] = <incomplete type>)"
                ],
            ],
            [
                "var shadowed_one",
                [
                    "(ShadowedOne) ::shadowed_one = (func_shadow::One = <incomplete type>, member = 47)"
                ],
            ],
            # Now test "frame variable --show-types output" which has multi-line
            # output and should not always show classes that were forcefully
            # completed to the user to let them know they have a type that should
            # have been complete but wasn't.
            [
                "var --show-types inherits_from_one",
                [
                    "(InheritsFromOne) ::inherits_from_one = {",
                    "  (One) One = <incomplete type> {}",
                    "  (int) member = 47",
                    "}",
                ],
            ],
            [
                "var --show-types inherits_from_two",
                [
                    "(InheritsFromTwo) ::inherits_from_two = {",
                    "  (Two) Two = <incomplete type> {}",
                    "  (int) member = 47",
                    "}",
                ],
            ],
            [
                "var  --show-types one_as_member",
                [
                    "(OneAsMember) ::one_as_member = {",
                    "  (member::One) one = <incomplete type> {}",
                    "  (int) member = 47",
                    "}",
                ],
            ],
            [
                "var  --show-types two_as_member",
                [
                    "(TwoAsMember) ::two_as_member = {",
                    "  (member::Two) two = <incomplete type> {}",
                    "  (int) member = 47",
                    "}",
                ],
            ],
            [
                "var  --show-types array_of_one",
                [
                    "(array::One[3]) ::array_of_one = {",
                    "  (array::One) [0] = <incomplete type> {}",
                    "  (array::One) [1] = <incomplete type> {}",
                    "  (array::One) [2] = <incomplete type> {}",
                    "}",
                ],
            ],
            [
                "var  --show-types array_of_two",
                [
                    "(array::Two[3]) ::array_of_two = {",
                    "  (array::Two) [0] = <incomplete type> {}",
                    "  (array::Two) [1] = <incomplete type> {}",
                    "  (array::Two) [2] = <incomplete type> {}",
                    "}",
                ],
            ],
            [
                "var  --show-types shadowed_one",
                [
                    "(ShadowedOne) ::shadowed_one = {",
                    "  (func_shadow::One) func_shadow::One = <incomplete type> {}",
                    "  (int) member = 47",
                    "}",
                ],
            ],
        ]
        for command, expect_items in command_expect_pairs:
            self.expect(command, substrs=expect_items)

    @skipIf(bugnumber="pr46284", debug_info="gmodules")
    @skipIfWindows  # Clang emits type info even with -flimit-debug-info
    # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call
    # by-value functions.
    @skipIf(compiler="clang", compiler_version=["<", "7.0"])
    def test_one_and_two_debug(self):
        self.build()
        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))

        self._check_debug_info_is_limited(target)

        lldbutil.run_to_name_breakpoint(self, "main", extra_images=["one", "two"])

        # But when other shared libraries are loaded, we should be able to see
        # all members.
        self.expect_expr("inherits_from_one.member", result_value="47")
        self.expect_expr("inherits_from_one.one", result_value="142")
        self.expect_expr("inherits_from_two.member", result_value="47")
        self.expect_expr("inherits_from_two.one", result_value="142")
        self.expect_expr("inherits_from_two.two", result_value="242")

        self.expect_expr("one_as_member.member", result_value="47")
        self.expect_expr("one_as_member.one.member", result_value="147")
        self.expect_expr("two_as_member.member", result_value="47")
        self.expect_expr("two_as_member.two.one.member", result_value="147")
        self.expect_expr("two_as_member.two.member", result_value="247")

        self.expect_expr("array_of_one[2].member", result_value="174")
        self.expect_expr("array_of_two[2].one[2].member", result_value="174")
        self.expect_expr("array_of_two[2].member", result_value="274")

        self.expect_expr("get_one().member", result_value="124")
        self.expect_expr("get_two().one().member", result_value="124")
        self.expect_expr("get_two().member", result_value="224")

        self.expect_expr("shadowed_one.member", result_value="47")
        self.expect_expr("shadowed_one.one", result_value="142")

        self._check_incomplete_frame_variable_output()

    @skipIf(bugnumber="pr46284", debug_info="gmodules")
    @skipIfWindows  # Clang emits type info even with -flimit-debug-info
    # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call
    # by-value functions.
    @skipIf(compiler="clang", compiler_version=["<", "7.0"])
    def test_two_debug(self):
        self.build(dictionary=dict(STRIP_ONE="1"))
        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))

        self._check_debug_info_is_limited(target)

        lldbutil.run_to_name_breakpoint(self, "main", extra_images=["one", "two"])

        # This time, we should only see the members from the second library.
        self.expect_expr("inherits_from_one.member", result_value="47")
        self.expect(
            "expr inherits_from_one.one",
            error=True,
            substrs=["no member named 'one' in 'InheritsFromOne'"],
        )
        self.expect_expr("inherits_from_two.member", result_value="47")
        self.expect(
            "expr inherits_from_two.one",
            error=True,
            substrs=["no member named 'one' in 'InheritsFromTwo'"],
        )
        self.expect_expr("inherits_from_two.two", result_value="242")

        self.expect_expr("one_as_member.member", result_value="47")
        self.expect(
            "expr one_as_member.one.member",
            error=True,
            substrs=["no member named 'member' in 'member::One'"],
        )
        self.expect_expr("two_as_member.member", result_value="47")
        self.expect(
            "expr two_as_member.two.one.member",
            error=True,
            substrs=["no member named 'member' in 'member::One'"],
        )
        self.expect_expr("two_as_member.two.member", result_value="247")

        self.expect(
            "expr array_of_one[2].member",
            error=True,
            substrs=["no member named 'member' in 'array::One'"],
        )
        self.expect(
            "expr array_of_two[2].one[2].member",
            error=True,
            substrs=["no member named 'member' in 'array::One'"],
        )
        self.expect_expr("array_of_two[2].member", result_value="274")

        self.expect(
            "expr get_one().member",
            error=True,
            substrs=["calling 'get_one' with incomplete return type 'result::One'"],
        )
        self.expect(
            "expr get_two().one().member",
            error=True,
            substrs=["calling 'one' with incomplete return type 'result::One'"],
        )
        self.expect_expr("get_two().member", result_value="224")

        self._check_incomplete_frame_variable_output()

    @skipIf(bugnumber="pr46284", debug_info="gmodules")
    @skipIfWindows  # Clang emits type info even with -flimit-debug-info
    # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call
    # by-value functions.
    @skipIf(compiler="clang", compiler_version=["<", "7.0"])
    def test_one_debug(self):
        self.build(dictionary=dict(STRIP_TWO="1"))
        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))

        self._check_debug_info_is_limited(target)

        lldbutil.run_to_name_breakpoint(self, "main", extra_images=["one", "two"])

        # In this case we should only see the members from the second library.
        # Note that we cannot see inherits_from_two.one because without debug
        # info for "Two", we cannot determine that it in fact inherits from
        # "One".
        self.expect_expr("inherits_from_one.member", result_value="47")
        self.expect_expr("inherits_from_one.one", result_value="142")
        self.expect_expr("inherits_from_two.member", result_value="47")
        self.expect(
            "expr inherits_from_two.one",
            error=True,
            substrs=["no member named 'one' in 'InheritsFromTwo'"],
        )
        self.expect(
            "expr inherits_from_two.two",
            error=True,
            substrs=["no member named 'two' in 'InheritsFromTwo'"],
        )

        self.expect_expr("one_as_member.member", result_value="47")
        self.expect_expr("one_as_member.one.member", result_value="147")
        self.expect_expr("two_as_member.member", result_value="47")
        self.expect(
            "expr two_as_member.two.one.member",
            error=True,
            substrs=["no member named 'one' in 'member::Two'"],
        )
        self.expect(
            "expr two_as_member.two.member",
            error=True,
            substrs=["no member named 'member' in 'member::Two'"],
        )

        self.expect_expr("array_of_one[2].member", result_value="174")
        self.expect(
            "expr array_of_two[2].one[2].member",
            error=True,
            substrs=["no member named 'one' in 'array::Two'"],
        )
        self.expect(
            "expr array_of_two[2].member",
            error=True,
            substrs=["no member named 'member' in 'array::Two'"],
        )

        self.expect_expr("get_one().member", result_value="124")
        self.expect(
            "expr get_two().one().member",
            error=True,
            substrs=["calling 'get_two' with incomplete return type 'result::Two'"],
        )
        self.expect(
            "expr get_two().member",
            error=True,
            substrs=["calling 'get_two' with incomplete return type 'result::Two'"],
        )

        self._check_incomplete_frame_variable_output()
