1"""
2Test breakpoint serialization.
3"""
4
5import os
6import json
7import lldb
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11
12
13class BreakpointSerialization(TestBase):
14
15    mydir = TestBase.compute_mydir(__file__)
16    NO_DEBUG_INFO_TESTCASE = True
17
18    @add_test_categories(['pyapi'])
19    @skipIfReproducer # side_effect bypasses reproducer
20    def test_resolvers(self):
21        """Use Python APIs to test that we serialize resolvers."""
22        self.build()
23        self.setup_targets_and_cleanup()
24        self.do_check_resolvers()
25
26    @skipIfReproducer # side_effect bypasses reproducer
27    def test_filters(self):
28        """Use Python APIs to test that we serialize search filters correctly."""
29        self.build()
30        self.setup_targets_and_cleanup()
31        self.do_check_filters()
32
33    @skipIfReproducer # side_effect bypasses reproducer
34    def test_options(self):
35        """Use Python APIs to test that we serialize breakpoint options correctly."""
36        self.build()
37        self.setup_targets_and_cleanup()
38        self.do_check_options()
39
40    @skipIfReproducer # side_effect bypasses reproducer
41    def test_appending(self):
42        """Use Python APIs to test that we serialize breakpoint options correctly."""
43        self.build()
44        self.setup_targets_and_cleanup()
45        self.do_check_appending()
46
47    @skipIfReproducer # side_effect bypasses reproducer
48    def test_name_filters(self):
49        """Use python APIs to test that reading in by name works correctly."""
50        self.build()
51        self.setup_targets_and_cleanup()
52        self.do_check_names()
53
54    @skipIfReproducer # side_effect bypasses reproducer
55    def test_scripted_extra_args(self):
56        self.build()
57        self.setup_targets_and_cleanup()
58        self.do_check_extra_args()
59
60    def test_structured_data_serialization(self):
61        target = self.dbg.GetDummyTarget()
62        self.assertTrue(target.IsValid(), VALID_TARGET)
63
64        interpreter = self.dbg.GetCommandInterpreter()
65        result = lldb.SBCommandReturnObject()
66        interpreter.HandleCommand("br set -f foo -l 42", result)
67        result = lldb.SBCommandReturnObject()
68        interpreter.HandleCommand("br set -c 'argc == 1' -n main", result)
69
70        bkp1 =  target.GetBreakpointAtIndex(0)
71        self.assertTrue(bkp1.IsValid(), VALID_BREAKPOINT)
72        stream = lldb.SBStream()
73        sd = bkp1.SerializeToStructuredData()
74        sd.GetAsJSON(stream)
75        serialized_data = json.loads(stream.GetData())
76        self.assertEqual(serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["FileName"], "foo")
77        self.assertEqual(serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["LineNumber"], 42)
78
79        bkp2 =  target.GetBreakpointAtIndex(1)
80        self.assertTrue(bkp2.IsValid(), VALID_BREAKPOINT)
81        stream = lldb.SBStream()
82        sd = bkp2.SerializeToStructuredData()
83        sd.GetAsJSON(stream)
84        serialized_data = json.loads(stream.GetData())
85        self.assertIn("main", serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["SymbolNames"])
86        self.assertEqual(serialized_data["Breakpoint"]["BKPTOptions"]["ConditionText"],"argc == 1")
87
88        invalid_bkp = lldb.SBBreakpoint()
89        self.assertFalse(invalid_bkp.IsValid(), "Breakpoint should not be valid.")
90        stream = lldb.SBStream()
91        sd = invalid_bkp.SerializeToStructuredData()
92        sd.GetAsJSON(stream)
93        self.assertFalse(stream.GetData(), "Invalid breakpoint should have an empty structured data")
94
95    def setup_targets_and_cleanup(self):
96        def cleanup ():
97            self.RemoveTempFile(self.bkpts_file_path)
98
99            if self.orig_target.IsValid():
100                self.dbg.DeleteTarget(self.orig_target)
101                self.dbg.DeleteTarget(self.copy_target)
102
103        self.addTearDownHook(cleanup)
104        self.RemoveTempFile(self.bkpts_file_path)
105
106        exe = self.getBuildArtifact("a.out")
107
108        # Create the targets we are making breakpoints in and copying them to:
109        self.orig_target = self.dbg.CreateTarget(exe)
110        self.assertTrue(self.orig_target, VALID_TARGET)
111
112        self.copy_target = self.dbg.CreateTarget(exe)
113        self.assertTrue(self.copy_target, VALID_TARGET)
114
115    def setUp(self):
116        # Call super's setUp().
117        TestBase.setUp(self)
118
119        self.bkpts_file_path = self.getBuildArtifact("breakpoints.json")
120        self.bkpts_file_spec = lldb.SBFileSpec(self.bkpts_file_path)
121
122    def check_equivalence(self, source_bps, do_write = True):
123
124        error = lldb.SBError()
125
126        if (do_write):
127            error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps)
128            self.assertTrue(error.Success(), "Failed writing breakpoints to file: %s."%(error.GetCString()))
129
130        copy_bps = lldb.SBBreakpointList(self.copy_target)
131        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
132        self.assertTrue(error.Success(), "Failed reading breakpoints from file: %s"%(error.GetCString()))
133
134        num_source_bps = source_bps.GetSize()
135        num_copy_bps = copy_bps.GetSize()
136        self.assertTrue(num_source_bps == num_copy_bps, "Didn't get same number of input and output breakpoints - orig: %d copy: %d"%(num_source_bps, num_copy_bps))
137
138        for i in range(0, num_source_bps):
139            source_bp = source_bps.GetBreakpointAtIndex(i)
140            source_desc = lldb.SBStream()
141            source_bp.GetDescription(source_desc, False)
142            source_text = source_desc.GetData()
143
144            # I am assuming here that the breakpoints will get written out in breakpoint ID order, and
145            # read back in ditto.  That is true right now, and I can't see any reason to do it differently
146            # but if we do we can go to writing the breakpoints one by one, or sniffing the descriptions to
147            # see which one is which.
148            copy_id = source_bp.GetID()
149            copy_bp = copy_bps.FindBreakpointByID(copy_id)
150            self.assertTrue(copy_bp.IsValid(), "Could not find copy breakpoint %d."%(copy_id))
151
152            copy_desc = lldb.SBStream()
153            copy_bp.GetDescription(copy_desc, False)
154            copy_text = copy_desc.GetData()
155
156            # These two should be identical.
157            self.trace("Source text for %d is %s."%(i, source_text))
158            self.assertTrue (source_text == copy_text, "Source and dest breakpoints are not identical: \nsource: %s\ndest: %s"%(source_text, copy_text))
159
160    def do_check_resolvers(self):
161        """Use Python APIs to check serialization of breakpoint resolvers"""
162
163        empty_module_list = lldb.SBFileSpecList()
164        empty_cu_list = lldb.SBFileSpecList()
165        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
166
167        # It isn't actually important for these purposes that these breakpoint
168        # actually have locations.
169        source_bps = lldb.SBBreakpointList(self.orig_target)
170        source_bps.Append(self.orig_target.BreakpointCreateByLocation("blubby.c", 666))
171        # Make sure we do one breakpoint right:
172        self.check_equivalence(source_bps)
173        source_bps.Clear()
174
175        source_bps.Append(self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list))
176        source_bps.Append(self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list))
177        source_bps.Append(self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec))
178
179        # And some number greater than one:
180        self.check_equivalence(source_bps)
181
182    def do_check_filters(self):
183        """Use Python APIs to check serialization of breakpoint filters."""
184        module_list = lldb.SBFileSpecList()
185        module_list.Append(lldb.SBFileSpec("SomeBinary"))
186        module_list.Append(lldb.SBFileSpec("SomeOtherBinary"))
187
188        cu_list = lldb.SBFileSpecList()
189        cu_list.Append(lldb.SBFileSpec("SomeCU.c"))
190        cu_list.Append(lldb.SBFileSpec("AnotherCU.c"))
191        cu_list.Append(lldb.SBFileSpec("ThirdCU.c"))
192
193        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
194
195        # It isn't actually important for these purposes that these breakpoint
196        # actually have locations.
197        source_bps = lldb.SBBreakpointList(self.orig_target)
198        bkpt = self.orig_target.BreakpointCreateByLocation(blubby_file_spec, 666, 0, module_list)
199        source_bps.Append(bkpt)
200
201        # Make sure we do one right:
202        self.check_equivalence(source_bps)
203        source_bps.Clear()
204
205        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, module_list, cu_list)
206        source_bps.Append(bkpt)
207        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, module_list, cu_list)
208        source_bps.Append(bkpt)
209        bkpt = self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec)
210        source_bps.Append(bkpt)
211
212        # And some number greater than one:
213        self.check_equivalence(source_bps)
214
215    def do_check_options(self):
216        """Use Python APIs to check serialization of breakpoint options."""
217
218        empty_module_list = lldb.SBFileSpecList()
219        empty_cu_list = lldb.SBFileSpecList()
220        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
221
222        # It isn't actually important for these purposes that these breakpoint
223        # actually have locations.
224        source_bps = lldb.SBBreakpointList(self.orig_target)
225
226        bkpt = self.orig_target.BreakpointCreateByLocation(
227            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
228        bkpt.SetEnabled(False)
229        bkpt.SetOneShot(True)
230        bkpt.SetThreadID(10)
231        source_bps.Append(bkpt)
232
233        # Make sure we get one right:
234        self.check_equivalence(source_bps)
235        source_bps.Clear()
236
237        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
238        bkpt.SetIgnoreCount(10)
239        bkpt.SetThreadName("grubby")
240        source_bps.Append(bkpt)
241
242        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
243        bkpt.SetCondition("gonna remove this")
244        bkpt.SetCondition("")
245        source_bps.Append(bkpt)
246
247        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list)
248        bkpt.SetCondition("something != something_else")
249        bkpt.SetQueueName("grubby")
250        bkpt.AddName("FirstName")
251        bkpt.AddName("SecondName")
252        bkpt.SetScriptCallbackBody('\tprint("I am a function that prints.")\n\tprint("I don\'t do anything else")\n')
253        source_bps.Append(bkpt)
254
255        bkpt = self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec)
256        cmd_list = lldb.SBStringList()
257        cmd_list.AppendString("frame var")
258        cmd_list.AppendString("thread backtrace")
259
260        bkpt.SetCommandLineCommands(cmd_list)
261        source_bps.Append(bkpt)
262
263        self.check_equivalence(source_bps)
264
265    def do_check_appending(self):
266        """Use Python APIs to check appending to already serialized options."""
267
268        empty_module_list = lldb.SBFileSpecList()
269        empty_cu_list = lldb.SBFileSpecList()
270        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
271
272        # It isn't actually important for these purposes that these breakpoint
273        # actually have locations.
274
275        all_bps = lldb.SBBreakpointList(self.orig_target)
276        source_bps = lldb.SBBreakpointList(self.orig_target)
277
278        bkpt = self.orig_target.BreakpointCreateByLocation(
279            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
280        bkpt.SetEnabled(False)
281        bkpt.SetOneShot(True)
282        bkpt.SetThreadID(10)
283        source_bps.Append(bkpt)
284        all_bps.Append(bkpt)
285
286        error = lldb.SBError()
287        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps)
288        self.assertTrue(error.Success(), "Failed writing breakpoints to file: %s."%(error.GetCString()))
289
290        source_bps.Clear()
291
292        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
293        bkpt.SetIgnoreCount(10)
294        bkpt.SetThreadName("grubby")
295        source_bps.Append(bkpt)
296        all_bps.Append(bkpt)
297
298        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list)
299        bkpt.SetCondition("something != something_else")
300        bkpt.SetQueueName("grubby")
301        bkpt.AddName("FirstName")
302        bkpt.AddName("SecondName")
303
304        source_bps.Append(bkpt)
305        all_bps.Append(bkpt)
306
307        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps, True)
308        self.assertTrue(error.Success(), "Failed appending breakpoints to file: %s."%(error.GetCString()))
309
310        self.check_equivalence(all_bps)
311
312    def do_check_names(self):
313        bkpt = self.orig_target.BreakpointCreateByLocation(
314            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
315        good_bkpt_name = "GoodBreakpoint"
316        write_bps = lldb.SBBreakpointList(self.orig_target)
317        bkpt.AddName(good_bkpt_name)
318        write_bps.Append(bkpt)
319
320        error = lldb.SBError()
321        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
322        self.assertTrue(error.Success(), "Failed writing breakpoints to file: %s."%(error.GetCString()))
323
324        copy_bps = lldb.SBBreakpointList(self.copy_target)
325        names_list = lldb.SBStringList()
326        names_list.AppendString("NoSuchName")
327
328        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, names_list, copy_bps)
329        self.assertTrue(error.Success(), "Failed reading breakpoints from file: %s"%(error.GetCString()))
330        self.assertTrue(copy_bps.GetSize() == 0, "Found breakpoints with a nonexistent name.")
331
332        names_list.AppendString(good_bkpt_name)
333        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, names_list, copy_bps)
334        self.assertTrue(error.Success(), "Failed reading breakpoints from file: %s"%(error.GetCString()))
335        self.assertTrue(copy_bps.GetSize() == 1, "Found the matching breakpoint.")
336
337    def do_check_extra_args(self):
338
339        import side_effect
340        interp = self.dbg.GetCommandInterpreter()
341        error = lldb.SBError()
342
343        script_name = os.path.join(self.getSourceDir(), "resolver.py")
344
345        command = "command script import " + script_name
346        result = lldb.SBCommandReturnObject()
347        interp.HandleCommand(command, result)
348        self.assertTrue(result.Succeeded(), "com scr imp failed: %s"%(result.GetError()))
349
350        # First make sure a scripted breakpoint with no args works:
351        bkpt = self.orig_target.BreakpointCreateFromScript("resolver.Resolver", lldb.SBStructuredData(),
352                                                           lldb.SBFileSpecList(), lldb.SBFileSpecList())
353        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
354        write_bps = lldb.SBBreakpointList(self.orig_target)
355
356        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
357        self.assertTrue(error.Success(), "Failed writing breakpoints: %s"%(error.GetCString()))
358
359        side_effect.g_extra_args = None
360        copy_bps = lldb.SBBreakpointList(self.copy_target)
361        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
362        self.assertTrue(error.Success(), "Failed reading breakpoints: %s"%(error.GetCString()))
363
364        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
365        no_keys = lldb.SBStringList()
366        side_effect.g_extra_args.GetKeys(no_keys)
367        self.assertEqual(no_keys.GetSize(), 0, "Should have no keys")
368
369        self.orig_target.DeleteAllBreakpoints()
370        self.copy_target.DeleteAllBreakpoints()
371
372        # Now try one with extra args:
373
374        extra_args = lldb.SBStructuredData()
375        stream = lldb.SBStream()
376        stream.Print('{"first_arg" : "first_value", "second_arg" : "second_value"}')
377        extra_args.SetFromJSON(stream)
378        self.assertTrue(extra_args.IsValid(), "SBStructuredData is valid.")
379
380        bkpt = self.orig_target.BreakpointCreateFromScript("resolver.Resolver",
381                                                           extra_args, lldb.SBFileSpecList(), lldb.SBFileSpecList())
382        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
383        write_bps = lldb.SBBreakpointList(self.orig_target)
384
385        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
386        self.assertTrue(error.Success(), "Failed writing breakpoints: %s"%(error.GetCString()))
387
388        orig_extra_args = side_effect.g_extra_args
389        self.assertTrue(orig_extra_args.IsValid(), "Extra args originally valid")
390
391        orig_keys = lldb.SBStringList()
392        orig_extra_args.GetKeys(orig_keys)
393        self.assertEqual(2, orig_keys.GetSize(), "Should have two keys")
394
395        side_effect.g_extra_args = None
396
397        copy_bps = lldb.SBBreakpointList(self.copy_target)
398        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
399        self.assertTrue(error.Success(), "Failed reading breakpoints: %s"%(error.GetCString()))
400
401        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
402
403        copy_extra_args = side_effect.g_extra_args
404        copy_keys = lldb.SBStringList()
405        copy_extra_args.GetKeys(copy_keys)
406        self.assertEqual(2, copy_keys.GetSize(), "Copy should have two keys")
407
408        for idx in range(0, orig_keys.GetSize()):
409            key = orig_keys.GetStringAtIndex(idx)
410            copy_value = copy_extra_args.GetValueForKey(key).GetStringValue(100)
411
412            if key == "first_arg":
413                self.assertEqual(copy_value, "first_value")
414            elif key == "second_arg":
415                self.assertEqual(copy_value, "second_value")
416            else:
417                self.Fail("Unknown key: %s"%(key))
418