Helper for populating reachable issue instances from the initial
        pre/postconditions. Also populates conditions/traces reachable from
        these instances. Traces are populated only in the direction this is
        called from: i.e. if initial_conditions are preconditions, only the
        backward trace is populated.

        Params:
        initial_conditions: The initial collection of pre/postconditions to
        start searching for issues from.

        graph: The trace graph to search for issues. Nodes/edges in this graph
        will be copied over to the local state

        get_issue_instances_from_condition_id: Function that returns all
        issue instances associated with a given a pre/postcondition id.

        get_condition_parent: Function that returns the parent condition of
        a given pre/postcondition. Given a pre/postcondition, p, its parent p',
        is the pre/postcondition that calls it, i.e. p.caller = p'.callee

        get_instance_leaves: Function that returns a collection of leaf names
        associated with the given issue instance ID.

        get_condition_leaves: Function that returns a collection of leaf names
        associated with the given (pre/post)condition ID.

        add_instance_condition_assoc: Function that takes in the issue
        instance and condition and adds the assoc between them.

        add_condition: Function that adds a condition to the graph given its
        id. This must add all the condition's assocs with the leaves because
        we don't filter out any condition-leaf assocs.

        add_traces: Function that takes a list of initial conditions and
        adds all conditions reachable from these to the graph.

def _populate_issues_from_affected_conditions(
        self,
        initial_conditions,
        graph: TraceGraph,
        get_issue_instances_from_condition_id,
        get_condition_parent,
        get_instance_leaves,
        get_condition_leaves,
        add_instance_condition_assoc,
        add_condition,
        add_traces,
    ) -> None:
        """Helper for populating reachable issue instances from the initial
        pre/postconditions. Also populates conditions/traces reachable from
        these instances. Traces are populated only in the direction this is
        called from: i.e. if initial_conditions are preconditions, only the
        backward trace is populated.

        Params:
        initial_conditions: The initial collection of pre/postconditions to
        start searching for issues from.

        graph: The trace graph to search for issues. Nodes/edges in this graph
        will be copied over to the local state

        get_issue_instances_from_condition_id: Function that returns all
        issue instances associated with a given a pre/postcondition id.

        get_condition_parent: Function that returns the parent condition of
        a given pre/postcondition. Given a pre/postcondition, p, its parent p',
        is the pre/postcondition that calls it, i.e. p.caller = p'.callee

        get_instance_leaves: Function that returns a collection of leaf names
        associated with the given issue instance ID.

        get_condition_leaves: Function that returns a collection of leaf names
        associated with the given (pre/post)condition ID.

        add_instance_condition_assoc: Function that takes in the issue
        instance and condition and adds the assoc between them.

        add_condition: Function that adds a condition to the graph given its
        id. This must add all the condition's assocs with the leaves because
        we don't filter out any condition-leaf assocs.

        add_traces: Function that takes a list of initial conditions and
        adds all conditions reachable from these to the graph.
        """
        visited: Set[int] = set()
        que = list(zip(initial_conditions, initial_conditions))

        while len(que) > 0:
            condition, initial_condition = que.pop()
            cond_id = condition.id.local_id
            if cond_id in visited:
                continue
            visited.add(cond_id)

            # Found instance(s) related to the current condition. Yay.
            # This instance may have been found before, but process it again
            # anyway because we need to add the assoc with this condition.
            for instance_id in get_issue_instances_from_condition_id(cond_id):
                # Check if the leaves (sources/sinks) of the issue reach
                # the same leaves as the initial conditions. The issue is
                # relevant only if the conditions intersect.
                instance = graph._issue_instances[instance_id]
                issue_leaves = set(get_instance_leaves(instance.id.local_id))
                initial_leaves = set(
                    get_condition_leaves(initial_condition.id.local_id)
                )
                leaves = issue_leaves.intersection(initial_leaves)
                if len(leaves) > 0:
                    if instance_id not in self._issue_instances:
                        self._populate_issue(graph, instance_id)
                    add_instance_condition_assoc(instance, condition)

            # Conditions that call this may have originated from other issues,
            # keep searching for parent conditions leading to this one.
            que.extend(
                [
                    (cond, initial_condition)
                    for cond in get_condition_parent(condition)
                    if cond.id.local_id not in visited
                ]
            )

        # Add traces leading out from initial_conditions, and all visited
        # conditions leading back towards the issues.
        initial_condition_ids = [
            condition.id.local_id for condition in initial_conditions
        ]
        add_traces(initial_condition_ids)
        for condition_id in visited:
            add_condition(condition_id)