You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
5.4 KiB

(*
* 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 Hashtbl = Caml.Hashtbl
module L = Logging
(** Module for Type Environments. *)
(** Hash tables on strings. *)
module TypenameHash = Hashtbl.Make (struct
type t = Typ.Name.t
let equal tn1 tn2 = Typ.Name.equal tn1 tn2
let hash = Hashtbl.hash
end)
(** Type for type environment. *)
type t = Typ.Struct.t TypenameHash.t
let pp fmt (tenv : t) =
TypenameHash.iter
(fun name typ ->
Format.fprintf fmt "@[<6>NAME: %s@]@," (Typ.Name.to_string name) ;
Format.fprintf fmt "@[<6>TYPE: %a@]@," (Typ.Struct.pp Pp.text name) typ )
tenv
(** Create a new type environment. *)
let create () = TypenameHash.create 1000
(** Construct a struct type in a type environment *)
let mk_struct tenv ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots name =
let struct_typ =
Typ.Struct.internal_mk_struct ?default ?fields ?statics ?methods ?exported_objc_methods ?supers
?annots ()
in
TypenameHash.replace tenv name struct_typ ;
struct_typ
(** Look up a name in the global type environment. *)
let lookup tenv name : Typ.Struct.t option =
try Some (TypenameHash.find tenv name)
with Caml.Not_found -> (
(* ToDo: remove the following additional lookups once C/C++ interop is resolved *)
match (name : Typ.Name.t) with
| CStruct m -> (
try Some (TypenameHash.find tenv (CppClass (m, NoTemplate))) with Caml.Not_found -> None )
| CppClass (m, NoTemplate) -> (
try Some (TypenameHash.find tenv (CStruct m)) with Caml.Not_found -> None )
| _ ->
None )
let compare_fields (name1, _, _) (name2, _, _) = Typ.Fieldname.compare name1 name2
let equal_fields f1 f2 = Int.equal (compare_fields f1 f2) 0
(** Add a field to a given struct in the global type environment. *)
let add_field tenv class_tn_name field =
match lookup tenv class_tn_name with
| Some ({fields} as struct_typ) ->
if not (List.mem ~equal:equal_fields fields field) then
let new_fields = List.merge [field] fields ~compare:compare_fields in
ignore (mk_struct tenv ~default:struct_typ ~fields:new_fields ~statics:[] class_tn_name)
| _ ->
()
type per_file = Global | FileLocal of t
let pp_per_file fmt = function
| Global ->
Format.fprintf fmt "Global"
| FileLocal tenv ->
Format.fprintf fmt "FileLocal @[<v>%a@]" pp tenv
module SQLite : SqliteUtils.Data with type t = per_file = struct
type t = per_file
let global_string = "global"
let serialize = function
| Global ->
Sqlite3.Data.TEXT global_string
| FileLocal tenv ->
Sqlite3.Data.BLOB (Marshal.to_string tenv [])
let deserialize = function[@warning "-8"]
| Sqlite3.Data.TEXT g when String.equal g global_string ->
Global
| Sqlite3.Data.BLOB b ->
FileLocal (Marshal.from_string b 0)
end
let merge ~src ~dst = TypenameHash.iter (fun pname cfg -> TypenameHash.replace dst pname cfg) src
let merge_per_file ~src ~dst =
match (src, dst) with
| Global, Global ->
Global
| FileLocal src_tenv, FileLocal dst_tenv ->
merge ~src:src_tenv ~dst:dst_tenv ; FileLocal dst_tenv
| Global, FileLocal _ | FileLocal _, Global ->
L.die InternalError "Cannot merge Global tenv with FileLocal tenv"
let load_statement =
ResultsDatabase.register_statement
"SELECT type_environment FROM source_files WHERE source_file = :k"
(** Serializer for type environments *)
let tenv_serializer : t Serialization.serializer =
Serialization.create_serializer Serialization.Key.tenv
let global_tenv : t option ref = ref None
let global_tenv_path = Config.(results_dir ^/ global_tenv_filename) |> DB.filename_from_string
let read path = Serialization.read_from_file tenv_serializer path
let load_global () : t option =
if is_none !global_tenv then global_tenv := read global_tenv_path ;
!global_tenv
let load source =
ResultsDatabase.with_registered_statement load_statement ~f:(fun db load_stmt ->
SourceFile.SQLite.serialize source
|> Sqlite3.bind load_stmt 1
|> SqliteUtils.check_result_code db ~log:"load bind source file" ;
SqliteUtils.result_single_column_option ~finalize:false ~log:"Tenv.load" db load_stmt
|> Option.bind ~f:(fun x ->
SQLite.deserialize x
|> function Global -> load_global () | FileLocal tenv -> Some tenv ) )
let store_debug_file tenv tenv_filename =
let debug_filename = DB.filename_to_string (DB.filename_add_suffix tenv_filename ".debug") in
let out_channel = Out_channel.create debug_filename in
let fmt = Format.formatter_of_out_channel out_channel in
pp fmt tenv ; Out_channel.close out_channel
let store_debug_file_for_source source_file tenv =
let tenv_filename_of_source_file =
DB.source_dir_get_internal_file (DB.source_dir_from_source_file source_file) ".tenv"
in
store_debug_file tenv tenv_filename_of_source_file
let store_to_filename tenv tenv_filename =
Serialization.write_to_file tenv_serializer tenv_filename ~data:tenv ;
if Config.debug_mode then store_debug_file tenv tenv_filename
let store_global tenv =
(* update in-memory global tenv for later uses by this process, e.g. in single-core mode the
frontend and backend run in the same process *)
global_tenv := Some tenv ;
store_to_filename tenv global_tenv_path