237 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
	
| from lldbsuite.test.decorators import *
 | |
| from lldbsuite.test.lldbtest import *
 | |
| from lldbsuite.test import lldbutil
 | |
| 
 | |
| """
 | |
| This test ensures that we only create Clang AST nodes in our module AST
 | |
| when we actually need them.
 | |
| 
 | |
| All tests in this file behave like this:
 | |
|   1. Use LLDB to do something (expression evaluation, breakpoint setting, etc.).
 | |
|   2. Check that certain Clang AST nodes were not loaded during the previous
 | |
|      step.
 | |
| """
 | |
| 
 | |
| class TestCase(TestBase):
 | |
| 
 | |
|     NO_DEBUG_INFO_TESTCASE = True
 | |
|     mydir = TestBase.compute_mydir(__file__)
 | |
| 
 | |
|     def setUp(self):
 | |
|       TestBase.setUp(self)
 | |
|       # Only build this test once.
 | |
|       self.build()
 | |
| 
 | |
|     # Clang declaration kind we are looking for.
 | |
|     class_decl_kind = "CXXRecordDecl"
 | |
|     # FIXME: This shouldn't be a CXXRecordDecl, but that's how we model
 | |
|     # structs in Clang.
 | |
|     struct_decl_kind = "CXXRecordDecl"
 | |
| 
 | |
|     # The decls we use in this program in the format that
 | |
|     # decl_in_line and decl_completed_in_line expect (which is a pair of
 | |
|     # node type and the unqualified declaration name.
 | |
|     struct_first_member_decl = [struct_decl_kind, "StructFirstMember"]
 | |
|     struct_behind_ptr_decl = [struct_decl_kind, "StructBehindPointer"]
 | |
|     struct_behind_ref_decl = [struct_decl_kind, "StructBehindRef"]
 | |
|     struct_member_decl = [struct_decl_kind, "StructMember"]
 | |
|     some_struct_decl = [struct_decl_kind, "SomeStruct"]
 | |
|     other_struct_decl = [struct_decl_kind, "OtherStruct"]
 | |
|     class_in_namespace_decl = [class_decl_kind, "ClassInNamespace"]
 | |
|     class_we_enter_decl = [class_decl_kind, "ClassWeEnter"]
 | |
|     class_member_decl = [struct_decl_kind, "ClassMember"]
 | |
|     unused_class_member_decl = [struct_decl_kind, "UnusedClassMember"]
 | |
|     unused_class_member_ptr_decl = [struct_decl_kind, "UnusedClassMemberPtr"]
 | |
| 
 | |
|     def assert_no_decls_loaded(self):
 | |
|         """
 | |
|         Asserts that no known declarations in this test are loaded
 | |
|         into the module's AST.
 | |
|         """
 | |
|         self.assert_decl_not_loaded(self.struct_first_member_decl)
 | |
|         self.assert_decl_not_loaded(self.struct_behind_ptr_decl)
 | |
|         self.assert_decl_not_loaded(self.struct_behind_ref_decl)
 | |
|         self.assert_decl_not_loaded(self.struct_member_decl)
 | |
|         self.assert_decl_not_loaded(self.some_struct_decl)
 | |
|         self.assert_decl_not_loaded(self.other_struct_decl)
 | |
|         self.assert_decl_not_loaded(self.class_in_namespace_decl)
 | |
|         self.assert_decl_not_loaded(self.class_member_decl)
 | |
|         self.assert_decl_not_loaded(self.unused_class_member_decl)
 | |
| 
 | |
|     def get_ast_dump(self):
 | |
|         """Returns the dumped Clang AST of the module as a string"""
 | |
|         res = lldb.SBCommandReturnObject()
 | |
|         ci = self.dbg.GetCommandInterpreter()
 | |
|         ci.HandleCommand('target modules dump ast a.out', res)
 | |
|         self.assertTrue(res.Succeeded())
 | |
|         return res.GetOutput()
 | |
| 
 | |
|     def decl_in_line(self, line, decl):
 | |
|         """
 | |
|         Returns true iff the given line declares the given Clang decl.
 | |
|         The line is expected to be in the form of Clang's AST dump.
 | |
|         """
 | |
