source/interprocedural/classInterval.ml (140 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
open ClassHierarchyGraph
include Interval.Int
let lower_bound_exn interval = Option.value_exn (Interval.Int.lbound interval)
let upper_bound_exn interval = Option.value_exn (Interval.Int.ubound interval)
let meet left right = Interval.Int.intersect left right
let join left right = Interval.Int.convex_hull [left; right]
let equal left right =
Interval.Int.is_subset left ~of_:right && Interval.Int.is_subset right ~of_:left
let is_empty = Interval.Int.is_empty
let bottom = empty
let top = create min_int max_int
let is_top interval =
(not (is_empty interval)) && lbound_exn interval == min_int && ubound_exn interval == max_int
let less_or_equal ~left ~right = Interval.Int.is_subset left ~of_:right
let pp formatter interval =
let pp_interval formatter interval =
if Interval.Int.is_empty interval then
Format.fprintf formatter ""
else
Format.fprintf
formatter
"%d,%d"
(Option.value_exn (Interval.Int.lbound interval))
(Option.value_exn (Interval.Int.ubound interval))
in
Format.fprintf formatter "@[[%a]@]" pp_interval interval
let show = Format.asprintf "%a" pp
type dfs_state =
| Grey
| Black
let compute_intervals class_hierarchy =
let roots = ClassHierarchyGraph.roots class_hierarchy in
let add_direct_cross_edge ~from_ ~to_ cross_edges =
let update = function
| Some nodes -> Some (ClassNameSet.add to_ nodes)
| None -> Some (ClassNameSet.singleton to_)
in
ClassNameMap.update from_ update cross_edges
in
let add_indirect_cross_edge ~from_ ~to_ cross_edges =
match ClassNameMap.find_opt to_ cross_edges with
| None -> cross_edges
| Some nodes ->
let update = function
| Some original_nodes -> Some (ClassNameSet.union nodes original_nodes)
| None -> Some nodes
in
ClassNameMap.update from_ update cross_edges
in
(* To compute cross edges, the key observation is that, during the DFS, if a node is black, then
* its associated cross edges are final (i.e., we cannot discover any new indirect or direct edges
* that originate from that node) *)
let rec depth_first_search node (intervals, states, cross_edges, time) =
let time = time + 1 in
let start = time in
let states = ClassNameMap.add node Grey states in
let intervals, states, cross_edges, time =
let visit_child child ((intervals, states, cross_edges, time) as accumulator) =
match ClassNameMap.find_opt child states with
| None ->
let intervals, states, cross_edges, time = depth_first_search child accumulator in
(* Now the child is black *)
let cross_edges = add_indirect_cross_edge ~from_:node ~to_:child cross_edges in
intervals, states, cross_edges, time
| Some Grey ->
failwith
(Format.asprintf "Found a back edge from %s to %s in the class hierarchy" node child)
| Some Black ->
let cross_edges =
cross_edges
|> add_direct_cross_edge ~from_:node ~to_:child
(* All cross edges of a black node are indirect cross edges *)
|> add_indirect_cross_edge ~from_:node ~to_:child
in
intervals, states, cross_edges, time
in
ClassNameSet.fold
visit_child
(children class_hierarchy node)
(intervals, states, cross_edges, time)
in
let time = time + 1 in
let finish = time in
let intervals = ClassNameMap.add node (create start finish) intervals in
intervals, ClassNameMap.add node Black states, cross_edges, time
in
let intervals, _, cross_edges, _ =
ClassNameSet.fold
depth_first_search
roots
(ClassNameMap.empty, ClassNameMap.empty, ClassNameMap.empty, 0)
in
let join_intervals_from nodes =
ClassNameSet.fold
(fun node accumulator ->
match ClassNameMap.find_opt node intervals with
| None -> failwith (Format.asprintf "Node %s should have an interval" node)
| Some interval -> join interval accumulator)
nodes
Interval.Int.empty
in
let add_interval_with_cross_edges node interval accumulator =
let interval =
match ClassNameMap.find_opt node cross_edges with
| None -> interval
| Some nodes -> join interval (join_intervals_from nodes)
in
ClassNameMap.add node interval accumulator
in
ClassNameMap.fold add_interval_with_cross_edges intervals ClassNameMap.empty
module SharedMemory = struct
include
Memory.WithCache.Make
(Analysis.SharedMemoryKeys.StringKey)
(struct
type t = Interval.Int.t
let prefix = Prefix.make ()
let description = "class intervals of classes"
let unmarshall value = Marshal.from_string value 0
end)
let add ~class_name ~interval = add class_name interval
let get ~class_name = get class_name
let store intervals =
ClassNameMap.iter (fun class_name interval -> add ~class_name ~interval) intervals
let of_class class_name = get ~class_name |> Option.value ~default:top
let of_type = function
| Some (Type.Primitive class_name) -> of_class class_name
| _ -> top
let of_definition definition =
match Target.create definition |> Target.class_name with
| Some class_name -> of_class class_name
| None -> top
end