docs: def _process_irrational_functions(self, functions, predetermined_function_addrs, blockaddr_to_function):
          """
          For unresolveable indirect jumps, angr marks those jump targets as individual functions. For example, usually
          the following pattern is seen:
  
          sub_0x400010:
              push ebp
              mov esp, ebp
              ...
              cmp eax, 10
              ja end
              mov eax, jumptable[eax]
              jmp eax
  
          sub_0x400080:
              # do something here
              jmp end
  
          end (0x400e00):
              pop ebp
              ret
  
          In the example above, `process_irrational_functions` will remove function 0x400080, and merge it with function
          0x400010.
  
          :param angr.knowledge_plugins.FunctionManager functions: all functions that angr recovers, including those ones
              that are misidentified as functions.
          :param dict blockaddr_to_function: A mapping between block addresses and Function instances.
          :return: A set of addresses of all removed functions
          :rtype: set
          """
  
          functions_to_remove = { }
  
          functions_can_be_removed = set(functions.keys()) - set(predetermined_function_addrs)
  
          for func_addr, function in functions.items():
  
              if func_addr in functions_to_remove:
                  continue
  
              # check all blocks and see if any block ends with an indirect jump and is not resolved
              has_unresolved_jumps = False
              # the functions to merge with must be locating between the unresolved basic block address and the endpoint
              # of the current function
              max_unresolved_jump_addr = 0
              for block_addr in function.block_addrs_set:
                  if block_addr in self.indirect_jumps and \
                          self.indirect_jumps[block_addr].jumpkind == 'Ijk_Boring' and \
                          not self.indirect_jumps[block_addr].resolved_targets:
                      # it's not resolved
                      # we should also make sure it's a jump, not a call
                      has_unresolved_jumps = True
                      max_unresolved_jump_addr = max(max_unresolved_jump_addr, block_addr)
  
              if not has_unresolved_jumps:
                  continue
  
              if function.startpoint is None:
                  continue
  
              startpoint_addr = function.startpoint.addr
              if not function.endpoints:
                  # Function should have at least one endpoint
                  continue
              endpoint_addr = max([ a.addr for a in function.endpoints ])
              the_endpoint = next(a for a in function.endpoints if a.addr == endpoint_addr)
              endpoint_addr += the_endpoint.size
  
              # sanity check: startpoint of the function should be greater than its endpoint
              if startpoint_addr >= endpoint_addr:
                  continue
              if max_unresolved_jump_addr <= startpoint_addr or max_unresolved_jump_addr >= endpoint_addr:
                  continue
  
              # scan forward from the endpoint to include any function tail jumps
              # Here is an example:
              # loc_8049562:
              #       mov eax, ebp
              #       add esp, 3ch
              #       ...
              #       ret
              # loc_804956c:
              #       mov ebp, 3
              #       jmp loc_8049562
              # loc_8049573:
              #       mov ebp, 4
              #       jmp loc_8049562
              #
              last_addr = endpoint_addr
              tmp_state = self.project.factory.blank_state(mode='fastpath')
              while True:
                  try:
                      # using successors is slow, but acceptable since we won't be creating millions of blocks here...
                      tmp_state.ip = last_addr
                      b = self.project.factory.successors(tmp_state, jumpkind='Ijk_Boring')
                      if len(b.successors) != 1:
                          break
                      if b.successors[0].history.jumpkind not in ('Ijk_Boring', 'Ijk_InvalICache'):
                          break
                      if b.successors[0].ip.symbolic:
                          break
                      suc_addr = b.successors[0].ip._model_concrete
                      if max(startpoint_addr, the_endpoint.addr - 0x40) <= suc_addr < the_endpoint.addr + the_endpoint.size:
                          # increment the endpoint_addr
                          endpoint_addr = b.addr + b.artifacts['irsb_size']
                      else:
                          break
  
                      last_addr = b.addr + b.artifacts['irsb_size']
  
                  except (SimTranslationError, SimMemoryError, SimIRSBError, SimEngineError):
                      break
  
              # find all functions that are between [ startpoint, endpoint ]
  
              should_merge = True
              functions_to_merge = set()
              for f_addr in functions_can_be_removed:
                  f = functions[f_addr]
                  if f_addr == func_addr:
                      continue
                  if max_unresolved_jump_addr < f_addr < endpoint_addr and \
                          all([max_unresolved_jump_addr < b_addr < endpoint_addr for b_addr in f.block_addrs]):
                      if f_addr in functions_to_remove:
                          # this function has already been merged with other functions before... it cannot be merged with
                          # this function anymore
                          should_merge = False
                          break
                      if f_addr in predetermined_function_addrs:
                          # this function is a legit one. it shouldn't be removed/merged
                          should_merge = False
                          break
                      functions_to_merge.add(f_addr)
  
              if not should_merge:
                  # we shouldn't merge...
                  continue
  
              for f_addr in functions_to_merge:
                  functions_to_remove[f_addr] = func_addr
  
          # merge all functions
          for to_remove, merge_with in functions_to_remove.items():
              func_merge_with = self._addr_to_function(merge_with, blockaddr_to_function, functions)
  
              for block_addr in functions[to_remove].block_addrs:
                  blockaddr_to_function[block_addr] = func_merge_with
  
              del functions[to_remove]
  
          return set(functions_to_remove.keys())