|         line = line.rstrip() + "\n"
 | |
|         decl_kind = "-" + decl[0] + " "
 | |
|         # Either the decl is somewhere in the line or at the end of
 | |
|         # the line.
 | |
|         decl_name = " " + decl[1] + " "
 | |
|         decl_name_eol = " " + decl[1] + "\n"
 | |
|         if not decl_kind in line:
 | |
|           return False
 | |
|         return decl_name in line or decl_name_eol in line
 | |
| 
 | |
|     def decl_completed_in_line(self, line, decl):
 | |
|         """
 | |
|         Returns true iff the given line declares the given Clang decl and
 | |
|         the decl was completed (i.e., it has no undeserialized declarations
 | |
|         in it).
 | |
|         """
 | |
|         return self.decl_in_line(line, decl) and not "<undeserialized declarations>" in line
 | |
| 
 | |
|     # The following asserts are used for checking if certain Clang declarations
 | |
|     # were loaded or not since the target was created.
 | |
| 
 | |
|     def assert_decl_loaded(self, decl):
 | |
|         """
 | |
|         Asserts the given decl is currently loaded.
 | |
|         Note: This test is about checking that types/declarations are not
 | |
|         loaded. If this assert fails it is usually fine to turn it into a
 | |
|         assert_decl_not_loaded or assert_decl_not_completed assuming LLDB's
 | |
|         functionality has not suffered by not loading this declaration.
 | |
|         """
 | |
|         ast = self.get_ast_dump()
 | |
|         found = False
 | |
|         for line in ast.splitlines():
 | |
|           if self.decl_in_line(line, decl):
 | |
|             found = True
 | |
|             self.assertTrue(self.decl_completed_in_line(line, decl),
 | |
|                             "Should have called assert_decl_not_completed")
 | |
|         self.assertTrue(found, "Declaration no longer loaded " + str(decl) +
 | |
|             ".\nAST:\n" + ast)
 | |
| 
 | |
|     def assert_decl_not_completed(self, decl):
 | |
|         """
 | |
|         Asserts that the given decl is currently not completed in the module's
 | |
|         AST. It may be loaded but then can can only contain undeserialized
 | |
|         declarations.
 | |
|         """
 | |
|         ast = self.get_ast_dump()
 | |
|         found = False
 | |
|         for line in ast.splitlines():
 | |
|           error_msg = "Unexpected completed decl: '" + line + "'.\nAST:\n" + ast
 | |
|           self.assertFalse(self.decl_completed_in_line(line, decl), error_msg)
 | |
| 
 | |
|     def assert_decl_not_loaded(self, decl):
 | |
|         """
 | |
|         Asserts that the given decl is currently not loaded in the module's
 | |
|         AST.
 | |
|         """
 | |
|         ast = self.get_ast_dump()
 | |
|         found = False
 | |
|         for line in ast.splitlines():
 | |
|           error_msg = "Unexpected loaded decl: '" + line + "'\nAST:\n" + ast
 | |
|           self.assertFalse(self.decl_in_line(line, decl), error_msg)
 | |
| 
 | |
| 
 | |
|     def clean_setup(self, location):
 | |
|         """
 | |
|         Runs to the line with the source line with the given location string
 | |
|         and ensures that our module AST is empty.
 | |
|         """
 | |
|         lldbutil.run_to_source_breakpoint(self,
 | |
|             "// Location: " + location, lldb.SBFileSpec("main.cpp"))
 | |
|         # Make sure no declarations are loaded initially.
 | |
|         self.assert_no_decls_loaded()
 | |
| 
 | |
|     @add_test_categories(["dwarf"])
 | |
|     def test_arithmetic_expression_in_main(self):
 | |
|         """ Runs a simple arithmetic expression which should load nothing. """
 | |
|         self.clean_setup(location="multiple locals function")
 | |
| 
 | |
|         self.expect("expr 1 + (int)2.0", substrs=['(int) $0'])
 | |
| 
 | |
|         # This should not have loaded any decls.
 | |
|         self.assert_no_decls_loaded()
 | |
| 
 | |
|     @add_test_categories(["dwarf"])
 | |
