lldb_webkit.py throws exception when generating summary of null StringImpl
[WebKit-https.git] / Tools / lldb / lldb_webkit.py
1 # Copyright (C) 2012 Apple. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
16 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 """
24     LLDB Support for WebKit Types
25
26     Add the following to your .lldbinit file to add WebKit Type summaries in LLDB and Xcode:
27
28     command script import {Path to WebKit Root}/Tools/lldb/lldb_webkit.py
29
30 """
31
32 import lldb
33 import string
34 import struct
35
36 def __lldb_init_module(debugger, dict):
37     debugger.HandleCommand('command script add -f lldb_webkit.btjs btjs')
38     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFString_SummaryProvider WTF::String')
39     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFStringImpl_SummaryProvider WTF::StringImpl')
40     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFAtomicString_SummaryProvider WTF::AtomicString')
41     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFVector_SummaryProvider -x "WTF::Vector<.+>$"')
42     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFHashTable_SummaryProvider -x "WTF::HashTable<.+>$"')
43     debugger.HandleCommand('type summary add --expand -F lldb_webkit.WTFMediaTime_SummaryProvider WTF::MediaTime')
44     debugger.HandleCommand('type synthetic add -x "WTF::Vector<.+>$" --python-class lldb_webkit.WTFVectorProvider')
45     debugger.HandleCommand('type synthetic add -x "WTF::HashTable<.+>$" --python-class lldb_webkit.WTFHashTableProvider')
46     debugger.HandleCommand('type summary add -F lldb_webkit.WebCoreLayoutUnit_SummaryProvider WebCore::LayoutUnit')
47     debugger.HandleCommand('type summary add -F lldb_webkit.WebCoreLayoutSize_SummaryProvider WebCore::LayoutSize')
48     debugger.HandleCommand('type summary add -F lldb_webkit.WebCoreLayoutPoint_SummaryProvider WebCore::LayoutPoint')
49
50 def WTFString_SummaryProvider(valobj, dict):
51     provider = WTFStringProvider(valobj, dict)
52     return "{ length = %d, contents = '%s' }" % (provider.get_length(), provider.to_string())
53
54
55 def WTFStringImpl_SummaryProvider(valobj, dict):
56     provider = WTFStringImplProvider(valobj, dict)
57     if not provider.is_initialized():
58         return ""
59     return "{ length = %d, is8bit = %d, contents = '%s' }" % (provider.get_length(), provider.is_8bit(), provider.to_string())
60
61
62 def WTFAtomicString_SummaryProvider(valobj, dict):
63     return WTFString_SummaryProvider(valobj.GetChildMemberWithName('m_string'), dict)
64
65
66 def WTFVector_SummaryProvider(valobj, dict):
67     provider = WTFVectorProvider(valobj, dict)
68     return "{ size = %d, capacity = %d }" % (provider.size, provider.capacity)
69
70
71 def WTFHashTable_SummaryProvider(valobj, dict):
72     provider = WTFHashTableProvider(valobj, dict)
73     return "{ tableSize = %d, keyCount = %d }" % (provider.tableSize(), provider.keyCount())
74
75
76 def WTFMediaTime_SummaryProvider(valobj, dict):
77     provider = WTFMediaTimeProvider(valobj, dict)
78     if provider.isInvalid():
79         return "{ Invalid }"
80     if provider.isPositiveInfinity():
81         return "{ +Infinity }"
82     if provider.isNegativeInfinity():
83         return "{ -Infinity }"
84     if provider.isIndefinite():
85         return "{ Indefinite }"
86     return "{ %d/%d, %f }" % (provider.timeValue(), provider.timeScale(), float(provider.timeValue()) / provider.timeScale())
87
88
89 def WebCoreLayoutUnit_SummaryProvider(valobj, dict):
90     provider = WebCoreLayoutUnitProvider(valobj, dict)
91     return "{ %s }" % provider.to_string()
92
93
94 def WebCoreLayoutSize_SummaryProvider(valobj, dict):
95     provider = WebCoreLayoutSizeProvider(valobj, dict)
96     return "{ width = %s, height = %s }" % (provider.get_width(), provider.get_height())
97
98
99 def WebCoreLayoutPoint_SummaryProvider(valobj, dict):
100     provider = WebCoreLayoutPointProvider(valobj, dict)
101     return "{ x = %s, y = %s }" % (provider.get_x(), provider.get_y())
102
103
104 def btjs(debugger, command, result, internal_dict):
105     '''Prints a stack trace of current thread with JavaScript frames decoded.  Takes optional frame count argument'''
106
107     target = debugger.GetSelectedTarget()
108     addressFormat = '#0{width}x'.format(width=target.GetAddressByteSize() * 2 + 2)
109     process = target.GetProcess()
110     thread = process.GetSelectedThread()
111
112     backtraceDepth = thread.GetNumFrames()
113
114     if len(command) == 1:
115         try:
116             backtraceDepth = int(command)
117         except ValueError:
118             return
119
120     threadFormat = '* thread #{num}: tid = {tid:#x}, {pcAddr:' + addressFormat + '}, queue = \'{queueName}, stop reason = {stopReason}'
121     print threadFormat.format(num=thread.GetIndexID(), tid=thread.GetThreadID(), pcAddr=thread.GetFrameAtIndex(0).GetPC(), queueName=thread.GetQueueName(), stopReason=thread.GetStopDescription(30))
122
123     for frame in thread:
124         if backtraceDepth < 1:
125             break
126
127         backtraceDepth = backtraceDepth - 1
128
129         function = frame.GetFunction()
130
131         if not frame or not frame.GetSymbol() or frame.GetSymbol().GetName() == "llint_entry":
132             callFrame = frame.GetSP()
133             JSFrameDescription = frame.EvaluateExpression("((JSC::CallFrame*)0x%x)->describeFrame()" % frame.GetFP()).GetSummary()
134             JSFrameDescription = string.strip(JSFrameDescription, '"')
135             frameFormat = '    frame #{num}: {addr:' + addressFormat + '} {desc}'
136             print frameFormat.format(num=frame.GetFrameID(), addr=frame.GetPC(), desc=JSFrameDescription)
137         else:
138             print '    %s' % frame
139
140 # FIXME: Provide support for the following types:
141 # def WTFVector_SummaryProvider(valobj, dict):
142 # def WTFCString_SummaryProvider(valobj, dict):
143 # def WebCoreKURLGooglePrivate_SummaryProvider(valobj, dict):
144 # def WebCoreQualifiedName_SummaryProvider(valobj, dict):
145 # def JSCIdentifier_SummaryProvider(valobj, dict):
146 # def JSCJSString_SummaryProvider(valobj, dict):
147
148
149 def guess_string_length(valobj, charSize, error):
150     if not valobj.GetValue():
151         return 0
152
153     maxLength = 256
154
155     pointer = valobj.GetValueAsUnsigned()
156     contents = valobj.GetProcess().ReadMemory(pointer, maxLength * charSize, lldb.SBError())
157     format = 'B' if charSize == 1 else 'H'
158
159     for i in xrange(0, maxLength):
160         if not struct.unpack_from(format, contents, i * charSize)[0]:
161             return i
162
163     return maxLength
164
165 def ustring_to_string(valobj, error, length=None):
166     if length is None:
167         length = guess_string_length(valobj, 2, error)
168     else:
169         length = int(length)
170
171     if length == 0:
172         return ""
173
174     pointer = valobj.GetValueAsUnsigned()
175     contents = valobj.GetProcess().ReadMemory(pointer, length * 2, lldb.SBError())
176
177     # lldb does not (currently) support returning unicode from python summary providers,
178     # so potentially convert this to ascii by escaping
179     string = contents.decode('utf16')
180     try:
181         return str(string)
182     except:
183         return string.encode('unicode_escape')
184
185 def lstring_to_string(valobj, error, length=None):
186     if length is None:
187         length = guess_string_length(valobj, 1, error)
188     else:
189         length = int(length)
190
191     if length == 0:
192         return ""
193
194     pointer = valobj.GetValueAsUnsigned()
195     contents = valobj.GetProcess().ReadMemory(pointer, length, lldb.SBError())
196
197     # lldb does not (currently) support returning unicode from python summary providers,
198     # so potentially convert this to ascii by escaping
199     string = contents.decode('utf8')
200     try:
201         return str(string)
202     except:
203         return string.encode('unicode_escape')
204
205 class WTFStringImplProvider:
206     def __init__(self, valobj, dict):
207         self.valobj = valobj
208
209     def get_length(self):
210         return self.valobj.GetChildMemberWithName('m_length').GetValueAsUnsigned(0)
211
212     def get_data8(self):
213         return self.valobj.GetChildAtIndex(2).GetChildMemberWithName('m_data8')
214
215     def get_data16(self):
216         return self.valobj.GetChildAtIndex(2).GetChildMemberWithName('m_data16')
217
218     def to_string(self):
219         error = lldb.SBError()
220
221         if not self.is_initialized():
222             return u""
223
224         if self.is_8bit():
225             return lstring_to_string(self.get_data8(), error, self.get_length())
226         return ustring_to_string(self.get_data16(), error, self.get_length())
227
228     def is_8bit(self):
229         # FIXME: find a way to access WTF::StringImpl::s_hashFlag8BitBuffer
230         return bool(self.valobj.GetChildMemberWithName('m_hashAndFlags').GetValueAsUnsigned(0) \
231             & 1 << 5)
232
233     def is_initialized(self):
234         return self.valobj.GetValueAsUnsigned() != 0
235
236
237 class WTFStringProvider:
238     def __init__(self, valobj, dict):
239         self.valobj = valobj
240
241     def stringimpl(self):
242         impl_ptr = self.valobj.GetChildMemberWithName('m_impl').GetChildMemberWithName('m_ptr')
243         return WTFStringImplProvider(impl_ptr, dict)
244
245     def get_length(self):
246         impl = self.stringimpl()
247         if not impl:
248             return 0
249         return impl.get_length()
250
251     def to_string(self):
252         impl = self.stringimpl()
253         if not impl:
254             return u""
255         return impl.to_string()
256
257
258 class WebCoreLayoutUnitProvider:
259     "Print a WebCore::LayoutUnit"
260     def __init__(self, valobj, dict):
261         self.valobj = valobj
262
263     def to_string(self):
264         layoutUnitValue = self.valobj.GetChildMemberWithName('m_value').GetValueAsSigned(0)
265         # figure out the layout unit denominator by checking infinite IntRect's value. It ensures that this function works even when subpixel is off.
266         infiniteWidth = self.valobj.GetFrame().EvaluateExpression('IntRect::infiniteRect()').GetChildMemberWithName('m_size').GetChildMemberWithName('m_width').GetValueAsSigned(0)
267         # denominator = maxint / current infinite width value
268         denominator = int(2147483647 / infiniteWidth) if infiniteWidth else 64
269         return "%gpx (%d)" % (float(layoutUnitValue) / denominator, layoutUnitValue)
270
271
272 class WebCoreLayoutSizeProvider:
273     "Print a WebCore::LayoutSize"
274     def __init__(self, valobj, dict):
275         self.valobj = valobj
276
277     def get_width(self):
278         return WebCoreLayoutUnitProvider(self.valobj.GetChildMemberWithName('m_width'), dict).to_string()
279
280     def get_height(self):
281         return WebCoreLayoutUnitProvider(self.valobj.GetChildMemberWithName('m_height'), dict).to_string()
282
283
284 class WebCoreLayoutPointProvider:
285     "Print a WebCore::LayoutPoint"
286     def __init__(self, valobj, dict):
287         self.valobj = valobj
288
289     def get_x(self):
290         return WebCoreLayoutUnitProvider(self.valobj.GetChildMemberWithName('m_x'), dict).to_string()
291
292     def get_y(self):
293         return WebCoreLayoutUnitProvider(self.valobj.GetChildMemberWithName('m_y'), dict).to_string()
294
295
296 class WTFVectorProvider:
297     def __init__(self, valobj, internal_dict):
298         self.valobj = valobj
299         self.update()
300
301     def num_children(self):
302         return self.size + 3
303
304     def get_child_index(self, name):
305         if name == "m_size":
306             return self.size
307         elif name == "m_capacity":
308             return self.size + 1
309         elif name == "m_buffer":
310             return self.size + 2
311         else:
312             return int(name.lstrip('[').rstrip(']'))
313
314     def get_child_at_index(self, index):
315         if index == self.size:
316             return self.valobj.GetChildMemberWithName("m_size")
317         elif index == self.size + 1:
318             return self.valobj.GetChildMemberWithName("m_capacity")
319         elif index == self.size + 2:
320             return self.buffer
321         elif index < self.size:
322             offset = index * self.data_size
323             child = self.buffer.CreateChildAtOffset('[' + str(index) + ']', offset, self.data_type)
324             return child
325         else:
326             return None
327
328     def update(self):
329         self.buffer = self.valobj.GetChildMemberWithName('m_buffer')
330         self.size = self.valobj.GetChildMemberWithName('m_size').GetValueAsUnsigned(0)
331         self.capacity = self.buffer.GetChildMemberWithName('m_capacity').GetValueAsUnsigned(0)
332         self.data_type = self.buffer.GetType().GetPointeeType()
333         self.data_size = self.data_type.GetByteSize()
334
335     def has_children(self):
336         return True
337
338
339 class WTFHashTableProvider:
340     def __init__(self, valobj, internal_dict):
341         self.valobj = valobj
342         self.update()
343
344     def num_children(self):
345         return self.tableSize() + 5
346
347     def get_child_index(self, name):
348         if name == "m_table":
349             return self.tableSize()
350         elif name == "m_tableSize":
351             return self.tableSize() + 1
352         elif name == "m_tableSizeMask":
353             return self.tableSize() + 2
354         elif name == "m_keyCount":
355             return self.tableSize() + 3
356         elif name == "m_deletedCount":
357             return self.tableSize() + 4
358         else:
359             return int(name.lstrip('[').rstrip(']'))
360
361     def get_child_at_index(self, index):
362         if index == self.tableSize():
363             return self.valobj.GetChildMemberWithName('m_table')
364         elif index == self.tableSize() + 1:
365             return self.valobj.GetChildMemberWithName('m_tableSize')
366         elif index == self.tableSize() + 2:
367             return self.valobj.GetChildMemberWithName('m_tableSizeMask')
368         elif index == self.tableSize() + 3:
369             return self.valobj.GetChildMemberWithName('m_keyCount')
370         elif index == self.tableSize() + 4:
371             return self.valobj.GetChildMemberWithName('m_deletedCount')
372         elif index < self.tableSize():
373             table = self.valobj.GetChildMemberWithName('m_table')
374             return table.CreateChildAtOffset('[' + str(index) + ']', index * self.data_size, self.data_type)
375         else:
376             return None
377
378     def tableSize(self):
379         return self.valobj.GetChildMemberWithName('m_tableSize').GetValueAsUnsigned(0)
380
381     def keyCount(self):
382         return self.valobj.GetChildMemberWithName('m_keyCount').GetValueAsUnsigned(0)
383
384     def update(self):
385         self.data_type = self.valobj.GetType().GetTemplateArgumentType(0)
386         self.data_size = self.data_type.GetByteSize()
387
388     def has_children(self):
389         return True
390
391
392 class WTFMediaTimeProvider:
393     def __init__(self, valobj, internal_dict):
394         self.valobj = valobj
395
396     def timeValue(self):
397         return self.valobj.GetChildMemberWithName('m_timeValue').GetValueAsSigned(0)
398
399     def timeScale(self):
400         return self.valobj.GetChildMemberWithName('m_timeScale').GetValueAsSigned(0)
401
402     def isInvalid(self):
403         return not self.valobj.GetChildMemberWithName('m_timeFlags').GetValueAsSigned(0) & (1 << 0)
404
405     def isPositiveInfinity(self):
406         return self.valobj.GetChildMemberWithName('m_timeFlags').GetValueAsSigned(0) & (1 << 2)
407
408     def isNegativeInfinity(self):
409         return self.valobj.GetChildMemberWithName('m_timeFlags').GetValueAsSigned(0) & (1 << 3)
410
411     def isIndefinite(self):
412         return self.valobj.GetChildMemberWithName('m_timeFlags').GetValueAsSigned(0) & (1 << 4)