docs: def _guess_data_type(self, data_addr, max_size, content_holder=None):
          """
          Make a guess to the data type.
  
          Users can provide their own data type guessing code when initializing CFGFast instance, and each guessing
          handler will be called if this method fails to determine what the data is.
  
          :param int data_addr: Address of the data.
          :param int max_size: The maximum size this data entry can be.
          :return: a tuple of (data type, size). (None, None) if we fail to determine the type or the size.
          :rtype: tuple
          """
  
          try:
              ref = next(iter(self.model.references.data_addr_to_ref[data_addr]))  # type: CodeReference
              irsb_addr = ref.block_addr
              stmt_idx = ref.stmt_idx
          except StopIteration:
              irsb_addr, stmt_idx = None, None
  
          if max_size is None:
              max_size = 0
  
          if self._seg_list.is_occupied(data_addr) and self._seg_list.occupied_by_sort(data_addr) == 'code':
              # it's a code reference
              # TODO: Further check if it's the beginning of an instruction
              return MemoryDataSort.CodeReference, 0
  
          pointer_size = self.project.arch.bytes
  
          # who's using it?
          if isinstance(self.project.loader.main_object, cle.MetaELF):
              plt_entry = self.project.loader.main_object.reverse_plt.get(irsb_addr, None)
              if plt_entry is not None:
                  # IRSB is owned by plt!
                  return MemoryDataSort.GOTPLTEntry, pointer_size
  
          # is it in a section with zero bytes, like .bss?
          obj = self.project.loader.find_object_containing(data_addr)
          section = obj.find_section_containing(data_addr)
          if section is not None and section.only_contains_uninitialized_data:
              # Nothing much you can do
              return None, None
  
          pointers_count = 0
  
          max_pointer_array_size = min(512 * pointer_size, max_size)
          for i in range(0, max_pointer_array_size, pointer_size):
              ptr = self._fast_memory_load_pointer(data_addr + i)
  
              if ptr is not None:
                  #if self._seg_list.is_occupied(ptr) and self._seg_list.occupied_by_sort(ptr) == 'code':
                  #    # it's a code reference
                  #    # TODO: Further check if it's the beginning of an instruction
                  #    pass
                  if self.project.loader.find_section_containing(ptr) is not None or \
                          self.project.loader.find_segment_containing(ptr) is not None or \
                          (self._extra_memory_regions and
                           next(((a < ptr < b) for (a, b) in self._extra_memory_regions), None)
                           ):
                      # it's a pointer of some sort
                      # TODO: Determine what sort of pointer it is
                      pointers_count += 1
                  else:
                      break
  
          if pointers_count:
              return MemoryDataSort.PointerArray, pointer_size * pointers_count
  
          try:
              data = self.project.loader.memory.load(data_addr, 1024)
          except KeyError:
              data = b''
  
          # Is it an unicode string?
          # TODO: Support unicode string longer than the max length
          if len(data) >= 4 and data[1] == 0 and data[3] == 0 and data[0] in self.PRINTABLES:
              def can_decode(n):
                  try:
                      data[:n*2].decode('utf_16_le')
                  except UnicodeDecodeError:
                      return False
                  return True
              if can_decode(4) or can_decode(5) or can_decode(6):
                  running_failures = 0
                  last_success = 4
                  for i in range(4, len(data) // 2):
                      if can_decode(i):
                          last_success = i
                          running_failures = 0
                          if data[i*2-2] == 0 and data[i*2-1] == 0:
                              break
                      else:
                          running_failures += 1
                          if running_failures > 3:
                              break
  
                  return MemoryDataSort.UnicodeString, last_success
  
          if data:
              try:
                  zero_pos = data.index(0)
              except ValueError:
                  zero_pos = None
              if (zero_pos is not None and zero_pos > 0 and all(c in self.PRINTABLES for c in data[:zero_pos])) or \
                      all(c in self.PRINTABLES for c in data):
                  # it's a string
                  # however, it may not be terminated
                  string_data = data if zero_pos is None else data[:zero_pos]
                  if content_holder is not None:
                      content_holder.append(string_data)
                  return MemoryDataSort.String, min(len(string_data) + 1, 1024)
  
          for handler in self._data_type_guessing_handlers:
              irsb = None if irsb_addr is None else self.model.get_any_node(irsb_addr).block.vex
              sort, size = handler(self, irsb, irsb_addr, stmt_idx, data_addr, max_size)
              if sort is not None:
                  return sort, size
  
          return None, None