1""" 2Test that re-running a process from within the same target 3after rebuilding the a dynamic library flushes the scratch 4TypeSystems tied to that process. 5""" 6 7import lldb 8from lldbsuite.test.lldbtest import * 9from lldbsuite.test import lldbutil 10from lldbsuite.test.decorators import * 11 12 13def isUbuntu18_04(): 14 """ 15 Check if the host OS is Ubuntu 18.04. 16 Derived from `platform.freedesktop_os_release` in Python 3.10. 17 """ 18 for path in ("/etc/os-release", "/usr/lib/os-release"): 19 if os.path.exists(path): 20 with open(path) as f: 21 contents = f.read() 22 if "Ubuntu 18.04" in contents: 23 return True 24 25 return False 26 27 28class TestRerunExprDylib(TestBase): 29 @skipTestIfFn(isUbuntu18_04, bugnumber="rdar://103831050") 30 @skipIfWindows 31 def test(self): 32 """ 33 Tests whether re-launching a process without destroying 34 the owning target keeps invalid ASTContexts in the 35 scratch AST's importer. 36 37 We test this by: 38 1. Evaluating an expression to import 'struct Foo' into 39 the scratch AST 40 2. Change the definition of 'struct Foo' and rebuild the dylib 41 3. Re-launch the process 42 4. Evaluate the same expression in (1). We expect to have only 43 the latest definition of 'struct Foo' in the scratch AST. 44 """ 45 46 DYLIB_NAME = "foo" 47 FULL_DYLIB_NAME = "libfoo.dylib" if self.platformIsDarwin() else "libfoo.so" 48 49 # Build libfoo.dylib 50 self.build( 51 dictionary={ 52 "DYLIB_CXX_SOURCES": "lib.cpp", 53 "DYLIB_ONLY": "YES", 54 "DYLIB_NAME": DYLIB_NAME, 55 "USE_LIBDL": "1", 56 "LD_EXTRAS": "-L.", 57 } 58 ) 59 60 # Build a.out 61 self.build( 62 dictionary={ 63 "EXE": "a.out", 64 "CXX_SOURCES": "main.cpp", 65 "USE_LIBDL": "1", 66 "CXXFLAGS_EXTRAS": f'-DLIB_NAME=\\"{FULL_DYLIB_NAME}\\"', 67 "LD_EXTRAS": "-L.", 68 } 69 ) 70 71 exe = self.getBuildArtifact("a.out") 72 target = self.dbg.CreateTarget(exe) 73 target.BreakpointCreateBySourceRegex("dlclose", lldb.SBFileSpec("main.cpp")) 74 target.BreakpointCreateBySourceRegex("return", lldb.SBFileSpec("main.cpp")) 75 process = target.LaunchSimple(None, None, self.get_process_working_directory()) 76 77 self.expect_expr( 78 "*foo", 79 result_type="Foo", 80 result_children=[ValueCheck(name="m_val", value="42")], 81 ) 82 83 # Delete the dylib to force make to rebuild it. 84 remove_file(self.getBuildArtifact(FULL_DYLIB_NAME)) 85 86 # Re-build libfoo.dylib 87 self.build( 88 dictionary={ 89 "DYLIB_CXX_SOURCES": "rebuild.cpp", 90 "DYLIB_ONLY": "YES", 91 "DYLIB_NAME": DYLIB_NAME, 92 "USE_LIBDL": "1", 93 "LD_EXTRAS": "-L.", 94 } 95 ) 96 97 # Rerun program within the same target 98 process.Continue() 99 process.Destroy() 100 process = target.LaunchSimple(None, None, self.get_process_working_directory()) 101 102 self.expect_expr( 103 "*foo", 104 result_type="Foo", 105 result_children=[ 106 ValueCheck( 107 name="Base", children=[ValueCheck(name="m_base_val", value="42")] 108 ), 109 ValueCheck(name="m_derived_val", value="137"), 110 ], 111 ) 112 113 self.filecheck("target module dump ast", __file__) 114 115 # The new definition 'struct Foo' is in the scratch AST 116 # CHECK: |-CXXRecordDecl {{.*}} struct Foo definition 117 # CHECK: | |-public 'Base' 118 # CHECK-NEXT: | `-FieldDecl {{.*}} m_derived_val 'int' 119 # CHECK-NEXT: `-CXXRecordDecl {{.*}} struct Base definition 120 # CHECK: `-FieldDecl {{.*}} m_base_val 'int' 121 122 # ...but the original definition of 'struct Foo' is not in the scratch AST anymore 123 # CHECK-NOT: FieldDecl {{.*}} m_val 'int' 124