(* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open! IStd module F = Format type field = Fieldname.t * Typ.t * Annot.Item.t [@@deriving compare, equal] type fields = field list [@@deriving equal] type java_class_kind = Interface | AbstractClass | NormalClass [@@deriving equal, compare] let pp_java_class_kind fmt kind = F.pp_print_string fmt ( match kind with | Interface -> "Interface" | AbstractClass -> "AbstractClass" | NormalClass -> "NormalClass" ) type java_class_info = { kind: java_class_kind (** class kind in Java *) ; loc: Location.t option (** None should correspond to rare cases when it was impossible to fetch the location in source file *) } [@@deriving equal] let pp_java_class_info fmt {kind; loc} = F.fprintf fmt "{kind= %a; loc= %a}" pp_java_class_kind kind (Pp.option Location.pp) loc let pp_java_class_info_opt fmt jopt = Pp.option pp_java_class_info fmt jopt (** Type for a structured value. *) type t = { fields: fields (** non-static fields *) ; statics: fields (** static fields *) ; supers: Typ.Name.t list (** superclasses *) ; objc_protocols: Typ.Name.t list (** ObjC protocols *) ; methods: Procname.t list (** methods defined *) ; exported_objc_methods: Procname.t list (** methods in ObjC interface, subset of [methods] *) ; annots: Annot.Item.t (** annotations *) ; java_class_info: java_class_info option (** present if and only if the class is Java *) ; dummy: bool (** dummy struct for class including static method *) } [@@deriving equal] type lookup = Typ.Name.t -> t option let pp_field pe f (field_name, typ, ann) = F.fprintf f "@\n\t\t%a %a %a" (Typ.pp_full pe) typ Fieldname.pp field_name Annot.Item.pp ann let pp pe name f ({ fields ; statics ; supers ; objc_protocols ; methods ; exported_objc_methods ; annots ; java_class_info ; dummy }[@warning "+9"]) = let pp_field pe f (field_name, typ, ann) = F.fprintf f "@;<0 2>%a %a %a" (Typ.pp_full pe) typ Fieldname.pp field_name Annot.Item.pp ann in let seq pp fmt = function | [] -> () | lst -> Pp.seq pp fmt lst ; F.pp_print_break fmt 0 0 in F.fprintf f "%a@,\ @[fields: {@[%a@]}@,\ statics: {@[%a@]}@,\ supers: {@[%a@]}@,\ objc_protocols: {@[%a@]}@,\ methods: {@[%a@]}@,\ exported_obj_methods: {@[%a@]}@,\ annots: {@[%a@]}@,\ java_class_info: {@[%a@]}@,\ dummy: %b@]@," Typ.Name.pp name (seq (pp_field pe)) fields (seq (pp_field pe)) statics (seq (fun f n -> F.fprintf f "@;<0 2>%a" Typ.Name.pp n)) supers (seq (fun f n -> F.fprintf f "@;<0 2>%a" Typ.Name.pp n)) objc_protocols (seq (fun f m -> F.fprintf f "@;<0 2>%a" Procname.pp m)) methods (seq (fun f m -> F.fprintf f "@;<0 2>%a" Procname.pp m)) exported_objc_methods Annot.Item.pp annots pp_java_class_info_opt java_class_info dummy let internal_mk_struct ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?objc_protocols ?annots ?java_class_info ?dummy () = let default_ = { fields= [] ; statics= [] ; methods= [] ; exported_objc_methods= [] ; supers= [] ; objc_protocols= [] ; annots= Annot.Item.empty ; java_class_info= None ; dummy= false } in let mk_struct_ ?(default = default_) ?(fields = default.fields) ?(statics = default.statics) ?(methods = default.methods) ?(exported_objc_methods = default.exported_objc_methods) ?(supers = default.supers) ?(objc_protocols = default.objc_protocols) ?(annots = default.annots) ?(dummy = default.dummy) () = { fields ; statics ; methods ; exported_objc_methods ; supers ; objc_protocols ; annots ; java_class_info ; dummy } in mk_struct_ ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?objc_protocols ?annots ?dummy () (** the element typ of the final extensible array in the given typ, if any *) let rec get_extensible_array_element_typ ~lookup (typ : Typ.t) = match typ.desc with | Tarray {elt} -> Some elt | Tstruct name -> ( match lookup name with | Some {fields} -> ( match List.last fields with | Some (_, fld_typ, _) -> get_extensible_array_element_typ ~lookup fld_typ | None -> None ) | None -> None ) | _ -> None (** If a struct type with field f, return the type of f. If not, return the default *) let fld_typ ~lookup ~default fn (typ : Typ.t) = (* Note: would be nice migrate it to get_field_info (for that one needs to ensure adding Tptr to pattern match does not break thing) *) match typ.desc with | Tstruct name -> ( match lookup name with | Some {fields} -> List.find ~f:(fun (f, _, _) -> Fieldname.equal f fn) fields |> Option.value_map ~f:snd3 ~default | None -> default ) | _ -> default type field_info = {typ: Typ.t; annotations: Annot.Item.t; is_static: bool} let find_field field_list field_name_to_lookup = List.find_map ~f:(fun (field_name, typ, annotations) -> if Fieldname.equal field_name field_name_to_lookup then Some (typ, annotations) else None ) field_list let get_field_info ~lookup field_name_to_lookup (typ : Typ.t) = let find_field_info field_list ~is_static = find_field field_list field_name_to_lookup |> Option.map ~f:(fun (typ, annotations) -> {typ; annotations; is_static}) in match typ.desc with | Tstruct name | Tptr ({desc= Tstruct name}, _) -> ( match lookup name with | Some {fields= non_statics; statics} -> (* Search in both lists and return the first found *) find_field_info statics ~is_static:true |> IOption.if_none_evalopt ~f:(fun () -> find_field_info non_statics ~is_static:false) | None -> None ) | _ -> None let get_field_type_and_annotation ~lookup field_name_to_lookup typ = get_field_info ~lookup field_name_to_lookup typ |> Option.map ~f:(fun {typ; annotations} -> (typ, annotations)) let is_dummy {dummy} = dummy let merge_lists ~compare ~newer ~current = let equal x y = Int.equal 0 (compare x y) in match (newer, current) with | [], _ -> current | _, [] -> newer | _, _ when List.equal equal newer current -> newer | _, _ -> List.dedup_and_sort ~compare (newer @ current) let merge_fields ~newer ~current = merge_lists ~compare:compare_field ~newer ~current let merge_supers ~newer ~current = merge_lists ~compare:Typ.Name.compare ~newer ~current let merge_methods ~newer ~current = merge_lists ~compare:Procname.compare ~newer ~current let merge_annots ~newer ~current = merge_lists ~compare:[%compare: Annot.t * bool] ~newer ~current let merge_kind ~newer ~current = (* choose the maximal, ie most concrete *) if compare_java_class_kind newer current < 0 then current else newer (* choose [Some] option if possible, [newer] if both [None] else [merge] *) let merge_opt ~merge ~newer ~current = match (newer, current) with | _, None -> newer | None, _ -> current | Some newer, Some current -> Some (merge ~newer ~current) let merge_loc ~newer ~current = if Location.equal Location.dummy newer then current else if Location.equal Location.dummy current then newer else if (* arbitrarily but deterministically choose one *) Location.compare newer current <= 0 then newer else current let merge_loc_opt ~newer ~current = merge_opt ~merge:merge_loc ~newer ~current let merge_java_class_info ~newer ~current = { kind= merge_kind ~newer:newer.kind ~current:current.kind ; loc= merge_loc_opt ~newer:newer.loc ~current:current.loc } let merge_java_class_info_opt ~newer ~current = merge_opt ~merge:merge_java_class_info ~newer ~current let full_merge ~newer ~current = let fields = merge_fields ~newer:newer.fields ~current:current.fields in let statics = merge_fields ~newer:newer.statics ~current:current.statics in let supers = merge_supers ~newer:newer.supers ~current:current.supers in (* the semantics of [subs] is such that no merging is attempted *) let methods = merge_methods ~newer:newer.methods ~current:current.methods in (* we are merging only Java classes, so [exported_obj_methods] should be empty, so no merge *) let annots = merge_annots ~newer:newer.annots ~current:current.annots in let java_class_info = merge_java_class_info_opt ~newer:newer.java_class_info ~current:current.java_class_info in {newer with fields; statics; supers; methods; annots; java_class_info} let merge typename ~newer ~current = match (typename : Typ.Name.t) with | CStruct _ | CUnion _ | ObjcClass _ | ObjcProtocol _ | CppClass _ -> if not (is_dummy newer) then newer else current | JavaClass _ when is_dummy newer -> current | JavaClass _ when is_dummy current -> newer | JavaClass _ when equal newer current -> newer | JavaClass _ -> full_merge ~newer ~current let is_not_java_interface = function | {java_class_info= Some {kind= Interface}} -> false | _ -> true