docs: def _explore(self):
          """
          Starting from the start_node, explore the entire VFG, and perform the following:
          - Generate def-use chains for all registers and memory addresses using a worklist
          """
  
          # TODO: The worklist algorithm can definitely use some optimizations. It is a future work.
  
          # The worklist holds individual VFGNodes that comes from the VFG
          # Initialize the worklist with all nodes in VFG
          worklist = list(self._vfg.graph.nodes())
          # Set up a set of worklist for fast inclusion test
          worklist_set = set(worklist)
  
          # A dict storing defs set
          # variable -> locations
          live_defs_per_node = { }
  
          while worklist:
              # Pop out a node
              node = worklist[0]
              worklist_set.remove(node)
              worklist = worklist[ 1 : ]
  
              # Grab all final states. There are usually more than one (one state for each successor), and we gotta
              # process all of them
              final_states = node.final_states
  
              if node in live_defs_per_node:
                  live_defs = live_defs_per_node[node]
              else:
                  live_defs = { }
                  live_defs_per_node[node] = live_defs
  
              successing_nodes = self._vfg.graph.successors(node)
              for state in final_states:
                  if state.history.jumpkind == 'Ijk_FakeRet' and len(final_states) > 1:
                      # Skip fakerets if there are other control flow transitions available
                      continue
  
                  # TODO: Match the jumpkind
                  # TODO: Support cases where IP is undecidable
                  corresponding_successors = [ n for n in successing_nodes if n.addr == state.solver.eval(state.ip) ]
                  if not corresponding_successors:
                      continue
                  successing_node = corresponding_successors[0]
  
                  new_defs = self._track(state, live_defs)
  
                  if successing_node in live_defs_per_node:
                      defs_for_next_node = live_defs_per_node[successing_node]
                  else:
                      defs_for_next_node = { }
                      live_defs_per_node[successing_node] = defs_for_next_node
  
                  changed = False
                  for var, code_loc_set in new_defs.items():
                      if var not in defs_for_next_node:
                          defs_for_next_node[var] = code_loc_set
                          changed = True
  
                      else:
                          for code_loc in code_loc_set:
                              if code_loc not in defs_for_next_node[var]:
                                  defs_for_next_node[var].add(code_loc)
                                  changed = True
  
                  if changed:
                      # Put all reachable successors back to our worklist again
                      if successing_node not in worklist_set:
                          worklist.append(successing_node)
                          worklist_set.add(successing_node)
                      all_successors_dict = networkx.dfs_successors(self._vfg.graph, source=successing_node)
                      for successors in all_successors_dict.values():
                          for s in successors:
                              if s not in worklist_set:
                                  worklist.append(s)
                                  worklist_set.add(s)