firestore.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. # Copyright 2020 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import ast
  15. import json
  16. """
  17. LLDB type summary providers for common Firestore types.
  18. This is primarily useful for debugging Firestore internals. It will add useful
  19. summaries and consolidate common types in a way that makes them easier to
  20. observe in the debugger.
  21. Use this by adding the following to your ~/.lldbinit file:
  22. command script import ~/path/to/firebase-ios-sdk/scripts/lldb/firestore.py
  23. Most of this implementation is based on "Variable Formatting" in the LLDB online
  24. manual: https://lldb.llvm.org/use/variable.html. There are two major features
  25. we're making use of:
  26. * Summary Providers: these are classes or functions that take an object and
  27. produce a (typically one line) summary of the type
  28. * Synthetic Children Providers: these are classes that provide an alternative
  29. view of the data. The children that are synthesized here show up in the
  30. graphical debugger.
  31. """
  32. class ForwardingSynthProvider(object):
  33. """A synthetic child provider that forwards all methods to another provider.
  34. Override the `delegate` method to customize the target to which this forwards.
  35. """
  36. def __init__(self, value, params):
  37. self.value = value
  38. def delegate(self):
  39. return self.value
  40. def has_children(self):
  41. return self.delegate().MightHaveChildren()
  42. def num_children(self):
  43. return self.delegate().GetNumChildren()
  44. def get_child_index(self, name):
  45. return self.delegate().GetIndexOfChildWithName(name)
  46. def get_child_at_index(self, index):
  47. return self.delegate().GetChildAtIndex(index)
  48. def update(self):
  49. # No additional state so nothing needs updating when the value changes.
  50. pass
  51. # Abseil
  52. class AbseilOptional_SynthProvider(object):
  53. """A synthetic child provider that hides the internals of absl::optional.
  54. """
  55. def __init__(self, value, params):
  56. self.value = value
  57. self.engaged = None
  58. self.data = None
  59. def update(self):
  60. # Unwrap all the internal optional_data and similar types
  61. value = self.value
  62. while True:
  63. if value.GetNumChildren() <= 0:
  64. break
  65. child = value.GetChildAtIndex(0)
  66. if not child.IsValid():
  67. break
  68. if 'optional_internal' not in child.GetType().GetName():
  69. break
  70. value = child
  71. # value should now point to the innermost absl::optional container type.
  72. self.engaged = value.GetChildMemberWithName('engaged_')
  73. if self.has_children():
  74. self.data = value.GetChildMemberWithName('data_')
  75. else:
  76. self.data = None
  77. def has_children(self):
  78. return self.engaged.GetValueAsUnsigned(0) != 0
  79. def num_children(self):
  80. return 2 if self.has_children() else 1
  81. def get_child_index(self, name):
  82. if name == 'engaged_':
  83. return 0
  84. if name == 'data_':
  85. return 1
  86. return -1
  87. def get_child_at_index(self, index):
  88. if index == 0:
  89. return self.engaged
  90. if index == 1:
  91. return self.data
  92. def AbseilOptional_SummaryProvider(value, params):
  93. # Operates on the synthetic children above, calling has_children.
  94. return 'engaged={0}'.format(format_bool(value.MightHaveChildren()))
  95. # model
  96. class DatabaseId_SynthProvider(ForwardingSynthProvider):
  97. """Makes DatabaseId behave as if `*rep_` were inline, hiding its
  98. `shared_ptr<Rep>` implementation details.
  99. """
  100. def delegate(self):
  101. return deref_shared(self.value.GetChildMemberWithName('rep_'))
  102. def DatabaseId_SummaryProvider(value, params):
  103. # Operates on the result of the SynthProvider; value is *rep_.
  104. parts = [
  105. get_string(value.GetChildMemberWithName('project_id')),
  106. get_string(value.GetChildMemberWithName('database_id'))
  107. ]
  108. return format_string('/'.join(parts))
  109. def DocumentKey_SummaryProvider(value, params):
  110. """Summarizes DocumentKey as if path_->segments_ were inline and a single,
  111. slash-delimited string like `"users/foo"`.
  112. """
  113. return deref_shared(value.GetChildMemberWithName('path_')).GetSummary()
  114. def ResourcePath_SummaryProvider(value, params):
  115. """Summarizes ResourcePath as if segments_ were a single string,
  116. slash-delimited string like `"users/foo"`.
  117. """
  118. segments = value.GetChildMemberWithName('segments_')
  119. segment_text = [get_string(child) for child in segments]
  120. return format_string('/'.join(segment_text))
  121. # api
  122. def DocumentReference_SummaryProvider(value, params):
  123. """Summarizes DocumentReference as a single slash-delimited string like
  124. `"users/foo"`.
  125. """
  126. return value.GetChildMemberWithName('key_').GetSummary()
  127. def DocumentSnapshot_SummaryProvider(value, params):
  128. """Summarizes DocumentSnapshot as a single slash-delimited string like
  129. `"users/foo"` that names the path of the document in the snapshot.
  130. """
  131. return value.GetChildMemberWithName('internal_key_').GetSummary()
  132. # Objective-C
  133. def FIRDocumentReference_SummaryProvider(value, params):
  134. return value.GetChildMemberWithName('_documentReference').GetSummary()
  135. def FIRDocumentSnapshot_SummaryProvider(value, params):
  136. return value.GetChildMemberWithName('_snapshot').GetSummary()
  137. def get_string(value):
  138. """Returns a Python string from the underlying LLDB SBValue."""
  139. # TODO(wilhuff): Actually use the SBData API to get this.
  140. # Get the summary as a C literal and parse it (for now). Using the SBData
  141. # API would allow this to directly read the string contents.
  142. summary = value.GetSummary()
  143. return ast.literal_eval(summary)
  144. def format_string(string):
  145. """Formats a Python string as a C++ string literal."""
  146. # JSON and C escapes work the ~same.
  147. return json.dumps(string)
  148. def format_bool(value):
  149. """Formats a Python value as a C++ bool literal."""
  150. return 'true' if value else 'false'
  151. def deref_shared(value):
  152. """Dereference a shared_ptr."""
  153. return value.GetChildMemberWithName('__ptr_').Dereference()
  154. def __lldb_init_module(debugger, params):
  155. def run(command):
  156. debugger.HandleCommand(command)
  157. def add_summary(provider, typename, *args):
  158. args = ' '.join(args)
  159. run('type summary add -w firestore -F {0} {1} {2}'.format(
  160. qname(provider), args, typename))
  161. def add_synthetic(provider, typename, *args):
  162. args = ' '.join(args)
  163. run('type synthetic add -l {0} -w firestore {1} {2}'.format(
  164. qname(provider), args, typename))
  165. optional_matcher = '-x absl::[^:]*::optional<.*>'
  166. add_summary(AbseilOptional_SummaryProvider, optional_matcher, '-e')
  167. add_synthetic(AbseilOptional_SynthProvider, optional_matcher)
  168. api = 'firebase::firestore::api::'
  169. add_summary(DocumentReference_SummaryProvider, api + 'DocumentReference')
  170. add_summary(DocumentSnapshot_SummaryProvider, api + 'DocumentSnapshot', '-e')
  171. model = 'firebase::firestore::model::'
  172. add_summary(DocumentKey_SummaryProvider, model + 'DocumentKey')
  173. add_summary(ResourcePath_SummaryProvider, model + 'ResourcePath')
  174. add_summary(DatabaseId_SummaryProvider, model + 'DatabaseId')
  175. add_synthetic(DatabaseId_SynthProvider, model + 'DatabaseId')
  176. add_summary(FIRDocumentReference_SummaryProvider, 'FIRDocumentReference')
  177. add_summary(FIRDocumentSnapshot_SummaryProvider, 'FIRDocumentSnapshot', '-e')
  178. run('type category enable firestore')
  179. def qname(fn):
  180. """Returns the module-qualified name of the given class or function."""
  181. return '{0}.{1}'.format(__name__, fn.__name__)