Summary:
When someone runs --changed-only mode, there is a risk of corrupting the results
for future analyses. The problem is that changed-only mode does not analyze the callers of changed
procedures. If a subsequent analysis relies on the specs of one of these callers, they will be stale
and may give the wrong results. To be concrete, let's say we know `Parent.foo()` calls `Child.bar()` and we do the following rounds of analysis:
Analysis round 1: Analyze all files, including `Parent` and `Child`
Analysis round 2: Analyze `Child.bar()` only with `--changed-only flag`. `Parent.foo()` is now stale.
Analysis round 3: Add procedure `Parent.baz()` that calls `Parent.foo()`, analyze in (any) incremental mode.
The analysis will only analyze `Parent.baz()`. However, the specs for `Parent.foo()` are stale and may give us bad results for `Parent.baz()`. We want the analysis to re-analyze `Parent.baz()`, but before this diff it will not.
This diff fixes this problem by adding a `STALE` status bit to procedure summaries. In `--changed-only` mode,
the callers of a changed procedures are not re-analyzed, but their summaries are marked as stale. For both
`--changed-only` and regular incremental mode, callees of changed procedures that are marked as stale are
re-analyzed even if they have not changed. This is better than a more obvious solution like deleting stale
procedure summaries, since that would force the next analysis to re-analyze all stale procedures even if it
does not need the results for whatever analysis it is doing. This scheme implemented in this diff ensures
that each analysis only does the work that it needs to compute reliable results for its changed procedures.
@ -33,6 +33,7 @@ val create : unit -> t (** Create an empty call graph *)
valextend:t->t->unit(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *)
valextend:t->t->unit(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *)
valget_all_children:t->Procname.t->Procname.Set.t(** Return all the children of [n], whether defined or not *)
valget_all_children:t->Procname.t->Procname.Set.t(** Return all the children of [n], whether defined or not *)
valget_ancestors:t->Procname.t->Procname.Set.t(** Compute the ancestors of the node, if not pre-computed already *)
valget_ancestors:t->Procname.t->Procname.Set.t(** Compute the ancestors of the node, if not pre-computed already *)
valget_heirs:t->Procname.t->Procname.Set.t(** Compute the heirs of the node, if not pre-computed already *)
valget_calls:t->Procname.t->in_out_calls(** Return the in/out calls of the node *)
valget_calls:t->Procname.t->in_out_calls(** Return the in/out calls of the node *)
valget_defined_nodes:t->Procname.tlist(** Return the list of nodes which are defined *)
valget_defined_nodes:t->Procname.tlist(** Return the list of nodes which are defined *)
valget_defined_children:t->Procname.t->Procname.Set.t(** Return the children of [n] which are defined *)
valget_defined_children:t->Procname.t->Procname.Set.t(** Return the children of [n] which are defined *)