docs: def _generate_cfgnode(self, cfg_job, current_function_addr):
          """
          Generate a CFGNode that starts at `cfg_job.addr`.
  
          Since lifting machine code to IRSBs is slow, self._nodes is used as a cache of CFGNodes.
  
          If the current architecture is ARM, this method will try to lift the block in the mode specified by the address
          (determined by the parity of the address: even for ARM, odd for THUMB), and in case of decoding failures, try
          the other mode. If the basic block is successfully decoded in the other mode (different from the initial one),
           `addr` and `current_function_addr` are updated.
  
          :param CFGJob cfg_job: The CFGJob instance.
          :param int current_function_addr: Address of the current function.
          :return: A 4-tuple of (new address, new function address, CFGNode instance, IRSB object)
          :rtype: tuple
          """
  
          addr = cfg_job.addr
  
          try:
  
              if addr in self._nodes:
                  cfg_node = self._nodes[addr]
                  irsb = cfg_node.irsb
  
                  if cfg_node.function_address != current_function_addr:
                      # the node has been assigned to another function before.
                      # we should update the function address.
                      current_function_addr = cfg_node.function_address
  
                  return addr, current_function_addr, cfg_node, irsb
  
              is_x86_x64_arch = self.project.arch.name in ('X86', 'AMD64')
  
              if is_arm_arch(self.project.arch):
                  real_addr = addr & (~1)
              else:
                  real_addr = addr
  
              # if possible, check the distance between `addr` and the end of this section
              distance = VEX_IRSB_MAX_SIZE
              obj = self.project.loader.find_object_containing(addr, membership_check=False)
              if obj:
                  # is there a section?
                  has_executable_section = len([ sec for sec in obj.sections if sec.is_executable ]) > 0  # pylint:disable=len-as-condition
                  section = self.project.loader.find_section_containing(addr)
                  if has_executable_section and section is None:
                      # the basic block should not exist here...
                      return None, None, None, None
                  if section is not None:
                      if not section.is_executable:
                          # the section is not executable...
                          return None, None, None, None
                      distance = section.vaddr + section.memsize - real_addr
                      distance = min(distance, VEX_IRSB_MAX_SIZE)
                  # TODO: handle segment information as well
  
              # also check the distance between `addr` and the closest function.
              # we don't want to have a basic block that spans across function boundaries
              next_func = self.functions.ceiling_func(addr + 1)
              if next_func is not None:
                  distance_to_func = (next_func.addr & (~1) if is_arm_arch(self.project.arch) else next_func.addr) - real_addr
                  if distance_to_func != 0:
                      if distance is None:
                          distance = distance_to_func
                      else:
                          distance = min(distance, distance_to_func)
  
              # in the end, check the distance between `addr` and the closest occupied region in segment list
              next_noncode_addr = self._seg_list.next_pos_with_sort_not_in(addr, { "code" }, max_distance=distance)
              if next_noncode_addr is not None:
                  distance_to_noncode_addr = next_noncode_addr - addr
                  distance = min(distance, distance_to_noncode_addr)
  
              # Let's try to create the pyvex IRSB directly, since it's much faster
              nodecode = False
              irsb = None
              irsb_string = None
              try:
                  lifted_block = self._lift(addr, size=distance, opt_level=self._iropt_level, collect_data_refs=True)
                  irsb = lifted_block.vex_nostmt
                  irsb_string = lifted_block.bytes[:irsb.size]
              except SimTranslationError:
                  nodecode = True
  
              if (nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode') and \
                      is_arm_arch(self.project.arch) and \
                      self._arch_options.switch_mode_on_nodecode:
                  # maybe the current mode is wrong?
                  nodecode = False
                  if addr % 2 == 0:
                      addr_0 = addr + 1
                  else:
                      addr_0 = addr - 1
  
                  if addr_0 in self._nodes:
                      # it has been analyzed before
                      cfg_node = self._nodes[addr_0]
                      irsb = cfg_node.irsb
                      return addr_0, cfg_node.function_address, cfg_node, irsb
  
                  try:
                      lifted_block = self._lift(addr_0, size=distance, opt_level=self._iropt_level,
                                                collect_data_refs=True)
                      irsb = lifted_block.vex_nostmt
                      irsb_string = lifted_block.bytes[:irsb.size]
                  except SimTranslationError:
                      nodecode = True
  
                  if not (nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode'):
                      # it is decodeable
                      if current_function_addr == addr:
                          current_function_addr = addr_0
                      addr = addr_0
  
              if nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode':
                  # decoding error
                  # we still occupy that location since it cannot be decoded anyways
                  if irsb is None:
                      irsb_size = 0
                  else:
                      irsb_size = irsb.size
                  # special handling for ud, ud1, and ud2 on x86 and x86-64
                  if is_x86_x64_arch \
                          and len(irsb_string) >= 2 \
                          and irsb_string[-2:] in {
                              b'\x0f\xff',  # ud0
                              b'\x0f\xb9',  # ud1
                              b'\x0f\x0b',  # ud2
                          }:
                      # ud0, ud1, and ud2 are actually valid instructions.
                      valid_ins = True
                      nodecode_size = 2
                  else:
                      valid_ins = False
                      nodecode_size = 1
                  self._seg_list.occupy(addr, irsb_size, 'code')
                  self._seg_list.occupy(addr + irsb_size, nodecode_size, 'nodecode')
                  if not valid_ins:
                      l.error("Decoding error occurred at address %#x of function %#x.",
                              addr + irsb_size,
                              current_function_addr
                              )
                      return None, None, None, None
  
              is_thumb = False
              # Occupy the block in segment list
              if irsb.size > 0:
                  if is_arm_arch(self.project.arch) and addr % 2 == 1:
                      # thumb mode
                      is_thumb=True
                  self._seg_list.occupy(real_addr, irsb.size, "code")
  
              # Create a CFG node, and add it to the graph
              cfg_node = CFGNode(addr, irsb.size, self.model,
                                 function_address=current_function_addr,
                                 block_id=addr,
                                 irsb=irsb,
                                 thumb=is_thumb,
                                 byte_string=irsb_string,
                                 )
              if self._cfb is not None:
                  self._cfb.add_obj(addr, lifted_block)
  
              self._nodes[addr] = cfg_node
              self._nodes_by_addr[addr].append(cfg_node)
  
              return addr, current_function_addr, cfg_node, irsb
  
          except (SimMemoryError, SimEngineError):
              return None, None, None, None