|     def test_printing_local_variable_in_other_struct_func(self):
 | |
|         """
 | |
|         Prints a local variable and makes sure no unrelated types are loaded.
 | |
|         """
 | |
|         self.clean_setup(location="other struct function")
 | |
| 
 | |
|         self.expect("expr other_struct_var", substrs=['(OtherStruct) $0'])
 | |
|         # The decl we run on was loaded.
 | |
|         self.assert_decl_loaded(self.other_struct_decl)
 | |
| 
 | |
|         # This should not have loaded anything else.
 | |
|         self.assert_decl_not_loaded(self.some_struct_decl)
 | |
|         self.assert_decl_not_loaded(self.class_in_namespace_decl)
 | |
| 
 | |
|     @add_test_categories(["dwarf"])
 | |
|     def test_printing_struct_with_multiple_locals(self):
 | |
|         """
 | |
|         Prints a local variable and checks that we don't load other local
 | |
|         variables.
 | |
|         """
 | |
|         self.clean_setup(location="multiple locals function")
 | |
| 
 | |
|         self.expect("expr struct_var", substrs=['(SomeStruct) $0'])
 | |
| 
 | |
|         # We loaded SomeStruct and its member types for printing.
 | |
|         self.assert_decl_loaded(self.some_struct_decl)
 | |
|         self.assert_decl_loaded(self.struct_behind_ptr_decl)
 | |
|         self.assert_decl_loaded(self.struct_behind_ref_decl)
 | |
| 
 | |
|         # FIXME: We don't use these variables, but we seem to load all local
 | |
|         # local variables.
 | |
|         self.assert_decl_not_completed(self.other_struct_decl)
 | |
|         self.assert_decl_not_completed(self.class_in_namespace_decl)
 | |
| 
 | |
|     @add_test_categories(["dwarf"])
 | |
|     def test_addr_of_struct(self):
 | |
|         """
 | |
|         Prints the address of a local variable (which is a struct).
 | |
|         """
 | |
|         self.clean_setup(location="multiple locals function")
 | |
| 
 | |
|         self.expect("expr &struct_var", substrs=['(SomeStruct *) $0'])
 | |
| 
 | |
|         # We loaded SomeStruct.
 | |
|         self.assert_decl_loaded(self.some_struct_decl)
 | |
| 
 | |
|         # The member declarations should not be completed.
 | |
|         self.assert_decl_not_completed(self.struct_behind_ptr_decl)
 | |
|         self.assert_decl_not_completed(self.struct_behind_ref_decl)
 | |
| 
 | |
|         # FIXME: The first member was behind a pointer so it shouldn't be
 | |
|         # completed. Somehow LLDB really wants to load the first member, so
 | |
|         # that is why have it defined here.
 | |
|         self.assert_decl_loaded(self.struct_first_member_decl)
 | |
| 
 | |
|         # FIXME: We don't use these variables, but we seem to load all local
 | |
|         # local variables.
 | |
|         self.assert_decl_not_completed(self.other_struct_decl)
 | |
|         self.assert_decl_not_completed(self.class_in_namespace_decl)
 | |
| 
 | |
|     @add_test_categories(["dwarf"])
 | |
|     def test_class_function_access_member(self):
 | |
|         self.clean_setup(location="class function")
 | |
| 
 | |
|         self.expect("expr member", substrs=['(ClassMember) $0'])
 | |
| 
 | |
|         # We loaded the current class we touched.
 | |
|         self.assert_decl_loaded(self.class_we_enter_decl)
 | |
|         # We loaded the unused members of this class.
 | |
|         self.assert_decl_loaded(self.unused_class_member_decl)
 | |
|         self.assert_decl_not_completed(self.unused_class_member_ptr_decl)
 | |
|         # We loaded the member we used.
 | |
|         self.assert_decl_loaded(self.class_member_decl)
 | |
| 
 | |
|         # This should not have loaded anything else.
 | |
|         self.assert_decl_not_loaded(self.other_struct_decl)
 | |
|         self.assert_decl_not_loaded(self.some_struct_decl)
 | |
|         self.assert_decl_not_loaded(self.class_in_namespace_decl)
 | |
| 
 |