diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f0f2f31e..386bfc61c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,28 +138,7 @@ module MF = MarkupFormatter - Use the `_hum` suffix to flag functions that output human-readable strings. -- Follow the ocp-indent style in infer/.ocpindent for indentation. - -- Spaces around binary operators, e.g., `let x = a + 5`. - -- Spaces around parentheses, no space inside parentheses. - -- Spaces around braces and brackets, spaces inside braces and brackets. - -- Space after `,` and `;`, e.g. `let (a, b) = ({ foo = 4; }, ())`. - -- Terminate multi-line values such as lists and records with `;` so that it's easy to add new lines - without modifying existing ones. For instance: -```OCaml -let foo = [ - value1; - value2; -] -``` - -### Reason - -Follow `refmt`. +- Format code with ocamlformat. ### C/C++/Objective-C diff --git a/Makefile b/Makefile index a3c8300e2..15587c4b0 100644 --- a/Makefile +++ b/Makefile @@ -113,27 +113,15 @@ fb-setup: OCAMLFORMAT_EXE=facebook/dependencies/ocamlformat/src/_build/opt/ocamlformat.exe -SRC_ML:=$(shell find * \( -name _build -or -name facebook-clang-plugins -or -path facebook/dependencies \) -not -prune -or -type f -and -name '*'.ml -or -name '*'.mli 2>/dev/null) - -.PHONY: fmt_all_ml -fmt_all_ml: - parallel $(OCAMLFORMAT_EXE) --no-warn-error -i -- $(SRC_ML) - -SRC_RE:=$(shell find infer/src -name '*'.re 2>/dev/null) -SRC_REI:=$(shell find infer/src -name '*'.rei 2>/dev/null) -SRC_RE_ML:=$(patsubst %.re,%.ml,$(SRC_RE)) -SRC_REI_MLI:=$(patsubst %.rei,%.mli,$(SRC_REI)) +.PHONY: fmt +fmt: + parallel $(OCAMLFORMAT_EXE) -i -- $$(git diff --name-only $$(git merge-base origin/master HEAD) | grep "\.mli\?$$") -%.ml: %.re - refmt --print=binary_reason $< | $(OCAMLFORMAT_EXE) $< --reason-impl -o $@ - -%.mli: %.rei - refmt --print=binary_reason $< | $(OCAMLFORMAT_EXE) $< --reason-intf -o $@ +SRC_ML:=$(shell find * \( -name _build -or -name facebook-clang-plugins -or -path facebook/dependencies \) -not -prune -or -type f -and -name '*'.ml -or -name '*'.mli 2>/dev/null) .PHONY: fmt_all -fmt_all: fmt_all_ml $(SRC_RE_ML) $(SRC_REI_MLI) - git add $(SRC_RE_ML) $(SRC_REI_MLI) - rm -f $(SRC_RE) $(SRC_REI) +fmt_all: + parallel $(OCAMLFORMAT_EXE) -i -- $(SRC_ML) .PHONY: src_build src_build: @@ -321,7 +309,7 @@ endif .PHONY: check_missing_mli check_missing_mli: - $(QUIET)for x in $$(find $(INFER_DIR)/src -name "*.ml" -or -name "*.re"); do \ + $(QUIET)for x in $$(find $(INFER_DIR)/src -name "*.ml"); do \ test -f "$$x"i || echo Missing "$$x"i; done .PHONY: toplevel diff --git a/Makefile.autoconf.in b/Makefile.autoconf.in index 9de7c2676..33b474566 100644 --- a/Makefile.autoconf.in +++ b/Makefile.autoconf.in @@ -52,6 +52,7 @@ MKDIR_P = $(shell $(MKDIR_P_CMD)) MVN = @MVN@ NCPU = @NCPU@ NDKBUILD = @NDKBUILD@ +OCAMLBUILD = @OCAMLBUILD@ OCAMLC = @OCAMLC@ OCAMLFIND = @OCAMLFIND@ OCAMLLEX = @OCAMLLEX@ @@ -61,7 +62,6 @@ OPAMSWITCH = @OPAMSWITCH@ PATH = @PATH@ prefix = @prefix@ PYTHON_lxml = @PYTHON_lxml@ -REBUILD = @REBUILD@ SHASUM = @SHASUM@ USER_JAVA_HOME = @USER_JAVA_HOME@ XCODE_SELECT = @XCODE_SELECT@ diff --git a/configure.ac b/configure.ac index cfc0c37a1..aef502f36 100644 --- a/configure.ac +++ b/configure.ac @@ -175,6 +175,7 @@ AC_ASSERT_PROG([ocamlc], [$OCAMLC]) # check the version of OCaml AC_ASSERT_OCAML_MIN_VERSION([4.04.2]) AC_ASSERT_PROG([ocamlopt], [$OCAMLOPT]) +AC_PATH_TOOL([OCAMLBUILD], [ocamlbuild], [no]) AC_ASSERT_PROG([ocamlbuild], [$OCAMLBUILD]) AC_PROG_FINDLIB AC_PROG_OCAMLLEX @@ -186,12 +187,7 @@ AC_ASSERT_OCAML_PKG([biniou]) AC_ASSERT_OCAML_PKG([camlzip], [zip]) AC_ASSERT_OCAML_PKG([easy-format]) AC_ASSERT_OCAML_PKG([oUnit], [], [2.0.0]) -AC_ASSERT_OCAML_PKG([reason]) AC_ASSERT_OCAML_PKG([yojson]) -# check for rebuild after checking for Reason since `rebuild` is normally provided by Reason. This -# way if Reason is missing we get a more helpful error message and not "rebuild not found". -AC_PATH_TOOL([REBUILD], [rebuild], [no]) -AC_ASSERT_PROG([rebuild], [$REBUILD]) AC_ARG_VAR([CAML_LD_LIBRARY_PATH], [Additional directories to search for dynamically-loaded libraries.]) diff --git a/dependencies/ocamldot/ocamldot.mll b/dependencies/ocamldot/ocamldot.mll index 9883a9280..524e2829f 100644 --- a/dependencies/ocamldot/ocamldot.mll +++ b/dependencies/ocamldot/ocamldot.mll @@ -320,7 +320,7 @@ let dir_to_mod_names graph dir = fold_dir (fun dir_to_mod_names path -> let file = Filename.basename path in let mod_name = String.capitalize (try Filename.chop_extension file with _ -> file) in - if ((Filename.check_suffix file ".ml" || Filename.check_suffix file ".re") + if ((Filename.check_suffix file ".ml") && StringSet.mem mod_name nodes) then let dir = Filename.dirname path in diff --git a/infer/src/IR/Annot.ml b/infer/src/IR/Annot.ml new file mode 100644 index 000000000..171148f53 --- /dev/null +++ b/infer/src/IR/Annot.ml @@ -0,0 +1,89 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Annotations *) +open! IStd +module L = Logging +module F = Format + +type parameters = string list [@@deriving compare] + +(** Type to represent one @Annotation. *) +type t = + { class_name: string (** name of the annotation *) + ; parameters: parameters (** currently only one string parameter *) } + [@@deriving compare] + +let volatile = {class_name= "volatile"; parameters= []} + +(** Pretty print an annotation. *) +let prefix = match Config.curr_language_is Config.Java with true -> "@" | false -> "_" + +let pp fmt annotation = F.fprintf fmt "%s%s" prefix annotation.class_name + +module Map = PrettyPrintable.MakePPMap (struct + type nonrec t = t + + let compare = compare + + let pp = pp +end) + +module Item = struct + (* Don't use nonrec due to https://github.com/janestreet/ppx_compare/issues/2 *) + (* type nonrec t = list (t, bool) [@@deriving compare]; *) + (** Annotation for one item: a list of annotations with visibility. *) + type _t = (t * bool) list [@@deriving compare] + + type t = _t [@@deriving compare] + + let equal = [%compare.equal : t] + + (** Pretty print an item annotation. *) + let pp fmt ann = + let pp fmt (a, _) = pp fmt a in + F.fprintf fmt "<%a>" (Pp.seq pp) ann + + let to_string ann = + let pp fmt = pp fmt ann in + F.asprintf "%t" pp + + (** Empty item annotation. *) + let empty = [] + + (** Check if the item annodation is empty. *) + let is_empty ia = List.is_empty ia +end + +module Class = struct + let objc_str = "ObjC-Class" + + let cpp_str = "Cpp-Class" + + let of_string class_string = [({class_name= class_string; parameters= []}, true)] + + let objc = of_string objc_str + + let cpp = of_string cpp_str +end + +module Method = struct + (** Annotation for a method: return value and list of parameters. *) + type t = Item.t * Item.t list [@@deriving compare] + + (** Pretty print a method annotation. *) + let pp s fmt (ia, ial) = F.fprintf fmt "%a %s(%a)" Item.pp ia s (Pp.seq Item.pp) ial + + (** Empty method annotation. *) + let empty = ([], []) + + (** Check if the method annodation is empty. *) + let is_empty (ia, ial) = List.for_all ~f:Item.is_empty (ia :: ial) +end diff --git a/infer/src/IR/Annot.mli b/infer/src/IR/Annot.mli new file mode 100644 index 000000000..0b6b3d87d --- /dev/null +++ b/infer/src/IR/Annot.mli @@ -0,0 +1,78 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Annotations *) +open! IStd +module F = Format + +type parameters = string list + +(** Type to represent one @Annotation. *) + +type t = + { class_name: string (** name of the annotation *) + ; parameters: parameters (** currently only one string parameter *) } + [@@deriving compare] + +(** annotation for fields/methods marked with the "volatile" keyword *) + +val volatile : t + +(** Pretty print an annotation. *) + +val pp : F.formatter -> t -> unit + +module Map : PrettyPrintable.PPMap with type key = t + +module Item : sig + (** Annotation for one item: a list of annotations with visibility. *) + + type nonrec t = (t * bool) list [@@deriving compare] + + val equal : t -> t -> bool + + (** Pretty print an item annotation. *) + + val pp : F.formatter -> t -> unit + + val to_string : t -> string + + (** Empty item annotation. *) + + val empty : t + + (** Check if the item annodation is empty. *) + + val is_empty : t -> bool +end + +module Class : sig + val objc : Item.t + + val cpp : Item.t +end + +module Method : sig + (** Annotation for a method: return value and list of parameters. *) + + type t = Item.t * Item.t list [@@deriving compare] + + (** Empty method annotation. *) + + val empty : t + + (** Check if the method annodation is empty. *) + + val is_empty : t -> bool + + (** Pretty print a method annotation. *) + + val pp : string -> F.formatter -> t -> unit +end diff --git a/infer/src/IR/Annot.re b/infer/src/IR/Annot.re deleted file mode 100644 index 6e3623be2..000000000 --- a/infer/src/IR/Annot.re +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Annotations */ -module L = Logging; - -module F = Format; - -type parameters = list string [@@deriving compare]; - - -/** Type to represent one @Annotation. */ -type t = { - class_name: string, /** name of the annotation */ - parameters /** currently only one string parameter */ -} -[@@deriving compare]; - -let volatile = {class_name: "volatile", parameters: []}; - - -/** Pretty print an annotation. */ -let prefix = Config.curr_language_is Config.Java ? "@" : "_"; - -let pp fmt annotation => F.fprintf fmt "%s%s" prefix annotation.class_name; - -module Map = - PrettyPrintable.MakePPMap { - type nonrec t = t; - let compare = compare; - let pp = pp; - }; - -module Item = { - - /** Annotation for one item: a list of annotations with visibility. */ - /* Don't use nonrec due to https://github.com/janestreet/ppx_compare/issues/2 */ - /* type nonrec t = list (t, bool) [@@deriving compare]; */ - type _t = list (t, bool) [@@deriving compare]; - type t = _t [@@deriving compare]; - let equal = [%compare.equal : t]; - - /** Pretty print an item annotation. */ - let pp fmt ann => { - let pp fmt (a, _) => pp fmt a; - F.fprintf fmt "<%a>" (Pp.seq pp) ann - }; - let to_string ann => { - let pp fmt => pp fmt ann; - F.asprintf "%t" pp - }; - - /** Empty item annotation. */ - let empty = []; - - /** Check if the item annodation is empty. */ - let is_empty ia => List.is_empty ia; -}; - -module Class = { - let objc_str = "ObjC-Class"; - let cpp_str = "Cpp-Class"; - let of_string class_string => [({class_name: class_string, parameters: []}, true)]; - let objc = of_string objc_str; - let cpp = of_string cpp_str; -}; - -module Method = { - - /** Annotation for a method: return value and list of parameters. */ - type t = (Item.t, list Item.t) [@@deriving compare]; - - /** Pretty print a method annotation. */ - let pp s fmt (ia, ial) => F.fprintf fmt "%a %s(%a)" Item.pp ia s (Pp.seq Item.pp) ial; - - /** Empty method annotation. */ - let empty = ([], []); - - /** Check if the method annodation is empty. */ - let is_empty (ia, ial) => List.for_all f::Item.is_empty [ia, ...ial]; -}; diff --git a/infer/src/IR/Annot.rei b/infer/src/IR/Annot.rei deleted file mode 100644 index 6c911a2e7..000000000 --- a/infer/src/IR/Annot.rei +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Annotations */ -module F = Format; - -type parameters = list string; - - -/** Type to represent one @Annotation. */ -type t = { - class_name: string, /** name of the annotation */ - parameters /** currently only one string parameter */ -} -[@@deriving compare]; - - -/** annotation for fields/methods marked with the "volatile" keyword */ -let volatile: t; - - -/** Pretty print an annotation. */ -let pp: F.formatter => t => unit; - -module Map: PrettyPrintable.PPMap with type key = t; - -module Item: { - - /** Annotation for one item: a list of annotations with visibility. */ - type nonrec t = list (t, bool) [@@deriving compare]; - let equal: t => t => bool; - - /** Pretty print an item annotation. */ - let pp: F.formatter => t => unit; - let to_string: t => string; - - /** Empty item annotation. */ - let empty: t; - - /** Check if the item annodation is empty. */ - let is_empty: t => bool; -}; - -module Class: {let objc: Item.t; let cpp: Item.t;}; - -module Method: { - - /** Annotation for a method: return value and list of parameters. */ - type t = (Item.t, list Item.t) [@@deriving compare]; - - /** Empty method annotation. */ - let empty: t; - - /** Check if the method annodation is empty. */ - let is_empty: t => bool; - - /** Pretty print a method annotation. */ - let pp: string => F.formatter => t => unit; -}; diff --git a/infer/src/IR/AttributesTable.ml b/infer/src/IR/AttributesTable.ml new file mode 100644 index 000000000..12043142d --- /dev/null +++ b/infer/src/IR/AttributesTable.ml @@ -0,0 +1,233 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +open! PVariant +module Hashtbl = Caml.Hashtbl +module F = Format +module L = Logging + +type attr_kind = ProcDefined | ProcObjCAccessor | ProcUndefined + +(** Module to manage the table of attributes. *) +let serializer : ProcAttributes.t Serialization.serializer = + Serialization.create_serializer Serialization.Key.attributes + +let attributes_filename ~proc_kind pname_file = + let file_suffix = + match proc_kind with + | ProcDefined + -> ".attr" + | ProcObjCAccessor + -> ".objc_acc.attr" + | ProcUndefined + -> ".decl.attr" + in + pname_file ^ file_suffix + +(** path to the .attr file for the given procedure in the current results directory *) +let res_dir_attr_filename ~create_dir ~proc_kind pname = + let pname_file = Typ.Procname.to_filename pname in + let attr_fname = attributes_filename ~proc_kind pname_file in + let bucket_dir = + let base = pname_file in + let len = String.length base in + if len < 2 then Filename.current_dir_name else String.sub base ~pos:(len - 2) ~len:2 + in + let filename = + DB.Results_dir.path_to_filename DB.Results_dir.Abs_root + [Config.attributes_dir_name; bucket_dir; attr_fname] + in + if create_dir then DB.filename_create_dir filename ; + filename + +(* Load the proc attribute for the defined filename if it exists, + otherwise try to load the declared filename. *) +let load_attr ~defined_only proc_name = + let attributes_file ~proc_kind proc_name = + Multilinks.resolve (res_dir_attr_filename ~create_dir:false ~proc_kind proc_name) + in + let attr = + Serialization.read_from_file serializer (attributes_file ~proc_kind:ProcDefined proc_name) + in + if is_none attr && not defined_only then + (* We try to load the objc accesor one if they exist, if not then we load the undefined one *) + let attr = + Serialization.read_from_file serializer + (attributes_file ~proc_kind:ProcObjCAccessor proc_name) + in + match attr with + | Some attr + -> Some attr + | None + -> Serialization.read_from_file serializer + (attributes_file ~proc_kind:ProcUndefined proc_name) + else attr + +let create_proc_kind (proc_attributes: ProcAttributes.t) = + if proc_attributes.is_defined then ProcDefined + else if Option.is_some proc_attributes.objc_accessor then ProcObjCAccessor + else ProcUndefined + +let less_relevant_proc_kinds proc_kind = + match proc_kind with + | ProcDefined + -> [ProcObjCAccessor; ProcUndefined] + | ProcObjCAccessor + -> [ProcUndefined] + | ProcUndefined + -> [] + +(* Write a proc attributes to file. + If defined, delete the declared file if it exists. *) +let write_and_delete proc_name (proc_attributes: ProcAttributes.t) = + let proc_kind = create_proc_kind proc_attributes in + let attributes_file proc_kind = res_dir_attr_filename ~create_dir:true ~proc_kind proc_name in + Serialization.write_to_file serializer (attributes_file proc_kind) ~data:proc_attributes ; + let upgrade_relevance less_relevant_proc_kind = + let fname_declared = DB.filename_to_string (attributes_file less_relevant_proc_kind) in + if Sys.file_exists fname_declared = `Yes then + try Unix.unlink fname_declared + with Unix.Unix_error _ -> () + in + List.iter ~f:upgrade_relevance (less_relevant_proc_kinds proc_kind) + +(* This creates an ordering in the attribute files: 1.defined, 2.objc accessor, 3.else. + To be used to figure out if we should override an existing attribute file with a new + one, if relevant information will be updated, or lost. + If the relevance is not upgraded, choose based on whether its associated file has higher + rank (alphabetically) than the other. *) +let should_override_attr (new_attr: ProcAttributes.t) (old_attr: ProcAttributes.t) = + if new_attr.is_defined then + if old_attr.is_defined then SourceFile.compare new_attr.loc.file old_attr.loc.file > 0 + else true (* new becomes defined, override *) + else if old_attr.is_defined then false (* old was defined, new isn't, don't override *) + else if Option.is_some new_attr.objc_accessor then + if Option.is_some old_attr.objc_accessor then + SourceFile.compare new_attr.loc.file old_attr.loc.file > 0 + else true (* new becomes objc accessor, override *) + else false + +(* new isn't defined or objc accessor, don't overide *) + +let store_attributes (proc_attributes: ProcAttributes.t) = + let proc_name = proc_attributes.proc_name in + let should_write = + match load_attr ~defined_only:false proc_name with + | None + -> true + | Some proc_attributes_on_disk + -> should_override_attr proc_attributes proc_attributes_on_disk + in + if should_write then write_and_delete proc_name proc_attributes + +let attr_tbl = Typ.Procname.Hash.create 16 + +let defined_attr_tbl = Typ.Procname.Hash.create 16 + +let load_attributes ~cache proc_name = + try Typ.Procname.Hash.find attr_tbl proc_name + with Not_found -> + let proc_attributes = load_attr ~defined_only:false proc_name in + ( match proc_attributes with + | Some attrs + -> if cache then ( + Typ.Procname.Hash.add attr_tbl proc_name proc_attributes ; + if attrs.is_defined then Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes ) + | None + -> () ) ; + proc_attributes + +let load_defined_attributes ~cache_none proc_name = + try Typ.Procname.Hash.find defined_attr_tbl proc_name + with Not_found -> + let proc_attributes = load_attr ~defined_only:true proc_name in + if proc_attributes <> None then ( + (* procedure just got defined, replace attribute in attr_tbl with defined version *) + Typ.Procname.Hash.replace attr_tbl proc_name proc_attributes ; + Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes ) + else if cache_none then Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes ; + proc_attributes + +(** Given the name of an ObjC class, extract the type from the tenv where the class was defined. We + do this by adding a method that is unique to each class, and then finding the tenv that + corresponds to the class definition. *) +let get_correct_type_from_objc_class_name type_name = + (* ToDo: this function should return a type that includes a reference to the tenv computed by: + let class_method = Typ.Procname.get_default_objc_class_method (Typ.Name.name type_name); + switch (find_tenv_from_class_of_proc class_method) { + | Some tenv => + *) + Some (Typ.mk (Tstruct type_name)) + +type t = + {num_bindings: int; num_buckets: int; max_bucket_length: int; serialized_size_kb: int option} + +let to_json at = + let extra_field = + match at.serialized_size_kb with Some v -> [("serialized_size_kb", `Int v)] | None -> [] + in + `Assoc + ( [ ("num_bindings", `Int at.num_bindings) + ; ("num_buckets", `Int at.num_buckets) + ; ("max_bucket_length", `Int at.max_bucket_length) ] + @ extra_field ) + +let from_json json = + let open! Yojson.Basic.Util in + { num_bindings= json |> member "num_bindings" |> to_int + ; num_buckets= json |> member "num_buckets" |> to_int + ; max_bucket_length= json |> member "max_bucket_length" |> to_int + ; serialized_size_kb= json |> member "serialized_size_kb" |> to_option to_int } + +let aggregate s = + let all_num_bindings = List.map ~f:(fun stats -> float_of_int stats.num_bindings) s in + let all_num_buckets = List.map ~f:(fun stats -> float_of_int stats.num_buckets) s in + let all_max_bucket_length = List.map ~f:(fun stats -> float_of_int stats.max_bucket_length) s in + let aggr_num_bindings = StatisticsToolbox.compute_statistics all_num_bindings in + let aggr_num_buckets = StatisticsToolbox.compute_statistics all_num_buckets in + let aggr_max_bucket_length = StatisticsToolbox.compute_statistics all_max_bucket_length in + `Assoc + [ ("num_bindings", StatisticsToolbox.to_json aggr_num_bindings) + ; ("num_buckets", StatisticsToolbox.to_json aggr_num_buckets) + ; ("max_bucket_length", StatisticsToolbox.to_json aggr_max_bucket_length) ] + +let stats () = + let stats = Typ.Procname.Hash.stats attr_tbl in + let {Hashtbl.num_bindings; num_buckets; max_bucket_length} = stats in + let serialized_size_kb = + match Config.developer_mode with + | true + -> Some (Marshal.data_size (Marshal.to_bytes attr_tbl []) 0 / 1024) + | false + -> None + in + {num_bindings; num_buckets; max_bucket_length; serialized_size_kb} + +(* Find the file where the procedure was captured, if a cfg for that file exists. + Return also a boolean indicating whether the procedure is defined in an + include file. *) +let find_file_capturing_procedure ?(cache= true) pname = + match load_attributes ~cache pname with + | None + -> None + | Some proc_attributes + -> let source_file = proc_attributes.ProcAttributes.source_file_captured in + let source_dir = DB.source_dir_from_source_file source_file in + let origin = + (* Procedure coming from include files if it has different location + than the file where it was captured. *) + match SourceFile.compare source_file proc_attributes.ProcAttributes.loc.file <> 0 with + | true + -> `Include + | false + -> `Source + in + let cfg_fname = DB.source_dir_get_internal_file source_dir ".cfg" in + let cfg_fname_exists = Sys.file_exists (DB.filename_to_string cfg_fname) = `Yes in + if cfg_fname_exists then Some (source_file, origin) else None diff --git a/infer/src/IR/AttributesTable.mli b/infer/src/IR/AttributesTable.mli new file mode 100644 index 000000000..de3ef6534 --- /dev/null +++ b/infer/src/IR/AttributesTable.mli @@ -0,0 +1,49 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Module to manage the table of attributes. *) + +open! IStd + +(** Save .attr file for the procedure into the attributes database. *) + +val store_attributes : ProcAttributes.t -> unit + +(** Load the attributes for the procedure from the attributes database. + If cache is true, add the attribute to the global cache *) + +val load_attributes : cache:bool -> Typ.Procname.t -> ProcAttributes.t option + +(** Load attrubutes for the procedure but only if is_defined is true *) + +val load_defined_attributes : cache_none:bool -> Typ.Procname.t -> ProcAttributes.t option + +(** Given the name of an ObjC class, extract the type from the tenv where the class was defined. We + do this by adding a method that is unique to each class, and then finding the tenv that + corresponds to the class definition. *) + +val get_correct_type_from_objc_class_name : Typ.Name.t -> Typ.t option + +(* Find the file where the procedure was captured, if a cfg for that file exists. + Return also a boolean indicating whether the procedure is defined in an + include file. + If cache is true, add the attribute to the global cache *) + +val find_file_capturing_procedure : + ?cache:bool -> Typ.Procname.t -> (SourceFile.t * [`Include | `Source]) option + +type t + +val stats : unit -> t + +val to_json : t -> Yojson.Basic.json + +val from_json : Yojson.Basic.json -> t + +val aggregate : t list -> Yojson.Basic.json diff --git a/infer/src/IR/AttributesTable.re b/infer/src/IR/AttributesTable.re deleted file mode 100644 index f6d14b6dc..000000000 --- a/infer/src/IR/AttributesTable.re +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -open! PVariant; - -module Hashtbl = Caml.Hashtbl; - -module F = Format; - -module L = Logging; - -type attr_kind = - | ProcDefined - | ProcObjCAccessor - | ProcUndefined; - - -/** Module to manage the table of attributes. */ -let serializer: Serialization.serializer ProcAttributes.t = - Serialization.create_serializer Serialization.Key.attributes; - -let attributes_filename ::proc_kind pname_file => { - let file_suffix = - switch proc_kind { - | ProcDefined => ".attr" - | ProcObjCAccessor => ".objc_acc.attr" - | ProcUndefined => ".decl.attr" - }; - pname_file ^ file_suffix -}; - - -/** path to the .attr file for the given procedure in the current results directory */ -let res_dir_attr_filename ::create_dir ::proc_kind pname => { - let pname_file = Typ.Procname.to_filename pname; - let attr_fname = attributes_filename ::proc_kind pname_file; - let bucket_dir = { - let base = pname_file; - let len = String.length base; - if (len < 2) { - Filename.current_dir_name - } else { - String.sub base pos::(len - 2) len::2 - } - }; - let filename = - DB.Results_dir.path_to_filename - DB.Results_dir.Abs_root [Config.attributes_dir_name, bucket_dir, attr_fname]; - if create_dir { - DB.filename_create_dir filename - }; - filename -}; - -/* Load the proc attribute for the defined filename if it exists, - otherwise try to load the declared filename. */ -let load_attr ::defined_only proc_name => { - let attributes_file ::proc_kind proc_name => - Multilinks.resolve (res_dir_attr_filename create_dir::false ::proc_kind proc_name); - let attr = - Serialization.read_from_file serializer (attributes_file proc_kind::ProcDefined proc_name); - if (is_none attr && not defined_only) { - /* We try to load the objc accesor one if they exist, if not then we load the undefined one */ - let attr = - Serialization.read_from_file - serializer (attributes_file proc_kind::ProcObjCAccessor proc_name); - switch attr { - | Some attr => Some attr - | None => - Serialization.read_from_file serializer (attributes_file proc_kind::ProcUndefined proc_name) - } - } else { - attr - } -}; - -let create_proc_kind (proc_attributes: ProcAttributes.t) => - if proc_attributes.is_defined { - ProcDefined - } else if ( - Option.is_some proc_attributes.objc_accessor - ) { - ProcObjCAccessor - } else { - ProcUndefined - }; - -let less_relevant_proc_kinds proc_kind => - switch proc_kind { - | ProcDefined => [ProcObjCAccessor, ProcUndefined] - | ProcObjCAccessor => [ProcUndefined] - | ProcUndefined => [] - }; - -/* Write a proc attributes to file. - If defined, delete the declared file if it exists. */ -let write_and_delete proc_name (proc_attributes: ProcAttributes.t) => { - let proc_kind = create_proc_kind proc_attributes; - let attributes_file proc_kind => res_dir_attr_filename create_dir::true ::proc_kind proc_name; - Serialization.write_to_file serializer (attributes_file proc_kind) data::proc_attributes; - let upgrade_relevance less_relevant_proc_kind => { - let fname_declared = DB.filename_to_string (attributes_file less_relevant_proc_kind); - if (Sys.file_exists fname_declared == `Yes) { - try (Unix.unlink fname_declared) { - | Unix.Unix_error _ => () - } - } - }; - List.iter f::upgrade_relevance (less_relevant_proc_kinds proc_kind) -}; - -/* This creates an ordering in the attribute files: 1.defined, 2.objc accessor, 3.else. - To be used to figure out if we should override an existing attribute file with a new - one, if relevant information will be updated, or lost. - If the relevance is not upgraded, choose based on whether its associated file has higher - rank (alphabetically) than the other. */ -let should_override_attr (new_attr: ProcAttributes.t) (old_attr: ProcAttributes.t) => - if new_attr.is_defined { - if old_attr.is_defined { - SourceFile.compare new_attr.loc.file old_attr.loc.file > 0 - } else { - true /* new becomes defined, override */ - } - } else if - old_attr.is_defined { - false /* old was defined, new isn't, don't override */ - } else if ( - Option.is_some new_attr.objc_accessor - ) { - if (Option.is_some old_attr.objc_accessor) { - SourceFile.compare new_attr.loc.file old_attr.loc.file > 0 - } else { - true /* new becomes objc accessor, override */ - } - } else { - false /* new isn't defined or objc accessor, don't overide */ - }; - -let store_attributes (proc_attributes: ProcAttributes.t) => { - let proc_name = proc_attributes.proc_name; - let should_write = - switch (load_attr defined_only::false proc_name) { - | None => true - | Some proc_attributes_on_disk => should_override_attr proc_attributes proc_attributes_on_disk - }; - if should_write { - write_and_delete proc_name proc_attributes - } -}; - -let attr_tbl = Typ.Procname.Hash.create 16; - -let defined_attr_tbl = Typ.Procname.Hash.create 16; - -let load_attributes ::cache proc_name => - try (Typ.Procname.Hash.find attr_tbl proc_name) { - | Not_found => - let proc_attributes = load_attr defined_only::false proc_name; - switch proc_attributes { - | Some attrs => - if cache { - Typ.Procname.Hash.add attr_tbl proc_name proc_attributes; - if attrs.is_defined { - Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes - } - } - | None => () - }; - proc_attributes - }; - -let load_defined_attributes ::cache_none proc_name => - try (Typ.Procname.Hash.find defined_attr_tbl proc_name) { - | Not_found => - let proc_attributes = load_attr defined_only::true proc_name; - if (proc_attributes != None) { - /* procedure just got defined, replace attribute in attr_tbl with defined version */ - Typ.Procname.Hash.replace attr_tbl proc_name proc_attributes; - Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes - } else if cache_none { - Typ.Procname.Hash.add defined_attr_tbl proc_name proc_attributes - }; - proc_attributes - }; - - -/** Given the name of an ObjC class, extract the type from the tenv where the class was defined. We - do this by adding a method that is unique to each class, and then finding the tenv that - corresponds to the class definition. */ -let get_correct_type_from_objc_class_name type_name => - /* ToDo: this function should return a type that includes a reference to the tenv computed by: - let class_method = Typ.Procname.get_default_objc_class_method (Typ.Name.name type_name); - switch (find_tenv_from_class_of_proc class_method) { - | Some tenv => - */ - Some (Typ.mk (Tstruct type_name)); - -type t = { - num_bindings: int, - num_buckets: int, - max_bucket_length: int, - serialized_size_kb: option int -}; - -let to_json at => { - let extra_field = - switch at.serialized_size_kb { - | Some v => [("serialized_size_kb", `Int v)] - | None => [] - }; - `Assoc ( - [ - ("num_bindings", `Int at.num_bindings), - ("num_buckets", `Int at.num_buckets), - ("max_bucket_length", `Int at.max_bucket_length) - ] @ extra_field - ) -}; - -let from_json json => { - open! Yojson.Basic.Util; - { - num_bindings: json |> member "num_bindings" |> to_int, - num_buckets: json |> member "num_buckets" |> to_int, - max_bucket_length: json |> member "max_bucket_length" |> to_int, - serialized_size_kb: json |> member "serialized_size_kb" |> to_option to_int - } -}; - -let aggregate s => { - let all_num_bindings = List.map f::(fun stats => float_of_int stats.num_bindings) s; - let all_num_buckets = List.map f::(fun stats => float_of_int stats.num_buckets) s; - let all_max_bucket_length = List.map f::(fun stats => float_of_int stats.max_bucket_length) s; - let aggr_num_bindings = StatisticsToolbox.compute_statistics all_num_bindings; - let aggr_num_buckets = StatisticsToolbox.compute_statistics all_num_buckets; - let aggr_max_bucket_length = StatisticsToolbox.compute_statistics all_max_bucket_length; - `Assoc [ - ("num_bindings", StatisticsToolbox.to_json aggr_num_bindings), - ("num_buckets", StatisticsToolbox.to_json aggr_num_buckets), - ("max_bucket_length", StatisticsToolbox.to_json aggr_max_bucket_length) - ] -}; - -let stats () => { - let stats = Typ.Procname.Hash.stats attr_tbl; - let {Hashtbl.num_bindings: num_bindings, num_buckets, max_bucket_length} = stats; - let serialized_size_kb = - Config.developer_mode ? - Some (Marshal.data_size (Marshal.to_bytes attr_tbl []) 0 / 1024) : None; - {num_bindings, num_buckets, max_bucket_length, serialized_size_kb} -}; - -/* Find the file where the procedure was captured, if a cfg for that file exists. - Return also a boolean indicating whether the procedure is defined in an - include file. */ -let find_file_capturing_procedure ::cache=true pname => - switch (load_attributes ::cache pname) { - | None => None - | Some proc_attributes => - let source_file = proc_attributes.ProcAttributes.source_file_captured; - let source_dir = DB.source_dir_from_source_file source_file; - let origin = - /* Procedure coming from include files if it has different location - than the file where it was captured. */ - SourceFile.compare source_file proc_attributes.ProcAttributes.loc.file != 0 ? - `Include : `Source; - let cfg_fname = DB.source_dir_get_internal_file source_dir ".cfg"; - let cfg_fname_exists = Sys.file_exists (DB.filename_to_string cfg_fname) == `Yes; - if cfg_fname_exists { - Some (source_file, origin) - } else { - None - } - }; diff --git a/infer/src/IR/AttributesTable.rei b/infer/src/IR/AttributesTable.rei deleted file mode 100644 index 73fc1479e..000000000 --- a/infer/src/IR/AttributesTable.rei +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/** Module to manage the table of attributes. */ -open! IStd; - - -/** Save .attr file for the procedure into the attributes database. */ -let store_attributes: ProcAttributes.t => unit; - - -/** Load the attributes for the procedure from the attributes database. - If cache is true, add the attribute to the global cache */ -let load_attributes: cache::bool => Typ.Procname.t => option ProcAttributes.t; - - -/** Load attrubutes for the procedure but only if is_defined is true */ -let load_defined_attributes: cache_none::bool => Typ.Procname.t => option ProcAttributes.t; - - -/** Given the name of an ObjC class, extract the type from the tenv where the class was defined. We - do this by adding a method that is unique to each class, and then finding the tenv that - corresponds to the class definition. */ -let get_correct_type_from_objc_class_name: Typ.Name.t => option Typ.t; - -/* Find the file where the procedure was captured, if a cfg for that file exists. - Return also a boolean indicating whether the procedure is defined in an - include file. - If cache is true, add the attribute to the global cache */ -let find_file_capturing_procedure: - cache::bool? => Typ.Procname.t => option (SourceFile.t, [ | `Include | `Source]); - -type t; - -let stats: unit => t; - -let to_json: t => Yojson.Basic.json; - -let from_json: Yojson.Basic.json => t; - -let aggregate: list t => Yojson.Basic.json; diff --git a/infer/src/IR/BUILTINS.mli b/infer/src/IR/BUILTINS.mli index 85b14f298..3e2c6a29e 100644 --- a/infer/src/IR/BUILTINS.mli +++ b/infer/src/IR/BUILTINS.mli @@ -12,76 +12,146 @@ open! IStd (** List of all builtins that are interpreted specially by the backend *) module type S = sig type t + val __assert_fail : t + val __builtin_va_arg : t + val __builtin_va_copy : t + val __builtin_va_end : t + val __builtin_va_start : t - val __cast : t (** [__cast(val,typ)] implements java's [typ(val)] *) + + val __cast : t + (** [__cast(val,typ)] implements java's [typ(val)] *) val __check_untainted : t + val __cxx_typeid : t + val __delete : t + val __delete_array : t + val __delete_locked_attribute : t + val __exit : t + val __get_array_length : t + val __get_hidden_field : t + val __get_type_of : t + val __infer_assume : t + val __infer_fail : t - val __instanceof : t (** [__instanceof(val,typ)] implements java's [val instanceof typ] *) + + val __instanceof : t + (** [__instanceof(val,typ)] implements java's [val instanceof typ] *) val __method_set_ignore_attribute : t + val __new : t + val __new_array : t + val __objc_alloc : t + val __objc_alloc_no_fail : t + val __objc_cast : t + val __objc_dictionary_literal : t + val __objc_release : t + val __objc_release_autorelease_pool : t + val __objc_release_cf : t + val __objc_retain : t + val __objc_retain_cf : t + val __placement_delete : t + val __placement_new : t + val __print_value : t + val __require_allocated_array : t + val __set_array_length : t + val __set_autorelease_attribute : t + val __set_file_attribute : t + val __set_hidden_field : t + val __set_lock_attribute : t + val __set_locked_attribute : t + val __set_mem_attribute : t + val __set_observer_attribute : t + val __set_taint_attribute : t + val __set_unlocked_attribute : t + val __set_unsubscribed_observer_attribute : t + val __set_untaint_attribute : t + val __split_get_nth : t + val __throw : t + val __unwrap_exception : t + val abort : t + val exit : t + val free : t + val fscanf : t + val fwscanf : t + val malloc : t + val malloc_no_fail : t + val nsArray_arrayWithObjects : t + val nsArray_arrayWithObjectsCount : t + val objc_cpp_throw : t + val pthread_create : t + val scanf : t + val sscanf : t + val swscanf : t + val vfscanf : t + val vfwscanf : t + val vscanf : t + val vsscanf : t + val vswscanf : t + val vwscanf : t + val wscanf : t end diff --git a/infer/src/IR/Binop.ml b/infer/src/IR/Binop.ml new file mode 100644 index 000000000..cf66e4972 --- /dev/null +++ b/infer/src/IR/Binop.ml @@ -0,0 +1,134 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Binary Operators *) +open! IStd +module L = Logging +module F = Format + +(** Binary operations *) +type t = + | PlusA (** arithmetic + *) + | PlusPI (** pointer + integer *) + | MinusA (** arithmetic - *) + | MinusPI (** pointer - integer *) + | MinusPP (** pointer - pointer *) + | Mult (** * *) + | Div (** / *) + | Mod (** % *) + | Shiftlt (** shift left *) + | Shiftrt (** shift right *) + | Lt (** < (arithmetic comparison) *) + | Gt (** > (arithmetic comparison) *) + | Le (** <= (arithmetic comparison) *) + | Ge (** >= (arithmetic comparison) *) + | Eq (** == (arithmetic comparison) *) + | Ne (** != (arithmetic comparison) *) + | BAnd (** bitwise and *) + | BXor (** exclusive-or *) + | BOr (** inclusive-or *) + | LAnd (** logical and. Does not always evaluate both operands. *) + | LOr (** logical or. Does not always evaluate both operands. *) + [@@deriving compare] + +let equal = [%compare.equal : t] + +(** This function returns true if the operation is injective + wrt. each argument: op(e,-) and op(-, e) is injective for all e. + The return value false means "don't know". *) +let injective = function PlusA | PlusPI | MinusA | MinusPI | MinusPP -> true | _ -> false + +(** This function returns true if the operation can be inverted. *) +let invertible = function PlusA | PlusPI | MinusA | MinusPI -> true | _ -> false + +(** This function inverts an invertible injective binary operator. + If the [binop] operation is not invertible, the function raises Assert_failure. *) +let invert bop = + match bop with + | PlusA + -> MinusA + | PlusPI + -> MinusPI + | MinusA + -> PlusA + | MinusPI + -> PlusPI + | _ + -> assert false + +(** This function returns true if 0 is the right unit of [binop]. + The return value false means "don't know". *) +let is_zero_runit = function PlusA | PlusPI | MinusA | MinusPI | MinusPP -> true | _ -> false + +let text = function + | PlusA + -> "+" + | PlusPI + -> "+" + | MinusA | MinusPP + -> "-" + | MinusPI + -> "-" + | Mult + -> "*" + | Div + -> "/" + | Mod + -> "%" + | Shiftlt + -> "<<" + | Shiftrt + -> ">>" + | Lt + -> "<" + | Gt + -> ">" + | Le + -> "<=" + | Ge + -> ">=" + | Eq + -> "==" + | Ne + -> "!=" + | BAnd + -> "&" + | BXor + -> "^" + | BOr + -> "|" + | LAnd + -> "&&" + | LOr + -> "||" + +(** Pretty print a binary operator. *) +let str pe binop = + match pe.Pp.kind with + | HTML -> ( + match binop with + | Ge + -> " >= " + | Le + -> " <= " + | Gt + -> " > " + | Lt + -> " < " + | Shiftlt + -> " << " + | Shiftrt + -> " >> " + | _ + -> text binop ) + | LATEX -> ( + match binop with Ge -> " \\geq " | Le -> " \\leq " | _ -> text binop ) + | _ + -> text binop diff --git a/infer/src/IR/Binop.mli b/infer/src/IR/Binop.mli new file mode 100644 index 000000000..567e2877b --- /dev/null +++ b/infer/src/IR/Binop.mli @@ -0,0 +1,66 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Binary Operators *) +open! IStd +module L = Logging +module F = Format + +(** Binary operations *) + +type t = + | PlusA (** arithmetic + *) + | PlusPI (** pointer + integer *) + | MinusA (** arithmetic - *) + | MinusPI (** pointer - integer *) + | MinusPP (** pointer - pointer *) + | Mult (** * *) + | Div (** / *) + | Mod (** % *) + | Shiftlt (** shift left *) + | Shiftrt (** shift right *) + | Lt (** < (arithmetic comparison) *) + | Gt (** > (arithmetic comparison) *) + | Le (** <= (arithmetic comparison) *) + | Ge (** >= (arithmetic comparison) *) + | Eq (** == (arithmetic comparison) *) + | Ne (** != (arithmetic comparison) *) + | BAnd (** bitwise and *) + | BXor (** exclusive-or *) + | BOr (** inclusive-or *) + | LAnd (** logical and. Does not always evaluate both operands. *) + | LOr (** logical or. Does not always evaluate both operands. *) + [@@deriving compare] + +val equal : t -> t -> bool + +(** This function returns true if the operation is injective + wrt. each argument: op(e,-) and op(-, e) is injective for all e. + The return value false means "don't know". *) + +val injective : t -> bool + +(** This function returns true if the operation can be inverted. *) + +val invertible : t -> bool + +(** This function inverts an invertible injective binary operator. + If the [binop] operation is not invertible, the function raises Assert_failure. *) + +val invert : t -> t + +(** This function returns true if 0 is the right unit of [binop]. + The return value false means "don't know". *) + +val is_zero_runit : t -> bool + +(** String representation of a binary operator. *) + +val str : Pp.env -> t -> string diff --git a/infer/src/IR/Binop.re b/infer/src/IR/Binop.re deleted file mode 100644 index 897e26a73..000000000 --- a/infer/src/IR/Binop.re +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Binary Operators */ -module L = Logging; - -module F = Format; - - -/** Binary operations */ -type t = - | PlusA /** arithmetic + */ - | PlusPI /** pointer + integer */ - | MinusA /** arithmetic - */ - | MinusPI /** pointer - integer */ - | MinusPP /** pointer - pointer */ - | Mult /** * */ - | Div /** / */ - | Mod /** % */ - | Shiftlt /** shift left */ - | Shiftrt /** shift right */ - | Lt /** < (arithmetic comparison) */ - | Gt /** > (arithmetic comparison) */ - | Le /** <= (arithmetic comparison) */ - | Ge /** >= (arithmetic comparison) */ - | Eq /** == (arithmetic comparison) */ - | Ne /** != (arithmetic comparison) */ - | BAnd /** bitwise and */ - | BXor /** exclusive-or */ - | BOr /** inclusive-or */ - | LAnd /** logical and. Does not always evaluate both operands. */ - | LOr /** logical or. Does not always evaluate both operands. */ -[@@deriving compare]; - -let equal = [%compare.equal : t]; - - -/** This function returns true if the operation is injective - wrt. each argument: op(e,-) and op(-, e) is injective for all e. - The return value false means "don't know". */ -let injective = - fun - | PlusA - | PlusPI - | MinusA - | MinusPI - | MinusPP => true - | _ => false; - - -/** This function returns true if the operation can be inverted. */ -let invertible = - fun - | PlusA - | PlusPI - | MinusA - | MinusPI => true - | _ => false; - - -/** This function inverts an invertible injective binary operator. - If the [binop] operation is not invertible, the function raises Assert_failure. */ -let invert bop => - switch bop { - | PlusA => MinusA - | PlusPI => MinusPI - | MinusA => PlusA - | MinusPI => PlusPI - | _ => assert false - }; - - -/** This function returns true if 0 is the right unit of [binop]. - The return value false means "don't know". */ -let is_zero_runit = - fun - | PlusA - | PlusPI - | MinusA - | MinusPI - | MinusPP => true - | _ => false; - -let text = - fun - | PlusA => "+" - | PlusPI => "+" - | MinusA - | MinusPP => "-" - | MinusPI => "-" - | Mult => "*" - | Div => "/" - | Mod => "%" - | Shiftlt => "<<" - | Shiftrt => ">>" - | Lt => "<" - | Gt => ">" - | Le => "<=" - | Ge => ">=" - | Eq => "==" - | Ne => "!=" - | BAnd => "&" - | BXor => "^" - | BOr => "|" - | LAnd => "&&" - | LOr => "||"; - - -/** Pretty print a binary operator. */ -let str pe binop => - switch pe.Pp.kind { - | HTML => - switch binop { - | Ge => " >= " - | Le => " <= " - | Gt => " > " - | Lt => " < " - | Shiftlt => " << " - | Shiftrt => " >> " - | _ => text binop - } - | LATEX => - switch binop { - | Ge => " \\geq " - | Le => " \\leq " - | _ => text binop - } - | _ => text binop - }; diff --git a/infer/src/IR/Binop.rei b/infer/src/IR/Binop.rei deleted file mode 100644 index 3b3c102e6..000000000 --- a/infer/src/IR/Binop.rei +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Binary Operators */ -module L = Logging; - -module F = Format; - - -/** Binary operations */ -type t = - | PlusA /** arithmetic + */ - | PlusPI /** pointer + integer */ - | MinusA /** arithmetic - */ - | MinusPI /** pointer - integer */ - | MinusPP /** pointer - pointer */ - | Mult /** * */ - | Div /** / */ - | Mod /** % */ - | Shiftlt /** shift left */ - | Shiftrt /** shift right */ - | Lt /** < (arithmetic comparison) */ - | Gt /** > (arithmetic comparison) */ - | Le /** <= (arithmetic comparison) */ - | Ge /** >= (arithmetic comparison) */ - | Eq /** == (arithmetic comparison) */ - | Ne /** != (arithmetic comparison) */ - | BAnd /** bitwise and */ - | BXor /** exclusive-or */ - | BOr /** inclusive-or */ - | LAnd /** logical and. Does not always evaluate both operands. */ - | LOr /** logical or. Does not always evaluate both operands. */ -[@@deriving compare]; - -let equal: t => t => bool; - - -/** This function returns true if the operation is injective - wrt. each argument: op(e,-) and op(-, e) is injective for all e. - The return value false means "don't know". */ -let injective: t => bool; - - -/** This function returns true if the operation can be inverted. */ -let invertible: t => bool; - - -/** This function inverts an invertible injective binary operator. - If the [binop] operation is not invertible, the function raises Assert_failure. */ -let invert: t => t; - - -/** This function returns true if 0 is the right unit of [binop]. - The return value false means "don't know". */ -let is_zero_runit: t => bool; - - -/** String representation of a binary operator. */ -let str: Pp.env => t => string; diff --git a/infer/src/IR/BuiltinDecl.ml b/infer/src/IR/BuiltinDecl.ml index 088465727..6502f4d85 100644 --- a/infer/src/IR/BuiltinDecl.ml +++ b/infer/src/IR/BuiltinDecl.ml @@ -13,92 +13,160 @@ type t = Typ.Procname.t let builtin_decls = ref Typ.Procname.Set.empty -let register pname = - builtin_decls := Typ.Procname.Set.add pname !builtin_decls +let register pname = builtin_decls := Typ.Procname.Set.add pname !builtin_decls let create_procname name = let pname = Typ.Procname.from_string_c_fun name in - register pname; - pname + register pname ; pname let create_objc_class_method class_name method_name = let method_kind = Typ.Procname.ObjCClassMethod in let tname = Typ.Name.Objc.from_string class_name in - let pname = Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp tname method_name method_kind Typ.NoTemplate ~is_generic_model:false) in - register pname; - pname + let pname = + Typ.Procname.ObjC_Cpp + (Typ.Procname.objc_cpp tname method_name method_kind Typ.NoTemplate ~is_generic_model:false) + in + register pname ; pname let is_declared pname = Typ.Procname.Set.mem pname !builtin_decls let __assert_fail = create_procname "__assert_fail" + let __builtin_va_arg = create_procname "__builtin_va_arg" + let __builtin_va_copy = create_procname "__builtin_va_copy" + let __builtin_va_end = create_procname "__builtin_va_end" + let __builtin_va_start = create_procname "__builtin_va_start" + let __cast = create_procname "__cast" + let __check_untainted = create_procname "__check_untainted" + let __cxx_typeid = create_procname "__cxx_typeid" + let __delete = create_procname "__delete" + let __delete_array = create_procname "__delete_array" + let __delete_locked_attribute = create_procname "__delete_locked_attribute" + let __exit = create_procname "_exit" + let __get_array_length = create_procname "__get_array_length" + let __get_hidden_field = create_procname "__get_hidden_field" + let __get_type_of = create_procname "__get_type_of" + let __infer_assume = create_procname "__infer_assume" + let __infer_fail = create_procname "__infer_fail" + let __instanceof = create_procname "__instanceof" + let __method_set_ignore_attribute = create_procname "__method_set_ignore_attribute" + let __new = create_procname "__new" + let __new_array = create_procname "__new_array" + let __objc_alloc = create_procname "__objc_alloc" + let __objc_alloc_no_fail = create_procname "__objc_alloc_no_fail" + let __objc_cast = create_procname "__objc_cast" + let __objc_dictionary_literal = create_objc_class_method "NSDictionary" "dictionaryWithObjects:forKeys:count:" + let __objc_release = create_procname "__objc_release" + let __objc_release_autorelease_pool = create_procname "__objc_release_autorelease_pool" + let __objc_release_cf = create_procname "__objc_release_cf" + let __objc_retain = create_procname "__objc_retain" + let __objc_retain_cf = create_procname "__objc_retain_cf" + let __placement_delete = create_procname "__placement_delete" + let __placement_new = create_procname "__placement_new" + let __print_value = create_procname "__print_value" + let __require_allocated_array = create_procname "__require_allocated_array" + let __set_array_length = create_procname "__set_array_length" + let __set_autorelease_attribute = create_procname "__set_autorelease_attribute" + let __set_file_attribute = create_procname "__set_file_attribute" + let __set_hidden_field = create_procname "__set_hidden_field" + let __set_lock_attribute = create_procname "__set_lock_attribute" + let __set_locked_attribute = create_procname "__set_locked_attribute" + let __set_mem_attribute = create_procname "__set_mem_attribute" + let __set_observer_attribute = create_procname "__set_observer_attribute" + let __set_taint_attribute = create_procname "__set_taint_attribute" + let __set_unlocked_attribute = create_procname "__set_unlocked_attribute" + let __set_unsubscribed_observer_attribute = create_procname "__set_unsubscribed_observer_attribute" + let __set_untaint_attribute = create_procname "__set_untaint_attribute" + let __split_get_nth = create_procname "__split_get_nth" + let __throw = create_procname "__throw" + let __unwrap_exception = create_procname "__unwrap_exception" + let abort = create_procname "abort" + let exit = create_procname "exit" + let free = create_procname "free" + let fscanf = create_procname "fscanf" + let fwscanf = create_procname "fwscanf" + let malloc = create_procname "malloc" + let malloc_no_fail = create_procname "malloc_no_fail" + let nsArray_arrayWithObjects = create_objc_class_method "NSArray" "arrayWithObjects:" + let nsArray_arrayWithObjectsCount = create_objc_class_method "NSArray" "arrayWithObjects:count:" + let objc_cpp_throw = create_procname "__infer_objc_cpp_throw" + let pthread_create = create_procname "pthread_create" + let scanf = create_procname "scanf" + let sscanf = create_procname "sscanf" + let swscanf = create_procname "swscanf" + let vfscanf = create_procname "vfscanf" + let vfwscanf = create_procname "vfwscanf" + let vscanf = create_procname "vscanf" + let vsscanf = create_procname "vsscanf" + let vswscanf = create_procname "vswscanf" + let vwscanf = create_procname "vwscanf" + let wscanf = create_procname "wscanf" diff --git a/infer/src/IR/CallFlags.ml b/infer/src/IR/CallFlags.ml new file mode 100644 index 000000000..8022a20cb --- /dev/null +++ b/infer/src/IR/CallFlags.ml @@ -0,0 +1,34 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Call Flags *) +open! IStd +module L = Logging +module F = Format + +(** Flags for a procedure call *) +type t = + { cf_virtual: bool + ; cf_interface: bool + ; cf_noreturn: bool + ; cf_is_objc_block: bool + ; cf_targets: Typ.Procname.t list } + [@@deriving compare] + +let pp f cf = + if cf.cf_virtual then F.fprintf f " virtual" ; + if cf.cf_noreturn then F.fprintf f " noreturn" + +let default = + { cf_virtual= false + ; cf_interface= false + ; cf_noreturn= false + ; cf_is_objc_block= false + ; cf_targets= [] } diff --git a/infer/src/IR/CallFlags.mli b/infer/src/IR/CallFlags.mli new file mode 100644 index 000000000..f5b1f6356 --- /dev/null +++ b/infer/src/IR/CallFlags.mli @@ -0,0 +1,30 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Call Flags *) +open! IStd +module L = Logging +module F = Format + +(** Flags for a procedure call *) + +type t = + { cf_virtual: bool + ; cf_interface: bool + ; cf_noreturn: bool + ; cf_is_objc_block: bool + ; cf_targets: Typ.Procname.t list } + [@@deriving compare] + +val pp : F.formatter -> t -> unit + +(** Default value where all fields are set to false *) + +val default : t diff --git a/infer/src/IR/CallFlags.re b/infer/src/IR/CallFlags.re deleted file mode 100644 index c5cd07d32..000000000 --- a/infer/src/IR/CallFlags.re +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Call Flags */ -module L = Logging; - -module F = Format; - - -/** Flags for a procedure call */ -type t = { - cf_virtual: bool, - cf_interface: bool, - cf_noreturn: bool, - cf_is_objc_block: bool, - cf_targets: list Typ.Procname.t -} -[@@deriving compare]; - -let pp f cf => { - if cf.cf_virtual { - F.fprintf f " virtual" - }; - if cf.cf_noreturn { - F.fprintf f " noreturn" - } -}; - -let default = { - cf_virtual: false, - cf_interface: false, - cf_noreturn: false, - cf_is_objc_block: false, - cf_targets: [] -}; diff --git a/infer/src/IR/CallFlags.rei b/infer/src/IR/CallFlags.rei deleted file mode 100644 index 68280e23b..000000000 --- a/infer/src/IR/CallFlags.rei +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Call Flags */ -module L = Logging; - -module F = Format; - - -/** Flags for a procedure call */ -type t = { - cf_virtual: bool, - cf_interface: bool, - cf_noreturn: bool, - cf_is_objc_block: bool, - cf_targets: list Typ.Procname.t -} -[@@deriving compare]; - -let pp: F.formatter => t => unit; - - -/** Default value where all fields are set to false */ -let default: t; diff --git a/infer/src/IR/CallSite.ml b/infer/src/IR/CallSite.ml index 9598b85a4..c44785219 100644 --- a/infer/src/IR/CallSite.ml +++ b/infer/src/IR/CallSite.ml @@ -8,35 +8,26 @@ *) open! IStd - module F = Format -type t = - { - pname : Typ.Procname.t; - loc : Location.t; - } -[@@deriving compare] +type t = {pname: Typ.Procname.t; loc: Location.t} [@@deriving compare] let equal = [%compare.equal : t] -let pname t = - t.pname +let pname t = t.pname + +let loc t = t.loc + +let make pname loc = {pname; loc} -let loc t = - t.loc +let dummy = make Typ.Procname.empty_block Location.dummy -let make pname loc = - { pname; loc; } +let pp fmt t = F.fprintf fmt "%a at %a" Typ.Procname.pp t.pname Location.pp t.loc -let dummy = - make Typ.Procname.empty_block Location.dummy +module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t -let pp fmt t = - F.fprintf fmt "%a at %a" Typ.Procname.pp t.pname Location.pp t.loc + let compare = compare -module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + let pp = pp +end) diff --git a/infer/src/IR/CallSite.mli b/infer/src/IR/CallSite.mli index 0bdb69eab..db5f48a69 100644 --- a/infer/src/IR/CallSite.mli +++ b/infer/src/IR/CallSite.mli @@ -8,7 +8,6 @@ *) open! IStd - module F = Format type t [@@deriving compare] diff --git a/infer/src/IR/Cfg.ml b/infer/src/IR/Cfg.ml new file mode 100644 index 000000000..79ba477b2 --- /dev/null +++ b/infer/src/IR/Cfg.ml @@ -0,0 +1,430 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module L = Logging +module F = Format + +(** data type for the control flow graph *) +type cfg = {proc_desc_table: (** Map proc name to procdesc *) Procdesc.t Typ.Procname.Hash.t} + +(** create a new empty cfg *) +let create_cfg () = {proc_desc_table= Typ.Procname.Hash.create 16} + +let add_proc_desc cfg pname pdesc = Typ.Procname.Hash.add cfg.proc_desc_table pname pdesc + +let remove_proc_desc cfg pname = Typ.Procname.Hash.remove cfg.proc_desc_table pname + +let iter_proc_desc cfg f = Typ.Procname.Hash.iter f cfg.proc_desc_table + +let find_proc_desc_from_name cfg pname = + try Some (Typ.Procname.Hash.find cfg.proc_desc_table pname) + with Not_found -> None + +(** Create a new procdesc *) +let create_proc_desc cfg (proc_attributes: ProcAttributes.t) = + let pdesc = Procdesc.from_proc_attributes ~called_from_cfg:true proc_attributes in + add_proc_desc cfg proc_attributes.proc_name pdesc ; pdesc + +(** Iterate over all the nodes in the cfg *) +let iter_all_nodes ?(sorted= false) f cfg = + let do_proc_desc _ (pdesc: Procdesc.t) = + List.iter ~f:(fun node -> f pdesc node) (Procdesc.get_nodes pdesc) + in + if not sorted then iter_proc_desc cfg do_proc_desc + else + Typ.Procname.Hash.fold + (fun _ pdesc desc_nodes -> + List.fold + ~f:(fun desc_nodes node -> (pdesc, node) :: desc_nodes) + ~init:desc_nodes (Procdesc.get_nodes pdesc)) + cfg.proc_desc_table [] + |> List.sort ~cmp:[%compare : Procdesc.t * Procdesc.Node.t] + |> List.iter ~f:(fun (d, n) -> f d n) + +(** Get all the procdescs (defined and declared) *) +let get_all_procs cfg = + let procs = ref [] in + let f _ pdesc = procs := pdesc :: !procs in + iter_proc_desc cfg f ; !procs + +(** Get the procedures whose body is defined in this cfg *) +let get_defined_procs cfg = List.filter ~f:Procdesc.is_defined (get_all_procs cfg) + +(** checks whether a cfg is connected or not *) +let check_cfg_connectedness cfg = + let is_exit_node n = + match Procdesc.Node.get_kind n with Procdesc.Node.Exit_node _ -> true | _ -> false + in + let broken_node n = + let succs = Procdesc.Node.get_succs n in + let preds = Procdesc.Node.get_preds n in + match Procdesc.Node.get_kind n with + | Procdesc.Node.Start_node _ + -> Int.equal (List.length succs) 0 || List.length preds > 0 + | Procdesc.Node.Exit_node _ + -> List.length succs > 0 || Int.equal (List.length preds) 0 + | Procdesc.Node.Stmt_node _ | Procdesc.Node.Prune_node _ | Procdesc.Node.Skip_node _ + -> Int.equal (List.length succs) 0 || Int.equal (List.length preds) 0 + | Procdesc.Node.Join_node -> + (* Join node has the exception that it may be without predecessors + and pointing to an exit node *) + (* if the if brances end with a return *) + match succs with [n'] when is_exit_node n' -> false | _ -> Int.equal (List.length preds) 0 + in + let do_pdesc pd = + let pname = Typ.Procname.to_string (Procdesc.get_proc_name pd) in + let nodes = Procdesc.get_nodes pd in + let broken = List.exists ~f:broken_node nodes in + if broken then L.internal_error "@\n ***BROKEN CFG: '%s'@\n" pname + in + let pdescs = get_all_procs cfg in + List.iter ~f:do_pdesc pdescs + +(** Serializer for control flow graphs *) +let cfg_serializer : cfg Serialization.serializer = + Serialization.create_serializer Serialization.Key.cfg + +(** Load a cfg from a file *) +let load_cfg_from_file (filename: DB.filename) : cfg option = + Serialization.read_from_file cfg_serializer filename + +(** Save the .attr files for the procedures in the cfg. *) +let save_attributes source_file cfg = + let save_proc pdesc = + let attributes = Procdesc.get_attributes pdesc in + let loc = attributes.loc in + let attributes' = + let loc' = if Location.equal loc Location.dummy then {loc with file= source_file} else loc in + {attributes with loc= loc'; source_file_captured= source_file} + in + AttributesTable.store_attributes attributes' + in + List.iter ~f:save_proc (get_all_procs cfg) + +(** Inline a synthetic (access or bridge) method. *) +let inline_synthetic_method ret_id etl pdesc loc_call : Sil.instr option = + let modified = ref None in + let found instr instr' = + modified := Some instr' ; + L.(debug Analysis Verbose) + "XX inline_synthetic_method found instr: %a@." (Sil.pp_instr Pp.text) instr ; + L.(debug Analysis Verbose) + "XX inline_synthetic_method instr': %a@." (Sil.pp_instr Pp.text) instr' + in + let do_instr _ instr = + match (instr, ret_id, etl) with + | ( Sil.Load (_, Exp.Lfield (Exp.Var _, fn, ft), bt, _) + , Some (ret_id, _) + , [(* getter for fields *) (e1, _)] ) + -> let instr' = Sil.Load (ret_id, Exp.Lfield (e1, fn, ft), bt, loc_call) in + found instr instr' + | Sil.Load (_, Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, _), Some (ret_id, _), [] + when Pvar.is_global pvar + -> (* getter for static fields *) + let instr' = Sil.Load (ret_id, Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, loc_call) in + found instr instr' + | Sil.Store (Exp.Lfield (_, fn, ft), bt, _, _), _, [(* setter for fields *) (e1, _); (e2, _)] + -> let instr' = Sil.Store (Exp.Lfield (e1, fn, ft), bt, e2, loc_call) in + found instr instr' + | Sil.Store (Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, _, _), _, [(e1, _)] + when Pvar.is_global pvar + -> (* setter for static fields *) + let instr' = Sil.Store (Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, e1, loc_call) in + found instr instr' + | Sil.Call (ret_id', Exp.Const Const.Cfun pn, etl', _, cf), _, _ + when Bool.equal (is_none ret_id) (is_none ret_id') + && Int.equal (List.length etl') (List.length etl) + -> let instr' = Sil.Call (ret_id, Exp.Const (Const.Cfun pn), etl, loc_call, cf) in + found instr instr' + | Sil.Call (ret_id', Exp.Const Const.Cfun pn, etl', _, cf), _, _ + when Bool.equal (is_none ret_id) (is_none ret_id') + && Int.equal (List.length etl' + 1) (List.length etl) + -> let etl1 = + match List.rev etl with + (* remove last element *) + | _ :: l + -> List.rev l + | [] + -> assert false + in + let instr' = Sil.Call (ret_id, Exp.Const (Const.Cfun pn), etl1, loc_call, cf) in + found instr instr' + | _ + -> () + in + Procdesc.iter_instrs do_instr pdesc ; !modified + +(** Find synthetic (access or bridge) Java methods in the procedure and inline them in the cfg. *) +let proc_inline_synthetic_methods cfg pdesc : unit = + let instr_inline_synthetic_method = function + | Sil.Call (ret_id, Exp.Const Const.Cfun pn, etl, loc, _) -> ( + match find_proc_desc_from_name cfg pn with + | Some pd + -> let is_access = Typ.Procname.java_is_access_method pn in + let attributes = Procdesc.get_attributes pd in + let is_synthetic = attributes.is_synthetic_method in + let is_bridge = attributes.is_bridge_method in + if is_access || is_bridge || is_synthetic then inline_synthetic_method ret_id etl pd loc + else None + | None + -> None ) + | _ + -> None + in + let node_inline_synthetic_methods node = + let modified = ref false in + let do_instr instr = + match instr_inline_synthetic_method instr with + | None + -> instr + | Some instr' + -> modified := true ; + instr' + in + let instrs = Procdesc.Node.get_instrs node in + let instrs' = List.map ~f:do_instr instrs in + if !modified then Procdesc.Node.replace_instrs node instrs' + in + Procdesc.iter_nodes node_inline_synthetic_methods pdesc + +(** Inline the java synthetic methods in the cfg *) +let inline_java_synthetic_methods cfg = + let f pname pdesc = if Typ.Procname.is_java pname then proc_inline_synthetic_methods cfg pdesc in + iter_proc_desc cfg f + +(** compute the list of procedures added or changed in [cfg_new] over [cfg_old] *) +let mark_unchanged_pdescs cfg_new cfg_old = + let pdescs_eq (pd1: Procdesc.t) (pd2: Procdesc.t) = + (* map of exp names in pd1 -> exp names in pd2 *) + let exp_map = ref Exp.Map.empty in + (* map of node id's in pd1 -> node id's in pd2 *) + let node_map = ref Procdesc.NodeMap.empty in + (* formals are the same if their types are the same *) + let formals_eq formals1 formals2 = + List.equal ~equal:(fun (_, typ1) (_, typ2) -> Typ.equal typ1 typ2) formals1 formals2 + in + let nodes_eq n1s n2s = + (* nodes are the same if they have the same id, instructions, and succs/preds up to renaming + with [exp_map] and [id_map] *) + let node_eq (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) = + let compare_id (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) = + try + let n1_mapping = Procdesc.NodeMap.find n1 !node_map in + Procdesc.Node.compare n1_mapping n2 + with Not_found -> + (* assume id's are equal and enforce by adding to [id_map] *) + node_map := Procdesc.NodeMap.add n1 n2 !node_map ; + 0 + in + let instrs_eq instrs1 instrs2 = + List.equal + ~equal:(fun i1 i2 -> + let n, exp_map' = Sil.compare_structural_instr i1 i2 !exp_map in + exp_map := exp_map' ; + Int.equal n 0) + instrs1 instrs2 + in + Int.equal (compare_id n1 n2) 0 + && List.equal ~equal:Procdesc.Node.equal (Procdesc.Node.get_succs n1) + (Procdesc.Node.get_succs n2) + && List.equal ~equal:Procdesc.Node.equal (Procdesc.Node.get_preds n1) + (Procdesc.Node.get_preds n2) + && instrs_eq (Procdesc.Node.get_instrs n1) (Procdesc.Node.get_instrs n2) + in + try List.for_all2_exn ~f:node_eq n1s n2s + with Invalid_argument _ -> false + in + let att1 = Procdesc.get_attributes pd1 and att2 = Procdesc.get_attributes pd2 in + Bool.equal att1.is_defined att2.is_defined && Typ.equal att1.ret_type att2.ret_type + && formals_eq att1.formals att2.formals + && nodes_eq (Procdesc.get_nodes pd1) (Procdesc.get_nodes pd2) + in + let old_procs = cfg_old.proc_desc_table in + let new_procs = cfg_new.proc_desc_table in + let mark_pdesc_if_unchanged pname (new_pdesc: Procdesc.t) = + try + let old_pdesc = Typ.Procname.Hash.find old_procs pname in + let changed = + (* in continue_capture mode keep the old changed bit *) + Config.continue_capture && (Procdesc.get_attributes old_pdesc).changed + || not (pdescs_eq old_pdesc new_pdesc) + in + (Procdesc.get_attributes new_pdesc).changed <- changed + with Not_found -> () + in + Typ.Procname.Hash.iter mark_pdesc_if_unchanged new_procs + +(** Save a cfg into a file *) +let store_cfg_to_file ~source_file (filename: DB.filename) (cfg: cfg) = + inline_java_synthetic_methods cfg ; + ( if Config.incremental_procs then + match load_cfg_from_file filename with + | Some old_cfg + -> mark_unchanged_pdescs cfg old_cfg + | None + -> () ) ; + (* NOTE: it's important to write attribute files to disk before writing .cfg file to disk. + OndemandCapture module relies on it - it uses existance of .cfg file as a barrier to make + sure that all attributes were written to disk (but not necessarily flushed) *) + save_attributes source_file cfg ; Serialization.write_to_file cfg_serializer filename ~data:cfg + +(** clone a procedure description and apply the type substitutions where + the parameters are used *) +let specialize_types_proc callee_pdesc resolved_pdesc substitutions = + let resolved_pname = Procdesc.get_proc_name resolved_pdesc + and callee_start_node = Procdesc.get_start_node callee_pdesc + and callee_exit_node = Procdesc.get_exit_node callee_pdesc in + let convert_pvar pvar = Pvar.mk (Pvar.get_name pvar) resolved_pname in + let mk_ptr_typ typename = + (* Only consider pointers from Java objects for now *) + Typ.mk (Tptr (Typ.mk (Tstruct typename), Typ.Pk_pointer)) + in + let convert_exp = function + | Exp.Lvar origin_pvar + -> Exp.Lvar (convert_pvar origin_pvar) + | exp + -> exp + in + let subst_map = ref Ident.IdentMap.empty in + let redirect_typename origin_id = + try Some (Ident.IdentMap.find origin_id !subst_map) + with Not_found -> None + in + let convert_instr instrs = function + | Sil.Load + ( id + , (Exp.Lvar origin_pvar as origin_exp) + , {Typ.desc= Tptr ({desc= Tstruct origin_typename}, Pk_pointer)} + , loc ) + -> let specialized_typname = + try Mangled.Map.find (Pvar.get_name origin_pvar) substitutions + with Not_found -> origin_typename + in + subst_map := Ident.IdentMap.add id specialized_typname !subst_map ; + Sil.Load (id, convert_exp origin_exp, mk_ptr_typ specialized_typname, loc) :: instrs + | Sil.Load (id, (Exp.Var origin_id as origin_exp), ({Typ.desc= Tstruct _} as origin_typ), loc) + -> let updated_typ : Typ.t = + try Typ.mk ~default:origin_typ (Tstruct (Ident.IdentMap.find origin_id !subst_map)) + with Not_found -> origin_typ + in + Sil.Load (id, convert_exp origin_exp, updated_typ, loc) :: instrs + | Sil.Load (id, origin_exp, origin_typ, loc) + -> Sil.Load (id, convert_exp origin_exp, origin_typ, loc) :: instrs + | Sil.Store (assignee_exp, origin_typ, origin_exp, loc) + -> let set_instr = + Sil.Store (convert_exp assignee_exp, origin_typ, convert_exp origin_exp, loc) + in + set_instr :: instrs + | Sil.Call + ( return_ids + , Exp.Const Const.Cfun Typ.Procname.Java callee_pname_java + , (Exp.Var id, _) :: origin_args + , loc + , call_flags ) + when call_flags.CallFlags.cf_virtual && redirect_typename id <> None + -> let redirected_typename = Option.value_exn (redirect_typename id) in + let redirected_typ = mk_ptr_typ redirected_typename in + let redirected_pname = + Typ.Procname.replace_class (Typ.Procname.Java callee_pname_java) redirected_typename + in + let args = + let other_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in + (Exp.Var id, redirected_typ) :: other_args + in + let call_instr = + Sil.Call (return_ids, Exp.Const (Const.Cfun redirected_pname), args, loc, call_flags) + in + call_instr :: instrs + | Sil.Call (return_ids, origin_call_exp, origin_args, loc, call_flags) + -> let converted_args = List.map ~f:(fun (exp, typ) -> (convert_exp exp, typ)) origin_args in + let call_instr = + Sil.Call (return_ids, convert_exp origin_call_exp, converted_args, loc, call_flags) + in + call_instr :: instrs + | Sil.Prune (origin_exp, loc, is_true_branch, if_kind) + -> Sil.Prune (convert_exp origin_exp, loc, is_true_branch, if_kind) :: instrs + | Sil.Declare_locals (typed_vars, loc) + -> let new_typed_vars = + List.map ~f:(fun (pvar, typ) -> (convert_pvar pvar, typ)) typed_vars + in + Sil.Declare_locals (new_typed_vars, loc) :: instrs + | Sil.Nullify _ | Abstract _ | Sil.Remove_temps _ + -> (* these are generated instructions that will be replaced by the preanalysis *) + instrs + in + let convert_node_kind = function + | Procdesc.Node.Start_node _ + -> Procdesc.Node.Start_node resolved_pname + | Procdesc.Node.Exit_node _ + -> Procdesc.Node.Exit_node resolved_pname + | node_kind + -> node_kind + in + let node_map = ref Procdesc.NodeMap.empty in + let rec convert_node node = + let loc = Procdesc.Node.get_loc node + and kind = convert_node_kind (Procdesc.Node.get_kind node) + and instrs = List.fold ~f:convert_instr ~init:[] (Procdesc.Node.get_instrs node) |> List.rev in + Procdesc.create_node resolved_pdesc loc kind instrs + and loop callee_nodes = + match callee_nodes with + | [] + -> [] + | node :: other_node + -> let converted_node = + try Procdesc.NodeMap.find node !node_map + with Not_found -> + let new_node = convert_node node + and successors = Procdesc.Node.get_succs node + and exn_nodes = Procdesc.Node.get_exn node in + node_map := Procdesc.NodeMap.add node new_node !node_map ; + if Procdesc.Node.equal node callee_start_node then + Procdesc.set_start_node resolved_pdesc new_node ; + if Procdesc.Node.equal node callee_exit_node then + Procdesc.set_exit_node resolved_pdesc new_node ; + Procdesc.node_set_succs_exn callee_pdesc new_node (loop successors) (loop exn_nodes) ; + new_node + in + converted_node :: loop other_node + in + ignore (loop [callee_start_node]) ; + resolved_pdesc + +(** Creates a copy of a procedure description and a list of type substitutions of the form + (name, typ) where name is a parameter. The resulting proc desc is isomorphic but + all the type of the parameters are replaced in the instructions according to the list. + The virtual calls are also replaced to match the parameter types *) +let specialize_types callee_pdesc resolved_pname args = + let callee_attributes = Procdesc.get_attributes callee_pdesc in + let resolved_params, substitutions = + List.fold2_exn + ~f:(fun (params, subts) (param_name, param_typ) (_, arg_typ) -> + match arg_typ.Typ.desc with + | Tptr ({desc= Tstruct typename}, Pk_pointer) + -> (* Replace the type of the parameter by the type of the argument *) + ((param_name, arg_typ) :: params, Mangled.Map.add param_name typename subts) + | _ + -> ((param_name, param_typ) :: params, subts)) + ~init:([], Mangled.Map.empty) callee_attributes.formals args + in + let resolved_attributes = + {callee_attributes with formals= List.rev resolved_params; proc_name= resolved_pname} + in + AttributesTable.store_attributes resolved_attributes ; + let resolved_pdesc = + let tmp_cfg = create_cfg () in + create_proc_desc tmp_cfg resolved_attributes + in + specialize_types_proc callee_pdesc resolved_pdesc substitutions + +let pp_proc_signatures fmt cfg = + F.fprintf fmt "METHOD SIGNATURES@\n@." ; + let sorted_procs = List.sort ~cmp:Procdesc.compare (get_all_procs cfg) in + List.iter ~f:(fun pdesc -> F.fprintf fmt "%a@." Procdesc.pp_signature pdesc) sorted_procs diff --git a/infer/src/IR/Cfg.mli b/infer/src/IR/Cfg.mli new file mode 100644 index 000000000..c1c451337 --- /dev/null +++ b/infer/src/IR/Cfg.mli @@ -0,0 +1,72 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Control Flow Graph for Interprocedural Analysis *) + +(** A control-flow graph *) + +type cfg + +(** Load a cfg from a file *) + +val load_cfg_from_file : DB.filename -> cfg option + +(** Save a cfg into a file, and save a copy of the source files if the boolean is true *) + +val store_cfg_to_file : source_file:SourceFile.t -> DB.filename -> cfg -> unit + +(** {2 Functions for manipulating an interprocedural CFG} *) + +(** create a new empty cfg *) + +val create_cfg : unit -> cfg + +(** Create a new procdesc *) + +val create_proc_desc : cfg -> ProcAttributes.t -> Procdesc.t + +(** Iterate over all the procdesc's *) + +val iter_proc_desc : cfg -> (Typ.Procname.t -> Procdesc.t -> unit) -> unit + +(** Find the procdesc given the proc name. Return None if not found. *) + +val find_proc_desc_from_name : cfg -> Typ.Procname.t -> Procdesc.t option + +(** Get all the procedures (defined and declared) *) + +val get_all_procs : cfg -> Procdesc.t list + +(** Get the procedures whose body is defined in this cfg *) + +val get_defined_procs : cfg -> Procdesc.t list + +(** Iterate over all the nodes in the cfg *) + +val iter_all_nodes : ?sorted:bool -> (Procdesc.t -> Procdesc.Node.t -> unit) -> cfg -> unit + +(** checks whether a cfg is connected or not *) + +val check_cfg_connectedness : cfg -> unit + +(** Remove the procdesc from the control flow graph. *) + +val remove_proc_desc : cfg -> Typ.Procname.t -> unit + +(** Creates a copy of a procedure description and a list of type substitutions of the form + (name, typ) where name is a parameter. The resulting procdesc is isomorphic but + all the type of the parameters are replaced in the instructions according to the list. + The virtual calls are also replaced to match the parameter types *) + +val specialize_types : Procdesc.t -> Typ.Procname.t -> (Exp.t * Typ.t) list -> Procdesc.t + +val pp_proc_signatures : Format.formatter -> cfg -> unit diff --git a/infer/src/IR/Cfg.re b/infer/src/IR/Cfg.re deleted file mode 100644 index b8ba58b0b..000000000 --- a/infer/src/IR/Cfg.re +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module L = Logging; - -module F = Format; - - -/** data type for the control flow graph */ -type cfg = {proc_desc_table: Typ.Procname.Hash.t Procdesc.t /** Map proc name to procdesc */}; - - -/** create a new empty cfg */ -let create_cfg () => {proc_desc_table: Typ.Procname.Hash.create 16}; - -let add_proc_desc cfg pname pdesc => Typ.Procname.Hash.add cfg.proc_desc_table pname pdesc; - -let remove_proc_desc cfg pname => Typ.Procname.Hash.remove cfg.proc_desc_table pname; - -let iter_proc_desc cfg f => Typ.Procname.Hash.iter f cfg.proc_desc_table; - -let find_proc_desc_from_name cfg pname => - try (Some (Typ.Procname.Hash.find cfg.proc_desc_table pname)) { - | Not_found => None - }; - - -/** Create a new procdesc */ -let create_proc_desc cfg (proc_attributes: ProcAttributes.t) => { - let pdesc = Procdesc.from_proc_attributes called_from_cfg::true proc_attributes; - add_proc_desc cfg proc_attributes.proc_name pdesc; - pdesc -}; - - -/** Iterate over all the nodes in the cfg */ -let iter_all_nodes ::sorted=false f cfg => { - let do_proc_desc _ (pdesc: Procdesc.t) => - List.iter f::(fun node => f pdesc node) (Procdesc.get_nodes pdesc); - if (not sorted) { - iter_proc_desc cfg do_proc_desc - } else { - Typ.Procname.Hash.fold - ( - fun _ pdesc desc_nodes => - List.fold - f::(fun desc_nodes node => [(pdesc, node), ...desc_nodes]) - init::desc_nodes - (Procdesc.get_nodes pdesc) - ) - cfg.proc_desc_table - [] |> - List.sort cmp::[%compare : (Procdesc.t, Procdesc.Node.t)] |> - List.iter f::(fun (d, n) => f d n) - } -}; - - -/** Get all the procdescs (defined and declared) */ -let get_all_procs cfg => { - let procs = ref []; - let f _ pdesc => procs := [pdesc, ...!procs]; - iter_proc_desc cfg f; - !procs -}; - - -/** Get the procedures whose body is defined in this cfg */ -let get_defined_procs cfg => List.filter f::Procdesc.is_defined (get_all_procs cfg); - - -/** checks whether a cfg is connected or not */ -let check_cfg_connectedness cfg => { - let is_exit_node n => - switch (Procdesc.Node.get_kind n) { - | Procdesc.Node.Exit_node _ => true - | _ => false - }; - let broken_node n => { - let succs = Procdesc.Node.get_succs n; - let preds = Procdesc.Node.get_preds n; - switch (Procdesc.Node.get_kind n) { - | Procdesc.Node.Start_node _ => Int.equal (List.length succs) 0 || List.length preds > 0 - | Procdesc.Node.Exit_node _ => List.length succs > 0 || Int.equal (List.length preds) 0 - | Procdesc.Node.Stmt_node _ - | Procdesc.Node.Prune_node _ - | Procdesc.Node.Skip_node _ => - Int.equal (List.length succs) 0 || Int.equal (List.length preds) 0 - | Procdesc.Node.Join_node => - /* Join node has the exception that it may be without predecessors - and pointing to an exit node */ - /* if the if brances end with a return */ - switch succs { - | [n'] when is_exit_node n' => false - | _ => Int.equal (List.length preds) 0 - } - } - }; - let do_pdesc pd => { - let pname = Typ.Procname.to_string (Procdesc.get_proc_name pd); - let nodes = Procdesc.get_nodes pd; - let broken = List.exists f::broken_node nodes; - if broken { - L.internal_error "@\n ***BROKEN CFG: '%s'@\n" pname - } - }; - let pdescs = get_all_procs cfg; - List.iter f::do_pdesc pdescs -}; - - -/** Serializer for control flow graphs */ -let cfg_serializer: Serialization.serializer cfg = - Serialization.create_serializer Serialization.Key.cfg; - - -/** Load a cfg from a file */ -let load_cfg_from_file (filename: DB.filename) :option cfg => - Serialization.read_from_file cfg_serializer filename; - - -/** Save the .attr files for the procedures in the cfg. */ -let save_attributes source_file cfg => { - let save_proc pdesc => { - let attributes = Procdesc.get_attributes pdesc; - let loc = attributes.loc; - let attributes' = { - let loc' = - if (Location.equal loc Location.dummy) { - {...loc, file: source_file} - } else { - loc - }; - {...attributes, loc: loc', source_file_captured: source_file} - }; - AttributesTable.store_attributes attributes' - }; - List.iter f::save_proc (get_all_procs cfg) -}; - - -/** Inline a synthetic (access or bridge) method. */ -let inline_synthetic_method ret_id etl pdesc loc_call :option Sil.instr => { - let modified = ref None; - let found instr instr' => { - modified := Some instr'; - L.(debug Analysis Verbose) - "XX inline_synthetic_method found instr: %a@." (Sil.pp_instr Pp.text) instr; - L.(debug Analysis Verbose) - "XX inline_synthetic_method instr': %a@." (Sil.pp_instr Pp.text) instr' - }; - let do_instr _ instr => - switch (instr, ret_id, etl) { - | ( - Sil.Load _ (Exp.Lfield (Exp.Var _) fn ft) bt _, - Some (ret_id, _), - [(e1, _)] /* getter for fields */ - ) => - let instr' = Sil.Load ret_id (Exp.Lfield e1 fn ft) bt loc_call; - found instr instr' - | (Sil.Load _ (Exp.Lfield (Exp.Lvar pvar) fn ft) bt _, Some (ret_id, _), []) - when Pvar.is_global pvar => - /* getter for static fields */ - let instr' = Sil.Load ret_id (Exp.Lfield (Exp.Lvar pvar) fn ft) bt loc_call; - found instr instr' - | (Sil.Store (Exp.Lfield _ fn ft) bt _ _, _, [(e1, _), (e2, _)] /* setter for fields */) => - let instr' = Sil.Store (Exp.Lfield e1 fn ft) bt e2 loc_call; - found instr instr' - | (Sil.Store (Exp.Lfield (Exp.Lvar pvar) fn ft) bt _ _, _, [(e1, _)]) when Pvar.is_global pvar => - /* setter for static fields */ - let instr' = Sil.Store (Exp.Lfield (Exp.Lvar pvar) fn ft) bt e1 loc_call; - found instr instr' - | (Sil.Call ret_id' (Exp.Const (Const.Cfun pn)) etl' _ cf, _, _) - when - Bool.equal (is_none ret_id) (is_none ret_id') && - Int.equal (List.length etl') (List.length etl) => - let instr' = Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl loc_call cf; - found instr instr' - | (Sil.Call ret_id' (Exp.Const (Const.Cfun pn)) etl' _ cf, _, _) - when - Bool.equal (is_none ret_id) (is_none ret_id') && - Int.equal (List.length etl' + 1) (List.length etl) => - let etl1 = - switch (List.rev etl) { - /* remove last element */ - | [_, ...l] => List.rev l - | [] => assert false - }; - let instr' = Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl1 loc_call cf; - found instr instr' - | _ => () - }; - Procdesc.iter_instrs do_instr pdesc; - !modified -}; - - -/** Find synthetic (access or bridge) Java methods in the procedure and inline them in the cfg. */ -let proc_inline_synthetic_methods cfg pdesc :unit => { - let instr_inline_synthetic_method = - fun - | Sil.Call ret_id (Exp.Const (Const.Cfun pn)) etl loc _ => - switch (find_proc_desc_from_name cfg pn) { - | Some pd => - let is_access = Typ.Procname.java_is_access_method pn; - let attributes = Procdesc.get_attributes pd; - let is_synthetic = attributes.is_synthetic_method; - let is_bridge = attributes.is_bridge_method; - if (is_access || is_bridge || is_synthetic) { - inline_synthetic_method ret_id etl pd loc - } else { - None - } - | None => None - } - | _ => None; - let node_inline_synthetic_methods node => { - let modified = ref false; - let do_instr instr => - switch (instr_inline_synthetic_method instr) { - | None => instr - | Some instr' => - modified := true; - instr' - }; - let instrs = Procdesc.Node.get_instrs node; - let instrs' = List.map f::do_instr instrs; - if !modified { - Procdesc.Node.replace_instrs node instrs' - } - }; - Procdesc.iter_nodes node_inline_synthetic_methods pdesc -}; - - -/** Inline the java synthetic methods in the cfg */ -let inline_java_synthetic_methods cfg => { - let f pname pdesc => - if (Typ.Procname.is_java pname) { - proc_inline_synthetic_methods cfg pdesc - }; - iter_proc_desc cfg f -}; - - -/** compute the list of procedures added or changed in [cfg_new] over [cfg_old] */ -let mark_unchanged_pdescs cfg_new cfg_old => { - let pdescs_eq (pd1: Procdesc.t) (pd2: Procdesc.t) => { - /* map of exp names in pd1 -> exp names in pd2 */ - let exp_map = ref Exp.Map.empty; - /* map of node id's in pd1 -> node id's in pd2 */ - let node_map = ref Procdesc.NodeMap.empty; - /* formals are the same if their types are the same */ - let formals_eq formals1 formals2 => - List.equal equal::(fun (_, typ1) (_, typ2) => Typ.equal typ1 typ2) formals1 formals2; - let nodes_eq n1s n2s => { - /* nodes are the same if they have the same id, instructions, and succs/preds up to renaming - with [exp_map] and [id_map] */ - let node_eq (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) => { - let compare_id (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) => - try { - let n1_mapping = Procdesc.NodeMap.find n1 !node_map; - Procdesc.Node.compare n1_mapping n2 - } { - | Not_found => - /* assume id's are equal and enforce by adding to [id_map] */ - node_map := Procdesc.NodeMap.add n1 n2 !node_map; - 0 - }; - let instrs_eq instrs1 instrs2 => - List.equal - equal::( - fun i1 i2 => { - let (n, exp_map') = Sil.compare_structural_instr i1 i2 !exp_map; - exp_map := exp_map'; - Int.equal n 0 - } - ) - instrs1 - instrs2; - Int.equal (compare_id n1 n2) 0 && - List.equal - equal::Procdesc.Node.equal (Procdesc.Node.get_succs n1) (Procdesc.Node.get_succs n2) && - List.equal - equal::Procdesc.Node.equal (Procdesc.Node.get_preds n1) (Procdesc.Node.get_preds n2) && - instrs_eq (Procdesc.Node.get_instrs n1) (Procdesc.Node.get_instrs n2) - }; - try (List.for_all2_exn f::node_eq n1s n2s) { - | Invalid_argument _ => false - } - }; - let att1 = Procdesc.get_attributes pd1 - and att2 = Procdesc.get_attributes pd2; - Bool.equal att1.is_defined att2.is_defined && - Typ.equal att1.ret_type att2.ret_type && - formals_eq att1.formals att2.formals && - nodes_eq (Procdesc.get_nodes pd1) (Procdesc.get_nodes pd2) - }; - let old_procs = cfg_old.proc_desc_table; - let new_procs = cfg_new.proc_desc_table; - let mark_pdesc_if_unchanged pname (new_pdesc: Procdesc.t) => - try { - let old_pdesc = Typ.Procname.Hash.find old_procs pname; - let changed = - /* in continue_capture mode keep the old changed bit */ - Config.continue_capture && (Procdesc.get_attributes old_pdesc).changed || - not (pdescs_eq old_pdesc new_pdesc); - (Procdesc.get_attributes new_pdesc).changed = changed - } { - | Not_found => () - }; - Typ.Procname.Hash.iter mark_pdesc_if_unchanged new_procs -}; - - -/** Save a cfg into a file */ -let store_cfg_to_file ::source_file (filename: DB.filename) (cfg: cfg) => { - inline_java_synthetic_methods cfg; - if Config.incremental_procs { - switch (load_cfg_from_file filename) { - | Some old_cfg => mark_unchanged_pdescs cfg old_cfg - | None => () - } - }; - /* NOTE: it's important to write attribute files to disk before writing .cfg file to disk. - OndemandCapture module relies on it - it uses existance of .cfg file as a barrier to make - sure that all attributes were written to disk (but not necessarily flushed) */ - save_attributes source_file cfg; - Serialization.write_to_file cfg_serializer filename data::cfg -}; - - -/** clone a procedure description and apply the type substitutions where - the parameters are used */ -let specialize_types_proc callee_pdesc resolved_pdesc substitutions => { - let resolved_pname = Procdesc.get_proc_name resolved_pdesc - and callee_start_node = Procdesc.get_start_node callee_pdesc - and callee_exit_node = Procdesc.get_exit_node callee_pdesc; - let convert_pvar pvar => Pvar.mk (Pvar.get_name pvar) resolved_pname; - let mk_ptr_typ typename => - /* Only consider pointers from Java objects for now */ - Typ.mk (Tptr (Typ.mk (Tstruct typename)) Typ.Pk_pointer); - let convert_exp = - fun - | Exp.Lvar origin_pvar => Exp.Lvar (convert_pvar origin_pvar) - | exp => exp; - let subst_map = ref Ident.IdentMap.empty; - let redirect_typename origin_id => - try (Some (Ident.IdentMap.find origin_id !subst_map)) { - | Not_found => None - }; - let convert_instr instrs => - fun - | Sil.Load - id - (Exp.Lvar origin_pvar as origin_exp) - {Typ.desc: Tptr {desc: Tstruct origin_typename} Pk_pointer} - loc => { - let specialized_typname = - try (Mangled.Map.find (Pvar.get_name origin_pvar) substitutions) { - | Not_found => origin_typename - }; - subst_map := Ident.IdentMap.add id specialized_typname !subst_map; - [Sil.Load id (convert_exp origin_exp) (mk_ptr_typ specialized_typname) loc, ...instrs] - } - | Sil.Load id (Exp.Var origin_id as origin_exp) ({Typ.desc: Tstruct _} as origin_typ) loc => { - let updated_typ: Typ.t = - try (Typ.mk default::origin_typ (Tstruct (Ident.IdentMap.find origin_id !subst_map))) { - | Not_found => origin_typ - }; - [Sil.Load id (convert_exp origin_exp) updated_typ loc, ...instrs] - } - | Sil.Load id origin_exp origin_typ loc => [ - Sil.Load id (convert_exp origin_exp) origin_typ loc, - ...instrs - ] - | Sil.Store assignee_exp origin_typ origin_exp loc => { - let set_instr = - Sil.Store (convert_exp assignee_exp) origin_typ (convert_exp origin_exp) loc; - [set_instr, ...instrs] - } - | Sil.Call - return_ids - (Exp.Const (Const.Cfun (Typ.Procname.Java callee_pname_java))) - [(Exp.Var id, _), ...origin_args] - loc - call_flags - when call_flags.CallFlags.cf_virtual && redirect_typename id != None => { - let redirected_typename = Option.value_exn (redirect_typename id); - let redirected_typ = mk_ptr_typ redirected_typename; - let redirected_pname = - Typ.Procname.replace_class (Typ.Procname.Java callee_pname_java) redirected_typename; - let args = { - let other_args = List.map f::(fun (exp, typ) => (convert_exp exp, typ)) origin_args; - [(Exp.Var id, redirected_typ), ...other_args] - }; - let call_instr = - Sil.Call return_ids (Exp.Const (Const.Cfun redirected_pname)) args loc call_flags; - [call_instr, ...instrs] - } - | Sil.Call return_ids origin_call_exp origin_args loc call_flags => { - let converted_args = List.map f::(fun (exp, typ) => (convert_exp exp, typ)) origin_args; - let call_instr = - Sil.Call return_ids (convert_exp origin_call_exp) converted_args loc call_flags; - [call_instr, ...instrs] - } - | Sil.Prune origin_exp loc is_true_branch if_kind => [ - Sil.Prune (convert_exp origin_exp) loc is_true_branch if_kind, - ...instrs - ] - | Sil.Declare_locals typed_vars loc => { - let new_typed_vars = List.map f::(fun (pvar, typ) => (convert_pvar pvar, typ)) typed_vars; - [Sil.Declare_locals new_typed_vars loc, ...instrs] - } - | Sil.Nullify _ - | Abstract _ - | Sil.Remove_temps _ => - /* these are generated instructions that will be replaced by the preanalysis */ - instrs; - let convert_node_kind = - fun - | Procdesc.Node.Start_node _ => Procdesc.Node.Start_node resolved_pname - | Procdesc.Node.Exit_node _ => Procdesc.Node.Exit_node resolved_pname - | node_kind => node_kind; - let node_map = ref Procdesc.NodeMap.empty; - let rec convert_node node => { - let loc = Procdesc.Node.get_loc node - and kind = convert_node_kind (Procdesc.Node.get_kind node) - and instrs = List.fold f::convert_instr init::[] (Procdesc.Node.get_instrs node) |> List.rev; - Procdesc.create_node resolved_pdesc loc kind instrs - } - and loop callee_nodes => - switch callee_nodes { - | [] => [] - | [node, ...other_node] => - let converted_node = - try (Procdesc.NodeMap.find node !node_map) { - | Not_found => - let new_node = convert_node node - and successors = Procdesc.Node.get_succs node - and exn_nodes = Procdesc.Node.get_exn node; - node_map := Procdesc.NodeMap.add node new_node !node_map; - if (Procdesc.Node.equal node callee_start_node) { - Procdesc.set_start_node resolved_pdesc new_node - }; - if (Procdesc.Node.equal node callee_exit_node) { - Procdesc.set_exit_node resolved_pdesc new_node - }; - Procdesc.node_set_succs_exn callee_pdesc new_node (loop successors) (loop exn_nodes); - new_node - }; - [converted_node, ...loop other_node] - }; - ignore (loop [callee_start_node]); - resolved_pdesc -}; - - -/** Creates a copy of a procedure description and a list of type substitutions of the form - (name, typ) where name is a parameter. The resulting proc desc is isomorphic but - all the type of the parameters are replaced in the instructions according to the list. - The virtual calls are also replaced to match the parameter types */ -let specialize_types callee_pdesc resolved_pname args => { - let callee_attributes = Procdesc.get_attributes callee_pdesc; - let (resolved_params, substitutions) = - List.fold2_exn - f::( - fun (params, subts) (param_name, param_typ) (_, arg_typ) => - switch arg_typ.Typ.desc { - | Tptr {desc: Tstruct typename} Pk_pointer => - /* Replace the type of the parameter by the type of the argument */ - ([(param_name, arg_typ), ...params], Mangled.Map.add param_name typename subts) - | _ => ([(param_name, param_typ), ...params], subts) - } - ) - init::([], Mangled.Map.empty) - callee_attributes.formals - args; - let resolved_attributes = { - ...callee_attributes, - formals: List.rev resolved_params, - proc_name: resolved_pname - }; - AttributesTable.store_attributes resolved_attributes; - let resolved_pdesc = { - let tmp_cfg = create_cfg (); - create_proc_desc tmp_cfg resolved_attributes - }; - specialize_types_proc callee_pdesc resolved_pdesc substitutions -}; - -let pp_proc_signatures fmt cfg => { - F.fprintf fmt "METHOD SIGNATURES@\n@."; - let sorted_procs = List.sort cmp::Procdesc.compare (get_all_procs cfg); - List.iter f::(fun pdesc => F.fprintf fmt "%a@." Procdesc.pp_signature pdesc) sorted_procs -}; diff --git a/infer/src/IR/Cfg.rei b/infer/src/IR/Cfg.rei deleted file mode 100644 index 5fdfa5f9c..000000000 --- a/infer/src/IR/Cfg.rei +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Control Flow Graph for Interprocedural Analysis */ - -/** A control-flow graph */ -type cfg; - - -/** Load a cfg from a file */ -let load_cfg_from_file: DB.filename => option cfg; - - -/** Save a cfg into a file, and save a copy of the source files if the boolean is true */ -let store_cfg_to_file: source_file::SourceFile.t => DB.filename => cfg => unit; - - -/** {2 Functions for manipulating an interprocedural CFG} */ - -/** create a new empty cfg */ -let create_cfg: unit => cfg; - - -/** Create a new procdesc */ -let create_proc_desc: cfg => ProcAttributes.t => Procdesc.t; - - -/** Iterate over all the procdesc's */ -let iter_proc_desc: cfg => (Typ.Procname.t => Procdesc.t => unit) => unit; - - -/** Find the procdesc given the proc name. Return None if not found. */ -let find_proc_desc_from_name: cfg => Typ.Procname.t => option Procdesc.t; - - -/** Get all the procedures (defined and declared) */ -let get_all_procs: cfg => list Procdesc.t; - - -/** Get the procedures whose body is defined in this cfg */ -let get_defined_procs: cfg => list Procdesc.t; - - -/** Iterate over all the nodes in the cfg */ -let iter_all_nodes: sorted::bool? => (Procdesc.t => Procdesc.Node.t => unit) => cfg => unit; - - -/** checks whether a cfg is connected or not */ -let check_cfg_connectedness: cfg => unit; - - -/** Remove the procdesc from the control flow graph. */ -let remove_proc_desc: cfg => Typ.Procname.t => unit; - - -/** Creates a copy of a procedure description and a list of type substitutions of the form - (name, typ) where name is a parameter. The resulting procdesc is isomorphic but - all the type of the parameters are replaced in the instructions according to the list. - The virtual calls are also replaced to match the parameter types */ -let specialize_types: Procdesc.t => Typ.Procname.t => list (Exp.t, Typ.t) => Procdesc.t; - -let pp_proc_signatures: Format.formatter => cfg => unit; diff --git a/infer/src/IR/Cg.ml b/infer/src/IR/Cg.ml new file mode 100644 index 000000000..3eead6706 --- /dev/null +++ b/infer/src/IR/Cg.ml @@ -0,0 +1,312 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Module for call graphs *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +type node = Typ.Procname.t + +type in_out_calls = + { in_calls: int (** total number of in calls transitively *) + ; out_calls: int (** total number of out calls transitively *) } + +type node_info = + { (** defined procedure as opposed to just declared *) + mutable defined: bool + ; mutable parents: Typ.Procname.Set.t + ; mutable children: Typ.Procname.Set.t (** ancestors are computed lazily *) + ; mutable ancestors: Typ.Procname.Set.t option (** heirs are computed lazily *) + ; mutable heirs: Typ.Procname.Set.t option (** recursive dependents are computed lazily *) + ; mutable recursive_dependents: Typ.Procname.Set.t option (** calls are computed lazily *) + ; mutable in_out_calls: in_out_calls option } + +(** Type for call graph *) +type t = + { source: SourceFile.t (** path for the source file *) + ; node_map: (** map from node to node_info *) node_info Typ.Procname.Hash.t } + +let create source = {source; node_map= Typ.Procname.Hash.create 3} + +let add_node g n ~defined = + try + let info = Typ.Procname.Hash.find g.node_map n in + (* defined and disabled only go from false to true + to avoid accidental overwrite to false by calling add_edge *) + if defined then info.defined <- true + with Not_found -> + let info = + { defined + ; parents= Typ.Procname.Set.empty + ; children= Typ.Procname.Set.empty + ; ancestors= None + ; heirs= None + ; recursive_dependents= None + ; in_out_calls= None } + in + Typ.Procname.Hash.add g.node_map n info + +let remove_node_defined g n = + try + let info = Typ.Procname.Hash.find g.node_map n in + info.defined <- false + with Not_found -> () + +let add_defined_node g n = add_node g n ~defined:true + +(** Compute the ancestors of the node, if not already computed *) +let compute_ancestors g node = + let todo = ref (Typ.Procname.Set.singleton node) in + let seen = ref Typ.Procname.Set.empty in + let result = ref Typ.Procname.Set.empty in + while not (Typ.Procname.Set.is_empty !todo) do + let current = Typ.Procname.Set.choose !todo in + todo := Typ.Procname.Set.remove current !todo ; + if not (Typ.Procname.Set.mem current !seen) then ( + seen := Typ.Procname.Set.add current !seen ; + let info = Typ.Procname.Hash.find g current in + match info.ancestors with + | Some ancestors + -> result := Typ.Procname.Set.union !result ancestors + | None + -> result := Typ.Procname.Set.union !result info.parents ; + todo := Typ.Procname.Set.union !todo info.parents ) + done ; + !result + +(** Compute the heirs of the node, if not already computed *) +let compute_heirs g node = + let todo = ref (Typ.Procname.Set.singleton node) in + let seen = ref Typ.Procname.Set.empty in + let result = ref Typ.Procname.Set.empty in + while not (Typ.Procname.Set.is_empty !todo) do + let current = Typ.Procname.Set.choose !todo in + todo := Typ.Procname.Set.remove current !todo ; + if not (Typ.Procname.Set.mem current !seen) then ( + seen := Typ.Procname.Set.add current !seen ; + let info = Typ.Procname.Hash.find g current in + match info.heirs with + | Some heirs + -> result := Typ.Procname.Set.union !result heirs + | None + -> result := Typ.Procname.Set.union !result info.children ; + todo := Typ.Procname.Set.union !todo info.children ) + done ; + !result + +(** Compute the ancestors of the node, if not pre-computed already *) +let get_ancestors (g: t) node = + let info = Typ.Procname.Hash.find g.node_map node in + match info.ancestors with + | None + -> let ancestors = compute_ancestors g.node_map node in + info.ancestors <- Some ancestors ; + let size = Typ.Procname.Set.cardinal ancestors in + if size > 1000 then + L.(debug Analysis Medium) "%a has %d ancestors@." Typ.Procname.pp node size ; + ancestors + | Some ancestors + -> ancestors + +(** Compute the heirs of the node, if not pre-computed already *) +let get_heirs (g: t) node = + let info = Typ.Procname.Hash.find g.node_map node in + match info.heirs with + | None + -> let heirs = compute_heirs g.node_map node in + info.heirs <- Some heirs ; + let size = Typ.Procname.Set.cardinal heirs in + if size > 1000 then L.(debug Analysis Medium) "%a has %d heirs@." Typ.Procname.pp node size ; + heirs + | Some heirs + -> heirs + +let node_defined (g: t) n = + try + let info = Typ.Procname.Hash.find g.node_map n in + info.defined + with Not_found -> false + +let add_edge g nfrom nto = + add_node g nfrom ~defined:false ; + add_node g nto ~defined:false ; + let info_from = Typ.Procname.Hash.find g.node_map nfrom in + let info_to = Typ.Procname.Hash.find g.node_map nto in + info_from.children <- Typ.Procname.Set.add nto info_from.children ; + info_to.parents <- Typ.Procname.Set.add nfrom info_to.parents + +(** iterate over the elements of a node_map in node order *) +let node_map_iter f g = + let table = ref [] in + Typ.Procname.Hash.iter (fun node info -> table := (node, info) :: !table) g.node_map ; + let cmp ((n1: Typ.Procname.t), _) ((n2: Typ.Procname.t), _) = Typ.Procname.compare n1 n2 in + List.iter ~f:(fun (n, info) -> f n info) (List.sort ~cmp !table) + +let get_nodes (g: t) = + let nodes = ref Typ.Procname.Set.empty in + let f node _ = nodes := Typ.Procname.Set.add node !nodes in + node_map_iter f g ; !nodes + +let compute_calls g node = + { in_calls= Typ.Procname.Set.cardinal (get_ancestors g node) + ; out_calls= Typ.Procname.Set.cardinal (get_heirs g node) } + +(** Compute the calls of the node, if not pre-computed already *) +let get_calls (g: t) node = + let info = Typ.Procname.Hash.find g.node_map node in + match info.in_out_calls with + | None + -> let calls = compute_calls g node in + info.in_out_calls <- Some calls ; + calls + | Some calls + -> calls + +let get_all_nodes (g: t) = + let nodes = Typ.Procname.Set.elements (get_nodes g) in + List.map ~f:(fun node -> (node, get_calls g node)) nodes + +let get_nodes_and_calls (g: t) = List.filter ~f:(fun (n, _) -> node_defined g n) (get_all_nodes g) + +let node_get_num_ancestors g n = (n, Typ.Procname.Set.cardinal (get_ancestors g n)) + +let get_edges (g: t) : ((node * int) * (node * int)) list = + let edges = ref [] in + let f node info = + Typ.Procname.Set.iter + (fun nto -> edges := (node_get_num_ancestors g node, node_get_num_ancestors g nto) :: !edges) + info.children + in + node_map_iter f g ; !edges + +(** Return all the children of [n], whether defined or not *) +let get_all_children (g: t) n = (Typ.Procname.Hash.find g.node_map n).children + +(** Return the children of [n] which are defined *) +let get_defined_children (g: t) n = Typ.Procname.Set.filter (node_defined g) (get_all_children g n) + +(** Return the parents of [n] *) +let get_parents (g: t) n = (Typ.Procname.Hash.find g.node_map n).parents + +(** Check if [source] recursively calls [dest] *) +let calls_recursively (g: t) source dest = Typ.Procname.Set.mem source (get_ancestors g dest) + +(** Return the children of [n] which are not heirs of [n] *) +let get_nonrecursive_dependents (g: t) n = + let is_not_recursive pn = not (Typ.Procname.Set.mem pn (get_ancestors g n)) in + let res0 = Typ.Procname.Set.filter is_not_recursive (get_all_children g n) in + let res = Typ.Procname.Set.filter (node_defined g) res0 in + res + +(** Return the ancestors of [n] which are also heirs of [n] *) +let compute_recursive_dependents (g: t) n = + let reached_from_n pn = Typ.Procname.Set.mem n (get_ancestors g pn) in + let res0 = Typ.Procname.Set.filter reached_from_n (get_ancestors g n) in + let res = Typ.Procname.Set.filter (node_defined g) res0 in + res + +(** Compute the ancestors of [n] which are also heirs of [n], if not pre-computed already *) +let get_recursive_dependents (g: t) n = + let info = Typ.Procname.Hash.find g.node_map n in + match info.recursive_dependents with + | None + -> let recursive_dependents = compute_recursive_dependents g n in + info.recursive_dependents <- Some recursive_dependents ; + recursive_dependents + | Some recursive_dependents + -> recursive_dependents + +(** Return the nodes dependent on [n] *) +let get_dependents (g: t) n = + Typ.Procname.Set.union (get_nonrecursive_dependents g n) (get_recursive_dependents g n) + +(** Return all the nodes with their defined children *) +let get_nodes_and_defined_children (g: t) = + let nodes = ref Typ.Procname.Set.empty in + node_map_iter (fun n info -> if info.defined then nodes := Typ.Procname.Set.add n !nodes) g ; + let nodes_list = Typ.Procname.Set.elements !nodes in + List.map ~f:(fun n -> (n, get_defined_children g n)) nodes_list + +(** nodes with defined flag, and edges *) +type nodes_and_edges = (node * bool) list * (node * node) list + +(** Return the list of nodes, with defined+disabled flags, and the list of edges *) +let get_nodes_and_edges (g: t) : nodes_and_edges = + let nodes = ref [] in + let edges = ref [] in + let do_children node nto = edges := (node, nto) :: !edges in + let f node info = + nodes := (node, info.defined) :: !nodes ; + Typ.Procname.Set.iter (do_children node) info.children + in + node_map_iter f g ; (!nodes, !edges) + +(** Return the list of nodes which are defined *) +let get_defined_nodes (g: t) = + let nodes, _ = get_nodes_and_edges g in + let get_node (node, _) = node in + List.map ~f:get_node (List.filter ~f:(fun (_, defined) -> defined) nodes) + +(** Return the path of the source file *) +let get_source (g: t) = g.source + +(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; + undefined nodes become defined if at least one side is. *) +let extend cg_old cg_new = + let nodes, edges = get_nodes_and_edges cg_new in + List.iter ~f:(fun (node, defined) -> add_node cg_old node ~defined) nodes ; + List.iter ~f:(fun (nfrom, nto) -> add_edge cg_old nfrom nto) edges + +(** Begin support for serialization *) +let callgraph_serializer : (SourceFile.t * nodes_and_edges) Serialization.serializer = + Serialization.create_serializer Serialization.Key.cg + +(** Load a call graph from a file *) +let load_from_file (filename: DB.filename) : t option = + match Serialization.read_from_file callgraph_serializer filename with + | None + -> None + | Some (source, (nodes, edges)) + -> let g = create source in + List.iter ~f:(fun (node, defined) -> if defined then add_defined_node g node) nodes ; + List.iter ~f:(fun (nfrom, nto) -> add_edge g nfrom nto) edges ; + Some g + +(** Save a call graph into a file *) +let store_to_file (filename: DB.filename) (call_graph: t) = + Serialization.write_to_file callgraph_serializer filename + ~data:(call_graph.source, get_nodes_and_edges call_graph) + +let pp_graph_dotty (g: t) fmt = + let nodes_with_calls = get_all_nodes g in + let get_shape (n, _) = if node_defined g n then "box" else "diamond" in + let pp_node fmt (n, _) = F.fprintf fmt "\"%s\"" (Typ.Procname.to_filename n) in + let pp_node_label fmt (n, calls) = + F.fprintf fmt "\"%a | calls=%d %d)\"" Typ.Procname.pp n calls.in_calls calls.out_calls + in + F.fprintf fmt "digraph {@\n" ; + List.iter + ~f:(fun nc -> + F.fprintf fmt "%a [shape=box,label=%a,color=%s,shape=%s]@\n" pp_node nc pp_node_label nc + "red" (get_shape nc)) + nodes_with_calls ; + List.iter ~f:(fun (s, d) -> F.fprintf fmt "%a -> %a@\n" pp_node s pp_node d) (get_edges g) ; + F.fprintf fmt "}@." + +(** Print the call graph as a dotty file. *) +let save_call_graph_dotty source (g: t) = + let fname_dot = + DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) ["call_graph.dot"] + in + let outc = Out_channel.create (DB.filename_to_string fname_dot) in + let fmt = F.formatter_of_out_channel outc in + pp_graph_dotty g fmt ; Out_channel.close outc diff --git a/infer/src/IR/Cg.mli b/infer/src/IR/Cg.mli new file mode 100644 index 000000000..86ee4e2d2 --- /dev/null +++ b/infer/src/IR/Cg.mli @@ -0,0 +1,127 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Module for call graphs *) + +type in_out_calls = + { in_calls: int (** total number of in calls transitively *) + ; out_calls: int (** total number of out calls transitively *) } + +type t + +(** the type of a call graph *) + +(** A call graph consists of a set of nodes (Typ.Procname.t), and edges between them. + A node can be defined or undefined (to represent whether we have code for it). + In an edge from [n1] to [n2], indicating that [n1] calls [n2], + [n1] is the parent and [n2] is the child. + Node [n1] is dependent on [n2] if there is a path from [n1] to [n2] + using the child relationship. *) + +(** [add_edge cg f t] adds an edge from [f] to [t] in the call graph [cg]. + The nodes are also added as undefined, unless already present. *) + +val add_edge : t -> Typ.Procname.t -> Typ.Procname.t -> unit + +(** Add a node to the call graph as defined *) + +val add_defined_node : t -> Typ.Procname.t -> unit + +(** Check if [source] recursively calls [dest] *) + +val calls_recursively : t -> Typ.Procname.t -> Typ.Procname.t -> bool + +(** Create an empty call graph *) + +val create : SourceFile.t -> t + +(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; + undefined nodes become defined if at least one side is. *) + +val extend : t -> t -> unit + +(** Return all the children of [n], whether defined or not *) + +val get_all_children : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Compute the ancestors of the node, if not pre-computed already *) + +val get_ancestors : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Compute the heirs of the node, if not pre-computed already *) + +val get_heirs : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the in/out calls of the node *) + +val get_calls : t -> Typ.Procname.t -> in_out_calls + +(** Return the list of nodes which are defined *) + +val get_defined_nodes : t -> Typ.Procname.t list + +(** Return the children of [n] which are defined *) + +val get_defined_children : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the nodes dependent on [n] *) + +val get_dependents : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the list of nodes with calls *) + +val get_nodes_and_calls : t -> (Typ.Procname.t * in_out_calls) list + +(** Return all the nodes with their defined children *) + +val get_nodes_and_defined_children : t -> (Typ.Procname.t * Typ.Procname.Set.t) list + +(** Return the list of nodes, with defined flag, and the list of edges *) + +val get_nodes_and_edges : + t -> (Typ.Procname.t * bool) list * (Typ.Procname.t * Typ.Procname.t) list + +(** Return the children of [n] which are not heirs of [n] and are defined *) + +val get_nonrecursive_dependents : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the parents of [n] *) + +val get_parents : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the ancestors of [n] which are also heirs of [n] *) + +val get_recursive_dependents : t -> Typ.Procname.t -> Typ.Procname.Set.t + +(** Return the path of the source file *) + +val get_source : t -> SourceFile.t + +(** Load a call graph from a file *) + +val load_from_file : DB.filename -> t option + +(** Returns true if the node is defined *) + +val node_defined : t -> Typ.Procname.t -> bool + +(** Remove the defined flag from a node, if it exists. *) + +val remove_node_defined : t -> Typ.Procname.t -> unit + +(** Print the call graph as a dotty file. *) + +val save_call_graph_dotty : SourceFile.t -> t -> unit + +(** Save a call graph into a file *) + +val store_to_file : DB.filename -> t -> unit diff --git a/infer/src/IR/Cg.re b/infer/src/IR/Cg.re deleted file mode 100644 index e2185bf61..000000000 --- a/infer/src/IR/Cg.re +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** Module for call graphs */ -module L = Logging; - -module F = Format; - -type node = Typ.Procname.t; - -type in_out_calls = { - in_calls: int, /** total number of in calls transitively */ - out_calls: int /** total number of out calls transitively */ -}; - -type node_info = { - /** defined procedure as opposed to just declared */ - mutable defined: bool, - mutable parents: Typ.Procname.Set.t, - mutable children: Typ.Procname.Set.t, - /** ancestors are computed lazily */ - mutable ancestors: option Typ.Procname.Set.t, - /** heirs are computed lazily */ - mutable heirs: option Typ.Procname.Set.t, - /** recursive dependents are computed lazily */ - mutable recursive_dependents: option Typ.Procname.Set.t, - /** calls are computed lazily */ - mutable in_out_calls: option in_out_calls -}; - - -/** Type for call graph */ -type t = { - source: SourceFile.t, /** path for the source file */ - node_map: Typ.Procname.Hash.t node_info /** map from node to node_info */ -}; - -let create source => {source, node_map: Typ.Procname.Hash.create 3}; - -let add_node g n ::defined => - try { - let info = Typ.Procname.Hash.find g.node_map n; - /* defined and disabled only go from false to true - to avoid accidental overwrite to false by calling add_edge */ - if defined { - info.defined = true - } - } { - | Not_found => - let info = { - defined, - parents: Typ.Procname.Set.empty, - children: Typ.Procname.Set.empty, - ancestors: None, - heirs: None, - recursive_dependents: None, - in_out_calls: None - }; - Typ.Procname.Hash.add g.node_map n info - }; - -let remove_node_defined g n => - try { - let info = Typ.Procname.Hash.find g.node_map n; - info.defined = false - } { - | Not_found => () - }; - -let add_defined_node g n => add_node g n defined::true; - - -/** Compute the ancestors of the node, if not already computed */ -let compute_ancestors g node => { - let todo = ref (Typ.Procname.Set.singleton node); - let seen = ref Typ.Procname.Set.empty; - let result = ref Typ.Procname.Set.empty; - while (not (Typ.Procname.Set.is_empty !todo)) { - let current = Typ.Procname.Set.choose !todo; - todo := Typ.Procname.Set.remove current !todo; - if (not (Typ.Procname.Set.mem current !seen)) { - seen := Typ.Procname.Set.add current !seen; - let info = Typ.Procname.Hash.find g current; - switch info.ancestors { - | Some ancestors => result := Typ.Procname.Set.union !result ancestors - | None => - result := Typ.Procname.Set.union !result info.parents; - todo := Typ.Procname.Set.union !todo info.parents - } - } - }; - !result -}; - - -/** Compute the heirs of the node, if not already computed */ -let compute_heirs g node => { - let todo = ref (Typ.Procname.Set.singleton node); - let seen = ref Typ.Procname.Set.empty; - let result = ref Typ.Procname.Set.empty; - while (not (Typ.Procname.Set.is_empty !todo)) { - let current = Typ.Procname.Set.choose !todo; - todo := Typ.Procname.Set.remove current !todo; - if (not (Typ.Procname.Set.mem current !seen)) { - seen := Typ.Procname.Set.add current !seen; - let info = Typ.Procname.Hash.find g current; - switch info.heirs { - | Some heirs => result := Typ.Procname.Set.union !result heirs - | None => - result := Typ.Procname.Set.union !result info.children; - todo := Typ.Procname.Set.union !todo info.children - } - } - }; - !result -}; - - -/** Compute the ancestors of the node, if not pre-computed already */ -let get_ancestors (g: t) node => { - let info = Typ.Procname.Hash.find g.node_map node; - switch info.ancestors { - | None => - let ancestors = compute_ancestors g.node_map node; - info.ancestors = Some ancestors; - let size = Typ.Procname.Set.cardinal ancestors; - if (size > 1000) { - L.(debug Analysis Medium) "%a has %d ancestors@." Typ.Procname.pp node size - }; - ancestors - | Some ancestors => ancestors - } -}; - - -/** Compute the heirs of the node, if not pre-computed already */ -let get_heirs (g: t) node => { - let info = Typ.Procname.Hash.find g.node_map node; - switch info.heirs { - | None => - let heirs = compute_heirs g.node_map node; - info.heirs = Some heirs; - let size = Typ.Procname.Set.cardinal heirs; - if (size > 1000) { - L.(debug Analysis Medium) "%a has %d heirs@." Typ.Procname.pp node size - }; - heirs - | Some heirs => heirs - } -}; - -let node_defined (g: t) n => - try { - let info = Typ.Procname.Hash.find g.node_map n; - info.defined - } { - | Not_found => false - }; - -let add_edge g nfrom nto => { - add_node g nfrom defined::false; - add_node g nto defined::false; - let info_from = Typ.Procname.Hash.find g.node_map nfrom; - let info_to = Typ.Procname.Hash.find g.node_map nto; - info_from.children = Typ.Procname.Set.add nto info_from.children; - info_to.parents = Typ.Procname.Set.add nfrom info_to.parents -}; - - -/** iterate over the elements of a node_map in node order */ -let node_map_iter f g => { - let table = ref []; - Typ.Procname.Hash.iter (fun node info => table := [(node, info), ...!table]) g.node_map; - let cmp (n1: Typ.Procname.t, _) (n2: Typ.Procname.t, _) => Typ.Procname.compare n1 n2; - List.iter f::(fun (n, info) => f n info) (List.sort ::cmp !table) -}; - -let get_nodes (g: t) => { - let nodes = ref Typ.Procname.Set.empty; - let f node _ => nodes := Typ.Procname.Set.add node !nodes; - node_map_iter f g; - !nodes -}; - -let compute_calls g node => { - in_calls: Typ.Procname.Set.cardinal (get_ancestors g node), - out_calls: Typ.Procname.Set.cardinal (get_heirs g node) -}; - - -/** Compute the calls of the node, if not pre-computed already */ -let get_calls (g: t) node => { - let info = Typ.Procname.Hash.find g.node_map node; - switch info.in_out_calls { - | None => - let calls = compute_calls g node; - info.in_out_calls = Some calls; - calls - | Some calls => calls - } -}; - -let get_all_nodes (g: t) => { - let nodes = Typ.Procname.Set.elements (get_nodes g); - List.map f::(fun node => (node, get_calls g node)) nodes -}; - -let get_nodes_and_calls (g: t) => - List.filter f::(fun (n, _) => node_defined g n) (get_all_nodes g); - -let node_get_num_ancestors g n => (n, Typ.Procname.Set.cardinal (get_ancestors g n)); - -let get_edges (g: t) :list ((node, int), (node, int)) => { - let edges = ref []; - let f node info => - Typ.Procname.Set.iter - ( - fun nto => - edges := [(node_get_num_ancestors g node, node_get_num_ancestors g nto), ...!edges] - ) - info.children; - node_map_iter f g; - !edges -}; - - -/** Return all the children of [n], whether defined or not */ -let get_all_children (g: t) n => (Typ.Procname.Hash.find g.node_map n).children; - - -/** Return the children of [n] which are defined */ -let get_defined_children (g: t) n => - Typ.Procname.Set.filter (node_defined g) (get_all_children g n); - - -/** Return the parents of [n] */ -let get_parents (g: t) n => (Typ.Procname.Hash.find g.node_map n).parents; - - -/** Check if [source] recursively calls [dest] */ -let calls_recursively (g: t) source dest => Typ.Procname.Set.mem source (get_ancestors g dest); - - -/** Return the children of [n] which are not heirs of [n] */ -let get_nonrecursive_dependents (g: t) n => { - let is_not_recursive pn => not (Typ.Procname.Set.mem pn (get_ancestors g n)); - let res0 = Typ.Procname.Set.filter is_not_recursive (get_all_children g n); - let res = Typ.Procname.Set.filter (node_defined g) res0; - res -}; - - -/** Return the ancestors of [n] which are also heirs of [n] */ -let compute_recursive_dependents (g: t) n => { - let reached_from_n pn => Typ.Procname.Set.mem n (get_ancestors g pn); - let res0 = Typ.Procname.Set.filter reached_from_n (get_ancestors g n); - let res = Typ.Procname.Set.filter (node_defined g) res0; - res -}; - - -/** Compute the ancestors of [n] which are also heirs of [n], if not pre-computed already */ -let get_recursive_dependents (g: t) n => { - let info = Typ.Procname.Hash.find g.node_map n; - switch info.recursive_dependents { - | None => - let recursive_dependents = compute_recursive_dependents g n; - info.recursive_dependents = Some recursive_dependents; - recursive_dependents - | Some recursive_dependents => recursive_dependents - } -}; - - -/** Return the nodes dependent on [n] */ -let get_dependents (g: t) n => - Typ.Procname.Set.union (get_nonrecursive_dependents g n) (get_recursive_dependents g n); - - -/** Return all the nodes with their defined children */ -let get_nodes_and_defined_children (g: t) => { - let nodes = ref Typ.Procname.Set.empty; - node_map_iter - ( - fun n info => - if info.defined { - nodes := Typ.Procname.Set.add n !nodes - } - ) - g; - let nodes_list = Typ.Procname.Set.elements !nodes; - List.map f::(fun n => (n, get_defined_children g n)) nodes_list -}; - - -/** nodes with defined flag, and edges */ -type nodes_and_edges = (list (node, bool), list (node, node)); - - -/** Return the list of nodes, with defined+disabled flags, and the list of edges */ -let get_nodes_and_edges (g: t) :nodes_and_edges => { - let nodes = ref []; - let edges = ref []; - let do_children node nto => edges := [(node, nto), ...!edges]; - let f node info => { - nodes := [(node, info.defined), ...!nodes]; - Typ.Procname.Set.iter (do_children node) info.children - }; - node_map_iter f g; - (!nodes, !edges) -}; - - -/** Return the list of nodes which are defined */ -let get_defined_nodes (g: t) => { - let (nodes, _) = get_nodes_and_edges g; - let get_node (node, _) => node; - List.map f::get_node (List.filter f::(fun (_, defined) => defined) nodes) -}; - - -/** Return the path of the source file */ -let get_source (g: t) => g.source; - - -/** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; - undefined nodes become defined if at least one side is. */ -let extend cg_old cg_new => { - let (nodes, edges) = get_nodes_and_edges cg_new; - List.iter f::(fun (node, defined) => add_node cg_old node ::defined) nodes; - List.iter f::(fun (nfrom, nto) => add_edge cg_old nfrom nto) edges -}; - - -/** Begin support for serialization */ -let callgraph_serializer: Serialization.serializer (SourceFile.t, nodes_and_edges) = - Serialization.create_serializer Serialization.Key.cg; - - -/** Load a call graph from a file */ -let load_from_file (filename: DB.filename) :option t => - switch (Serialization.read_from_file callgraph_serializer filename) { - | None => None - | Some (source, (nodes, edges)) => - let g = create source; - List.iter - f::( - fun (node, defined) => - if defined { - add_defined_node g node - } - ) - nodes; - List.iter f::(fun (nfrom, nto) => add_edge g nfrom nto) edges; - Some g - }; - - -/** Save a call graph into a file */ -let store_to_file (filename: DB.filename) (call_graph: t) => - Serialization.write_to_file - callgraph_serializer filename data::(call_graph.source, get_nodes_and_edges call_graph); - -let pp_graph_dotty (g: t) fmt => { - let nodes_with_calls = get_all_nodes g; - let get_shape (n, _) => - if (node_defined g n) { - "box" - } else { - "diamond" - }; - let pp_node fmt (n, _) => F.fprintf fmt "\"%s\"" (Typ.Procname.to_filename n); - let pp_node_label fmt (n, calls) => - F.fprintf fmt "\"%a | calls=%d %d)\"" Typ.Procname.pp n calls.in_calls calls.out_calls; - F.fprintf fmt "digraph {@\n"; - List.iter - f::( - fun nc => - F.fprintf - fmt - "%a [shape=box,label=%a,color=%s,shape=%s]@\n" - pp_node - nc - pp_node_label - nc - "red" - (get_shape nc) - ) - nodes_with_calls; - List.iter f::(fun (s, d) => F.fprintf fmt "%a -> %a@\n" pp_node s pp_node d) (get_edges g); - F.fprintf fmt "}@." -}; - - -/** Print the call graph as a dotty file. */ -let save_call_graph_dotty source (g: t) => { - let fname_dot = - DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) ["call_graph.dot"]; - let outc = Out_channel.create (DB.filename_to_string fname_dot); - let fmt = F.formatter_of_out_channel outc; - pp_graph_dotty g fmt; - Out_channel.close outc -}; diff --git a/infer/src/IR/Cg.rei b/infer/src/IR/Cg.rei deleted file mode 100644 index 4f55b3ec4..000000000 --- a/infer/src/IR/Cg.rei +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Module for call graphs */ -type in_out_calls = { - in_calls: int, /** total number of in calls transitively */ - out_calls: int /** total number of out calls transitively */ -}; - -type t; /** the type of a call graph */ - - -/** A call graph consists of a set of nodes (Typ.Procname.t), and edges between them. - A node can be defined or undefined (to represent whether we have code for it). - In an edge from [n1] to [n2], indicating that [n1] calls [n2], - [n1] is the parent and [n2] is the child. - Node [n1] is dependent on [n2] if there is a path from [n1] to [n2] - using the child relationship. */ - -/** [add_edge cg f t] adds an edge from [f] to [t] in the call graph [cg]. - The nodes are also added as undefined, unless already present. */ -let add_edge: t => Typ.Procname.t => Typ.Procname.t => unit; - - -/** Add a node to the call graph as defined */ -let add_defined_node: t => Typ.Procname.t => unit; - - -/** Check if [source] recursively calls [dest] */ -let calls_recursively: t => Typ.Procname.t => Typ.Procname.t => bool; - - -/** Create an empty call graph */ -let create: SourceFile.t => t; - - -/** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; - undefined nodes become defined if at least one side is. */ -let extend: t => t => unit; - - -/** Return all the children of [n], whether defined or not */ -let get_all_children: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Compute the ancestors of the node, if not pre-computed already */ -let get_ancestors: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Compute the heirs of the node, if not pre-computed already */ -let get_heirs: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the in/out calls of the node */ -let get_calls: t => Typ.Procname.t => in_out_calls; - - -/** Return the list of nodes which are defined */ -let get_defined_nodes: t => list Typ.Procname.t; - - -/** Return the children of [n] which are defined */ -let get_defined_children: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the nodes dependent on [n] */ -let get_dependents: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the list of nodes with calls */ -let get_nodes_and_calls: t => list (Typ.Procname.t, in_out_calls); - - -/** Return all the nodes with their defined children */ -let get_nodes_and_defined_children: t => list (Typ.Procname.t, Typ.Procname.Set.t); - - -/** Return the list of nodes, with defined flag, and the list of edges */ -let get_nodes_and_edges: t => (list (Typ.Procname.t, bool), list (Typ.Procname.t, Typ.Procname.t)); - - -/** Return the children of [n] which are not heirs of [n] and are defined */ -let get_nonrecursive_dependents: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the parents of [n] */ -let get_parents: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the ancestors of [n] which are also heirs of [n] */ -let get_recursive_dependents: t => Typ.Procname.t => Typ.Procname.Set.t; - - -/** Return the path of the source file */ -let get_source: t => SourceFile.t; - - -/** Load a call graph from a file */ -let load_from_file: DB.filename => option t; - - -/** Returns true if the node is defined */ -let node_defined: t => Typ.Procname.t => bool; - - -/** Remove the defined flag from a node, if it exists. */ -let remove_node_defined: t => Typ.Procname.t => unit; - - -/** Print the call graph as a dotty file. */ -let save_call_graph_dotty: SourceFile.t => t => unit; - - -/** Save a call graph into a file */ -let store_to_file: DB.filename => t => unit; diff --git a/infer/src/IR/Const.ml b/infer/src/IR/Const.ml new file mode 100644 index 000000000..90b177f5b --- /dev/null +++ b/infer/src/IR/Const.ml @@ -0,0 +1,69 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Constants *) +open! IStd +module L = Logging +module F = Format + +type t = + | Cint of IntLit.t (** integer constants *) + | Cfun of Typ.Procname.t (** function names *) + | Cstr of string (** string constants *) + | Cfloat of float (** float constants *) + | Cclass of Ident.name (** class constant *) + [@@deriving compare] + +let equal = [%compare.equal : t] + +let kind_equal c1 c2 = + let const_kind_number = function + | Cint _ + -> 1 + | Cfun _ + -> 2 + | Cstr _ + -> 3 + | Cfloat _ + -> 4 + | Cclass _ + -> 5 + in + Int.equal (const_kind_number c1) (const_kind_number c2) + +let pp pe f = function + | Cint i + -> IntLit.pp f i + | Cfun fn -> ( + match pe.Pp.kind with + | HTML + -> F.fprintf f "_fun_%s" (Escape.escape_xml (Typ.Procname.to_string fn)) + | _ + -> F.fprintf f "_fun_%s" (Typ.Procname.to_string fn) ) + | Cstr s + -> F.fprintf f "\"%s\"" (String.escaped s) + | Cfloat v + -> F.fprintf f "%f" v + | Cclass c + -> F.fprintf f "%a" Ident.pp_name c + +let to_string c = F.asprintf "%a" (pp Pp.text) c + +let iszero_int_float = function Cint i -> IntLit.iszero i | Cfloat 0.0 -> true | _ -> false + +let isone_int_float = function Cint i -> IntLit.isone i | Cfloat 1.0 -> true | _ -> false + +let isminusone_int_float = function + | Cint i + -> IntLit.isminusone i + | Cfloat -1.0 + -> true + | _ + -> false diff --git a/infer/src/IR/Const.mli b/infer/src/IR/Const.mli new file mode 100644 index 000000000..1b53b8b33 --- /dev/null +++ b/infer/src/IR/Const.mli @@ -0,0 +1,42 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Constants *) +open! IStd +module L = Logging +module F = Format + +(** Constants *) + +type t = + | Cint of IntLit.t (** integer constants *) + | Cfun of Typ.Procname.t (** function names *) + | Cstr of string (** string constants *) + | Cfloat of float (** float constants *) + | Cclass of Ident.name (** class constant *) + [@@deriving compare] + +val equal : t -> t -> bool + +(** Return true if the constants have the same kind (both integers, ...) *) + +val kind_equal : t -> t -> bool + +(** Pretty print a const *) + +val pp : Pp.env -> F.formatter -> t -> unit + +val to_string : t -> string + +val iszero_int_float : t -> bool + +val isone_int_float : t -> bool + +val isminusone_int_float : t -> bool diff --git a/infer/src/IR/Const.re b/infer/src/IR/Const.re deleted file mode 100644 index 172983aca..000000000 --- a/infer/src/IR/Const.re +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Constants */ -module L = Logging; - -module F = Format; - -type t = - | Cint IntLit.t /** integer constants */ - | Cfun Typ.Procname.t /** function names */ - | Cstr string /** string constants */ - | Cfloat float /** float constants */ - | Cclass Ident.name /** class constant */ -[@@deriving compare]; - -let equal = [%compare.equal : t]; - -let kind_equal c1 c2 => { - let const_kind_number = - fun - | Cint _ => 1 - | Cfun _ => 2 - | Cstr _ => 3 - | Cfloat _ => 4 - | Cclass _ => 5; - Int.equal (const_kind_number c1) (const_kind_number c2) -}; - -let pp pe f => - fun - | Cint i => IntLit.pp f i - | Cfun fn => - switch pe.Pp.kind { - | HTML => F.fprintf f "_fun_%s" (Escape.escape_xml (Typ.Procname.to_string fn)) - | _ => F.fprintf f "_fun_%s" (Typ.Procname.to_string fn) - } - | Cstr s => F.fprintf f "\"%s\"" (String.escaped s) - | Cfloat v => F.fprintf f "%f" v - | Cclass c => F.fprintf f "%a" Ident.pp_name c; - -let to_string c => F.asprintf "%a" (pp Pp.text) c; - -let iszero_int_float = - fun - | Cint i => IntLit.iszero i - | Cfloat 0.0 => true - | _ => false; - -let isone_int_float = - fun - | Cint i => IntLit.isone i - | Cfloat 1.0 => true - | _ => false; - -let isminusone_int_float = - fun - | Cint i => IntLit.isminusone i - | Cfloat (-1.0) => true - | _ => false; diff --git a/infer/src/IR/Const.rei b/infer/src/IR/Const.rei deleted file mode 100644 index 5cafaf5a9..000000000 --- a/infer/src/IR/Const.rei +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Constants */ -module L = Logging; - -module F = Format; - - -/** Constants */ -type t = - | Cint IntLit.t /** integer constants */ - | Cfun Typ.Procname.t /** function names */ - | Cstr string /** string constants */ - | Cfloat float /** float constants */ - | Cclass Ident.name /** class constant */ -[@@deriving compare]; - -let equal: t => t => bool; - - -/** Return true if the constants have the same kind (both integers, ...) */ -let kind_equal: t => t => bool; - - -/** Pretty print a const */ -let pp: Pp.env => F.formatter => t => unit; - -let to_string: t => string; - -let iszero_int_float: t => bool; - -let isone_int_float: t => bool; - -let isminusone_int_float: t => bool; diff --git a/infer/src/IR/DecompiledExp.ml b/infer/src/IR/DecompiledExp.ml new file mode 100644 index 000000000..4c3a8807f --- /dev/null +++ b/infer/src/IR/DecompiledExp.ml @@ -0,0 +1,138 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Decompiled Expressions *) +open! IStd +module L = Logging +module F = Format + +(** expression representing the result of decompilation *) +type t = + | Darray of t * t + | Dbinop of Binop.t * t * t + | Dconst of Const.t + | Dsizeof of Typ.t * t option * Subtype.t + | Dderef of t + | Dfcall of t * t list * Location.t * CallFlags.t + | Darrow of t * Typ.Fieldname.t + | Ddot of t * Typ.Fieldname.t + | Dpvar of Pvar.t + | Dpvaraddr of Pvar.t + | Dunop of Unop.t * t + | Dunknown + | Dretcall of t * t list * Location.t * CallFlags.t + +(** Value paths: identify an occurrence of a value in a symbolic heap + each expression represents a path, with Dpvar being the simplest one *) +type vpath = t option + +let java () = Config.equal_language !Config.curr_language Config.Java + +let eradicate_java () = Config.eradicate && java () + +(** convert a dexp to a string *) +let rec to_string = function + | Darray (de1, de2) + -> to_string de1 ^ "[" ^ to_string de2 ^ "]" + | Dbinop (op, de1, de2) + -> "(" ^ to_string de1 ^ Binop.str Pp.text op ^ to_string de2 ^ ")" + | Dconst Cfun pn + -> Typ.Procname.to_simplified_string pn + | Dconst c + -> Const.to_string c + | Dderef de + -> "*" ^ to_string de + | Dfcall (fun_dexp, args, _, {cf_virtual= isvirtual}) + -> let pp_arg fmt de = F.fprintf fmt "%s" (to_string de) in + let pp_args fmt des = + if eradicate_java () then ( if des <> [] then F.fprintf fmt "..." ) + else Pp.comma_seq pp_arg fmt des + in + let pp_fun fmt = function + | Dconst Cfun pname + -> let s = + match pname with + | Typ.Procname.Java pname_java + -> Typ.Procname.java_get_method pname_java + | _ + -> Typ.Procname.to_string pname + in + F.fprintf fmt "%s" s + | de + -> F.fprintf fmt "%s" (to_string de) + in + let receiver, args' = + match args with + | (Dpvar pv) :: args' when isvirtual && Pvar.is_this pv + -> (None, args') + | a :: args' when isvirtual + -> (Some a, args') + | _ + -> (None, args) + in + let pp fmt = + let pp_receiver fmt = function None -> () | Some arg -> F.fprintf fmt "%a." pp_arg arg in + F.fprintf fmt "%a%a(%a)" pp_receiver receiver pp_fun fun_dexp pp_args args' + in + F.asprintf "%t" pp + | Darrow (Dpvar pv, f) when Pvar.is_this pv + -> (* this->fieldname *) + Typ.Fieldname.to_simplified_string f + | Darrow (de, f) + -> if Typ.Fieldname.is_hidden f then to_string de + else if java () then to_string de ^ "." ^ Typ.Fieldname.to_flat_string f + else to_string de ^ "->" ^ Typ.Fieldname.to_string f + | Ddot (Dpvar _, fe) when eradicate_java () + -> (* static field access *) + Typ.Fieldname.to_simplified_string fe + | Ddot (de, f) + -> if Typ.Fieldname.is_hidden f then "&" ^ to_string de + else if java () then to_string de ^ "." ^ Typ.Fieldname.to_flat_string f + else to_string de ^ "." ^ Typ.Fieldname.to_string f + | Dpvar pv + -> Mangled.to_string (Pvar.get_name pv) + | Dpvaraddr pv + -> let s = + if eradicate_java () then Pvar.get_simplified_name pv + else Mangled.to_string (Pvar.get_name pv) + in + let ampersand = if eradicate_java () then "" else "&" in + ampersand ^ s + | Dunop (op, de) + -> Unop.str op ^ to_string de + | Dsizeof (typ, _, _) + -> F.asprintf "%a" (Typ.pp_full Pp.text) typ + | Dunknown + -> "unknown" + | Dretcall (de, _, _, _) + -> "returned by " ^ to_string de + +(** Pretty print a dexp. *) +let pp fmt de = F.fprintf fmt "%s" (to_string de) + +(** Pretty print a value path *) +let pp_vpath pe fmt vpath = + let pp fmt = function Some de -> pp fmt de | None -> () in + if Pp.equal_print_kind pe.Pp.kind Pp.HTML then + F.fprintf fmt " %a{vpath: %a}%a" Io_infer.Html.pp_start_color Pp.Orange pp vpath + Io_infer.Html.pp_end_color () + else F.fprintf fmt "%a" pp vpath + +let rec has_tmp_var = function + | Dpvar pvar | Dpvaraddr pvar + -> Pvar.is_frontend_tmp pvar + | Dderef dexp | Ddot (dexp, _) | Darrow (dexp, _) | Dunop (_, dexp) | Dsizeof (_, Some dexp, _) + -> has_tmp_var dexp + | Darray (dexp1, dexp2) | Dbinop (_, dexp1, dexp2) + -> has_tmp_var dexp1 || has_tmp_var dexp2 + | Dretcall (dexp, dexp_list, _, _) | Dfcall (dexp, dexp_list, _, _) + -> has_tmp_var dexp || List.exists ~f:has_tmp_var dexp_list + | Dconst _ | Dunknown | Dsizeof (_, None, _) + -> false diff --git a/infer/src/IR/DecompiledExp.mli b/infer/src/IR/DecompiledExp.mli new file mode 100644 index 000000000..92da9c869 --- /dev/null +++ b/infer/src/IR/DecompiledExp.mli @@ -0,0 +1,52 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Decompiled Expressions *) +open! IStd +module L = Logging +module F = Format + +(** expression representing the result of decompilation *) + +type t = + | Darray of t * t + | Dbinop of Binop.t * t * t + | Dconst of Const.t + | Dsizeof of Typ.t * t option * Subtype.t + | Dderef of t + | Dfcall of t * t list * Location.t * CallFlags.t + | Darrow of t * Typ.Fieldname.t + | Ddot of t * Typ.Fieldname.t + | Dpvar of Pvar.t + | Dpvaraddr of Pvar.t + | Dunop of Unop.t * t + | Dunknown + | Dretcall of t * t list * Location.t * CallFlags.t + +(** Value paths: identify an occurrence of a value in a symbolic heap + each expression represents a path, with Dpvar being the simplest one *) + +type vpath = t option + +(** convert to a string *) + +val to_string : t -> string + +(** pretty print *) + +val pp : F.formatter -> t -> unit + +(** Pretty print a value path *) + +val pp_vpath : Pp.env -> F.formatter -> vpath -> unit + +(** return true if [dexp] contains a temporary pvar *) + +val has_tmp_var : t -> bool diff --git a/infer/src/IR/DecompiledExp.re b/infer/src/IR/DecompiledExp.re deleted file mode 100644 index b74de34f3..000000000 --- a/infer/src/IR/DecompiledExp.re +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Decompiled Expressions */ -module L = Logging; - -module F = Format; - - -/** expression representing the result of decompilation */ -type t = - | Darray t t - | Dbinop Binop.t t t - | Dconst Const.t - | Dsizeof Typ.t (option t) Subtype.t - | Dderef t - | Dfcall t (list t) Location.t CallFlags.t - | Darrow t Typ.Fieldname.t - | Ddot t Typ.Fieldname.t - | Dpvar Pvar.t - | Dpvaraddr Pvar.t - | Dunop Unop.t t - | Dunknown - | Dretcall t (list t) Location.t CallFlags.t; - - -/** Value paths: identify an occurrence of a value in a symbolic heap - each expression represents a path, with Dpvar being the simplest one */ -type vpath = option t; - -let java () => Config.equal_language !Config.curr_language Config.Java; - -let eradicate_java () => Config.eradicate && java (); - - -/** convert a dexp to a string */ -let rec to_string = - fun - | Darray de1 de2 => to_string de1 ^ "[" ^ to_string de2 ^ "]" - | Dbinop op de1 de2 => "(" ^ to_string de1 ^ Binop.str Pp.text op ^ to_string de2 ^ ")" - | Dconst (Cfun pn) => Typ.Procname.to_simplified_string pn - | Dconst c => Const.to_string c - | Dderef de => "*" ^ to_string de - | Dfcall fun_dexp args _ {cf_virtual: isvirtual} => { - let pp_arg fmt de => F.fprintf fmt "%s" (to_string de); - let pp_args fmt des => - if (eradicate_java ()) { - if (des != []) { - F.fprintf fmt "..." - } - } else { - Pp.comma_seq pp_arg fmt des - }; - let pp_fun fmt => ( - fun - | Dconst (Cfun pname) => { - let s = - switch pname { - | Typ.Procname.Java pname_java => Typ.Procname.java_get_method pname_java - | _ => Typ.Procname.to_string pname - }; - F.fprintf fmt "%s" s - } - | de => F.fprintf fmt "%s" (to_string de) - ); - let (receiver, args') = - switch args { - | [Dpvar pv, ...args'] when isvirtual && Pvar.is_this pv => (None, args') - | [a, ...args'] when isvirtual => (Some a, args') - | _ => (None, args) - }; - let pp fmt => { - let pp_receiver fmt => ( - fun - | None => () - | Some arg => F.fprintf fmt "%a." pp_arg arg - ); - F.fprintf fmt "%a%a(%a)" pp_receiver receiver pp_fun fun_dexp pp_args args' - }; - F.asprintf "%t" pp - } - | Darrow (Dpvar pv) f when Pvar.is_this pv => - /* this->fieldname */ - Typ.Fieldname.to_simplified_string f - | Darrow de f => - if (Typ.Fieldname.is_hidden f) { - to_string de - } else if (java ()) { - to_string de ^ "." ^ Typ.Fieldname.to_flat_string f - } else { - to_string de ^ "->" ^ Typ.Fieldname.to_string f - } - | Ddot (Dpvar _) fe when eradicate_java () => - /* static field access */ - Typ.Fieldname.to_simplified_string fe - | Ddot de f => - if (Typ.Fieldname.is_hidden f) { - "&" ^ to_string de - } else if (java ()) { - to_string de ^ "." ^ Typ.Fieldname.to_flat_string f - } else { - to_string de ^ "." ^ Typ.Fieldname.to_string f - } - | Dpvar pv => Mangled.to_string (Pvar.get_name pv) - | Dpvaraddr pv => { - let s = - if (eradicate_java ()) { - Pvar.get_simplified_name pv - } else { - Mangled.to_string (Pvar.get_name pv) - }; - let ampersand = - if (eradicate_java ()) { - "" - } else { - "&" - }; - ampersand ^ s - } - | Dunop op de => Unop.str op ^ to_string de - | Dsizeof typ _ _ => F.asprintf "%a" (Typ.pp_full Pp.text) typ - | Dunknown => "unknown" - | Dretcall de _ _ _ => "returned by " ^ to_string de; - - -/** Pretty print a dexp. */ -let pp fmt de => F.fprintf fmt "%s" (to_string de); - - -/** Pretty print a value path */ -let pp_vpath pe fmt vpath => { - let pp fmt => - fun - | Some de => pp fmt de - | None => (); - if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) { - F.fprintf - fmt - " %a{vpath: %a}%a" - Io_infer.Html.pp_start_color - Pp.Orange - pp - vpath - Io_infer.Html.pp_end_color - () - } else { - F.fprintf fmt "%a" pp vpath - } -}; - -let rec has_tmp_var = - fun - | Dpvar pvar - | Dpvaraddr pvar => Pvar.is_frontend_tmp pvar - | Dderef dexp - | Ddot dexp _ - | Darrow dexp _ - | Dunop _ dexp - | Dsizeof _ (Some dexp) _ => has_tmp_var dexp - | Darray dexp1 dexp2 - | Dbinop _ dexp1 dexp2 => has_tmp_var dexp1 || has_tmp_var dexp2 - | Dretcall dexp dexp_list _ _ - | Dfcall dexp dexp_list _ _ => has_tmp_var dexp || List.exists f::has_tmp_var dexp_list - | Dconst _ - | Dunknown - | Dsizeof _ None _ => false; diff --git a/infer/src/IR/DecompiledExp.rei b/infer/src/IR/DecompiledExp.rei deleted file mode 100644 index d3470db90..000000000 --- a/infer/src/IR/DecompiledExp.rei +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Decompiled Expressions */ -module L = Logging; - -module F = Format; - - -/** expression representing the result of decompilation */ -type t = - | Darray t t - | Dbinop Binop.t t t - | Dconst Const.t - | Dsizeof Typ.t (option t) Subtype.t - | Dderef t - | Dfcall t (list t) Location.t CallFlags.t - | Darrow t Typ.Fieldname.t - | Ddot t Typ.Fieldname.t - | Dpvar Pvar.t - | Dpvaraddr Pvar.t - | Dunop Unop.t t - | Dunknown - | Dretcall t (list t) Location.t CallFlags.t; - - -/** Value paths: identify an occurrence of a value in a symbolic heap - each expression represents a path, with Dpvar being the simplest one */ -type vpath = option t; - - -/** convert to a string */ -let to_string: t => string; - - -/** pretty print */ -let pp: F.formatter => t => unit; - - -/** Pretty print a value path */ -let pp_vpath: Pp.env => F.formatter => vpath => unit; - - -/** return true if [dexp] contains a temporary pvar */ -let has_tmp_var: t => bool; diff --git a/infer/src/IR/Errlog.ml b/infer/src/IR/Errlog.ml index b21436ad3..ce3b8ceee 100644 --- a/infer/src/IR/Errlog.ml +++ b/infer/src/IR/Errlog.ml @@ -9,7 +9,6 @@ open! IStd module Hashtbl = Caml.Hashtbl - module L = Logging module F = Format @@ -20,28 +19,28 @@ type node_tag = | Procedure_end of Typ.Procname.t (** Element of a loc trace *) -type loc_trace_elem = { - lt_level : int; (** nesting level of procedure calls *) - lt_loc : Location.t; (** source location at the current step in the trace *) - lt_description : string; (** description of the current step in the trace *) - lt_node_tags : node_tag list (** tags describing the node at the current location *) -} +type loc_trace_elem = + { lt_level: int (** nesting level of procedure calls *) + ; lt_loc: Location.t (** source location at the current step in the trace *) + ; lt_description: string (** description of the current step in the trace *) + ; lt_node_tags: node_tag list (** tags describing the node at the current location *) } -let pp_loc_trace_elem fmt { lt_level; lt_loc; } = - F.fprintf fmt "%d %a" lt_level Location.pp lt_loc +let pp_loc_trace_elem fmt {lt_level; lt_loc} = F.fprintf fmt "%d %a" lt_level Location.pp lt_loc -let pp_loc_trace fmt l = - PrettyPrintable.pp_collection ~pp_item:pp_loc_trace_elem fmt l +let pp_loc_trace fmt l = PrettyPrintable.pp_collection ~pp_item:pp_loc_trace_elem fmt l let contains_exception loc_trace_elem = let pred nt = match nt with - | Exception _ -> true - | Condition _ | Procedure_start _ | Procedure_end _ -> false in + | Exception _ + -> true + | Condition _ | Procedure_start _ | Procedure_end _ + -> false + in List.exists ~f:pred loc_trace_elem.lt_node_tags let make_trace_element lt_level lt_loc lt_description lt_node_tags = - { lt_level; lt_loc; lt_description; lt_node_tags } + {lt_level; lt_loc; lt_description; lt_node_tags} (** Trace of locations *) type loc_trace = loc_trace_elem list @@ -49,56 +48,55 @@ type loc_trace = loc_trace_elem list let compute_local_exception_line loc_trace = let compute_local_exception_line state step = match state with - | `Stop _ -> state - | `Continue (last_known_step_at_level_zero_opt, line_opt) -> - let last_known_step_at_level_zero_opt' = - if Int.equal step.lt_level 0 then Some step - else last_known_step_at_level_zero_opt in + | `Stop _ + -> state + | `Continue (last_known_step_at_level_zero_opt, line_opt) + -> let last_known_step_at_level_zero_opt' = + if Int.equal step.lt_level 0 then Some step else last_known_step_at_level_zero_opt + in match last_known_step_at_level_zero_opt' with - | Some step_zero when contains_exception step -> - `Stop (last_known_step_at_level_zero_opt', Some step_zero.lt_loc.line) - | _ -> `Continue (last_known_step_at_level_zero_opt', line_opt) in + | Some step_zero when contains_exception step + -> `Stop (last_known_step_at_level_zero_opt', Some step_zero.lt_loc.line) + | _ + -> `Continue (last_known_step_at_level_zero_opt', line_opt) + in snd (List_.fold_until ~init:(`Continue (None, None)) ~f:compute_local_exception_line loc_trace) -type node_id_key = { - node_id : int; - node_key : int -} +type node_id_key = {node_id: int; node_key: int} -type err_key = { - err_kind : Exceptions.err_kind; - in_footprint : bool; - err_name : Localise.t; - err_desc : Localise.error_desc; - severity : string -}[@@deriving compare] +type err_key = + { err_kind: Exceptions.err_kind + ; in_footprint: bool + ; err_name: Localise.t + ; err_desc: Localise.error_desc + ; severity: string } + [@@deriving compare] (** Data associated to a specific error *) -type err_data = { - node_id_key : node_id_key; - session : int; - loc : Location.t; - loc_in_ml_source : L.ml_loc option; - loc_trace : loc_trace; - err_class : Exceptions.err_class; - visibility : Exceptions.visibility; - linters_def_file : string option; - doc_url : string option; -} - -let compare_err_data err_data1 err_data2 = - Location.compare err_data1.loc err_data2.loc +type err_data = + { node_id_key: node_id_key + ; session: int + ; loc: Location.t + ; loc_in_ml_source: L.ml_loc option + ; loc_trace: loc_trace + ; err_class: Exceptions.err_class + ; visibility: Exceptions.visibility + ; linters_def_file: string option + ; doc_url: string option } + +let compare_err_data err_data1 err_data2 = Location.compare err_data1.loc err_data2.loc module ErrDataSet = (* set err_data with no repeated loc *) - Caml.Set.Make(struct - type t = err_data - let compare = compare_err_data - end) +Caml.Set.Make (struct + type t = err_data + + let compare = compare_err_data +end) (** Hash table to implement error logs *) module ErrLogHash = struct module Key = struct - type t = err_key[@@deriving compare] + type t = err_key [@@deriving compare] (* NOTE: changing the hash function can change the order in which issues are reported. *) let hash key = @@ -108,10 +106,10 @@ module ErrLogHash = struct let equal key1 key2 = [%compare.equal : Exceptions.err_kind * bool * Localise.t] (key1.err_kind, key1.in_footprint, key1.err_name) - (key2.err_kind, key2.in_footprint, key2.err_name) && - Localise.error_desc_equal key1.err_desc key2.err_desc - + (key2.err_kind, key2.in_footprint, key2.err_name) + && Localise.error_desc_equal key1.err_desc key2.err_desc end + include Hashtbl.Make (Key) end @@ -122,7 +120,7 @@ type t = ErrDataSet.t ErrLogHash.t let compare x y = let bindings x = ErrLogHash.fold (fun k d l -> (k, d) :: l) x [] in - [%compare: (ErrLogHash.Key.t * ErrDataSet.t) list] (bindings x) (bindings y) + [%compare : (ErrLogHash.Key.t * ErrDataSet.t) list] (bindings x) (bindings y) (** Empty error log *) let empty () = ErrLogHash.create 13 @@ -132,67 +130,71 @@ type iter_fun = err_key -> err_data -> unit (** Apply f to nodes and error names *) let iter (f: iter_fun) (err_log: t) = - ErrLogHash.iter (fun err_key set -> - ErrDataSet.iter (fun err_data -> f err_key err_data) set) + ErrLogHash.iter + (fun err_key set -> ErrDataSet.iter (fun err_data -> f err_key err_data) set) err_log (** Return the number of elements in the error log which satisfy [filter] *) let size filter (err_log: t) = let count = ref 0 in - ErrLogHash.iter (fun key err_datas -> - if filter key.err_kind key.in_footprint - then count := !count + (ErrDataSet.cardinal err_datas)) err_log; + ErrLogHash.iter + (fun key err_datas -> + if filter key.err_kind key.in_footprint then count := !count + ErrDataSet.cardinal err_datas) + err_log ; !count (** Print errors from error log *) -let pp_errors fmt (errlog : t) = +let pp_errors fmt (errlog: t) = let f key _ = if Exceptions.equal_err_kind key.err_kind Exceptions.Kerror then - F.fprintf fmt "%a@ " Localise.pp key.err_name in + F.fprintf fmt "%a@ " Localise.pp key.err_name + in ErrLogHash.iter f errlog (** Print warnings from error log *) -let pp_warnings fmt (errlog : t) = +let pp_warnings fmt (errlog: t) = let f key _ = if Exceptions.equal_err_kind key.err_kind Exceptions.Kwarning then - F.fprintf fmt "%a %a@ " Localise.pp key.err_name Localise.pp_error_desc key.err_desc in + F.fprintf fmt "%a %a@ " Localise.pp key.err_name Localise.pp_error_desc key.err_desc + in ErrLogHash.iter f errlog (** Print an error log in html format *) let pp_html source path_to_root fmt (errlog: t) = let pp_eds fmt err_datas = - let pp_nodeid_session_loc - fmt err_data = - Io_infer.Html.pp_session_link - source path_to_root fmt - (err_data.node_id_key.node_id, err_data.session, err_data.loc.Location.line) in - ErrDataSet.iter (pp_nodeid_session_loc fmt) err_datas in + let pp_nodeid_session_loc fmt err_data = + Io_infer.Html.pp_session_link source path_to_root fmt + (err_data.node_id_key.node_id, err_data.session, err_data.loc.Location.line) + in + ErrDataSet.iter (pp_nodeid_session_loc fmt) err_datas + in let pp_err_log do_fp ek key err_datas = - if Exceptions.equal_err_kind key.err_kind ek && Bool.equal do_fp key.in_footprint - then - F.fprintf fmt "
%a %a %a" - Localise.pp key.err_name - Localise.pp_error_desc key.err_desc - pp_eds err_datas in - F.fprintf fmt "%aERRORS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); - ErrLogHash.iter (pp_err_log true Exceptions.Kerror) errlog; - F.fprintf fmt "%aERRORS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); - ErrLogHash.iter (pp_err_log false Exceptions.Kerror) errlog; - F.fprintf fmt "%aWARNINGS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); - ErrLogHash.iter (pp_err_log true Exceptions.Kwarning) errlog; - F.fprintf fmt "%aWARNINGS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); - ErrLogHash.iter (pp_err_log false Exceptions.Kwarning) errlog; - F.fprintf fmt "%aINFOS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); - ErrLogHash.iter (pp_err_log true Exceptions.Kinfo) errlog; - F.fprintf fmt "%aINFOS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); + if Exceptions.equal_err_kind key.err_kind ek && Bool.equal do_fp key.in_footprint then + F.fprintf fmt "
%a %a %a" Localise.pp key.err_name Localise.pp_error_desc key.err_desc + pp_eds err_datas + in + F.fprintf fmt "%aERRORS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ; + ErrLogHash.iter (pp_err_log true Exceptions.Kerror) errlog ; + F.fprintf fmt "%aERRORS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ; + ErrLogHash.iter (pp_err_log false Exceptions.Kerror) errlog ; + F.fprintf fmt "%aWARNINGS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ; + ErrLogHash.iter (pp_err_log true Exceptions.Kwarning) errlog ; + F.fprintf fmt "%aWARNINGS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ; + ErrLogHash.iter (pp_err_log false Exceptions.Kwarning) errlog ; + F.fprintf fmt "%aINFOS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ; + ErrLogHash.iter (pp_err_log true Exceptions.Kinfo) errlog ; + F.fprintf fmt "%aINFOS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ; ErrLogHash.iter (pp_err_log false Exceptions.Kinfo) errlog - (* I use string in case we want to display a different name to the user*) -let severity_to_str severity = match severity with - | Exceptions.High -> "HIGH" - | Exceptions.Medium -> "MEDIUM" - | Exceptions.Low -> "LOW" +let severity_to_str severity = + match severity with + | Exceptions.High + -> "HIGH" + | Exceptions.Medium + -> "MEDIUM" + | Exceptions.Low + -> "LOW" (** Add an error description to the error log unless there is one already at the same node + session; return true if added *) @@ -200,121 +202,120 @@ let add_issue tbl err_key (err_datas: ErrDataSet.t) : bool = try let current_eds = ErrLogHash.find tbl err_key in if ErrDataSet.subset err_datas current_eds then false - else - begin - ErrLogHash.replace tbl err_key (ErrDataSet.union err_datas current_eds); - true - end - with Not_found -> - begin - ErrLogHash.add tbl err_key err_datas; - true - end + else ( + ErrLogHash.replace tbl err_key (ErrDataSet.union err_datas current_eds) ; + true ) + with Not_found -> ErrLogHash.add tbl err_key err_datas ; true (** Update an old error log with a new one *) let update errlog_old errlog_new = - ErrLogHash.iter - (fun err_key l -> - ignore (add_issue errlog_old err_key l)) errlog_new + ErrLogHash.iter (fun err_key l -> ignore (add_issue errlog_old err_key l)) errlog_new let log_issue err_kind err_log loc (node_id, node_key) session ltr ?linters_def_file ?doc_url exn = let err_name, err_desc, ml_loc_opt, visibility, severity, force_kind, eclass = - Exceptions.recognize_exception exn in - let err_kind = match force_kind with - | Some err_kind -> err_kind - | _ -> err_kind in - let hide_java_loc_zero = (* hide java errors at location zero unless in -developer_mode *) - not Config.developer_mode && - Config.curr_language_is Config.Java && - Int.equal loc.Location.line 0 in + Exceptions.recognize_exception exn + in + let err_kind = match force_kind with Some err_kind -> err_kind | _ -> err_kind in + let hide_java_loc_zero = + (* hide java errors at location zero unless in -developer_mode *) + not Config.developer_mode && Config.curr_language_is Config.Java + && Int.equal loc.Location.line 0 + in let hide_memory_error = match Localise.error_desc_get_bucket err_desc with - | Some bucket when String.equal bucket Mleak_buckets.ml_bucket_unknown_origin -> - not Mleak_buckets.should_raise_leak_unknown_origin - | _ -> false in + | Some bucket when String.equal bucket Mleak_buckets.ml_bucket_unknown_origin + -> not Mleak_buckets.should_raise_leak_unknown_origin + | _ + -> false + in let log_it = - Exceptions.equal_visibility visibility Exceptions.Exn_user || - (Config.developer_mode && - Exceptions.equal_visibility visibility Exceptions.Exn_developer) in - if log_it && not hide_java_loc_zero && not hide_memory_error then begin + Exceptions.equal_visibility visibility Exceptions.Exn_user + || Config.developer_mode && Exceptions.equal_visibility visibility Exceptions.Exn_developer + in + if log_it && not hide_java_loc_zero && not hide_memory_error then let added = let node_id_key = {node_id; node_key} in - let err_data = { - node_id_key; - session; - loc; - loc_in_ml_source = ml_loc_opt; - loc_trace = ltr; - err_class = eclass; - visibility; - linters_def_file; - doc_url; - } in - let err_key = { - err_kind; - in_footprint = !Config.footprint; - err_name; - err_desc; - severity = severity_to_str severity - } in - add_issue err_log err_key (ErrDataSet.singleton err_data) in - let should_print_now = - match exn with - | Exceptions.Internal_error _ -> true - | _ -> added in + let err_data = + { node_id_key + ; session + ; loc + ; loc_in_ml_source= ml_loc_opt + ; loc_trace= ltr + ; err_class= eclass + ; visibility + ; linters_def_file + ; doc_url } + in + let err_key = + { err_kind + ; in_footprint= !Config.footprint + ; err_name + ; err_desc + ; severity= severity_to_str severity } + in + add_issue err_log err_key (ErrDataSet.singleton err_data) + in + let should_print_now = match exn with Exceptions.Internal_error _ -> true | _ -> added in let print_now () = let ex_name, desc, ml_loc_opt, _, _, _, _ = Exceptions.recognize_exception exn in - L.(debug Analysis Medium) "@\n%a@\n@?" - (Exceptions.pp_err ~node_key loc err_kind ex_name desc ml_loc_opt) (); - if err_kind <> Exceptions.Kerror then begin + L.(debug Analysis Medium) + "@\n%a@\n@?" (Exceptions.pp_err ~node_key loc err_kind ex_name desc ml_loc_opt) () ; + if err_kind <> Exceptions.Kerror then let warn_str = let pp fmt = - Format.fprintf fmt "%s %a" - (Localise.to_issue_id err_name) - Localise.pp_error_desc desc in - F.asprintf "%t" pp in - let d = match err_kind with - | Exceptions.Kerror -> L.d_error - | Exceptions.Kwarning -> L.d_warning - | Exceptions.Kinfo - | Exceptions.Kadvice - | Exceptions.Klike -> L.d_info in - d warn_str; L.d_ln(); - end in + Format.fprintf fmt "%s %a" (Localise.to_issue_id err_name) Localise.pp_error_desc desc + in + F.asprintf "%t" pp + in + let d = + match err_kind with + | Exceptions.Kerror + -> L.d_error + | Exceptions.Kwarning + -> L.d_warning + | Exceptions.Kinfo | Exceptions.Kadvice | Exceptions.Klike + -> L.d_info + in + d warn_str ; L.d_ln () + in if should_print_now then print_now () - end type err_log = t (** Global per-file error table *) module Err_table = struct type t = err_log + let create = empty - let count_err err_table err_name locs = - ignore (add_issue err_table err_name locs) + let count_err err_table err_name locs = ignore (add_issue err_table err_name locs) - let table_size filter (err_table: t) = - size filter err_table + let table_size filter (err_table: t) = size filter err_table let pp_stats_footprint ekind fmt (err_table: err_log) = - let err_name_map = ref String.Map.empty in (* map error name to count *) + let err_name_map = ref String.Map.empty in + (* map error name to count *) let count_err (err_name: Localise.t) n = let err_string = Localise.to_issue_id err_name in - let count = try String.Map.find_exn !err_name_map err_string with Not_found -> 0 in - err_name_map := String.Map.add ~key:err_string ~data:(count + n) !err_name_map in + let count = + try String.Map.find_exn !err_name_map err_string + with Not_found -> 0 + in + err_name_map := String.Map.add ~key:err_string ~data:(count + n) !err_name_map + in let count key err_datas = - if Exceptions.equal_err_kind ekind key.err_kind && key.in_footprint - then count_err key.err_name (ErrDataSet.cardinal err_datas) in - ErrLogHash.iter count err_table; + if Exceptions.equal_err_kind ekind key.err_kind && key.in_footprint then + count_err key.err_name (ErrDataSet.cardinal err_datas) + in + ErrLogHash.iter count err_table ; let pp ~key:err_string ~data:count = F.fprintf fmt " %s:%d" err_string count in String.Map.iteri ~f:pp !err_name_map - module LocMap = - Caml.Map.Make(struct - type t = ErrDataSet.elt - let compare = compare_err_data - end) + module LocMap = Caml.Map.Make (struct + type t = ErrDataSet.elt + + let compare = compare_err_data + end) let print_err_table_details fmt err_table = let map_err_fp = ref LocMap.empty in @@ -325,40 +326,53 @@ module Err_table = struct let map_advice = ref LocMap.empty in let map_likes = ref LocMap.empty in let add_err nslm key = - let map = match key.in_footprint, key.err_kind with - | true, Exceptions.Kerror -> map_err_fp - | false, Exceptions.Kerror -> map_err_re - | true, Exceptions.Kwarning -> map_warn_fp - | false, Exceptions.Kwarning -> map_warn_re - | _, Exceptions.Kinfo -> map_info - | _, Exceptions.Kadvice -> map_advice - | _, Exceptions.Klike -> map_likes in + let map = + match (key.in_footprint, key.err_kind) with + | true, Exceptions.Kerror + -> map_err_fp + | false, Exceptions.Kerror + -> map_err_re + | true, Exceptions.Kwarning + -> map_warn_fp + | false, Exceptions.Kwarning + -> map_warn_re + | _, Exceptions.Kinfo + -> map_info + | _, Exceptions.Kadvice + -> map_advice + | _, Exceptions.Klike + -> map_likes + in try let err_list = LocMap.find nslm !map in map := LocMap.add nslm ((key.err_name, key.err_desc) :: err_list) !map - with Not_found -> - map := LocMap.add nslm [(key.err_name, key.err_desc)] !map in - let f err_name eds = - ErrDataSet.iter (fun loc -> add_err loc err_name) eds in - ErrLogHash.iter f err_table; - + with Not_found -> map := LocMap.add nslm [(key.err_name, key.err_desc)] !map + in + let f err_name eds = ErrDataSet.iter (fun loc -> add_err loc err_name) eds in + ErrLogHash.iter f err_table ; let pp ekind err_data fmt err_names = - List.iter ~f:(fun (err_name, desc) -> - Exceptions.pp_err - ~node_key:err_data.node_id_key.node_key err_data.loc ekind err_name desc - err_data.loc_in_ml_source fmt ()) err_names in - F.fprintf fmt "@.Detailed errors during footprint phase:@."; - LocMap.iter (fun nslm err_names -> - F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) !map_err_fp; - F.fprintf fmt "@.Detailed errors during re-execution phase:@."; - LocMap.iter (fun nslm err_names -> - F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) !map_err_re; - F.fprintf fmt "@.Detailed warnings during footprint phase:@."; - LocMap.iter (fun nslm err_names -> - F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) !map_warn_fp; - F.fprintf fmt "@.Detailed warnings during re-execution phase:@."; - LocMap.iter (fun nslm err_names -> - F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) !map_warn_re + List.iter + ~f:(fun (err_name, desc) -> + Exceptions.pp_err ~node_key:err_data.node_id_key.node_key err_data.loc ekind err_name + desc err_data.loc_in_ml_source fmt ()) + err_names + in + F.fprintf fmt "@.Detailed errors during footprint phase:@." ; + LocMap.iter + (fun nslm err_names -> F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) + !map_err_fp ; + F.fprintf fmt "@.Detailed errors during re-execution phase:@." ; + LocMap.iter + (fun nslm err_names -> F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) + !map_err_re ; + F.fprintf fmt "@.Detailed warnings during footprint phase:@." ; + LocMap.iter + (fun nslm err_names -> F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) + !map_warn_fp ; + F.fprintf fmt "@.Detailed warnings during re-execution phase:@." ; + LocMap.iter + (fun nslm err_names -> F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) + !map_warn_re end type err_table = Err_table.t @@ -367,8 +381,7 @@ type err_table = Err_table.t let create_err_table = Err_table.create (** Print an error log and add it to the global per-file table *) -let extend_table err_table err_log = - ErrLogHash.iter (Err_table.count_err err_table) err_log +let extend_table err_table err_log = ErrLogHash.iter (Err_table.count_err err_table) err_log (** Size of the global per-file error table for the footprint phase *) let err_table_size_footprint ekind = @@ -379,5 +392,4 @@ let err_table_size_footprint ekind = let pp_err_table_stats ekind = Err_table.pp_stats_footprint ekind (** Print details of the global per-file error table *) -let print_err_table_details = - Err_table.print_err_table_details +let print_err_table_details = Err_table.print_err_table_details diff --git a/infer/src/IR/Errlog.mli b/infer/src/IR/Errlog.mli index d500b2e17..3095b6e57 100644 --- a/infer/src/IR/Errlog.mli +++ b/infer/src/IR/Errlog.mli @@ -18,102 +18,98 @@ type node_tag = | Procedure_end of Typ.Procname.t (** Element of a loc trace *) -type loc_trace_elem = private { - lt_level : int; (** nesting level of procedure calls *) - lt_loc : Location.t; (** source location at the current step in the trace *) - lt_description : string; (** description of the current step in the trace *) - lt_node_tags : node_tag list (** tags describing the node at the current location *) -} +type loc_trace_elem = private + { lt_level: int (** nesting level of procedure calls *) + ; lt_loc: Location.t (** source location at the current step in the trace *) + ; lt_description: string (** description of the current step in the trace *) + ; lt_node_tags: node_tag list (** tags describing the node at the current location *) } -(** build a loc_trace_elem from its constituents (unambiguously identified by their types). *) val make_trace_element : int -> Location.t -> string -> node_tag list -> loc_trace_elem +(** build a loc_trace_elem from its constituents (unambiguously identified by their types). *) (** Trace of locations *) type loc_trace = loc_trace_elem list +val compute_local_exception_line : loc_trace -> int option (** Look at all the trace steps and find those that are arising any exception, then bind them to the closest step at level 0. This extra information adds value to the report itself, and may avoid digging into the trace to understand the cause of the report. *) -val compute_local_exception_line : loc_trace -> int option -type node_id_key = private { - node_id : int; - node_key : int -} +type node_id_key = private {node_id: int; node_key: int} -type err_key = private { - err_kind : Exceptions.err_kind; - in_footprint : bool; - err_name : Localise.t; - err_desc : Localise.error_desc; - severity : string -}[@@deriving compare] +type err_key = private + { err_kind: Exceptions.err_kind + ; in_footprint: bool + ; err_name: Localise.t + ; err_desc: Localise.error_desc + ; severity: string } + [@@deriving compare] (** Data associated to a specific error *) -type err_data = private { - node_id_key : node_id_key; - session : int; - loc : Location.t; - loc_in_ml_source : Logging.ml_loc option; - loc_trace : loc_trace; - err_class : Exceptions.err_class; - visibility : Exceptions.visibility; - linters_def_file : string option; - doc_url : string option; (* url to documentation of the issue type *) -} +type err_data = private + { node_id_key: node_id_key + ; session: int + ; loc: Location.t + ; loc_in_ml_source: Logging.ml_loc option + ; loc_trace: loc_trace + ; err_class: Exceptions.err_class + ; visibility: Exceptions.visibility + ; linters_def_file: string option + ; doc_url: string option + (* url to documentation of the issue type *) } (** Type of the error log *) -type t[@@deriving compare] +type t [@@deriving compare] -(** Empty error log *) val empty : unit -> t +(** Empty error log *) (** type of the function to be passed to iter *) type iter_fun = err_key -> err_data -> unit -(** Apply f to nodes and error names *) val iter : iter_fun -> t -> unit +(** Apply f to nodes and error names *) val pp_loc_trace_elem : Format.formatter -> loc_trace_elem -> unit val pp_loc_trace : Format.formatter -> loc_trace -> unit -(** Print errors from error log *) val pp_errors : Format.formatter -> t -> unit +(** Print errors from error log *) -(** Print warnings from error log *) val pp_warnings : Format.formatter -> t -> unit +(** Print warnings from error log *) -(** Print an error log in html format *) val pp_html : SourceFile.t -> DB.Results_dir.path -> Format.formatter -> t -> unit +(** Print an error log in html format *) -(** Return the number of elements in the error log which satisfy the filter. *) val size : (Exceptions.err_kind -> bool -> bool) -> t -> int +(** Return the number of elements in the error log which satisfy the filter. *) -(** Update an old error log with a new one *) val update : t -> t -> unit +(** Update an old error log with a new one *) val log_issue : - Exceptions.err_kind -> t -> Location.t -> (int * int) -> int -> loc_trace -> - ?linters_def_file:string -> ?doc_url:string -> exn -> unit + Exceptions.err_kind -> t -> Location.t -> int * int -> int -> loc_trace + -> ?linters_def_file:string -> ?doc_url:string -> exn -> unit (** {2 Functions for manipulating per-file error tables} *) (** Type for per-file error tables *) type err_table -(** Create an error table *) val create_err_table : unit -> err_table +(** Create an error table *) -(** Add an error log to the global per-file table *) val extend_table : err_table -> t -> unit +(** Add an error log to the global per-file table *) -(** Size of the global per-file error table for the footprint phase *) val err_table_size_footprint : Exceptions.err_kind -> err_table -> int +(** Size of the global per-file error table for the footprint phase *) -(** Print stats for the global per-file error table *) val pp_err_table_stats : Exceptions.err_kind -> Format.formatter -> err_table -> unit +(** Print stats for the global per-file error table *) -(** Print details of the global per-file error table *) val print_err_table_details : Format.formatter -> err_table -> unit +(** Print details of the global per-file error table *) diff --git a/infer/src/IR/Exceptions.ml b/infer/src/IR/Exceptions.ml index 692bd37f8..3a23b521d 100644 --- a/infer/src/IR/Exceptions.ml +++ b/infer/src/IR/Exceptions.ml @@ -9,30 +9,27 @@ *) open! IStd - module L = Logging module F = Format (** visibility of the exception *) type visibility = - | Exn_user (** always add to error log *) - | Exn_developer (** only add to error log in developer mode *) - | Exn_system (** never add to error log *) -[@@deriving compare] + | Exn_user (** always add to error log *) + | Exn_developer (** only add to error log in developer mode *) + | Exn_system (** never add to error log *) + [@@deriving compare] let equal_visibility = [%compare.equal : visibility] let string_of_visibility vis = - match vis with - | Exn_user -> "user" - | Exn_developer -> "developer" - | Exn_system -> "system" + match vis with Exn_user -> "user" | Exn_developer -> "developer" | Exn_system -> "system" (** severity of bugs *) -type severity = - | High (* high severity bug *) +type severity = High (* high severity bug *) | Medium (* medium severity bug *) - | Low (* low severity bug *) + | Low + +(* low severity bug *) (** class of error/warning *) type err_class = Checker | Prover | Nocat | Linters [@@deriving compare] @@ -45,305 +42,434 @@ type err_kind = Kwarning | Kerror | Kinfo | Kadvice | Klike [@@deriving compare] let equal_err_kind = [%compare.equal : err_kind] exception Abduction_case_not_implemented of L.ml_loc + exception Analysis_stops of Localise.error_desc * L.ml_loc option + exception Array_out_of_bounds_l1 of Localise.error_desc * L.ml_loc + exception Array_out_of_bounds_l2 of Localise.error_desc * L.ml_loc + exception Array_out_of_bounds_l3 of Localise.error_desc * L.ml_loc + exception Array_of_pointsto of L.ml_loc + exception Bad_footprint of L.ml_loc + exception Cannot_star of L.ml_loc + exception Class_cast_exception of Localise.error_desc * L.ml_loc + exception Codequery of Localise.error_desc + exception Comparing_floats_for_equality of Localise.error_desc * L.ml_loc + exception Condition_is_assignment of Localise.error_desc * L.ml_loc + exception Condition_always_true_false of Localise.error_desc * bool * L.ml_loc + exception Context_leak of Localise.error_desc * L.ml_loc + exception Custom_error of string * Localise.error_desc + exception Dangling_pointer_dereference of - PredSymb.dangling_kind option * Localise.error_desc * L.ml_loc + PredSymb.dangling_kind option * Localise.error_desc * L.ml_loc + exception Deallocate_stack_variable of Localise.error_desc + exception Deallocate_static_memory of Localise.error_desc + exception Deallocation_mismatch of Localise.error_desc * L.ml_loc + exception Divide_by_zero of Localise.error_desc * L.ml_loc + exception Double_lock of Localise.error_desc * L.ml_loc + exception Empty_vector_access of Localise.error_desc * L.ml_loc + exception Eradicate of string * Localise.error_desc + exception Field_not_null_checked of Localise.error_desc * L.ml_loc + exception Frontend_warning of (string * string option) * Localise.error_desc * L.ml_loc + exception Checkers of string * Localise.error_desc + exception Inherently_dangerous_function of Localise.error_desc + exception Internal_error of Localise.error_desc + exception Java_runtime_exception of Typ.Name.t * string * Localise.error_desc + exception Leak of - bool * Sil.hpred * (visibility * Localise.error_desc) - * bool * PredSymb.resource * L.ml_loc + bool * Sil.hpred * (visibility * Localise.error_desc) * bool * PredSymb.resource * L.ml_loc + exception Missing_fld of Typ.Fieldname.t * L.ml_loc + exception Premature_nil_termination of Localise.error_desc * L.ml_loc + exception Null_dereference of Localise.error_desc * L.ml_loc + exception Null_test_after_dereference of Localise.error_desc * L.ml_loc + exception Parameter_not_null_checked of Localise.error_desc * L.ml_loc + exception Pointer_size_mismatch of Localise.error_desc * L.ml_loc + exception Precondition_not_found of Localise.error_desc * L.ml_loc + exception Precondition_not_met of Localise.error_desc * L.ml_loc + exception Retain_cycle of Sil.hpred * Localise.error_desc * L.ml_loc + exception Registered_observer_being_deallocated of Localise.error_desc * L.ml_loc + exception Return_expression_required of Localise.error_desc * L.ml_loc + exception Return_statement_missing of Localise.error_desc * L.ml_loc + exception Return_value_ignored of Localise.error_desc * L.ml_loc + exception Skip_function of Localise.error_desc + exception Skip_pointer_dereference of Localise.error_desc * L.ml_loc + exception Stack_variable_address_escape of Localise.error_desc * L.ml_loc + exception Symexec_memory_error of L.ml_loc + exception Tainted_value_reaching_sensitive_function of Localise.error_desc * L.ml_loc + exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * L.ml_loc + exception Uninitialized_value of Localise.error_desc * L.ml_loc + exception Unknown_proc + exception Unreachable_code_after of Localise.error_desc * L.ml_loc + exception Unsafe_guarded_by_access of Localise.error_desc * L.ml_loc + exception Use_after_free of Localise.error_desc * L.ml_loc -exception Wrong_argument_number of L.ml_loc +exception Wrong_argument_number of L.ml_loc (** Turn an exception into a descriptive string, error description, location in ml source, and category *) let recognize_exception exn = - let err_name, desc, (ml_loc_opt : L.ml_loc option), visibility, severity, force_kind, eclass = - match exn with (* all the names of Exn_user errors must be defined in Localise *) - | Abduction_case_not_implemented ml_loc -> - (Localise.from_string "Abduction_case_not_implemented", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Context_leak (desc, _) -> - (Localise.context_leak, - desc, None, Exn_user, High, None, Nocat) - | Analysis_stops (desc, ml_loc_opt) -> - let visibility = if Config.analysis_stops then Exn_user else Exn_developer in + let err_name, desc, (ml_loc_opt: L.ml_loc option), visibility, severity, force_kind, eclass = + match exn with + (* all the names of Exn_user errors must be defined in Localise *) + | Abduction_case_not_implemented ml_loc + -> ( Localise.from_string "Abduction_case_not_implemented" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Context_leak (desc, _) + -> (Localise.context_leak, desc, None, Exn_user, High, None, Nocat) + | Analysis_stops (desc, ml_loc_opt) + -> let visibility = if Config.analysis_stops then Exn_user else Exn_developer in (Localise.analysis_stops, desc, ml_loc_opt, visibility, Medium, None, Nocat) - | Array_of_pointsto ml_loc -> - (Localise.from_string "Array_of_pointsto", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Array_out_of_bounds_l1 (desc, ml_loc) -> - (Localise.array_out_of_bounds_l1, - desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) - | Array_out_of_bounds_l2 (desc, ml_loc) -> - (Localise.array_out_of_bounds_l2, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Array_out_of_bounds_l3 (desc, ml_loc) -> - (Localise.array_out_of_bounds_l3, - desc, Some ml_loc, Exn_developer, Medium, None, Nocat) - | Assert_failure (f, l, c) -> - let ml_loc = (f, l, c, c) in - (Localise.from_string "Assert_failure", - Localise.no_desc, Some ml_loc, Exn_developer, High, None, Nocat) - | Bad_footprint ml_loc -> - (Localise.from_string "Bad_footprint", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Cannot_star ml_loc -> - (Localise.from_string "Cannot_star", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Class_cast_exception (desc, ml_loc) -> - (Localise.class_cast_exception, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Codequery desc -> - (Localise.from_string "Codequery", - desc, None, Exn_user, High, None, Prover) - | Comparing_floats_for_equality(desc, ml_loc) -> - (Localise.comparing_floats_for_equality, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Condition_always_true_false (desc, b, ml_loc) -> - let name = - if b then Localise.condition_always_true - else Localise.condition_always_false in + | Array_of_pointsto ml_loc + -> ( Localise.from_string "Array_of_pointsto" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Array_out_of_bounds_l1 (desc, ml_loc) + -> (Localise.array_out_of_bounds_l1, desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) + | Array_out_of_bounds_l2 (desc, ml_loc) + -> (Localise.array_out_of_bounds_l2, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Array_out_of_bounds_l3 (desc, ml_loc) + -> (Localise.array_out_of_bounds_l3, desc, Some ml_loc, Exn_developer, Medium, None, Nocat) + | Assert_failure (f, l, c) + -> let ml_loc = (f, l, c, c) in + ( Localise.from_string "Assert_failure" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , High + , None + , Nocat ) + | Bad_footprint ml_loc + -> ( Localise.from_string "Bad_footprint" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Cannot_star ml_loc + -> ( Localise.from_string "Cannot_star" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Class_cast_exception (desc, ml_loc) + -> (Localise.class_cast_exception, desc, Some ml_loc, Exn_user, High, None, Prover) + | Codequery desc + -> (Localise.from_string "Codequery", desc, None, Exn_user, High, None, Prover) + | Comparing_floats_for_equality (desc, ml_loc) + -> (Localise.comparing_floats_for_equality, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Condition_always_true_false (desc, b, ml_loc) + -> let name = if b then Localise.condition_always_true else Localise.condition_always_false in (name, desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Custom_error (error_msg, desc) -> - (Localise.from_string error_msg, - desc, None, Exn_user, High, None, Checker) - | Condition_is_assignment(desc, ml_loc) -> - (Localise.condition_is_assignment, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Dangling_pointer_dereference (dko, desc, ml_loc) -> - let visibility = match dko with - | Some _ -> Exn_user (* only show to the user if the category was identified *) - | None -> Exn_developer in - (Localise.dangling_pointer_dereference, - desc, Some ml_loc, visibility, High, None, Prover) - | Deallocate_stack_variable desc -> - (Localise.deallocate_stack_variable, - desc, None, Exn_user, High, None, Prover) - | Deallocate_static_memory desc -> - (Localise.deallocate_static_memory, - desc, None, Exn_user, High, None, Prover) - | Deallocation_mismatch (desc, ml_loc) -> - (Localise.deallocation_mismatch, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Divide_by_zero (desc, ml_loc) -> - (Localise.divide_by_zero, - desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) - | Double_lock (desc, ml_loc) -> - (Localise.double_lock, - desc, Some ml_loc, Exn_user, High, Some Kerror, Prover) - | Eradicate (kind_s, desc) -> - (Localise.from_string kind_s, desc, None, Exn_user, High, None, Prover) - | Empty_vector_access (desc, ml_loc) -> - (Localise.empty_vector_access, - desc, Some ml_loc, Exn_user, High, Some Kerror, Prover) - | Field_not_null_checked (desc, ml_loc) -> - (Localise.field_not_null_checked, - desc, Some ml_loc, Exn_user, Medium, Some Kwarning, Nocat) - | Frontend_warning ((name, hum), desc, ml_loc) -> - (Localise.from_string name ?hum, - desc, Some ml_loc, Exn_user, Medium, None, Linters) - | Checkers (kind_s, desc) -> - (Localise.from_string kind_s, - desc, None, Exn_user, High, None, Prover) - | Null_dereference (desc, ml_loc) -> - (Localise.null_dereference, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Null_test_after_dereference (desc, ml_loc) -> - (Localise.null_test_after_dereference, - desc, Some ml_loc, Exn_user, High, None, Nocat) - | Pointer_size_mismatch (desc, ml_loc) -> - (Localise.pointer_size_mismatch, - desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) - | Inherently_dangerous_function desc -> - (Localise.inherently_dangerous_function, - desc, None, Exn_developer, Medium, None, Nocat) - | Internal_error desc -> - (Localise.from_string "Internal_error", - desc, None, Exn_developer, High, None, Nocat) - | Invalid_argument s -> - let desc = Localise.verbatim_desc s in - (Localise.from_string "Invalid_argument", - desc, None, Exn_system, Low, None, Nocat) - | Java_runtime_exception (exn_name, _, desc) -> - let exn_str = Typ.Name.name exn_name in + | Custom_error (error_msg, desc) + -> (Localise.from_string error_msg, desc, None, Exn_user, High, None, Checker) + | Condition_is_assignment (desc, ml_loc) + -> (Localise.condition_is_assignment, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Dangling_pointer_dereference (dko, desc, ml_loc) + -> let visibility = + match dko with + | Some _ + -> Exn_user (* only show to the user if the category was identified *) + | None + -> Exn_developer + in + (Localise.dangling_pointer_dereference, desc, Some ml_loc, visibility, High, None, Prover) + | Deallocate_stack_variable desc + -> (Localise.deallocate_stack_variable, desc, None, Exn_user, High, None, Prover) + | Deallocate_static_memory desc + -> (Localise.deallocate_static_memory, desc, None, Exn_user, High, None, Prover) + | Deallocation_mismatch (desc, ml_loc) + -> (Localise.deallocation_mismatch, desc, Some ml_loc, Exn_user, High, None, Prover) + | Divide_by_zero (desc, ml_loc) + -> (Localise.divide_by_zero, desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) + | Double_lock (desc, ml_loc) + -> (Localise.double_lock, desc, Some ml_loc, Exn_user, High, Some Kerror, Prover) + | Eradicate (kind_s, desc) + -> (Localise.from_string kind_s, desc, None, Exn_user, High, None, Prover) + | Empty_vector_access (desc, ml_loc) + -> (Localise.empty_vector_access, desc, Some ml_loc, Exn_user, High, Some Kerror, Prover) + | Field_not_null_checked (desc, ml_loc) + -> (Localise.field_not_null_checked, desc, Some ml_loc, Exn_user, Medium, Some Kwarning, Nocat) + | Frontend_warning ((name, hum), desc, ml_loc) + -> (Localise.from_string name ?hum, desc, Some ml_loc, Exn_user, Medium, None, Linters) + | Checkers (kind_s, desc) + -> (Localise.from_string kind_s, desc, None, Exn_user, High, None, Prover) + | Null_dereference (desc, ml_loc) + -> (Localise.null_dereference, desc, Some ml_loc, Exn_user, High, None, Prover) + | Null_test_after_dereference (desc, ml_loc) + -> (Localise.null_test_after_dereference, desc, Some ml_loc, Exn_user, High, None, Nocat) + | Pointer_size_mismatch (desc, ml_loc) + -> (Localise.pointer_size_mismatch, desc, Some ml_loc, Exn_user, High, Some Kerror, Checker) + | Inherently_dangerous_function desc + -> (Localise.inherently_dangerous_function, desc, None, Exn_developer, Medium, None, Nocat) + | Internal_error desc + -> (Localise.from_string "Internal_error", desc, None, Exn_developer, High, None, Nocat) + | Invalid_argument s + -> let desc = Localise.verbatim_desc s in + (Localise.from_string "Invalid_argument", desc, None, Exn_system, Low, None, Nocat) + | Java_runtime_exception (exn_name, _, desc) + -> let exn_str = Typ.Name.name exn_name in (Localise.from_string exn_str, desc, None, Exn_user, High, None, Prover) - | Leak (fp_part, _, (exn_vis, error_desc), done_array_abstraction, resource, ml_loc) -> - if done_array_abstraction - then (Localise.from_string "Leak_after_array_abstraction", - error_desc, Some ml_loc, Exn_developer, High, None, Prover) - else if fp_part - then (Localise.from_string "Leak_in_footprint", - error_desc, Some ml_loc, Exn_developer, High, None, Prover) + | Leak (fp_part, _, (exn_vis, error_desc), done_array_abstraction, resource, ml_loc) + -> if done_array_abstraction then + ( Localise.from_string "Leak_after_array_abstraction" + , error_desc + , Some ml_loc + , Exn_developer + , High + , None + , Prover ) + else if fp_part then + ( Localise.from_string "Leak_in_footprint" + , error_desc + , Some ml_loc + , Exn_developer + , High + , None + , Prover ) else - let loc_str = match resource with - | PredSymb.Rmemory _ -> Localise.memory_leak - | PredSymb.Rfile -> Localise.resource_leak - | PredSymb.Rlock -> Localise.resource_leak - | PredSymb.Rignore -> Localise.memory_leak in + let loc_str = + match resource with + | PredSymb.Rmemory _ + -> Localise.memory_leak + | PredSymb.Rfile + -> Localise.resource_leak + | PredSymb.Rlock + -> Localise.resource_leak + | PredSymb.Rignore + -> Localise.memory_leak + in (loc_str, error_desc, Some ml_loc, exn_vis, High, None, Prover) - | Match_failure (f, l, c) -> - let ml_loc = (f, l, c, c) in - (Localise.from_string "Match failure", - Localise.no_desc, Some ml_loc, Exn_developer, High, None, Nocat) - | Missing_fld (fld, ml_loc) -> - let desc = Localise.verbatim_desc (Typ.Fieldname.to_full_string fld) in - (Localise.from_string "Missing_fld" ~hum:"Missing Field", - desc, Some ml_loc, Exn_developer, Medium, None, Nocat) - | Premature_nil_termination (desc, ml_loc) -> - (Localise.premature_nil_termination, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Not_found -> - (Localise.from_string "Not_found", - Localise.no_desc, None, Exn_system, Low, None, Nocat) - | Parameter_not_null_checked (desc, ml_loc) -> - (Localise.parameter_not_null_checked, - desc, Some ml_loc, Exn_user, Medium, Some Kwarning, Nocat) - | Precondition_not_found (desc, ml_loc) -> - (Localise.precondition_not_found, - desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Precondition_not_met (desc, ml_loc) -> - (Localise.precondition_not_met, - desc, Some ml_loc, Exn_developer, Medium, Some Kwarning, Nocat) (* always a warning *) - | Retain_cycle (_, desc, ml_loc) -> - (Localise.retain_cycle, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Registered_observer_being_deallocated (desc, ml_loc) -> - (Localise.registered_observer_being_deallocated, - desc, Some ml_loc, Exn_user, High, Some Kerror, Nocat) - | Return_expression_required (desc, ml_loc) -> - (Localise.return_expression_required, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Stack_variable_address_escape (desc, ml_loc) -> - (Localise.stack_variable_address_escape, - desc, Some ml_loc, Exn_user, High, Some Kerror, Nocat) - | Return_statement_missing (desc, ml_loc) -> - (Localise.return_statement_missing, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Return_value_ignored (desc, ml_loc) -> - (Localise.return_value_ignored, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | SymOp.Analysis_failure_exe _ -> - (Localise.from_string "Failure_exe", - Localise.no_desc, None, Exn_system, Low, None, Nocat) - | Skip_function desc -> - (Localise.skip_function, desc, None, Exn_developer, Low, None, Nocat) - | Skip_pointer_dereference (desc, ml_loc) -> - (Localise.skip_pointer_dereference, - desc, Some ml_loc, Exn_user, Medium, Some Kinfo, Nocat) (* always an info *) - | Symexec_memory_error ml_loc -> - (Localise.from_string "Symexec_memory_error" ~hum:"Symbolic Execution Memory Error", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Sys_error s -> - let desc = Localise.verbatim_desc s in - (Localise.from_string "Sys_error" ~hum:"System Error", - desc, None, Exn_system, Low, None, Nocat) - | Tainted_value_reaching_sensitive_function (desc, ml_loc) -> - (Localise.tainted_value_reaching_sensitive_function, - desc, Some ml_loc, Exn_user, Medium, Some Kerror, Nocat) - | Unix.Unix_error (_, s1, s2) -> - let desc = Localise.verbatim_desc (s1 ^ s2) in - (Localise.from_string "Unix_error", - desc, None, Exn_system, Low, None, Nocat) - | Uninitialized_value (desc, ml_loc) -> - (Localise.uninitialized_value, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Unary_minus_applied_to_unsigned_expression(desc, ml_loc) -> - (Localise.unary_minus_applied_to_unsigned_expression, - desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Unknown_proc -> - (Localise.from_string "Unknown_proc" ~hum:"Unknown Procedure", - Localise.no_desc, None, Exn_developer, Low, None, Nocat) - | Unreachable_code_after (desc, ml_loc) -> - (Localise.unreachable_code_after, desc, Some ml_loc, Exn_user, Medium, None, Nocat) - | Unsafe_guarded_by_access (desc, ml_loc) -> - (Localise.unsafe_guarded_by_access, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Use_after_free (desc, ml_loc) -> - (Localise.use_after_free, - desc, Some ml_loc, Exn_user, High, None, Prover) - | Wrong_argument_number ml_loc -> - (Localise.from_string "Wrong_argument_number" ~hum:"Wrong Argument Number", - Localise.no_desc, Some ml_loc, Exn_developer, Low, None, Nocat) - | Failure _ as f -> - raise f - | exn -> - let exn_name = Exn.to_string exn in - (Localise.from_string exn_name, - Localise.no_desc, None, Exn_developer, Low, None, Nocat) in + | Match_failure (f, l, c) + -> let ml_loc = (f, l, c, c) in + ( Localise.from_string "Match failure" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , High + , None + , Nocat ) + | Missing_fld (fld, ml_loc) + -> let desc = Localise.verbatim_desc (Typ.Fieldname.to_full_string fld) in + ( Localise.from_string "Missing_fld" ~hum:"Missing Field" + , desc + , Some ml_loc + , Exn_developer + , Medium + , None + , Nocat ) + | Premature_nil_termination (desc, ml_loc) + -> (Localise.premature_nil_termination, desc, Some ml_loc, Exn_user, High, None, Prover) + | Not_found + -> (Localise.from_string "Not_found", Localise.no_desc, None, Exn_system, Low, None, Nocat) + | Parameter_not_null_checked (desc, ml_loc) + -> ( Localise.parameter_not_null_checked + , desc + , Some ml_loc + , Exn_user + , Medium + , Some Kwarning + , Nocat ) + | Precondition_not_found (desc, ml_loc) + -> (Localise.precondition_not_found, desc, Some ml_loc, Exn_developer, Low, None, Nocat) + | Precondition_not_met (desc, ml_loc) + -> ( Localise.precondition_not_met + , desc + , Some ml_loc + , Exn_developer + , Medium + , Some Kwarning + , Nocat ) + (* always a warning *) + | Retain_cycle (_, desc, ml_loc) + -> (Localise.retain_cycle, desc, Some ml_loc, Exn_user, High, None, Prover) + | Registered_observer_being_deallocated (desc, ml_loc) + -> ( Localise.registered_observer_being_deallocated + , desc + , Some ml_loc + , Exn_user + , High + , Some Kerror + , Nocat ) + | Return_expression_required (desc, ml_loc) + -> (Localise.return_expression_required, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Stack_variable_address_escape (desc, ml_loc) + -> ( Localise.stack_variable_address_escape + , desc + , Some ml_loc + , Exn_user + , High + , Some Kerror + , Nocat ) + | Return_statement_missing (desc, ml_loc) + -> (Localise.return_statement_missing, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Return_value_ignored (desc, ml_loc) + -> (Localise.return_value_ignored, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | SymOp.Analysis_failure_exe _ + -> (Localise.from_string "Failure_exe", Localise.no_desc, None, Exn_system, Low, None, Nocat) + | Skip_function desc + -> (Localise.skip_function, desc, None, Exn_developer, Low, None, Nocat) + | Skip_pointer_dereference (desc, ml_loc) + -> (Localise.skip_pointer_dereference, desc, Some ml_loc, Exn_user, Medium, Some Kinfo, Nocat) + (* always an info *) + | Symexec_memory_error ml_loc + -> ( Localise.from_string "Symexec_memory_error" ~hum:"Symbolic Execution Memory Error" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Sys_error s + -> let desc = Localise.verbatim_desc s in + ( Localise.from_string "Sys_error" ~hum:"System Error" + , desc + , None + , Exn_system + , Low + , None + , Nocat ) + | Tainted_value_reaching_sensitive_function (desc, ml_loc) + -> ( Localise.tainted_value_reaching_sensitive_function + , desc + , Some ml_loc + , Exn_user + , Medium + , Some Kerror + , Nocat ) + | Unix.Unix_error (_, s1, s2) + -> let desc = Localise.verbatim_desc (s1 ^ s2) in + (Localise.from_string "Unix_error", desc, None, Exn_system, Low, None, Nocat) + | Uninitialized_value (desc, ml_loc) + -> (Localise.uninitialized_value, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Unary_minus_applied_to_unsigned_expression (desc, ml_loc) + -> ( Localise.unary_minus_applied_to_unsigned_expression + , desc + , Some ml_loc + , Exn_user + , Medium + , None + , Nocat ) + | Unknown_proc + -> ( Localise.from_string "Unknown_proc" ~hum:"Unknown Procedure" + , Localise.no_desc + , None + , Exn_developer + , Low + , None + , Nocat ) + | Unreachable_code_after (desc, ml_loc) + -> (Localise.unreachable_code_after, desc, Some ml_loc, Exn_user, Medium, None, Nocat) + | Unsafe_guarded_by_access (desc, ml_loc) + -> (Localise.unsafe_guarded_by_access, desc, Some ml_loc, Exn_user, High, None, Prover) + | Use_after_free (desc, ml_loc) + -> (Localise.use_after_free, desc, Some ml_loc, Exn_user, High, None, Prover) + | Wrong_argument_number ml_loc + -> ( Localise.from_string "Wrong_argument_number" ~hum:"Wrong Argument Number" + , Localise.no_desc + , Some ml_loc + , Exn_developer + , Low + , None + , Nocat ) + | Failure _ as f + -> raise f + | exn + -> let exn_name = Exn.to_string exn in + (Localise.from_string exn_name, Localise.no_desc, None, Exn_developer, Low, None, Nocat) + in (err_name, desc, ml_loc_opt, visibility, severity, force_kind, eclass) (** print a description of the exception to the html output *) let print_exception_html s exn = let err_name, desc, ml_loc_opt, _, _, _, _ = recognize_exception exn in - let ml_loc_string = match ml_loc_opt with - | None -> "" - | Some ml_loc -> " " ^ L.ml_loc_to_string ml_loc in + let ml_loc_string = + match ml_loc_opt with None -> "" | Some ml_loc -> " " ^ L.ml_loc_to_string ml_loc + in let desc_str = F.asprintf "%a" Localise.pp_error_desc desc in - (L.d_strln_color Red) (s ^ (Localise.to_issue_id err_name) ^ " " ^ desc_str ^ ml_loc_string) + L.d_strln_color Red (s ^ Localise.to_issue_id err_name ^ " " ^ desc_str ^ ml_loc_string) (** string describing an error kind *) let err_kind_string = function - | Kwarning -> "WARNING" - | Kerror -> "ERROR" - | Kinfo -> "INFO" - | Kadvice -> "ADVICE" - | Klike -> "LIKE" + | Kwarning + -> "WARNING" + | Kerror + -> "ERROR" + | Kinfo + -> "INFO" + | Kadvice + -> "ADVICE" + | Klike + -> "LIKE" (** string describing an error class *) let err_class_string = function - | Checker -> "CHECKER" - | Prover -> "PROVER" - | Nocat -> "" - | Linters -> "Linters" + | Checker + -> "CHECKER" + | Prover + -> "PROVER" + | Nocat + -> "" + | Linters + -> "Linters" (** whether to print the bug key together with the error message *) let print_key = false @@ -352,17 +478,10 @@ let print_key = false let pp_err ~node_key loc ekind ex_name desc ml_loc_opt fmt () = let kind = err_kind_string (if equal_err_kind ekind Kinfo then Kwarning else ekind) in let pp_key fmt k = if print_key then F.fprintf fmt " key: %d " k else () in - F.fprintf fmt "%a:%d: %s: %a %a%a%a@\n" - SourceFile.pp loc.Location.file - loc.Location.line - kind - Localise.pp ex_name - Localise.pp_error_desc desc - pp_key node_key - L.pp_ml_loc_opt ml_loc_opt + F.fprintf fmt "%a:%d: %s: %a %a%a%a@\n" SourceFile.pp loc.Location.file loc.Location.line kind + Localise.pp ex_name Localise.pp_error_desc desc pp_key node_key L.pp_ml_loc_opt ml_loc_opt (** Return true if the exception is not serious and should be handled in timeout mode *) let handle_exception exn = let _, _, _, visibility, _, _, _ = recognize_exception exn in - equal_visibility visibility Exn_user || - equal_visibility visibility Exn_developer + equal_visibility visibility Exn_user || equal_visibility visibility Exn_developer diff --git a/infer/src/IR/Exceptions.mli b/infer/src/IR/Exceptions.mli index 8a95078e0..9ac55d2d7 100644 --- a/infer/src/IR/Exceptions.mli +++ b/infer/src/IR/Exceptions.mli @@ -14,10 +14,10 @@ open! IStd (** visibility of the exception *) type visibility = - | Exn_user (** always add to error log *) - | Exn_developer (** only add to error log in developer mode *) - | Exn_system (** never add to error log *) -[@@deriving compare] + | Exn_user (** always add to error log *) + | Exn_developer (** only add to error log in developer mode *) + | Exn_system (** never add to error log *) + [@@deriving compare] val equal_visibility : visibility -> visibility -> bool @@ -25,9 +25,9 @@ val string_of_visibility : visibility -> string (** severity of bugs *) type severity = - | High (** high severity bug *) - | Medium (** medium severity bug *) - | Low (** low severity bug *) + | High (** high severity bug *) + | Medium (** medium severity bug *) + | Low (** low severity bug *) (** kind of error/warning *) type err_kind = Kwarning | Kerror | Kinfo | Kadvice | Klike [@@deriving compare] @@ -40,82 +40,144 @@ type err_class = Checker | Prover | Nocat | Linters val equal_err_class : err_class -> err_class -> bool exception Abduction_case_not_implemented of Logging.ml_loc + exception Analysis_stops of Localise.error_desc * Logging.ml_loc option + exception Array_of_pointsto of Logging.ml_loc + exception Array_out_of_bounds_l1 of Localise.error_desc * Logging.ml_loc + exception Array_out_of_bounds_l2 of Localise.error_desc * Logging.ml_loc + exception Array_out_of_bounds_l3 of Localise.error_desc * Logging.ml_loc + exception Bad_footprint of Logging.ml_loc + exception Cannot_star of Logging.ml_loc + exception Class_cast_exception of Localise.error_desc * Logging.ml_loc + exception Codequery of Localise.error_desc + exception Comparing_floats_for_equality of Localise.error_desc * Logging.ml_loc + exception Condition_always_true_false of Localise.error_desc * bool * Logging.ml_loc + exception Condition_is_assignment of Localise.error_desc * Logging.ml_loc + exception Context_leak of Localise.error_desc * Logging.ml_loc + exception Custom_error of string * Localise.error_desc -exception Dangling_pointer_dereference of - PredSymb.dangling_kind option * Localise.error_desc * Logging.ml_loc + +exception + Dangling_pointer_dereference of + PredSymb.dangling_kind option * Localise.error_desc * Logging.ml_loc + exception Deallocate_stack_variable of Localise.error_desc + exception Deallocate_static_memory of Localise.error_desc + exception Deallocation_mismatch of Localise.error_desc * Logging.ml_loc + exception Double_lock of Localise.error_desc * Logging.ml_loc + exception Divide_by_zero of Localise.error_desc * Logging.ml_loc + exception Field_not_null_checked of Localise.error_desc * Logging.ml_loc + exception Empty_vector_access of Localise.error_desc * Logging.ml_loc + exception Eradicate of string * Localise.error_desc + exception Checkers of string * Localise.error_desc + exception Frontend_warning of (string * string option) * Localise.error_desc * Logging.ml_loc + exception Inherently_dangerous_function of Localise.error_desc + exception Internal_error of Localise.error_desc + exception Java_runtime_exception of Typ.Name.t * string * Localise.error_desc -exception Leak of - bool * Sil.hpred * (visibility * Localise.error_desc) - * bool * PredSymb.resource * Logging.ml_loc + +exception + Leak of + bool * Sil.hpred * (visibility * Localise.error_desc) * bool * PredSymb.resource * Logging.ml_loc + exception Missing_fld of Typ.Fieldname.t * Logging.ml_loc + exception Premature_nil_termination of Localise.error_desc * Logging.ml_loc + exception Null_dereference of Localise.error_desc * Logging.ml_loc + exception Null_test_after_dereference of Localise.error_desc * Logging.ml_loc + exception Parameter_not_null_checked of Localise.error_desc * Logging.ml_loc + exception Pointer_size_mismatch of Localise.error_desc * Logging.ml_loc + exception Precondition_not_found of Localise.error_desc * Logging.ml_loc + exception Precondition_not_met of Localise.error_desc * Logging.ml_loc + exception Retain_cycle of Sil.hpred * Localise.error_desc * Logging.ml_loc + exception Registered_observer_being_deallocated of Localise.error_desc * Logging.ml_loc + exception Return_expression_required of Localise.error_desc * Logging.ml_loc + exception Return_statement_missing of Localise.error_desc * Logging.ml_loc + exception Return_value_ignored of Localise.error_desc * Logging.ml_loc + exception Skip_function of Localise.error_desc + exception Skip_pointer_dereference of Localise.error_desc * Logging.ml_loc + exception Stack_variable_address_escape of Localise.error_desc * Logging.ml_loc + exception Symexec_memory_error of Logging.ml_loc + exception Tainted_value_reaching_sensitive_function of Localise.error_desc * Logging.ml_loc + exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * Logging.ml_loc + exception Uninitialized_value of Localise.error_desc * Logging.ml_loc + exception Unknown_proc + exception Unreachable_code_after of Localise.error_desc * Logging.ml_loc + exception Unsafe_guarded_by_access of Localise.error_desc * Logging.ml_loc + exception Use_after_free of Localise.error_desc * Logging.ml_loc + exception Wrong_argument_number of Logging.ml_loc -(** string describing an error class *) val err_class_string : err_class -> string +(** string describing an error class *) -(** string describing an error kind *) val err_kind_string : err_kind -> string +(** string describing an error kind *) -(** Return true if the exception is not serious and should be handled in timeout mode *) val handle_exception : exn -> bool +(** Return true if the exception is not serious and should be handled in timeout mode *) -(** print a description of the exception to the html output *) val print_exception_html : string -> exn -> unit +(** print a description of the exception to the html output *) +val pp_err : + node_key:int -> Location.t -> err_kind -> Localise.t -> Localise.error_desc + -> Logging.ml_loc option -> Format.formatter -> unit -> unit (** pretty print an error *) -val pp_err : node_key:int -> Location.t -> err_kind -> Localise.t -> Localise.error_desc -> - Logging.ml_loc option -> Format.formatter -> unit -> unit +val recognize_exception : + exn + -> Localise.t + * Localise.error_desc + * Logging.ml_loc option + * visibility + * severity + * err_kind option + * err_class (** Turn an exception into an error name, error description, location in ml source, and category *) -val recognize_exception : exn -> - (Localise.t * Localise.error_desc * (Logging.ml_loc option) * visibility * - severity * err_kind option * err_class) diff --git a/infer/src/IR/Exp.ml b/infer/src/IR/Exp.ml new file mode 100644 index 000000000..c8a3569da --- /dev/null +++ b/infer/src/IR/Exp.ml @@ -0,0 +1,259 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Expressions *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +(* reverse the natural order on Var *) +type _ident = Ident.t + +let compare__ident x y = Ident.compare y x + +type closure = {name: Typ.Procname.t; captured_vars: (t * Pvar.t * Typ.t) list} + +(** This records information about a [sizeof(typ)] expression. + + [nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known. + + If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number + of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may + differ from the [static_length] obtained from the type definition, e.g. when an array is + over-allocated. + + If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*) +and sizeof_data = {typ: Typ.t; nbytes: int option; dynamic_length: t option; subtype: Subtype.t} + +(** Program expressions. *) +and t = + (** Pure variable: it is not an lvalue *) + | Var of _ident (** Unary operator with type of the result if known *) + | UnOp of Unop.t * t * Typ.t option (** Binary operator *) + | BinOp of Binop.t * t * t (** Exception *) + | Exn of t (** Anonymous function *) + | Closure of closure (** Constants *) + | Const of Const.t (** Type cast *) + | Cast of Typ.t * t (** The address of a program variable *) + | Lvar of Pvar.t (** A field offset, the type is the surrounding struct type *) + | Lfield of t * Typ.Fieldname.t * Typ.t (** An array index offset: [exp1\[exp2\]] *) + | Lindex of t * t + | Sizeof of sizeof_data + [@@deriving compare] + +let equal = [%compare.equal : t] + +let hash = Hashtbl.hash + +module Set = Caml.Set.Make (struct + type nonrec t = t + + let compare = compare +end) + +module Map = Caml.Map.Make (struct + type nonrec t = t + + let compare = compare +end) + +module Hash = Hashtbl.Make (struct + type nonrec t = t + + let equal = equal + + let hash = hash +end) + +let rec is_array_index_of exp1 exp2 = + match exp1 with Lindex (exp, _) -> is_array_index_of exp exp2 | _ -> equal exp1 exp2 + +let is_null_literal = function Const Cint n -> IntLit.isnull n | _ -> false + +let is_this = function Lvar pvar -> Pvar.is_this pvar | _ -> false + +let is_zero = function Const Cint n -> IntLit.iszero n | _ -> false + +(** {2 Utility Functions for Expressions} *) + +(** Turn an expression representing a type into the type it represents + If not a sizeof, return the default type if given, otherwise raise an exception *) +let texp_to_typ default_opt = function + | Sizeof {typ} + -> typ + | _ + -> Typ.unsome "texp_to_typ" default_opt + +(** Return the root of [lexp]. *) +let rec root_of_lexp lexp = + match (lexp : t) with + | Var _ + -> lexp + | Const _ + -> lexp + | Cast (_, e) + -> root_of_lexp e + | UnOp _ | BinOp _ | Exn _ | Closure _ + -> lexp + | Lvar _ + -> lexp + | Lfield (e, _, _) + -> root_of_lexp e + | Lindex (e, _) + -> root_of_lexp e + | Sizeof _ + -> lexp + +(** Checks whether an expression denotes a location by pointer arithmetic. + Currently, catches array - indexing expressions such as a[i] only. *) +let rec pointer_arith = function + | Lfield (e, _, _) + -> pointer_arith e + | Lindex _ + -> true + | _ + -> false + +let get_undefined footprint = + Var (Ident.create_fresh (if footprint then Ident.kfootprint else Ident.kprimed)) + +(** returns true if the expression represents a stack-directed address *) +let rec is_stack_addr e = + match (e : t) with + | Lvar pv + -> not (Pvar.is_global pv) + | UnOp (_, e', _) | Cast (_, e') | Lfield (e', _, _) | Lindex (e', _) + -> is_stack_addr e' + | _ + -> false + +(** returns true if the express operates on address of local variable *) +let rec has_local_addr e = + match (e : t) with + | Lvar pv + -> Pvar.is_local pv + | UnOp (_, e', _) | Cast (_, e') | Lfield (e', _, _) + -> has_local_addr e' + | BinOp (_, e0, e1) | Lindex (e0, e1) + -> has_local_addr e0 || has_local_addr e1 + | _ + -> false + +(** Create integer constant *) +let int i = Const (Cint i) + +(** Create float constant *) +let float v = Const (Cfloat v) + +(** Integer constant 0 *) +let zero = int IntLit.zero + +(** Null constant *) +let null = int IntLit.null + +(** Integer constant 1 *) +let one = int IntLit.one + +(** Integer constant -1 *) +let minus_one = int IntLit.minus_one + +(** Create integer constant corresponding to the boolean value *) +let bool b = if b then one else zero + +(** Create expresstion [e1 == e2] *) +let eq e1 e2 = BinOp (Eq, e1, e2) + +(** Create expresstion [e1 != e2] *) +let ne e1 e2 = BinOp (Ne, e1, e2) + +(** Create expression [e1 <= e2] *) +let le e1 e2 = BinOp (Le, e1, e2) + +(** Create expression [e1 < e2] *) +let lt e1 e2 = BinOp (Lt, e1, e2) + +(** Extract the ids and pvars from an expression *) +let get_vars exp = + let rec get_vars_ exp vars = + match exp with + | Lvar pvar + -> (fst vars, pvar :: snd vars) + | Var id + -> (id :: fst vars, snd vars) + | Cast (_, e) | UnOp (_, e, _) | Lfield (e, _, _) | Exn e + -> get_vars_ e vars + | BinOp (_, e1, e2) | Lindex (e1, e2) + -> get_vars_ e1 vars |> get_vars_ e2 + | Closure {captured_vars} + -> List.fold + ~f:(fun vars_acc (captured_exp, _, _) -> get_vars_ captured_exp vars_acc) + ~init:vars captured_vars + | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) + -> vars + (* TODO: Sizeof dynamic length expressions may contain variables, do not ignore them. *) + | Sizeof _ + -> vars + in + get_vars_ exp ([], []) + +(** Pretty print an expression. *) +let rec pp_ pe pp_t f e = + let pp_exp = pp_ pe pp_t in + let print_binop_stm_output e1 op e2 = + match (op : Binop.t) with + | Eq | Ne | PlusA | Mult + -> F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe op) pp_exp e1 + | Lt + -> F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Gt) pp_exp e1 + | Gt + -> F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Lt) pp_exp e1 + | Le + -> F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Ge) pp_exp e1 + | Ge + -> F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Le) pp_exp e1 + | _ + -> F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2 + in + match (e : t) with + | Var id + -> Ident.pp pe f id + | Const c + -> F.fprintf f "%a" (Const.pp pe) c + | Cast (typ, e) + -> F.fprintf f "(%a)%a" pp_t typ pp_exp e + | UnOp (op, e, _) + -> F.fprintf f "%s%a" (Unop.str op) pp_exp e + | BinOp (op, Const c, e2) when Config.smt_output + -> print_binop_stm_output (Const c) op e2 + | BinOp (op, e1, e2) + -> F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2 + | Exn e + -> F.fprintf f "EXN %a" pp_exp e + | Closure {name; captured_vars} + -> let id_exps = List.map ~f:(fun (id_exp, _, _) -> id_exp) captured_vars in + F.fprintf f "(%a)" (Pp.comma_seq pp_exp) (Const (Cfun name) :: id_exps) + | Lvar pv + -> Pvar.pp pe f pv + | Lfield (e, fld, _) + -> F.fprintf f "%a.%a" pp_exp e Typ.Fieldname.pp fld + | Lindex (e1, e2) + -> F.fprintf f "%a[%a]" pp_exp e1 pp_exp e2 + | Sizeof {typ; nbytes; dynamic_length; subtype} + -> let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" pp_exp) l in + let pp_size f size = Option.iter ~f:(Int.pp f) size in + F.fprintf f "sizeof(%a%a%a%a)" pp_t typ pp_size nbytes pp_len dynamic_length Subtype.pp + subtype + +let pp_printenv pe pp_typ f e = pp_ pe (pp_typ pe) f e + +let pp f e = pp_printenv Pp.text Typ.pp f e + +let to_string e = F.asprintf "%a" pp e diff --git a/infer/src/IR/Exp.mli b/infer/src/IR/Exp.mli new file mode 100644 index 000000000..5dddfd601 --- /dev/null +++ b/infer/src/IR/Exp.mli @@ -0,0 +1,159 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Expressions *) +open! IStd +module L = Logging +module F = Format + +type closure = {name: Typ.Procname.t; captured_vars: (t * Pvar.t * Typ.t) list} + +(** This records information about a [sizeof(typ)] expression. + + [nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known. + + If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number + of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may + differ from the [static_length] obtained from the type definition, e.g. when an array is + over-allocated. + + If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*) +and sizeof_data = {typ: Typ.t; nbytes: int option; dynamic_length: t option; subtype: Subtype.t} + +(** Program expressions. *) +and t = + (** Pure variable: it is not an lvalue *) + | Var of Ident.t (** Unary operator with type of the result if known *) + | UnOp of Unop.t * t * Typ.t option (** Binary operator *) + | BinOp of Binop.t * t * t (** Exception *) + | Exn of t (** Anonymous function *) + | Closure of closure (** Constants *) + | Const of Const.t (** Type cast *) + | Cast of Typ.t * t (** The address of a program variable *) + | Lvar of Pvar.t (** A field offset, the type is the surrounding struct type *) + | Lfield of t * Typ.Fieldname.t * Typ.t (** An array index offset: [exp1\[exp2\]] *) + | Lindex of t * t + | Sizeof of sizeof_data + [@@deriving compare] + +(** Equality for expressions. *) + +val equal : t -> t -> bool + +(** Hash function for expressions. *) + +val hash : t -> int + +(** Set of expressions. *) + +module Set : Caml.Set.S with type elt = t + +(** Map with expression keys. *) + +module Map : Caml.Map.S with type key = t + +(** Hashtable with expression keys. *) + +module Hash : Caml.Hashtbl.S with type key = t + +(** returns true is index is an array index of arr. *) + +val is_array_index_of : t -> t -> bool + +val is_null_literal : t -> bool + +(** return true if [exp] is the special this/self expression *) + +val is_this : t -> bool + +val is_zero : t -> bool + +(** {2 Utility Functions for Expressions} *) + +(** Turn an expression representing a type into the type it represents + If not a sizeof, return the default type if given, otherwise raise an exception *) + +val texp_to_typ : Typ.t option -> t -> Typ.t + +(** Return the root of [lexp]. *) + +val root_of_lexp : t -> t + +(** Get an expression "undefined", the boolean indicates + whether the undefined value goest into the footprint *) + +val get_undefined : bool -> t + +(** Checks whether an expression denotes a location using pointer arithmetic. + Currently, catches array - indexing expressions such as a[i] only. *) + +val pointer_arith : t -> bool + +(** returns true if the expression represents a stack-directed address *) + +val is_stack_addr : t -> bool + +(** returns true if the expression operates on address of local variable *) + +val has_local_addr : t -> bool + +(** Integer constant 0 *) + +val zero : t + +(** Null constant *) + +val null : t + +(** Integer constant 1 *) + +val one : t + +(** Integer constant -1 *) + +val minus_one : t + +(** Create integer constant *) + +val int : IntLit.t -> t + +(** Create float constant *) + +val float : float -> t + +(** Create integer constant corresponding to the boolean value *) + +val bool : bool -> t + +(** Create expresstion [e1 == e2] *) + +val eq : t -> t -> t + +(** Create expresstion [e1 != e2] *) + +val ne : t -> t -> t + +(** Create expresstion [e1 <= e2] *) + +val le : t -> t -> t + +(** Create expression [e1 < e2] *) + +val lt : t -> t -> t + +(** Extract the ids and pvars from an expression *) + +val get_vars : t -> Ident.t list * Pvar.t list + +val pp_printenv : Pp.env -> (Pp.env -> F.formatter -> Typ.t -> unit) -> F.formatter -> t -> unit + +val pp : F.formatter -> t -> unit + +val to_string : t -> string diff --git a/infer/src/IR/Exp.re b/infer/src/IR/Exp.re deleted file mode 100644 index f2e7ba2e7..000000000 --- a/infer/src/IR/Exp.re +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** The Smallfoot Intermediate Language: Expressions */ -module L = Logging; - -module F = Format; - -/* reverse the natural order on Var */ -type _ident = Ident.t; - -let compare__ident x y => Ident.compare y x; - -type closure = {name: Typ.Procname.t, captured_vars: list (t, Pvar.t, Typ.t)} -/** This records information about a [sizeof(typ)] expression. - - [nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known. - - If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number - of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may - differ from the [static_length] obtained from the type definition, e.g. when an array is - over-allocated. - - If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*/ -and sizeof_data = {typ: Typ.t, nbytes: option int, dynamic_length: option t, subtype: Subtype.t} -/** Program expressions. */ -and t = - /** Pure variable: it is not an lvalue */ - | Var _ident - /** Unary operator with type of the result if known */ - | UnOp Unop.t t (option Typ.t) - /** Binary operator */ - | BinOp Binop.t t t - /** Exception */ - | Exn t - /** Anonymous function */ - | Closure closure - /** Constants */ - | Const Const.t - /** Type cast */ - | Cast Typ.t t - /** The address of a program variable */ - | Lvar Pvar.t - /** A field offset, the type is the surrounding struct type */ - | Lfield t Typ.Fieldname.t Typ.t - /** An array index offset: [exp1\[exp2\]] */ - | Lindex t t - | Sizeof sizeof_data -[@@deriving compare]; - -let equal = [%compare.equal : t]; - -let hash = Hashtbl.hash; - -module Set = - Caml.Set.Make { - type nonrec t = t; - let compare = compare; - }; - -module Map = - Caml.Map.Make { - type nonrec t = t; - let compare = compare; - }; - -module Hash = - Hashtbl.Make { - type nonrec t = t; - let equal = equal; - let hash = hash; - }; - -let rec is_array_index_of exp1 exp2 => - switch exp1 { - | Lindex exp _ => is_array_index_of exp exp2 - | _ => equal exp1 exp2 - }; - -let is_null_literal = - fun - | Const (Cint n) => IntLit.isnull n - | _ => false; - -let is_this = - fun - | Lvar pvar => Pvar.is_this pvar - | _ => false; - -let is_zero = - fun - | Const (Cint n) => IntLit.iszero n - | _ => false; - - -/** {2 Utility Functions for Expressions} */ - -/** Turn an expression representing a type into the type it represents - If not a sizeof, return the default type if given, otherwise raise an exception */ -let texp_to_typ default_opt => - fun - | Sizeof {typ} => typ - | _ => Typ.unsome "texp_to_typ" default_opt; - - -/** Return the root of [lexp]. */ -let rec root_of_lexp lexp => - switch (lexp: t) { - | Var _ => lexp - | Const _ => lexp - | Cast _ e => root_of_lexp e - | UnOp _ - | BinOp _ - | Exn _ - | Closure _ => lexp - | Lvar _ => lexp - | Lfield e _ _ => root_of_lexp e - | Lindex e _ => root_of_lexp e - | Sizeof _ => lexp - }; - - -/** Checks whether an expression denotes a location by pointer arithmetic. - Currently, catches array - indexing expressions such as a[i] only. */ -let rec pointer_arith = - fun - | Lfield e _ _ => pointer_arith e - | Lindex _ => true - | _ => false; - -let get_undefined footprint => - Var (Ident.create_fresh (if footprint {Ident.kfootprint} else {Ident.kprimed})); - - -/** returns true if the expression represents a stack-directed address */ -let rec is_stack_addr e => - switch (e: t) { - | Lvar pv => not (Pvar.is_global pv) - | UnOp _ e' _ - | Cast _ e' - | Lfield e' _ _ - | Lindex e' _ => is_stack_addr e' - | _ => false - }; - - -/** returns true if the express operates on address of local variable */ -let rec has_local_addr e => - switch (e: t) { - | Lvar pv => Pvar.is_local pv - | UnOp _ e' _ - | Cast _ e' - | Lfield e' _ _ => has_local_addr e' - | BinOp _ e0 e1 - | Lindex e0 e1 => has_local_addr e0 || has_local_addr e1 - | _ => false - }; - - -/** Create integer constant */ -let int i => Const (Cint i); - - -/** Create float constant */ -let float v => Const (Cfloat v); - - -/** Integer constant 0 */ -let zero = int IntLit.zero; - - -/** Null constant */ -let null = int IntLit.null; - - -/** Integer constant 1 */ -let one = int IntLit.one; - - -/** Integer constant -1 */ -let minus_one = int IntLit.minus_one; - - -/** Create integer constant corresponding to the boolean value */ -let bool b => if b {one} else {zero}; - - -/** Create expresstion [e1 == e2] */ -let eq e1 e2 => BinOp Eq e1 e2; - - -/** Create expresstion [e1 != e2] */ -let ne e1 e2 => BinOp Ne e1 e2; - - -/** Create expression [e1 <= e2] */ -let le e1 e2 => BinOp Le e1 e2; - - -/** Create expression [e1 < e2] */ -let lt e1 e2 => BinOp Lt e1 e2; - - -/** Extract the ids and pvars from an expression */ -let get_vars exp => { - let rec get_vars_ exp vars => - switch exp { - | Lvar pvar => (fst vars, [pvar, ...snd vars]) - | Var id => ([id, ...fst vars], snd vars) - | Cast _ e - | UnOp _ e _ - | Lfield e _ _ - | Exn e => get_vars_ e vars - | BinOp _ e1 e2 - | Lindex e1 e2 => get_vars_ e1 vars |> get_vars_ e2 - | Closure {captured_vars} => - List.fold - f::(fun vars_acc (captured_exp, _, _) => get_vars_ captured_exp vars_acc) - init::vars - captured_vars - | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) => vars - /* TODO: Sizeof dynamic length expressions may contain variables, do not ignore them. */ - | Sizeof _ => vars - }; - get_vars_ exp ([], []) -}; - - -/** Pretty print an expression. */ -let rec pp_ pe pp_t f e => { - let pp_exp = pp_ pe pp_t; - let print_binop_stm_output e1 op e2 => - switch (op: Binop.t) { - | Eq - | Ne - | PlusA - | Mult => F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe op) pp_exp e1 - | Lt => F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Gt) pp_exp e1 - | Gt => F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Lt) pp_exp e1 - | Le => F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Ge) pp_exp e1 - | Ge => F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Le) pp_exp e1 - | _ => F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2 - }; - switch (e: t) { - | Var id => (Ident.pp pe) f id - | Const c => F.fprintf f "%a" (Const.pp pe) c - | Cast typ e => F.fprintf f "(%a)%a" pp_t typ pp_exp e - | UnOp op e _ => F.fprintf f "%s%a" (Unop.str op) pp_exp e - | BinOp op (Const c) e2 when Config.smt_output => print_binop_stm_output (Const c) op e2 - | BinOp op e1 e2 => F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2 - | Exn e => F.fprintf f "EXN %a" pp_exp e - | Closure {name, captured_vars} => - let id_exps = List.map f::(fun (id_exp, _, _) => id_exp) captured_vars; - F.fprintf f "(%a)" (Pp.comma_seq pp_exp) [Const (Cfun name), ...id_exps] - | Lvar pv => Pvar.pp pe f pv - | Lfield e fld _ => F.fprintf f "%a.%a" pp_exp e Typ.Fieldname.pp fld - | Lindex e1 e2 => F.fprintf f "%a[%a]" pp_exp e1 pp_exp e2 - | Sizeof {typ, nbytes, dynamic_length, subtype} => - let pp_len f l => Option.iter f::(F.fprintf f "[%a]" pp_exp) l; - let pp_size f size => Option.iter f::(Int.pp f) size; - F.fprintf f "sizeof(%a%a%a%a)" pp_t typ pp_size nbytes pp_len dynamic_length Subtype.pp subtype - } -}; - -let pp_printenv pe pp_typ f e => pp_ pe (pp_typ pe) f e; - -let pp f e => pp_printenv Pp.text Typ.pp f e; - -let to_string e => F.asprintf "%a" pp e; diff --git a/infer/src/IR/Exp.rei b/infer/src/IR/Exp.rei deleted file mode 100644 index 36715bc2c..000000000 --- a/infer/src/IR/Exp.rei +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Expressions */ -module L = Logging; - -module F = Format; - -type closure = {name: Typ.Procname.t, captured_vars: list (t, Pvar.t, Typ.t)} -/** This records information about a [sizeof(typ)] expression. - - [nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known. - - If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number - of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may - differ from the [static_length] obtained from the type definition, e.g. when an array is - over-allocated. - - If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*/ -and sizeof_data = {typ: Typ.t, nbytes: option int, dynamic_length: option t, subtype: Subtype.t} -/** Program expressions. */ -and t = - /** Pure variable: it is not an lvalue */ - | Var Ident.t - /** Unary operator with type of the result if known */ - | UnOp Unop.t t (option Typ.t) - /** Binary operator */ - | BinOp Binop.t t t - /** Exception */ - | Exn t - /** Anonymous function */ - | Closure closure - /** Constants */ - | Const Const.t - /** Type cast */ - | Cast Typ.t t - /** The address of a program variable */ - | Lvar Pvar.t - /** A field offset, the type is the surrounding struct type */ - | Lfield t Typ.Fieldname.t Typ.t - /** An array index offset: [exp1\[exp2\]] */ - | Lindex t t - | Sizeof sizeof_data -[@@deriving compare]; - - -/** Equality for expressions. */ -let equal: t => t => bool; - - -/** Hash function for expressions. */ -let hash: t => int; - - -/** Set of expressions. */ -module Set: Caml.Set.S with type elt = t; - - -/** Map with expression keys. */ -module Map: Caml.Map.S with type key = t; - - -/** Hashtable with expression keys. */ -module Hash: Caml.Hashtbl.S with type key = t; - - -/** returns true is index is an array index of arr. */ -let is_array_index_of: t => t => bool; - -let is_null_literal: t => bool; - - -/** return true if [exp] is the special this/self expression */ -let is_this: t => bool; - -let is_zero: t => bool; - - -/** {2 Utility Functions for Expressions} */ - -/** Turn an expression representing a type into the type it represents - If not a sizeof, return the default type if given, otherwise raise an exception */ -let texp_to_typ: option Typ.t => t => Typ.t; - - -/** Return the root of [lexp]. */ -let root_of_lexp: t => t; - - -/** Get an expression "undefined", the boolean indicates - whether the undefined value goest into the footprint */ -let get_undefined: bool => t; - - -/** Checks whether an expression denotes a location using pointer arithmetic. - Currently, catches array - indexing expressions such as a[i] only. */ -let pointer_arith: t => bool; - - -/** returns true if the expression represents a stack-directed address */ -let is_stack_addr: t => bool; - - -/** returns true if the expression operates on address of local variable */ -let has_local_addr: t => bool; - - -/** Integer constant 0 */ -let zero: t; - - -/** Null constant */ -let null: t; - - -/** Integer constant 1 */ -let one: t; - - -/** Integer constant -1 */ -let minus_one: t; - - -/** Create integer constant */ -let int: IntLit.t => t; - - -/** Create float constant */ -let float: float => t; - - -/** Create integer constant corresponding to the boolean value */ -let bool: bool => t; - - -/** Create expresstion [e1 == e2] */ -let eq: t => t => t; - - -/** Create expresstion [e1 != e2] */ -let ne: t => t => t; - - -/** Create expresstion [e1 <= e2] */ -let le: t => t => t; - - -/** Create expression [e1 < e2] */ -let lt: t => t => t; - - -/** Extract the ids and pvars from an expression */ -let get_vars: t => (list Ident.t, list Pvar.t); - -let pp_printenv: Pp.env => (Pp.env => F.formatter => Typ.t => unit) => F.formatter => t => unit; - -let pp: F.formatter => t => unit; - -let to_string: t => string; diff --git a/infer/src/IR/HilExp.ml b/infer/src/IR/HilExp.ml index 086ab49b8..0ebb5e3ff 100644 --- a/infer/src/IR/HilExp.ml +++ b/infer/src/IR/HilExp.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -21,107 +20,109 @@ type t = | Constant of Const.t | Cast of Typ.t * t | Sizeof of Typ.t * t option -[@@deriving compare] + [@@deriving compare] let rec pp fmt = function - | AccessPath access_path -> - AccessPath.Raw.pp fmt access_path - | UnaryOperator (op, e, _) -> - F.fprintf fmt "%s%a" (Unop.str op) pp e - | BinaryOperator(op, e1, e2) -> - F.fprintf fmt "%a %s %a" pp e1 (Binop.str Pp.text op) pp e2 - | Exception e -> - F.fprintf fmt "exception %a" pp e - | Closure (pname, _) -> - F.fprintf fmt "closure(%a)" Typ.Procname.pp pname - | Constant c -> - (Const.pp Pp.text) fmt c - | Cast (typ, e) -> - F.fprintf fmt "(%a) %a" (Typ.pp_full Pp.text) typ pp e - | Sizeof (typ, length) -> - let pp_length fmt = Option.iter ~f:(F.fprintf fmt "[%a]" pp) in + | AccessPath access_path + -> AccessPath.Raw.pp fmt access_path + | UnaryOperator (op, e, _) + -> F.fprintf fmt "%s%a" (Unop.str op) pp e + | BinaryOperator (op, e1, e2) + -> F.fprintf fmt "%a %s %a" pp e1 (Binop.str Pp.text op) pp e2 + | Exception e + -> F.fprintf fmt "exception %a" pp e + | Closure (pname, _) + -> F.fprintf fmt "closure(%a)" Typ.Procname.pp pname + | Constant c + -> Const.pp Pp.text fmt c + | Cast (typ, e) + -> F.fprintf fmt "(%a) %a" (Typ.pp_full Pp.text) typ pp e + | Sizeof (typ, length) + -> let pp_length fmt = Option.iter ~f:(F.fprintf fmt "[%a]" pp) in F.fprintf fmt "sizeof(%a%a)" (Typ.pp_full Pp.text) typ pp_length length let get_access_paths exp0 = let rec get_access_paths_ exp acc = match exp with - | AccessPath ap -> - ap :: acc - | Cast (_, e) | UnaryOperator (_, e, _) | Exception e | Sizeof (_, Some e) -> - get_access_paths_ e acc - | BinaryOperator (_, e1, e2) -> - get_access_paths_ e1 acc - |> get_access_paths_ e2 - | Closure _ | Constant _ | Sizeof _ -> - acc in + | AccessPath ap + -> ap :: acc + | Cast (_, e) | UnaryOperator (_, e, _) | Exception e | Sizeof (_, Some e) + -> get_access_paths_ e acc + | BinaryOperator (_, e1, e2) + -> get_access_paths_ e1 acc |> get_access_paths_ e2 + | Closure _ | Constant _ | Sizeof _ + -> acc + in get_access_paths_ exp0 [] (* convert an SIL expression into an HIL expression. the [f_resolve_id] function should map an SSA temporary variable to the access path it represents. evaluating the HIL expression should produce the same result as evaluating the SIL expression and replacing the temporary variables using [f_resolve_id] *) -let rec of_sil ~f_resolve_id (exp : Exp.t) typ = match exp with - | Var id -> - let ap = match f_resolve_id (Var.of_id id) with - | Some access_path -> access_path - | None -> AccessPath.of_id id typ in +let rec of_sil ~f_resolve_id (exp: Exp.t) typ = + match exp with + | Var id + -> let ap = + match f_resolve_id (Var.of_id id) with + | Some access_path + -> access_path + | None + -> AccessPath.of_id id typ + in AccessPath ap - | UnOp (op, e, typ_opt) -> - UnaryOperator (op, of_sil ~f_resolve_id e typ, typ_opt) - | BinOp (op, e0, e1) -> - BinaryOperator (op, of_sil ~f_resolve_id e0 typ, of_sil ~f_resolve_id e1 typ) - | Exn e -> - Exception (of_sil ~f_resolve_id e typ) - | Const c -> - Constant c - | Cast (cast_typ, e) -> - Cast (cast_typ, of_sil ~f_resolve_id e typ) - | Sizeof {typ; dynamic_length} -> - Sizeof (typ, Option.map ~f:(fun e -> of_sil ~f_resolve_id e typ) dynamic_length) - | Closure closure -> - let environment = + | UnOp (op, e, typ_opt) + -> UnaryOperator (op, of_sil ~f_resolve_id e typ, typ_opt) + | BinOp (op, e0, e1) + -> BinaryOperator (op, of_sil ~f_resolve_id e0 typ, of_sil ~f_resolve_id e1 typ) + | Exn e + -> Exception (of_sil ~f_resolve_id e typ) + | Const c + -> Constant c + | Cast (cast_typ, e) + -> Cast (cast_typ, of_sil ~f_resolve_id e typ) + | Sizeof {typ; dynamic_length} + -> Sizeof (typ, Option.map ~f:(fun e -> of_sil ~f_resolve_id e typ) dynamic_length) + | Closure closure + -> let environment = List.map ~f:(fun (value, pvar, typ) -> - AccessPath.base_of_pvar pvar typ, of_sil ~f_resolve_id value typ) - closure.captured_vars in + (AccessPath.base_of_pvar pvar typ, of_sil ~f_resolve_id value typ)) + closure.captured_vars + in Closure (closure.name, environment) | Lfield (root_exp, fld, root_exp_typ) -> ( - match AccessPath.of_lhs_exp exp typ ~f_resolve_id with - | Some access_path -> - AccessPath access_path - | None -> - (* unsupported field expression: represent with a dummy variable *) - of_sil ~f_resolve_id - (Exp.Lfield - (Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0), - fld, - root_exp_typ)) - typ ) - | Lindex (Const (Cstr s), index_exp) -> - (* indexed string literal (e.g., "foo"[1]). represent this by introducing a dummy variable + match AccessPath.of_lhs_exp exp typ ~f_resolve_id with + | Some access_path + -> AccessPath access_path + | None + -> (* unsupported field expression: represent with a dummy variable *) + of_sil ~f_resolve_id + (Exp.Lfield + ( Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0) + , fld + , root_exp_typ )) typ ) + | Lindex (Const Cstr s, index_exp) + -> (* indexed string literal (e.g., "foo"[1]). represent this by introducing a dummy variable for the string literal. if you actually need to see the value of the string literal in the analysis, you should probably be using SIL. this is unsound if the code modifies the literal, e.g. using `const_cast` *) of_sil ~f_resolve_id (Exp.Lindex (Var (Ident.create_normal (Ident.string_to_name s) 0), index_exp)) typ | Lindex (root_exp, index_exp) -> ( - match AccessPath.of_lhs_exp exp typ ~f_resolve_id with - | Some access_path -> - AccessPath access_path - | None -> - (* unsupported index expression: represent with a dummy variable *) - of_sil ~f_resolve_id - (Exp.Lindex - (Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0), - index_exp)) - typ ) + match AccessPath.of_lhs_exp exp typ ~f_resolve_id with + | Some access_path + -> AccessPath access_path + | None + -> (* unsupported index expression: represent with a dummy variable *) + of_sil ~f_resolve_id + (Exp.Lindex + ( Var (Ident.create_normal (Ident.string_to_name (Exp.to_string root_exp)) 0) + , index_exp )) typ ) | Lvar _ -> - match AccessPath.of_lhs_exp exp typ ~f_resolve_id with - | Some access_path -> - AccessPath access_path - | None -> - failwithf "Couldn't convert var expression %a to access path" Exp.pp exp + match AccessPath.of_lhs_exp exp typ ~f_resolve_id with + | Some access_path + -> AccessPath access_path + | None + -> failwithf "Couldn't convert var expression %a to access path" Exp.pp exp -let is_null_literal = function - | Constant (Cint n) -> IntLit.isnull n - | _ -> false +let is_null_literal = function Constant Cint n -> IntLit.isnull n | _ -> false diff --git a/infer/src/IR/HilExp.mli b/infer/src/IR/HilExp.mli index 61a17dba1..7018814b7 100644 --- a/infer/src/IR/HilExp.mli +++ b/infer/src/IR/HilExp.mli @@ -8,36 +8,29 @@ *) open! IStd - module F = Format type t = - | AccessPath of AccessPath.Raw.t - (** access path (e.g., x.f.g or x[i]) *) + | AccessPath of AccessPath.Raw.t (** access path (e.g., x.f.g or x[i]) *) | UnaryOperator of Unop.t * t * Typ.t option - (** Unary operator with type of the result if known *) - | BinaryOperator of Binop.t * t * t - (** Binary operator *) - | Exception of t - (** Exception *) - | Closure of Typ.Procname.t * (AccessPath.base * t) list - (** Name of function + environment *) - | Constant of Const.t - (** Constants *) - | Cast of Typ.t * t - (** Type cast *) + (** Unary operator with type of the result if known *) + | BinaryOperator of Binop.t * t * t (** Binary operator *) + | Exception of t (** Exception *) + | Closure of Typ.Procname.t * (AccessPath.base * t) list (** Name of function + environment *) + | Constant of Const.t (** Constants *) + | Cast of Typ.t * t (** Type cast *) | Sizeof of Typ.t * t option - (** C-style sizeof(), and also used to treate a type as an expression. Refer to [Exp] module for + (** C-style sizeof(), and also used to treate a type as an expression. Refer to [Exp] module for canonical documentation *) -[@@deriving compare] + [@@deriving compare] val pp : F.formatter -> t -> unit -(** Convert SIL expression to HIL expression *) val of_sil : f_resolve_id:(Var.t -> AccessPath.Raw.t option) -> Exp.t -> Typ.t -> t +(** Convert SIL expression to HIL expression *) +val get_access_paths : t -> AccessPath.Raw.t list (** Get all the access paths used in the given HIL expression, including duplicates if a path is used more than once. *) -val get_access_paths : t -> AccessPath.Raw.t list val is_null_literal : t -> bool diff --git a/infer/src/IR/HilInstr.ml b/infer/src/IR/HilInstr.ml index 65d526b7f..4a3e8ebfd 100644 --- a/infer/src/IR/HilInstr.ml +++ b/infer/src/IR/HilInstr.ml @@ -8,108 +8,110 @@ *) open! IStd - module F = Format module L = Logging -type call = - | Direct of Typ.Procname.t - | Indirect of AccessPath.Raw.t -[@@deriving compare] +type call = Direct of Typ.Procname.t | Indirect of AccessPath.Raw.t [@@deriving compare] let pp_call fmt = function - | Direct pname -> Typ.Procname.pp fmt pname - | Indirect access_path -> F.fprintf fmt "*%a" AccessPath.Raw.pp access_path + | Direct pname + -> Typ.Procname.pp fmt pname + | Indirect access_path + -> F.fprintf fmt "*%a" AccessPath.Raw.pp access_path type t = | Assign of AccessPath.Raw.t * HilExp.t * Location.t | Assume of HilExp.t * [`Then | `Else] * Sil.if_kind * Location.t | Call of AccessPath.base option * call * HilExp.t list * CallFlags.t * Location.t -[@@deriving compare] + [@@deriving compare] let pp fmt = function - | Assign (access_path, exp, loc) -> - F.fprintf fmt "%a := %a [%a]" AccessPath.Raw.pp access_path HilExp.pp exp Location.pp loc - | Assume (exp, _, _, loc) -> - F.fprintf fmt "assume %a [%a]" HilExp.pp exp Location.pp loc - | Call (ret_opt, call, actuals, _, loc) -> - let pp_ret fmt = Option.iter ~f:(F.fprintf fmt "%a := " AccessPath.pp_base) in + | Assign (access_path, exp, loc) + -> F.fprintf fmt "%a := %a [%a]" AccessPath.Raw.pp access_path HilExp.pp exp Location.pp loc + | Assume (exp, _, _, loc) + -> F.fprintf fmt "assume %a [%a]" HilExp.pp exp Location.pp loc + | Call (ret_opt, call, actuals, _, loc) + -> let pp_ret fmt = Option.iter ~f:(F.fprintf fmt "%a := " AccessPath.pp_base) in let pp_actuals fmt = PrettyPrintable.pp_collection ~pp_item:HilExp.pp fmt in F.fprintf fmt "%a%a(%a) [%a]" pp_ret ret_opt pp_call call pp_actuals actuals Location.pp loc -type translation = - | Instr of t - | Bind of Var.t * AccessPath.Raw.t - | Unbind of Var.t list - | Ignore +type translation = Instr of t | Bind of Var.t * AccessPath.Raw.t | Unbind of Var.t list | Ignore (* convert an SIL instruction into an HIL instruction. the [f_resolve_id] function should map an SSA temporary variable to the access path it represents. evaluating the HIL instruction should produce the same result as evaluating the SIL instruction and replacing the temporary variables using [f_resolve_id]. *) -let of_sil ~f_resolve_id (instr : Sil.instr) = +let of_sil ~f_resolve_id (instr: Sil.instr) = let analyze_id_assignment lhs_id rhs_exp rhs_typ loc = let rhs_hil_exp = HilExp.of_sil ~f_resolve_id rhs_exp rhs_typ in match HilExp.get_access_paths rhs_hil_exp with - | [rhs_access_path] -> - Bind (lhs_id, rhs_access_path) - | _ -> - Instr (Assign (((lhs_id, rhs_typ), []), rhs_hil_exp, loc)) in + | [rhs_access_path] + -> Bind (lhs_id, rhs_access_path) + | _ + -> Instr (Assign (((lhs_id, rhs_typ), []), rhs_hil_exp, loc)) + in match instr with - | Load (lhs_id, rhs_exp, rhs_typ, loc) -> - analyze_id_assignment (Var.of_id lhs_id) rhs_exp rhs_typ loc - | Store (Lvar lhs_pvar, lhs_typ, rhs_exp, loc) - when Pvar.is_ssa_frontend_tmp lhs_pvar -> - analyze_id_assignment (Var.of_pvar lhs_pvar) rhs_exp lhs_typ loc - | Call (Some (ret_id, _), Const (Cfun callee_pname), - (target_exp, _) :: (Sizeof {typ=cast_typ}, _) :: _ , loc, _) - when Typ.Procname.equal callee_pname BuiltinDecl.__cast -> - analyze_id_assignment (Var.of_id ret_id) target_exp cast_typ loc - | Store (lhs_exp, typ, rhs_exp, loc) -> - let lhs_access_path = + | Load (lhs_id, rhs_exp, rhs_typ, loc) + -> analyze_id_assignment (Var.of_id lhs_id) rhs_exp rhs_typ loc + | Store (Lvar lhs_pvar, lhs_typ, rhs_exp, loc) when Pvar.is_ssa_frontend_tmp lhs_pvar + -> analyze_id_assignment (Var.of_pvar lhs_pvar) rhs_exp lhs_typ loc + | Call + ( Some (ret_id, _) + , Const Cfun callee_pname + , (target_exp, _) :: (Sizeof {typ= cast_typ}, _) :: _ + , loc + , _ ) + when Typ.Procname.equal callee_pname BuiltinDecl.__cast + -> analyze_id_assignment (Var.of_id ret_id) target_exp cast_typ loc + | Store (lhs_exp, typ, rhs_exp, loc) + -> let lhs_access_path = match HilExp.of_sil ~f_resolve_id lhs_exp typ with - | AccessPath ap -> - ap - | BinaryOperator (_, exp0, exp1) -> + | AccessPath ap + -> ap + | BinaryOperator (_, exp0, exp1) -> ( + match (* pointer arithmetic. somewhere in one of the expressions, there should be at least one pointer type represented as an access path. just use that access path and forget about the arithmetic. if you need to model this more precisely, you should be using SIL instead *) - begin - match HilExp.get_access_paths exp0 with - | ap :: _ -> - ap - | [] -> - begin - match HilExp.get_access_paths exp1 with - | ap :: _ -> - ap - | [] -> - invalid_argf - "Invalid pointer arithmetic expression %a used as LHS" Exp.pp lhs_exp - end - end - | _ -> - invalid_argf "Non-assignable LHS expression %a" Exp.pp lhs_exp in + HilExp.get_access_paths exp0 + with + | ap :: _ + -> ap + | [] -> + match HilExp.get_access_paths exp1 with + | ap :: _ + -> ap + | [] + -> invalid_argf "Invalid pointer arithmetic expression %a used as LHS" Exp.pp lhs_exp + ) + | _ + -> invalid_argf "Non-assignable LHS expression %a" Exp.pp lhs_exp + in Instr (Assign (lhs_access_path, HilExp.of_sil ~f_resolve_id rhs_exp typ, loc)) - | Call (ret_opt, call_exp, formals, loc, call_flags) -> - let hil_ret = Option.map ~f:(fun (ret_id, ret_typ) -> Var.of_id ret_id, ret_typ) ret_opt in + | Call (ret_opt, call_exp, formals, loc, call_flags) + -> let hil_ret = Option.map ~f:(fun (ret_id, ret_typ) -> (Var.of_id ret_id, ret_typ)) ret_opt in let hil_call = match HilExp.of_sil ~f_resolve_id call_exp (Typ.mk Tvoid) with - | Constant (Cfun procname) -> Direct procname - | AccessPath access_path -> Indirect access_path - | call_exp -> invalid_argf "Unexpected call expression %a" HilExp.pp call_exp in + | Constant Cfun procname + -> Direct procname + | AccessPath access_path + -> Indirect access_path + | call_exp + -> invalid_argf "Unexpected call expression %a" HilExp.pp call_exp + in let formals = List.map ~f:(fun (exp, typ) -> HilExp.of_sil ~f_resolve_id exp typ) formals in Instr (Call (hil_ret, hil_call, formals, call_flags, loc)) - | Prune (exp, loc, true_branch, if_kind) -> - let hil_exp = HilExp.of_sil ~f_resolve_id exp (Typ.mk (Tint IBool)) in + | Prune (exp, loc, true_branch, if_kind) + -> let hil_exp = HilExp.of_sil ~f_resolve_id exp (Typ.mk (Tint IBool)) in let branch = if true_branch then `Then else `Else in Instr (Assume (hil_exp, branch, if_kind, loc)) - | Nullify (pvar, _) -> - Unbind [Var.of_pvar pvar] - | Remove_temps (ids, _) -> - Unbind (List.map ~f:Var.of_id ids) + | Nullify (pvar, _) + -> Unbind [Var.of_pvar pvar] + | Remove_temps (ids, _) + -> Unbind (List.map ~f:Var.of_id ids) (* ignoring for now; will translate as builtin function call if needed *) - | Abstract _ | Declare_locals _ -> - (* these don't seem useful for most analyses. can translate them later if we want to *) + | Abstract _ + | Declare_locals _ + -> (* these don't seem useful for most analyses. can translate them later if we want to *) Ignore diff --git a/infer/src/IR/HilInstr.mli b/infer/src/IR/HilInstr.mli index c72557f9f..3686d2883 100644 --- a/infer/src/IR/HilInstr.mli +++ b/infer/src/IR/HilInstr.mli @@ -8,34 +8,29 @@ *) open! IStd - module F = Format (** type of a procedure call; either direct or via function pointer *) -type call = - | Direct of Typ.Procname.t - | Indirect of AccessPath.Raw.t -[@@deriving compare] +type call = Direct of Typ.Procname.t | Indirect of AccessPath.Raw.t [@@deriving compare] val pp_call : F.formatter -> call -> unit type t = - | Assign of AccessPath.Raw.t * HilExp.t * Location.t - (** LHS access path, RHS expression *) + | Assign of AccessPath.Raw.t * HilExp.t * Location.t (** LHS access path, RHS expression *) | Assume of HilExp.t * [`Then | `Else] * Sil.if_kind * Location.t - (** Assumed expression, true_branch boolean, source of the assume (conditional, ternary, etc.) *) + (** Assumed expression, true_branch boolean, source of the assume (conditional, ternary, etc.) *) | Call of AccessPath.base option * call * HilExp.t list * CallFlags.t * Location.t - (** Var to hold the return if it exists, call expression, formals *) -[@@deriving compare] + (** Var to hold the return if it exists, call expression, formals *) + [@@deriving compare] val pp : F.formatter -> t -> unit (** Result of translating an SIL instruction *) type translation = - | Instr of t (** HIL instruction to execute *) - | Bind of Var.t * AccessPath.Raw.t (** add binding to identifier map *) - | Unbind of Var.t list (** remove binding from identifier map *) - | Ignore (** no-op *) + | Instr of t (** HIL instruction to execute *) + | Bind of Var.t * AccessPath.Raw.t (** add binding to identifier map *) + | Unbind of Var.t list (** remove binding from identifier map *) + | Ignore (** no-op *) -(** Convert an SIL instruction to an HIL instruction *) val of_sil : f_resolve_id:(Var.t -> AccessPath.Raw.t option) -> Sil.instr -> translation +(** Convert an SIL instruction to an HIL instruction *) diff --git a/infer/src/IR/Ident.ml b/infer/src/IR/Ident.ml new file mode 100644 index 000000000..8c4ebac5a --- /dev/null +++ b/infer/src/IR/Ident.ml @@ -0,0 +1,256 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Module for Names and Identifiers *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +module Name = struct + type t = Primed | Normal | Footprint | Spec | FromString of string [@@deriving compare] + + let primed = "t" + + let normal = "n" + + let footprint = "f" + + let spec = "val" + + let from_string s = FromString s + + let to_string = function + | Primed + -> primed + | Normal + -> normal + | Footprint + -> footprint + | Spec + -> spec + | FromString s + -> s +end + +type name = Name.t [@@deriving compare] + +let name_spec = Name.Spec + +let name_primed = Name.Primed + +let equal_name = [%compare.equal : name] + +type kind = + | KNone + (** special kind of "null ident" (basically, a more compact way of implementing an ident option). + useful for situations when an instruction requires an id, but no one should read the result. *) + | KFootprint + | KNormal + | KPrimed + [@@deriving compare] + +let kfootprint = KFootprint + +let knormal = KNormal + +let kprimed = KPrimed + +let equal_kind = [%compare.equal : kind] + +(* timestamp for a path identifier *) +let path_ident_stamp = -3 + +type t = {kind: kind; name: Name.t; stamp: int} [@@deriving compare] + +(* most unlikely first *) +let equal i1 i2 = + Int.equal i1.stamp i2.stamp && equal_kind i1.kind i2.kind && equal_name i1.name i2.name + +(** {2 Set for identifiers} *) +module IdentSet = Caml.Set.Make (struct + type nonrec t = t + + let compare = compare +end) + +module IdentMap = Caml.Map.Make (struct + type nonrec t = t + + let compare = compare +end) + +module IdentHash = Hashtbl.Make (struct + type nonrec t = t + + let equal = equal + + let hash (id: t) = Hashtbl.hash id +end) + +let idlist_to_idset ids = List.fold ~f:(fun set id -> IdentSet.add id set) ~init:IdentSet.empty ids + +(** {2 Conversion between Names and Strings} *) +module NameHash = Hashtbl.Make (struct + type t = name + + let equal = equal_name + + let hash = Hashtbl.hash +end) + +(** Convert a string to a name *) +let string_to_name = Name.from_string + +(** Convert a name to a string. *) +let name_to_string = Name.to_string + +(** {2 Functions and Hash Tables for Managing Stamps} *) + +(** Set the stamp of the identifier *) +let set_stamp i stamp = {i with stamp} + +(** Get the stamp of the identifier *) +let get_stamp i = i.stamp + +module NameGenerator = struct + type t = int NameHash.t + + let create () : t = NameHash.create 17 + + (** Map from names to stamps. *) + let name_map = ref (create ()) + + let get_current () = !name_map + + let set_current map = name_map := map + + (** Reset the name generator *) + let reset () = name_map := create () + + (** Create a fresh identifier with the given kind and name. *) + let create_fresh_ident kind name = + let stamp = + try + let stamp = NameHash.find !name_map name in + NameHash.replace !name_map name (stamp + 1) ; + stamp + 1 + with Not_found -> + NameHash.add !name_map name 0 ; + 0 + in + {kind; name; stamp} + + (** Make sure that fresh ids after whis one will be with different stamps *) + let update_name_hash name stamp = + try + let curr_stamp = NameHash.find !name_map name in + let new_stamp = max curr_stamp stamp in + NameHash.replace !name_map name new_stamp + with Not_found -> NameHash.add !name_map name stamp +end + +(** Name used for the return variable *) +let name_return = Mangled.from_string "return" + +(** Return the standard name for the given kind *) +let standard_name kind = + if equal_kind kind KNormal || equal_kind kind KNone then Name.Normal + else if equal_kind kind KFootprint then Name.Footprint + else Name.Primed + +(** Every identifier with a given stamp should unltimately be created using this function *) +let create_with_stamp kind name stamp = + NameGenerator.update_name_hash name stamp ; {kind; name; stamp} + +(** Create an identifier with default name for the given kind *) +let create kind stamp = create_with_stamp kind (standard_name kind) stamp + +(** Generate a normal identifier with the given name and stamp *) +let create_normal name stamp = create_with_stamp KNormal name stamp + +(** Create a fresh identifier with default name for the given kind. *) +let create_fresh kind = NameGenerator.create_fresh_ident kind (standard_name kind) + +let create_none () = create_fresh KNone + +(** Generate a primed identifier with the given name and stamp *) +let create_primed name stamp = create_with_stamp KPrimed name stamp + +(** Generate a footprint identifier with the given name and stamp *) +let create_footprint name stamp = create_with_stamp KFootprint name stamp + +(** {2 Functions for Identifiers} *) + +(** Get a name of an identifier *) +let get_name id = id.name + +let has_kind id kind = equal_kind id.kind kind + +let is_primed (id: t) = has_kind id KPrimed + +let is_normal (id: t) = has_kind id KNormal || has_kind id KNone + +let is_footprint (id: t) = has_kind id KFootprint + +let is_none (id: t) = has_kind id KNone + +let is_path (id: t) = has_kind id KNormal && Int.equal id.stamp path_ident_stamp + +let make_unprimed id = + if not (has_kind id KPrimed) then assert false + else if has_kind id KNone then {id with kind= KNone} + else {id with kind= KNormal} + +(** Update the name generator so that the given id's are not generated again *) +let update_name_generator ids = + let upd id = ignore (create_with_stamp id.kind id.name id.stamp) in + List.iter ~f:upd ids + +(** Generate a normal identifier whose name encodes a path given as a string. *) +let create_path pathstring = + create_normal (string_to_name ("%path%" ^ pathstring)) path_ident_stamp + +(** {2 Pretty Printing} *) + +(** Convert an identifier to a string. *) +let to_string id = + if has_kind id KNone then "_" + else + let base_name = name_to_string id.name in + let prefix = if has_kind id KFootprint then "@" else if has_kind id KNormal then "" else "_" in + let suffix = "$" ^ string_of_int id.stamp in + prefix ^ base_name ^ suffix + +(** Pretty print a name. *) +let pp_name f name = F.fprintf f "%s" (name_to_string name) + +(** Pretty print a name in latex. *) +let pp_name_latex style f (name: name) = Latex.pp_string style f (name_to_string name) + +(** Pretty print an identifier. *) +let pp pe f id = + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "%s" (to_string id) + | LATEX + -> let base_name = name_to_string id.name in + let style = + if has_kind id KFootprint then Latex.Boldface + else if has_kind id KNormal then Latex.Roman + else Latex.Roman + in + F.fprintf f "%a_{%s}" (Latex.pp_string style) base_name (string_of_int id.stamp) + +(** pretty printer for lists of identifiers *) +let pp_list pe = Pp.comma_seq (pp pe) + +(** pretty printer for lists of names *) +let pp_name_list = Pp.comma_seq pp_name diff --git a/infer/src/IR/Ident.mli b/infer/src/IR/Ident.mli new file mode 100644 index 000000000..f6674ce2d --- /dev/null +++ b/infer/src/IR/Ident.mli @@ -0,0 +1,193 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Identifiers: program variables and logical variables *) + +(** Program and logical variables. *) + +type t [@@deriving compare] + +(** Equality for identifiers. *) + +val equal : t -> t -> bool + +(** Names used to replace strings. *) + +type name [@@deriving compare] + +(** Equality for names. *) + +val equal_name : name -> name -> bool + +(** Kind of identifiers. *) + +type kind [@@deriving compare] + +(** Equality for kind. *) + +val equal_kind : kind -> kind -> bool + +(** Set for identifiers. *) + +module IdentSet : Caml.Set.S with type elt = t + +(** Hash table with ident as key. *) + +module IdentHash : Caml.Hashtbl.S with type key = t + +(** Map with ident as key. *) + +module IdentMap : Caml.Map.S with type key = t + +module NameGenerator : sig + type t + + (** Get the current name generator. *) + + val get_current : unit -> t + + (** Reset the name generator. *) + + val reset : unit -> unit + + (** Set the current name generator. *) + + val set_current : t -> unit +end + +(** Convert an identfier list to an identifier set *) + +val idlist_to_idset : t list -> IdentSet.t + +val kprimed : kind + +val knormal : kind + +val kfootprint : kind + +(** hash table with names as keys *) + +module NameHash : Caml.Hashtbl.S with type key = name + +(** Name used for primed tmp variables *) + +val name_primed : name + +(** Name used for spec variables *) + +val name_spec : name + +(** Name used for the return variable *) + +val name_return : Mangled.t + +(** Convert a string to a name. *) + +val string_to_name : string -> name + +(** Convert a name to a string. *) + +val name_to_string : name -> string + +(** Name of the identifier. *) + +val get_name : t -> name + +(** Create an identifier with default name for the given kind *) + +val create : kind -> int -> t + +(** Generate a normal identifier with the given name and stamp. *) + +val create_normal : name -> int -> t + +(** Create a "null" identifier for situations where the IR requires an id that will never be read *) + +val create_none : unit -> t + +(** Generate a primed identifier with the given name and stamp. *) + +val create_primed : name -> int -> t + +(** Generate a footprint identifier with the given name and stamp. *) + +val create_footprint : name -> int -> t + +(** Update the name generator so that the given id's are not generated again *) + +val update_name_generator : t list -> unit + +(** Create a fresh identifier with default name for the given kind. *) + +val create_fresh : kind -> t + +(** Generate a normal identifier whose name encodes a path given as a string. *) + +val create_path : string -> t + +(** Check whether an identifier is primed or not. *) + +val is_primed : t -> bool + +(** Check whether an identifier is normal or not. *) + +val is_normal : t -> bool + +(** Check whether an identifier is footprint or not. *) + +val is_footprint : t -> bool + +(** Check whether an identifier represents a path or not. *) + +val is_path : t -> bool + +(** Check whether an identifier is the special "none" identifier *) + +val is_none : t -> bool + +(** Convert a primed ident into a nonprimed one, keeping the stamp. *) + +val make_unprimed : t -> t + +(** Get the stamp of the identifier *) + +val get_stamp : t -> int + +(** Set the stamp of the identifier *) + +val set_stamp : t -> int -> t + +(** {2 Pretty Printing} *) + +(** Pretty print a name. *) + +val pp_name : Format.formatter -> name -> unit + +(** Pretty print a name in latex. *) + +val pp_name_latex : Latex.style -> Format.formatter -> name -> unit + +(** Pretty print an identifier. *) + +val pp : Pp.env -> Format.formatter -> t -> unit + +(** Convert an identifier to a string. *) + +val to_string : t -> string + +(** Pretty print a list of identifiers. *) + +val pp_list : Pp.env -> Format.formatter -> t list -> unit + +(** Pretty print a list of names. *) + +val pp_name_list : Format.formatter -> name list -> unit diff --git a/infer/src/IR/Ident.re b/infer/src/IR/Ident.re deleted file mode 100644 index 105711283..000000000 --- a/infer/src/IR/Ident.re +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** Module for Names and Identifiers */ -module L = Logging; - -module F = Format; - -module Name = { - type t = - | Primed - | Normal - | Footprint - | Spec - | FromString string - [@@deriving compare]; - let primed = "t"; - let normal = "n"; - let footprint = "f"; - let spec = "val"; - let from_string s => FromString s; - let to_string = - fun - | Primed => primed - | Normal => normal - | Footprint => footprint - | Spec => spec - | FromString s => s; -}; - -type name = Name.t [@@deriving compare]; - -let name_spec = Name.Spec; - -let name_primed = Name.Primed; - -let equal_name = [%compare.equal : name]; - -type kind = - | KNone - /** special kind of "null ident" (basically, a more compact way of implementing an ident option). - useful for situations when an instruction requires an id, but no one should read the result. */ - | KFootprint - | KNormal - | KPrimed -[@@deriving compare]; - -let kfootprint = KFootprint; - -let knormal = KNormal; - -let kprimed = KPrimed; - -let equal_kind = [%compare.equal : kind]; - -/* timestamp for a path identifier */ -let path_ident_stamp = (-3); - -type t = {kind, name: Name.t, stamp: int} [@@deriving compare]; - -/* most unlikely first */ -let equal i1 i2 => - Int.equal i1.stamp i2.stamp && equal_kind i1.kind i2.kind && equal_name i1.name i2.name; - - -/** {2 Set for identifiers} */ -module IdentSet = - Caml.Set.Make { - type nonrec t = t; - let compare = compare; - }; - -module IdentMap = - Caml.Map.Make { - type nonrec t = t; - let compare = compare; - }; - -module IdentHash = - Hashtbl.Make { - type nonrec t = t; - let equal = equal; - let hash (id: t) => Hashtbl.hash id; - }; - -let idlist_to_idset ids => - List.fold f::(fun set id => IdentSet.add id set) init::IdentSet.empty ids; - - -/** {2 Conversion between Names and Strings} */ -module NameHash = - Hashtbl.Make { - type t = name; - let equal = equal_name; - let hash = Hashtbl.hash; - }; - - -/** Convert a string to a name */ -let string_to_name = Name.from_string; - - -/** Convert a name to a string. */ -let name_to_string = Name.to_string; - - -/** {2 Functions and Hash Tables for Managing Stamps} */ - -/** Set the stamp of the identifier */ -let set_stamp i stamp => {...i, stamp}; - - -/** Get the stamp of the identifier */ -let get_stamp i => i.stamp; - -module NameGenerator = { - type t = NameHash.t int; - let create () :t => NameHash.create 17; - - /** Map from names to stamps. */ - let name_map = ref (create ()); - let get_current () => !name_map; - let set_current map => name_map := map; - - /** Reset the name generator */ - let reset () => name_map := create (); - - /** Create a fresh identifier with the given kind and name. */ - let create_fresh_ident kind name => { - let stamp = - try { - let stamp = NameHash.find !name_map name; - NameHash.replace !name_map name (stamp + 1); - stamp + 1 - } { - | Not_found => - NameHash.add !name_map name 0; - 0 - }; - {kind, name, stamp} - }; - - /** Make sure that fresh ids after whis one will be with different stamps */ - let update_name_hash name stamp => - try { - let curr_stamp = NameHash.find !name_map name; - let new_stamp = max curr_stamp stamp; - NameHash.replace !name_map name new_stamp - } { - | Not_found => NameHash.add !name_map name stamp - }; -}; - - -/** Name used for the return variable */ -let name_return = Mangled.from_string "return"; - - -/** Return the standard name for the given kind */ -let standard_name kind => - if (equal_kind kind KNormal || equal_kind kind KNone) { - Name.Normal - } else if ( - equal_kind kind KFootprint - ) { - Name.Footprint - } else { - Name.Primed - }; - - -/** Every identifier with a given stamp should unltimately be created using this function */ -let create_with_stamp kind name stamp => { - NameGenerator.update_name_hash name stamp; - {kind, name, stamp} -}; - - -/** Create an identifier with default name for the given kind */ -let create kind stamp => create_with_stamp kind (standard_name kind) stamp; - - -/** Generate a normal identifier with the given name and stamp */ -let create_normal name stamp => create_with_stamp KNormal name stamp; - - -/** Create a fresh identifier with default name for the given kind. */ -let create_fresh kind => NameGenerator.create_fresh_ident kind (standard_name kind); - -let create_none () => create_fresh KNone; - - -/** Generate a primed identifier with the given name and stamp */ -let create_primed name stamp => create_with_stamp KPrimed name stamp; - - -/** Generate a footprint identifier with the given name and stamp */ -let create_footprint name stamp => create_with_stamp KFootprint name stamp; - - -/** {2 Functions for Identifiers} */ - -/** Get a name of an identifier */ -let get_name id => id.name; - -let has_kind id kind => equal_kind id.kind kind; - -let is_primed (id: t) => has_kind id KPrimed; - -let is_normal (id: t) => has_kind id KNormal || has_kind id KNone; - -let is_footprint (id: t) => has_kind id KFootprint; - -let is_none (id: t) => has_kind id KNone; - -let is_path (id: t) => has_kind id KNormal && Int.equal id.stamp path_ident_stamp; - -let make_unprimed id => - if (not (has_kind id KPrimed)) { - assert false - } else if (has_kind id KNone) { - {...id, kind: KNone} - } else { - {...id, kind: KNormal} - }; - - -/** Update the name generator so that the given id's are not generated again */ -let update_name_generator ids => { - let upd id => ignore (create_with_stamp id.kind id.name id.stamp); - List.iter f::upd ids -}; - - -/** Generate a normal identifier whose name encodes a path given as a string. */ -let create_path pathstring => - create_normal (string_to_name ("%path%" ^ pathstring)) path_ident_stamp; - - -/** {2 Pretty Printing} */ - -/** Convert an identifier to a string. */ -let to_string id => - if (has_kind id KNone) { - "_" - } else { - let base_name = name_to_string id.name; - let prefix = - if (has_kind id KFootprint) { - "@" - } else if (has_kind id KNormal) { - "" - } else { - "_" - }; - let suffix = "$" ^ string_of_int id.stamp; - prefix ^ base_name ^ suffix - }; - - -/** Pretty print a name. */ -let pp_name f name => F.fprintf f "%s" (name_to_string name); - - -/** Pretty print a name in latex. */ -let pp_name_latex style f (name: name) => Latex.pp_string style f (name_to_string name); - - -/** Pretty print an identifier. */ -let pp pe f id => - switch pe.Pp.kind { - | TEXT - | HTML => F.fprintf f "%s" (to_string id) - | LATEX => - let base_name = name_to_string id.name; - let style = - if (has_kind id KFootprint) { - Latex.Boldface - } else if (has_kind id KNormal) { - Latex.Roman - } else { - Latex.Roman - }; - F.fprintf f "%a_{%s}" (Latex.pp_string style) base_name (string_of_int id.stamp) - }; - - -/** pretty printer for lists of identifiers */ -let pp_list pe => Pp.comma_seq (pp pe); - - -/** pretty printer for lists of names */ -let pp_name_list = Pp.comma_seq pp_name; diff --git a/infer/src/IR/Ident.rei b/infer/src/IR/Ident.rei deleted file mode 100644 index 86e6cf5ab..000000000 --- a/infer/src/IR/Ident.rei +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Identifiers: program variables and logical variables */ - -/** Program and logical variables. */ -type t [@@deriving compare]; - - -/** Equality for identifiers. */ -let equal: t => t => bool; - - -/** Names used to replace strings. */ -type name [@@deriving compare]; - - -/** Equality for names. */ -let equal_name: name => name => bool; - - -/** Kind of identifiers. */ -type kind [@@deriving compare]; - - -/** Equality for kind. */ -let equal_kind: kind => kind => bool; - - -/** Set for identifiers. */ -module IdentSet: Caml.Set.S with type elt = t; - - -/** Hash table with ident as key. */ -module IdentHash: Caml.Hashtbl.S with type key = t; - - -/** Map with ident as key. */ -module IdentMap: Caml.Map.S with type key = t; - -module NameGenerator: { - type t; - - /** Get the current name generator. */ - let get_current: unit => t; - - /** Reset the name generator. */ - let reset: unit => unit; - - /** Set the current name generator. */ - let set_current: t => unit; -}; - - -/** Convert an identfier list to an identifier set */ -let idlist_to_idset: list t => IdentSet.t; - -let kprimed: kind; - -let knormal: kind; - -let kfootprint: kind; - - -/** hash table with names as keys */ -module NameHash: Caml.Hashtbl.S with type key = name; - - -/** Name used for primed tmp variables */ -let name_primed: name; - - -/** Name used for spec variables */ -let name_spec: name; - - -/** Name used for the return variable */ -let name_return: Mangled.t; - - -/** Convert a string to a name. */ -let string_to_name: string => name; - - -/** Convert a name to a string. */ -let name_to_string: name => string; - - -/** Name of the identifier. */ -let get_name: t => name; - - -/** Create an identifier with default name for the given kind */ -let create: kind => int => t; - - -/** Generate a normal identifier with the given name and stamp. */ -let create_normal: name => int => t; - - -/** Create a "null" identifier for situations where the IR requires an id that will never be read */ -let create_none: unit => t; - - -/** Generate a primed identifier with the given name and stamp. */ -let create_primed: name => int => t; - - -/** Generate a footprint identifier with the given name and stamp. */ -let create_footprint: name => int => t; - - -/** Update the name generator so that the given id's are not generated again */ -let update_name_generator: list t => unit; - - -/** Create a fresh identifier with default name for the given kind. */ -let create_fresh: kind => t; - - -/** Generate a normal identifier whose name encodes a path given as a string. */ -let create_path: string => t; - - -/** Check whether an identifier is primed or not. */ -let is_primed: t => bool; - - -/** Check whether an identifier is normal or not. */ -let is_normal: t => bool; - - -/** Check whether an identifier is footprint or not. */ -let is_footprint: t => bool; - - -/** Check whether an identifier represents a path or not. */ -let is_path: t => bool; - - -/** Check whether an identifier is the special "none" identifier */ -let is_none: t => bool; - - -/** Convert a primed ident into a nonprimed one, keeping the stamp. */ -let make_unprimed: t => t; - - -/** Get the stamp of the identifier */ -let get_stamp: t => int; - - -/** Set the stamp of the identifier */ -let set_stamp: t => int => t; - - -/** {2 Pretty Printing} */ - -/** Pretty print a name. */ -let pp_name: Format.formatter => name => unit; - - -/** Pretty print a name in latex. */ -let pp_name_latex: Latex.style => Format.formatter => name => unit; - - -/** Pretty print an identifier. */ -let pp: Pp.env => Format.formatter => t => unit; - - -/** Convert an identifier to a string. */ -let to_string: t => string; - - -/** Pretty print a list of identifiers. */ -let pp_list: Pp.env => Format.formatter => list t => unit; - - -/** Pretty print a list of names. */ -let pp_name_list: Format.formatter => list name => unit; diff --git a/infer/src/IR/IntLit.ml b/infer/src/IR/IntLit.ml new file mode 100644 index 000000000..665534336 --- /dev/null +++ b/infer/src/IR/IntLit.ml @@ -0,0 +1,116 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module F = Format + +(* the first bool indicates whether this is an unsigned value, + and the second whether it is a pointer *) + +(** signed and unsigned integer literals *) +type t = bool * Int64.t * bool + +let area u i = + match (i < 0L, u) with + | true, false + -> 1 + (* only representable as signed *) + | false, _ + -> 2 + (* in the intersection between signed and unsigned *) + | true, true + -> 3 + +(* only representable as unsigned *) + +let to_signed (unsigned, i, ptr) = + if Int.equal (area unsigned i) 3 then None + else Some (* not representable as signed *) + (false, i, ptr) + +let compare (unsigned1, i1, _) (unsigned2, i2, _) = + let n = Bool.compare unsigned1 unsigned2 in + if n <> 0 then n else Int64.compare i1 i2 + +let compare_value (unsigned1, i1, _) (unsigned2, i2, _) = + [%compare : int * Int64.t] (area unsigned1 i1, i1) (area unsigned2 i2, i2) + +let eq i1 i2 = Int.equal (compare_value i1 i2) 0 + +let neq i1 i2 = compare_value i1 i2 <> 0 + +let leq i1 i2 = compare_value i1 i2 <= 0 + +let lt i1 i2 = compare_value i1 i2 < 0 + +let geq i1 i2 = compare_value i1 i2 >= 0 + +let gt i1 i2 = compare_value i1 i2 > 0 + +let of_int64 i = (false, i, false) + +let of_int32 i = of_int64 (Int64.of_int32 i) + +let of_int64_unsigned i unsigned = (unsigned, i, false) + +let of_int i = of_int64 (Int64.of_int i) + +let to_int (_, i, _) = Int64.to_int_exn i + +let null = (false, 0L, true) + +let zero = of_int 0 + +let one = of_int 1 + +let two = of_int 2 + +let minus_one = of_int (-1) + +let isone (_, i, _) = Int64.equal i 1L + +let iszero (_, i, _) = Int64.equal i 0L + +let isnull (_, i, ptr) = Int64.equal i 0L && ptr + +let isminusone (unsigned, i, _) = not unsigned && Int64.equal i (-1L) + +let isnegative (unsigned, i, _) = not unsigned && i < 0L + +let neg (unsigned, i, ptr) = (unsigned, Int64.neg i, ptr) + +let lift binop (unsigned1, i1, ptr1) (unsigned2, i2, ptr2) = + (unsigned1 || unsigned2, binop i1 i2, ptr1 || ptr2) + +let lift1 unop (unsigned, i, ptr) = (unsigned, unop i, ptr) + +let add i1 i2 = lift Int64.( + ) i1 i2 + +let mul i1 i2 = lift Int64.( * ) i1 i2 + +let div i1 i2 = lift Int64.( / ) i1 i2 + +let rem i1 i2 = lift Int64.rem i1 i2 + +let logand i1 i2 = lift Int64.bit_and i1 i2 + +let logor i1 i2 = lift Int64.bit_or i1 i2 + +let logxor i1 i2 = lift Int64.bit_xor i1 i2 + +let lognot i = lift1 Int64.bit_not i + +let sub i1 i2 = add i1 (neg i2) + +let pp f (unsigned, n, ptr) = + if ptr && Int64.equal n 0L then F.fprintf f "null" + else if unsigned then F.fprintf f "%Lu" n + else F.fprintf f "%Ld" n + +let to_string i = F.asprintf "%a" pp i diff --git a/infer/src/IR/IntLit.mli b/infer/src/IR/IntLit.mli new file mode 100644 index 000000000..beb910956 --- /dev/null +++ b/infer/src/IR/IntLit.mli @@ -0,0 +1,97 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd +module F = Format + +(** signed and unsigned integer literals *) + +type t + +val add : t -> t -> t + +(** compare integers ignoring the distinction between pointers and non-pointers *) + +val compare : t -> t -> int + +(** compare the value of the integers, notice this is different from const compare, + which distinguished between signed and unsigned +1 *) + +val compare_value : t -> t -> int + +val div : t -> t -> t + +val eq : t -> t -> bool + +val of_int : int -> t + +val of_int32 : int32 -> t + +val of_int64 : int64 -> t + +val of_int64_unsigned : int64 -> bool -> t + +val geq : t -> t -> bool + +val gt : t -> t -> bool + +val isminusone : t -> bool + +val isnegative : t -> bool + +val isnull : t -> bool + +val isone : t -> bool + +val iszero : t -> bool + +val leq : t -> t -> bool + +val logand : t -> t -> t + +val lognot : t -> t + +val logor : t -> t -> t + +val logxor : t -> t -> t + +val lt : t -> t -> bool + +val minus_one : t + +val mul : t -> t -> t + +val neg : t -> t + +val neq : t -> t -> bool + +val null : t + +(** null behaves like zero except for the function isnull *) + +val one : t + +val pp : F.formatter -> t -> unit + +val rem : t -> t -> t + +val sub : t -> t -> t + +val to_int : t -> int + +val to_signed : t -> t option + +(** convert to signed if the value is representable *) + +val to_string : t -> string + +val two : t + +val zero : t diff --git a/infer/src/IR/IntLit.re b/infer/src/IR/IntLit.re deleted file mode 100644 index a53f52add..000000000 --- a/infer/src/IR/IntLit.re +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module F = Format; - - -/** signed and unsigned integer literals */ -/* the first bool indicates whether this is an unsigned value, - and the second whether it is a pointer */ -type t = (bool, Int64.t, bool); - -let area u i => - switch (i < 0L, u) { - | (true, false) => 1 /* only representable as signed */ - | (false, _) => 2 /* in the intersection between signed and unsigned */ - | (true, true) => 3 /* only representable as unsigned */ - }; - -let to_signed (unsigned, i, ptr) => - if (Int.equal (area unsigned i) 3) { - None - } else { - Some - /* not representable as signed */ - (false, i, ptr) - }; - -let compare (unsigned1, i1, _) (unsigned2, i2, _) => { - let n = Bool.compare unsigned1 unsigned2; - if (n != 0) { - n - } else { - Int64.compare i1 i2 - } -}; - -let compare_value (unsigned1, i1, _) (unsigned2, i2, _) => - [%compare : (int, Int64.t)] (area unsigned1 i1, i1) (area unsigned2 i2, i2); - -let eq i1 i2 => Int.equal (compare_value i1 i2) 0; - -let neq i1 i2 => compare_value i1 i2 != 0; - -let leq i1 i2 => compare_value i1 i2 <= 0; - -let lt i1 i2 => compare_value i1 i2 < 0; - -let geq i1 i2 => compare_value i1 i2 >= 0; - -let gt i1 i2 => compare_value i1 i2 > 0; - -let of_int64 i => (false, i, false); - -let of_int32 i => of_int64 (Int64.of_int32 i); - -let of_int64_unsigned i unsigned => (unsigned, i, false); - -let of_int i => of_int64 (Int64.of_int i); - -let to_int (_, i, _) => Int64.to_int_exn i; - -let null = (false, 0L, true); - -let zero = of_int 0; - -let one = of_int 1; - -let two = of_int 2; - -let minus_one = of_int (-1); - -let isone (_, i, _) => Int64.equal i 1L; - -let iszero (_, i, _) => Int64.equal i 0L; - -let isnull (_, i, ptr) => Int64.equal i 0L && ptr; - -let isminusone (unsigned, i, _) => not unsigned && Int64.equal i (-1L); - -let isnegative (unsigned, i, _) => not unsigned && i < 0L; - -let neg (unsigned, i, ptr) => (unsigned, Int64.neg i, ptr); - -let lift binop (unsigned1, i1, ptr1) (unsigned2, i2, ptr2) => ( - unsigned1 || unsigned2, - binop i1 i2, - ptr1 || ptr2 -); - -let lift1 unop (unsigned, i, ptr) => (unsigned, unop i, ptr); - -let add i1 i2 => lift Int64.(+) i1 i2; - -let mul i1 i2 => lift Int64.( * ) i1 i2; - -let div i1 i2 => lift Int64.(/) i1 i2; - -let rem i1 i2 => lift Int64.rem i1 i2; - -let logand i1 i2 => lift Int64.bit_and i1 i2; - -let logor i1 i2 => lift Int64.bit_or i1 i2; - -let logxor i1 i2 => lift Int64.bit_xor i1 i2; - -let lognot i => lift1 Int64.bit_not i; - -let sub i1 i2 => add i1 (neg i2); - -let pp f (unsigned, n, ptr) => - if (ptr && Int64.equal n 0L) { - F.fprintf f "null" - } else if unsigned { - F.fprintf f "%Lu" n - } else { - F.fprintf f "%Ld" n - }; - -let to_string i => F.asprintf "%a" pp i; diff --git a/infer/src/IR/IntLit.rei b/infer/src/IR/IntLit.rei deleted file mode 100644 index ebe230a81..000000000 --- a/infer/src/IR/IntLit.rei +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module F = Format; - - -/** signed and unsigned integer literals */ -type t; - -let add: t => t => t; - - -/** compare integers ignoring the distinction between pointers and non-pointers */ -let compare: t => t => int; - - -/** compare the value of the integers, notice this is different from const compare, - which distinguished between signed and unsigned +1 */ -let compare_value: t => t => int; - -let div: t => t => t; - -let eq: t => t => bool; - -let of_int: int => t; - -let of_int32: int32 => t; - -let of_int64: int64 => t; - -let of_int64_unsigned: int64 => bool => t; - -let geq: t => t => bool; - -let gt: t => t => bool; - -let isminusone: t => bool; - -let isnegative: t => bool; - -let isnull: t => bool; - -let isone: t => bool; - -let iszero: t => bool; - -let leq: t => t => bool; - -let logand: t => t => t; - -let lognot: t => t; - -let logor: t => t => t; - -let logxor: t => t => t; - -let lt: t => t => bool; - -let minus_one: t; - -let mul: t => t => t; - -let neg: t => t; - -let neq: t => t => bool; - -let null: t; /** null behaves like zero except for the function isnull */ - -let one: t; - -let pp: F.formatter => t => unit; - -let rem: t => t => t; - -let sub: t => t => t; - -let to_int: t => int; - -let to_signed: t => option t; /** convert to signed if the value is representable */ - -let to_string: t => string; - -let two: t; - -let zero: t; diff --git a/infer/src/IR/Io_infer.ml b/infer/src/IR/Io_infer.ml index a10d63dba..b025a5ac4 100644 --- a/infer/src/IR/Io_infer.ml +++ b/infer/src/IR/Io_infer.ml @@ -15,25 +15,26 @@ open! IStd module F = Format (* =============== START of module Html =============== *) -module Html = -struct +module Html = struct (** Create a new html file *) let create pk path = - let fname, dir_path = match List.rev path with - | fname :: path_rev -> - fname, List.rev ((fname ^ ".html") :: path_rev) - | [] -> - raise (Failure "Html.create") in + let fname, dir_path = + match List.rev path with + | fname :: path_rev + -> (fname, List.rev ((fname ^ ".html") :: path_rev)) + | [] + -> raise (Failure "Html.create") + in let fd = DB.Results_dir.create_file pk dir_path in let outc = Unix.out_channel_of_descr fd in let fmt = F.formatter_of_out_channel outc in let s = -{| + {| -|} ^ - fname ^ -{| +|} + ^ fname + ^ {| -|} in - F.fprintf fmt "%s" s; - (fd, fmt) +|} + in + F.fprintf fmt "%s" s ; (fd, fmt) (** Get the full html filename from a path *) let get_full_fname source path = - let dir_path = match List.rev path with - | fname :: path_rev -> - List.rev ((fname ^ ".html") :: path_rev) - | [] -> - raise (Failure "Html.open_out") in + let dir_path = + match List.rev path with + | fname :: path_rev + -> List.rev ((fname ^ ".html") :: path_rev) + | [] + -> raise (Failure "Html.open_out") + in DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) dir_path (** Open an Html file to append data *) let open_out source path = let full_fname = get_full_fname source path in let fd = - Unix.openfile - (DB.filename_to_string full_fname) - ~mode:Unix.[O_WRONLY; O_APPEND] - ~perm:0o777 in + Unix.openfile (DB.filename_to_string full_fname) + ~mode:Unix.([O_WRONLY; O_APPEND]) + ~perm:0o777 + in let outc = Unix.out_channel_of_descr fd in let fmt = F.formatter_of_out_channel outc in (fd, fmt) @@ -107,212 +110,223 @@ td.rowname { text-align:right; font-weight:bold; color:#444444; padding-right:2e (** Return true if the html file was modified since the beginning of the analysis *) let modified_during_analysis source path = let fname = get_full_fname source path in - if DB.file_exists fname then - DB.file_modified_time fname >= Config.initial_analysis_time + if DB.file_exists fname then DB.file_modified_time fname >= Config.initial_analysis_time else false (** Close an Html file *) - let close (fd, fmt) = - F.fprintf fmt "@\n@."; - Unix.close fd + let close (fd, fmt) = F.fprintf fmt "@\n@." ; Unix.close fd (** Print a horizontal line *) - let pp_hline fmt () = - F.fprintf fmt "
@\n" + let pp_hline fmt () = F.fprintf fmt "
@\n" (** Print start color *) - let pp_start_color fmt color = - F.fprintf fmt "%s" ("") + let pp_start_color fmt color = F.fprintf fmt "%s" ("") (** Print end color *) - let pp_end_color fmt () = - F.fprintf fmt "%s" "" + let pp_end_color fmt () = F.fprintf fmt "%s" "" - let pp_link ?(name = None) ?(pos = None) ~path fmt text = - let pos_str = match pos with - | None -> "" - | Some s -> "#" ^ s in + let pp_link ?(name= None) ?(pos= None) ~path fmt text = + let pos_str = match pos with None -> "" | Some s -> "#" ^ s in let escaped_path = List.map ~f:Escape.escape_url path in let link_str = - (DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Rel escaped_path)) - ^ ".html" - ^ pos_str in - let name_str = match name with - | None -> "" - | Some n -> "name=\"" ^ n ^ "\"" in + DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Rel escaped_path) + ^ ".html" ^ pos_str + in + let name_str = match name with None -> "" | Some n -> "name=\"" ^ n ^ "\"" in let pr_str = "" ^ text ^ "" in F.fprintf fmt " %s" pr_str (** File name for the node, given the procedure name and node id *) - let node_filename pname id = (Typ.Procname.to_filename pname) ^ "_node" ^ string_of_int id + let node_filename pname id = Typ.Procname.to_filename pname ^ "_node" ^ string_of_int id (** Print an html link to the given node. *) let pp_node_link path_to_root pname ~description ~preds ~succs ~exn ~isvisited ~isproof fmt id = let display_name = - (if String.equal description "" then "N" else String.sub description ~pos:0 ~len:1) - ^ "_" - ^ (string_of_int id) in + (if String.equal description "" then "N" else String.sub description ~pos:0 ~len:1) ^ "_" + ^ string_of_int id + in let node_fname = node_filename pname id in let style_class = - if not isvisited - then "dangling" - else if isproof then "visitedproof" else "visited" in + if not isvisited then "dangling" else if isproof then "visitedproof" else "visited" + in let node_text = let pp fmt = Format.fprintf fmt - "%s\ - \ - node%d preds:%a succs:%a exn:%a %s%s\ - \ - " - style_class display_name id - (Pp.seq Format.pp_print_int) preds - (Pp.seq Format.pp_print_int) succs - (Pp.seq Format.pp_print_int) exn - description - (if not isvisited then "\nNOT VISITED" else "") in - F.asprintf "%t" pp in - pp_link ~path: (path_to_root @ ["nodes"; node_fname]) fmt node_text + "%snode%d preds:%a succs:%a exn:%a %s%s" + style_class display_name id (Pp.seq Format.pp_print_int) preds + (Pp.seq Format.pp_print_int) succs (Pp.seq Format.pp_print_int) exn description + (if not isvisited then "\nNOT VISITED" else "") + in + F.asprintf "%t" pp + in + pp_link ~path:(path_to_root @ ["nodes"; node_fname]) fmt node_text (** Print an html link to the given proc *) let pp_proc_link path_to_root proc_name fmt text = - pp_link ~path: (path_to_root @ [Typ.Procname.to_filename proc_name]) fmt text + pp_link ~path:(path_to_root @ [Typ.Procname.to_filename proc_name]) fmt text (** Print an html link to the given line number of the current source file *) - let pp_line_link ?(with_name = false) ?(text = None) source path_to_root fmt linenum = + let pp_line_link ?(with_name= false) ?(text= None) source path_to_root fmt linenum = let fname = DB.source_file_encoding source in let linenum_str = string_of_int linenum in let name = "LINE" ^ linenum_str in pp_link - ~name: (if with_name then Some name else None) - ~pos: (Some name) - ~path: (path_to_root @ [".."; fname]) + ~name:(if with_name then Some name else None) + ~pos:(Some name) + ~path:(path_to_root @ [".."; fname]) fmt (match text with Some s -> s | None -> linenum_str) (** Print an html link given node id and session *) - let pp_session_link ?(with_name = false) ?proc_name source path_to_root fmt + let pp_session_link ?(with_name= false) ?proc_name source path_to_root fmt (node_id, session, linenum) = let node_name = "node" ^ string_of_int node_id in - let node_fname = match proc_name with - | Some pname -> node_filename pname node_id - | None -> node_name in + let node_fname = + match proc_name with Some pname -> node_filename pname node_id | None -> node_name + in let path_to_node = path_to_root @ ["nodes"; node_fname] in - let pos = "session" ^ (string_of_int session) in + let pos = "session" ^ string_of_int session in pp_link - ~name: (if with_name then Some pos else None) - ~pos: (Some pos) - ~path: path_to_node - fmt - (node_name ^ "#" ^ pos); + ~name:(if with_name then Some pos else None) + ~pos:(Some pos) ~path:path_to_node fmt + (node_name ^ "#" ^ pos) ; F.fprintf fmt "(%a)" (pp_line_link source path_to_root) linenum end -(* =============== END of module Html =============== *) +(* =============== END of module Html =============== *) (* =============== START of module Xml =============== *) + (** Create and print xml trees *) -module Xml = -struct +module Xml = struct let tag_branch = "branch" + let tag_call_trace = "call_trace" + let tag_callee = "callee" + let tag_callee_id = "callee_id" + let tag_caller = "caller" + let tag_caller_id = "caller_id" + let tag_class = "class" + let tag_code = "code" + let tag_description = "description" + let tag_err = "err" + let tag_flags = "flags" + let tag_file = "file" + let tag_hash = "hash" + let tag_in_calls = "in_calls" + let tag_key = "key" + let tag_kind = "kind" + let tag_level = "level" + let tag_line = "line" + let tag_loc = "loc" + let tag_name = "name" + let tag_name_id = "name_id" + let tag_node = "node" + let tag_out_calls = "out_calls" + let tag_precondition = "precondition" + let tag_procedure = "procedure" + let tag_procedure_id = "procedure_id" + let tag_proof_coverage = "proof_coverage" + let tag_proof_trace = "proof_trace" + let tag_qualifier = "qualifier" + let tag_qualifier_tags = "qualifier_tags" + let tag_rank = "rank" + let tag_severity = "severity" + let tag_signature = "signature" + let tag_specs = "specs" + let tag_symop = "symop" + let tag_time = "time" + let tag_to = "to" + let tag_top = "top" + let tag_trace = "trace" + let tag_type = "type" + let tag_weight = "weight" - type tree = { name: string; attributes: (string * string) list; forest: node list } - and node = - | Tree of tree - | String of string + type tree = {name: string; attributes: (string * string) list; forest: node list} + + and node = Tree of tree | String of string let pp = F.fprintf - let create_tree name attributes forest = - Tree { name = name; attributes = attributes; forest = forest } + let create_tree name attributes forest = Tree {name; attributes; forest} - let pp_attribute fmt (name, value) = - pp fmt "%s=\"%s\"" name value + let pp_attribute fmt (name, value) = pp fmt "%s=\"%s\"" name value - let pp_attributes fmt l = - Pp.seq pp_attribute fmt l + let pp_attributes fmt l = Pp.seq pp_attribute fmt l (** print an xml node *) let rec pp_node newline indent fmt = function - | Tree { name = name; attributes = attributes; forest = forest } -> - let indent' = if String.equal newline "" then "" else indent ^ " " in + | Tree {name; attributes; forest} + -> let indent' = if String.equal newline "" then "" else indent ^ " " in let space = if List.is_empty attributes then "" else " " in - let pp_inside fmt () = match forest with - | [] -> - () - | [String s] -> - pp fmt "%s" s - | _ -> - pp fmt "%s%a%s" newline (pp_forest newline indent') forest indent in - pp fmt "%s<%s%s%a>%a%s" - indent - name - space - pp_attributes attributes - pp_inside () - name + let pp_inside fmt () = + match forest with + | [] + -> () + | [(String s)] + -> pp fmt "%s" s + | _ + -> pp fmt "%s%a%s" newline (pp_forest newline indent') forest indent + in + pp fmt "%s<%s%s%a>%a%s" indent name space pp_attributes attributes pp_inside () name newline - | String s -> - F.fprintf fmt "%s%s%s" indent s newline - and pp_forest newline indent fmt forest = - List.iter ~f:(pp_node newline indent fmt) forest + | String s + -> F.fprintf fmt "%s%s%s" indent s newline + + and pp_forest newline indent fmt forest = List.iter ~f:(pp_node newline indent fmt) forest let pp_prelude fmt = pp fmt "%s" "@\n" - let pp_open fmt name = - pp_prelude fmt; - pp fmt "<%s>@\n" name + let pp_open fmt name = pp_prelude fmt ; pp fmt "<%s>@\n" name - let pp_close fmt name = - pp fmt "@." name + let pp_close fmt name = pp fmt "@." name - let pp_inner_node fmt node = - pp_node "\n" "" fmt node + let pp_inner_node fmt node = pp_node "\n" "" fmt node (** print an xml document, if the first parameter is false on a single line without preamble *) let pp_document on_several_lines fmt node = let newline = if on_several_lines then "\n" else "" in - if on_several_lines then pp_prelude fmt; - pp_node newline "" fmt node; + if on_several_lines then pp_prelude fmt ; + pp_node newline "" fmt node ; if on_several_lines then pp fmt "@." end + (* =============== END of module Xml =============== *) diff --git a/infer/src/IR/Io_infer.mli b/infer/src/IR/Io_infer.mli index a76436c96..af50ba7c2 100644 --- a/infer/src/IR/Io_infer.mli +++ b/infer/src/IR/Io_infer.mli @@ -13,115 +13,152 @@ open! IStd (** Module to handle IO. Includes html and xml modules. *) module Html : sig - (** Close an Html file *) val close : Unix.File_descr.t * Format.formatter -> unit + (** Close an Html file *) - (** Create a new html file *) val create : DB.Results_dir.path_kind -> DB.Results_dir.path -> Unix.File_descr.t * Format.formatter + (** Create a new html file *) - (** Return true if the html file was modified since the beginning of the analysis *) val modified_during_analysis : SourceFile.t -> DB.Results_dir.path -> bool + (** Return true if the html file was modified since the beginning of the analysis *) - (** File name for the node, given the procedure name and node id *) val node_filename : Typ.Procname.t -> int -> string + (** File name for the node, given the procedure name and node id *) - (** Open an Html file to append data *) val open_out : SourceFile.t -> DB.Results_dir.path -> Unix.File_descr.t * Format.formatter + (** Open an Html file to append data *) - (** Print an html link to the given line number of the current source file *) val pp_line_link : - ?with_name: bool -> ?text: (string option) -> - SourceFile.t -> DB.Results_dir.path -> Format.formatter -> int -> unit + ?with_name:bool -> ?text:string option -> SourceFile.t -> DB.Results_dir.path + -> Format.formatter -> int -> unit + (** Print an html link to the given line number of the current source file *) - (** Print a horizontal line *) val pp_hline : Format.formatter -> unit -> unit + (** Print a horizontal line *) - (** Print end color *) val pp_end_color : Format.formatter -> unit -> unit + (** Print end color *) + val pp_node_link : + DB.Results_dir.path -> Typ.Procname.t -> description:string -> preds:int list -> succs:int list + -> exn:int list -> isvisited:bool -> isproof:bool -> Format.formatter -> int -> unit (** Print an html link to the given node. Usage: [pp_node_link path_to_root ... fmt id]. [path_to_root] is the path to the dir for the procedure in the spec db. [id] is the node identifier. *) - val pp_node_link : - DB.Results_dir.path -> Typ.Procname.t -> - description:string -> preds:int list -> succs:int list -> exn:int list -> - isvisited:bool -> isproof:bool -> Format.formatter -> int -> unit + val pp_proc_link : DB.Results_dir.path -> Typ.Procname.t -> Format.formatter -> string -> unit (** Print an html link to the given proc *) - val pp_proc_link : - DB.Results_dir.path -> Typ.Procname.t -> Format.formatter -> string -> unit - (** Print an html link given node id and session *) val pp_session_link : - ?with_name: bool -> ?proc_name: Typ.Procname.t -> SourceFile.t -> - string list -> Format.formatter -> int * int * int -> unit + ?with_name:bool -> ?proc_name:Typ.Procname.t -> SourceFile.t -> string list -> Format.formatter + -> int * int * int -> unit + (** Print an html link given node id and session *) - (** Print start color *) val pp_start_color : Format.formatter -> Pp.color -> unit + (** Print start color *) end (** Create and print xml trees *) module Xml : sig val tag_branch : string + val tag_call_trace : string + val tag_callee : string + val tag_callee_id : string + val tag_caller : string + val tag_caller_id : string + val tag_class : string + val tag_code : string + val tag_description : string + val tag_err : string + val tag_file : string + val tag_flags : string + val tag_hash : string + val tag_in_calls : string + val tag_key : string + val tag_kind : string + val tag_level : string + val tag_line : string + val tag_loc : string + val tag_name : string + val tag_name_id : string + val tag_node : string + val tag_out_calls : string + val tag_precondition : string + val tag_procedure : string + val tag_procedure_id : string + val tag_proof_coverage : string + val tag_proof_trace : string + val tag_qualifier : string + val tag_qualifier_tags : string + val tag_rank : string + val tag_severity : string + val tag_signature : string + val tag_specs : string + val tag_symop : string + val tag_time : string + val tag_to : string + val tag_top : string + val tag_trace : string + val tag_type : string + val tag_weight : string - type tree = { name: string; attributes: (string * string) list; forest: node list } - and node = - | Tree of tree - | String of string - (** create a tree *) + type tree = {name: string; attributes: (string * string) list; forest: node list} + + and node = Tree of tree | String of string (** create a tree *) + val create_tree : string -> (string * string) list -> node list -> node - (** print an xml document, if the first parameter is false on a single line without preamble *) val pp_document : bool -> Format.formatter -> node -> unit + (** print an xml document, if the first parameter is false on a single line without preamble *) - (** print the opening lines of an xml document consisting of a main tree with the given name *) val pp_open : Format.formatter -> string -> unit + (** print the opening lines of an xml document consisting of a main tree with the given name *) - (** print the closing lines of an xml document consisting of a main tree with the given name *) val pp_close : Format.formatter -> string -> unit + (** print the closing lines of an xml document consisting of a main tree with the given name *) - (** print a node between a [pp_open] and a [pp_close] *) val pp_inner_node : Format.formatter -> node -> unit + (** print a node between a [pp_open] and a [pp_close] *) end diff --git a/infer/src/IR/LintIssues.ml b/infer/src/IR/LintIssues.ml index d04fc44af..644a470da 100644 --- a/infer/src/IR/LintIssues.ml +++ b/infer/src/IR/LintIssues.ml @@ -13,16 +13,16 @@ open! IStd let errLogMap = ref Typ.Procname.Map.empty -let exists_issues () = - not (Typ.Procname.Map.is_empty !errLogMap) +let exists_issues () = not (Typ.Procname.Map.is_empty !errLogMap) let get_err_log procname = try Typ.Procname.Map.find procname !errLogMap with Not_found -> let errlog = Errlog.empty () in - errLogMap := Typ.Procname.Map.add procname errlog !errLogMap; errlog + errLogMap := Typ.Procname.Map.add procname errlog !errLogMap ; + errlog -let lint_issues_serializer : (Errlog.t Typ.Procname.Map.t) Serialization.serializer = +let lint_issues_serializer : Errlog.t Typ.Procname.Map.t Serialization.serializer = Serialization.create_serializer Serialization.Key.lint_issues (** Save issues to a file *) @@ -30,27 +30,33 @@ let store_issues filename errLogMap = Serialization.write_to_file lint_issues_serializer filename ~data:errLogMap (** Load issues from the given file *) -let load_issues issues_file = - Serialization.read_from_file lint_issues_serializer issues_file +let load_issues issues_file = Serialization.read_from_file lint_issues_serializer issues_file (** Load all the lint issues in the given dir and update the issues map *) let load_issues_to_errlog_map dir = let issues_dir = Filename.concat Config.results_dir dir in - let children_opt = try Some (Sys.readdir issues_dir) with Sys_error _ -> None in + let children_opt = + try Some (Sys.readdir issues_dir) + with Sys_error _ -> None + in let load_issues_to_map issues_file = let file = DB.filename_from_string (Filename.concat issues_dir issues_file) in match load_issues file with - | Some map -> - errLogMap := Typ.Procname.Map.merge ( - fun _ issues1 issues2 -> - match issues1, issues2 with - | Some issues1, Some issues2 -> - Errlog.update issues1 issues2; Some issues1 - | Some issues1, None -> Some issues1 - | None, Some issues2 -> Some issues2 - | None, None -> None - ) !errLogMap map - | None -> () in - match children_opt with - | Some children -> Array.iter ~f:load_issues_to_map children - | None -> () + | Some map + -> errLogMap + := Typ.Procname.Map.merge + (fun _ issues1 issues2 -> + match (issues1, issues2) with + | Some issues1, Some issues2 + -> Errlog.update issues1 issues2 ; Some issues1 + | Some issues1, None + -> Some issues1 + | None, Some issues2 + -> Some issues2 + | None, None + -> None) + !errLogMap map + | None + -> () + in + match children_opt with Some children -> Array.iter ~f:load_issues_to_map children | None -> () diff --git a/infer/src/IR/LintIssues.mli b/infer/src/IR/LintIssues.mli index 197f4c6d5..4ff112899 100644 --- a/infer/src/IR/LintIssues.mli +++ b/infer/src/IR/LintIssues.mli @@ -15,11 +15,11 @@ val errLogMap : Errlog.t Typ.Procname.Map.t ref val exists_issues : unit -> bool -(** Save issues to a file *) val get_err_log : Typ.Procname.t -> Errlog.t +(** Save issues to a file *) -(** Load issues from the given file *) val store_issues : DB.filename -> Errlog.t Typ.Procname.Map.t -> unit +(** Load issues from the given file *) -(** Load all the lint issues in the given dir and update the issues map *) val load_issues_to_errlog_map : string -> unit +(** Load all the lint issues in the given dir and update the issues map *) diff --git a/infer/src/IR/Localise.ml b/infer/src/IR/Localise.ml index e41911900..13cfe6aa4 100644 --- a/infer/src/IR/Localise.ml +++ b/infer/src/IR/Localise.ml @@ -15,18 +15,18 @@ open! IStd module F = Format module MF = MarkupFormatter -type t = string * string [@@deriving compare] (* issue_id, human_readable *) +type t = string * string [@@deriving compare] + +(* issue_id, human_readable *) let equal = [%compare.equal : t] (** create from an ordinary string *) let from_string ?hum s : t = let prettify () = - String.lowercase s - |> String.split ~on:'_' - |> List.map ~f:String.capitalize - |> String.concat ~sep:" " - |> String.strip in + String.lowercase s |> String.split ~on:'_' |> List.map ~f:String.capitalize + |> String.concat ~sep:" " |> String.strip + in (s, match hum with Some str -> str | _ -> prettify ()) (** return the id of an issue *) @@ -38,172 +38,286 @@ let to_human_readable_string (_, s) = s let pp fmt t = Format.fprintf fmt "%s" (to_issue_id t) let analysis_stops = from_string "ANALYSIS_STOPS" + let array_out_of_bounds_l1 = from_string "ARRAY_OUT_OF_BOUNDS_L1" + let array_out_of_bounds_l2 = from_string "ARRAY_OUT_OF_BOUNDS_L2" + let array_out_of_bounds_l3 = from_string "ARRAY_OUT_OF_BOUNDS_L3" + let buffer_overrun = from_string "BUFFER_OVERRUN" + let checkers_access_global = from_string "CHECKERS_ACCESS_GLOBAL" + let checkers_immutable_cast = from_string "CHECKERS_IMMUTABLE_CAST" + let checkers_print_c_call = from_string "CHECKERS_PRINT_C_CALL" + let checkers_print_objc_method_calls = from_string "CHECKERS_PRINT_OBJC_METHOD_CALLS" + let checkers_printf_args = from_string "CHECKERS_PRINTF_ARGS" + let checkers_repeated_calls = from_string "CHECKERS_REPEATED_CALLS" + let checkers_trace_calls_sequence = from_string "CHECKERS_TRACE_CALLS_SEQUENCE" + let class_cast_exception = from_string "CLASS_CAST_EXCEPTION" + let cluster_callback = from_string "CLUSTER_CALLBACK" + let comparing_floats_for_equality = from_string "COMPARING_FLOAT_FOR_EQUALITY" + let condition_always_false = from_string "CONDITION_ALWAYS_FALSE" + let condition_always_true = from_string "CONDITION_ALWAYS_TRUE" + let condition_is_assignment = from_string "CONDITION_IS_ASSIGNMENT" + let context_leak = from_string "CONTEXT_LEAK" + let dangling_pointer_dereference = from_string "DANGLING_POINTER_DEREFERENCE" + let deallocate_stack_variable = from_string "DEALLOCATE_STACK_VARIABLE" + let deallocate_static_memory = from_string "DEALLOCATE_STATIC_MEMORY" + let deallocation_mismatch = from_string "DEALLOCATION_MISMATCH" + let divide_by_zero = from_string "DIVIDE_BY_ZERO" + let double_lock = from_string "DOUBLE_LOCK" + let empty_vector_access = from_string "EMPTY_VECTOR_ACCESS" + let eradicate_condition_redundant = from_string "ERADICATE_CONDITION_REDUNDANT" ~hum:"Condition Redundant" + let eradicate_condition_redundant_nonnull = from_string "ERADICATE_CONDITION_REDUNDANT_NONNULL" ~hum:"Condition Redundant Non-Null" + let eradicate_field_not_initialized = from_string "ERADICATE_FIELD_NOT_INITIALIZED" ~hum:"Field Not Initialized" + let eradicate_field_not_mutable = from_string "ERADICATE_FIELD_NOT_MUTABLE" ~hum:"Field Not Mutable" + let eradicate_field_not_nullable = from_string "ERADICATE_FIELD_NOT_NULLABLE" ~hum:"Field Not Nullable" + let eradicate_field_over_annotated = from_string "ERADICATE_FIELD_OVER_ANNOTATED" ~hum:"Field Over Annotated" + let eradicate_field_value_absent = from_string "ERADICATE_FIELD_VALUE_ABSENT" ~hum:"Field Value Absent" + let eradicate_inconsistent_subclass_parameter_annotation = from_string "ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION" - ~hum: "Inconsistent Subclass Parameter Annotation" + ~hum:"Inconsistent Subclass Parameter Annotation" + let eradicate_inconsistent_subclass_return_annotation = from_string "ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION" - ~hum: "Inconsistent Subclass Return Annotation" + ~hum:"Inconsistent Subclass Return Annotation" + let eradicate_null_field_access = from_string "ERADICATE_NULL_FIELD_ACCESS" ~hum:"Null Field Access" -let eradicate_null_method_call = - from_string "ERADICATE_NULL_METHOD_CALL" ~hum:"Null Method Call" + +let eradicate_null_method_call = from_string "ERADICATE_NULL_METHOD_CALL" ~hum:"Null Method Call" + let eradicate_parameter_not_nullable = from_string "ERADICATE_PARAMETER_NOT_NULLABLE" ~hum:"Parameter Not Nullable" + let eradicate_parameter_value_absent = from_string "ERADICATE_PARAMETER_VALUE_ABSENT" ~hum:"Parameter Value Absent" + let eradicate_return_not_nullable = from_string "ERADICATE_RETURN_NOT_NULLABLE" ~hum:"Return Not Nullable" + let eradicate_return_over_annotated = from_string "ERADICATE_RETURN_OVER_ANNOTATED" ~hum:"Return Over Annotated" + let eradicate_return_value_not_present = from_string "ERADICATE_RETURN_VALUE_NOT_PRESENT" ~hum:"Return Value Not Present" + let eradicate_value_not_present = from_string "ERADICATE_VALUE_NOT_PRESENT" ~hum:"Value Not Present" + let field_should_be_nullable = from_string "FIELD_SHOULD_BE_NULLABLE" + let field_not_null_checked = from_string "IVAR_NOT_NULL_CHECKED" + let inherently_dangerous_function = from_string "INHERENTLY_DANGEROUS_FUNCTION" + let memory_leak = from_string "MEMORY_LEAK" + let null_dereference = from_string "NULL_DEREFERENCE" + let null_test_after_dereference = from_string "NULL_TEST_AFTER_DEREFERENCE" + let parameter_not_null_checked = from_string "PARAMETER_NOT_NULL_CHECKED" + let pointer_size_mismatch = from_string "POINTER_SIZE_MISMATCH" + let precondition_not_found = from_string "PRECONDITION_NOT_FOUND" + let precondition_not_met = from_string "PRECONDITION_NOT_MET" + let premature_nil_termination = from_string "PREMATURE_NIL_TERMINATION_ARGUMENT" + let proc_callback = from_string "PROC_CALLBACK" ~hum:"Procedure Callback" + let quandary_taint_error = from_string "QUANDARY_TAINT_ERROR" + let registered_observer_being_deallocated = from_string "REGISTERED_OBSERVER_BEING_DEALLOCATED" + let resource_leak = from_string "RESOURCE_LEAK" + let retain_cycle = from_string "RETAIN_CYCLE" + let return_expression_required = from_string "RETURN_EXPRESSION_REQUIRED" + let return_statement_missing = from_string "RETURN_STATEMENT_MISSING" + let return_value_ignored = from_string "RETURN_VALUE_IGNORED" + let skip_function = from_string "SKIP_FUNCTION" + let skip_pointer_dereference = from_string "SKIP_POINTER_DEREFERENCE" + let stack_variable_address_escape = from_string "STACK_VARIABLE_ADDRESS_ESCAPE" + let static_initialization_order_fiasco = from_string "STATIC_INITIALIZATION_ORDER_FIASCO" + let tainted_value_reaching_sensitive_function = from_string "TAINTED_VALUE_REACHING_SENSITIVE_FUNCTION" + let thread_safety_violation = from_string "THREAD_SAFETY_VIOLATION" + let unary_minus_applied_to_unsigned_expression = from_string "UNARY_MINUS_APPLIED_TO_UNSIGNED_EXPRESSION" + let uninitialized_value = from_string "UNINITIALIZED_VALUE" + let unreachable_code_after = from_string "UNREACHABLE_CODE" + let unsafe_guarded_by_access = from_string "UNSAFE_GUARDED_BY_ACCESS" + let use_after_free = from_string "USE_AFTER_FREE" module Tags = struct type t = (string * string) list [@@deriving compare] - let accessed_line = "accessed_line" (* line where value was last accessed *) - let alloc_function = "alloc_function" (* allocation function used *) - let alloc_call = "alloc_call" (* call in the current procedure which triggers the allocation *) - let alloc_line = "alloc_line" (* line of alloc_call *) - let array_index = "array_index" (* index of the array *) - let array_size = "array_size" (* size of the array *) - let assigned_line = "assigned_line" (* line where value was last assigned *) - let bucket = "bucket" (* bucket to classify likelyhood of real bug *) - let call_procedure = "call_procedure" (* name of the procedure called *) - let call_line = "call_line" (* line of call_procedure *) - let dealloc_function = "dealloc_function" (* deallocation function used *) - let dealloc_call = "dealloc_call" (* call in the current procedure which triggers the deallocation *) - let dealloc_line = "dealloc_line" (* line of dealloc_call *) - let dereferenced_line = "dereferenced_line" (* line where value was dereferenced *) - let escape_to = "escape_to" (* expression wher a value escapes to *) - let line = "line" (* line of the error *) - let type1 = "type1" (* 1st Java type *) - let type2 = "type2" (* 2nd Java type *) - let value = "value" (* string describing a C value, e.g. "x.date" *) - let parameter_not_null_checked = "parameter_not_null_checked" (* describes a NPE that comes from parameter not nullable *) - let field_not_null_checked = "field_not_null_checked" (* describes a NPE that comes from field not nullable *) - let nullable_src = "nullable_src" (* @Nullable-annoted field/param/retval that causes a warning *) - let weak_captured_var_src = "weak_captured_var_src" (* Weak variable captured in a block that causes a warning *) + + let accessed_line = "accessed_line" + + (* line where value was last accessed *) + let alloc_function = "alloc_function" + + (* allocation function used *) + let alloc_call = "alloc_call" + + (* call in the current procedure which triggers the allocation *) + let alloc_line = "alloc_line" + + (* line of alloc_call *) + let array_index = "array_index" + + (* index of the array *) + let array_size = "array_size" + + (* size of the array *) + let assigned_line = "assigned_line" + + (* line where value was last assigned *) + let bucket = "bucket" + + (* bucket to classify likelyhood of real bug *) + let call_procedure = "call_procedure" + + (* name of the procedure called *) + let call_line = "call_line" + + (* line of call_procedure *) + let dealloc_function = "dealloc_function" + + (* deallocation function used *) + let dealloc_call = "dealloc_call" + + (* call in the current procedure which triggers the deallocation *) + let dealloc_line = "dealloc_line" + + (* line of dealloc_call *) + let dereferenced_line = "dereferenced_line" + + (* line where value was dereferenced *) + let escape_to = "escape_to" + + (* expression wher a value escapes to *) + let line = "line" + + (* line of the error *) + let type1 = "type1" + + (* 1st Java type *) + let type2 = "type2" + + (* 2nd Java type *) + let value = "value" + + (* string describing a C value, e.g. "x.date" *) + let parameter_not_null_checked = "parameter_not_null_checked" + + (* describes a NPE that comes from parameter not nullable *) + let field_not_null_checked = "field_not_null_checked" + + (* describes a NPE that comes from field not nullable *) + let nullable_src = "nullable_src" + + (* @Nullable-annoted field/param/retval that causes a warning *) + let weak_captured_var_src = "weak_captured_var_src" + + (* Weak variable captured in a block that causes a warning *) let double_lock = "double_lock" + let empty_vector_access = "empty_vector_access" + let create () = ref [] + let add tags tag value = List.Assoc.add ~equal:String.equal tags tag value + let update tags tag value = tags := add !tags tag value + let get tags tag = List.Assoc.find ~equal:String.equal tags tag + let tag_value_records_of_tags tags = - List.map ~f:(fun (tag, value) -> {Jsonbug_t.tag; value}) tags + List.map ~f:(fun (tag, value) -> {Jsonbug_t.tag= tag; value}) tags let tags_of_tag_value_records (tag_value_records: Jsonbug_t.tag_value_record list) = List.map ~f:(fun {Jsonbug_t.tag; value} -> (tag, value)) tag_value_records let lines_of_tags (tags: t) = - let line_tags = String.Set.of_list [ - dereferenced_line; - call_line; - assigned_line; - alloc_line; - accessed_line; - dealloc_line; - ] in - List.filter_map ~f:(fun (tag, value) -> - if String.Set.mem line_tags tag then Some (int_of_string value) else None) tags + let line_tags = + String.Set.of_list + [dereferenced_line; call_line; assigned_line; alloc_line; accessed_line; dealloc_line] + in + List.filter_map + ~f:(fun (tag, value) -> + if String.Set.mem line_tags tag then Some (int_of_string value) else None) + tags end -type error_desc = { - descriptions : string list; - advice : string option; - tags : Tags.t; - dotty : string option; -} [@@deriving compare] +type error_desc = + {descriptions: string list; advice: string option; tags: Tags.t; dotty: string option} + [@@deriving compare] (** empty error description *) -let no_desc: error_desc = { - descriptions = []; - advice = None; - tags = []; - dotty = None; -} +let no_desc : error_desc = {descriptions= []; advice= None; tags= []; dotty= None} (** verbatim desc from a string, not to be used for user-visible descs *) -let verbatim_desc s = { no_desc with descriptions = [s] } +let verbatim_desc s = {no_desc with descriptions= [s]} -let custom_desc s tags = { no_desc with descriptions = [s]; tags = tags } +let custom_desc s tags = {no_desc with descriptions= [s]; tags} let custom_desc_with_advice description advice tags = - { no_desc with descriptions = [description]; advice = Some advice; tags = tags } + {no_desc with descriptions= [description]; advice= Some advice; tags} (** pretty print an error description *) let pp_error_desc fmt err_desc = @@ -212,9 +326,7 @@ let pp_error_desc fmt err_desc = (** pretty print an error advice *) let pp_error_advice fmt err_desc = - match err_desc.advice with - | Some advice -> F.fprintf fmt "%s" advice - | None -> () + match err_desc.advice with Some advice -> F.fprintf fmt "%s" advice | None -> () (** get tags of error description *) let error_desc_get_tags err_desc = err_desc.tags @@ -222,23 +334,25 @@ let error_desc_get_tags err_desc = err_desc.tags let error_desc_get_dotty err_desc = err_desc.dotty module BucketLevel = struct - let b1 = "B1" (* highest likelyhood *) + let b1 = "B1" + + (* highest likelyhood *) let b2 = "B2" + let b3 = "B3" + let b4 = "B4" - let b5 = "B5" (* lowest likelyhood *) + + let b5 = "B5" + + (* lowest likelyhood *) end (** takes in input a tag to extract from the given error_desc and returns its value *) let error_desc_extract_tag_value err_desc tag_to_extract = - let find_value tag v = - match v with - | (t, _) when String.equal t tag -> true - | _ -> false in - match List.find ~f:(find_value tag_to_extract) err_desc.tags with - | Some (_, s) -> s - | None -> "" + let find_value tag v = match v with t, _ when String.equal t tag -> true | _ -> false in + match List.find ~f:(find_value tag_to_extract) err_desc.tags with Some (_, s) -> s | None -> "" let error_desc_to_tag_value_pairs err_desc = err_desc.tags @@ -246,20 +360,18 @@ let error_desc_to_tag_value_pairs err_desc = err_desc.tags let error_desc_get_tag_value error_desc = error_desc_extract_tag_value error_desc Tags.value (** returns the content of the call_procedure tag of the error_desc *) -let error_desc_get_tag_call_procedure error_desc = error_desc_extract_tag_value error_desc Tags.call_procedure +let error_desc_get_tag_call_procedure error_desc = + error_desc_extract_tag_value error_desc Tags.call_procedure (** get the bucket value of an error_desc, if any *) -let error_desc_get_bucket err_desc = - Tags.get err_desc.tags Tags.bucket +let error_desc_get_bucket err_desc = Tags.get err_desc.tags Tags.bucket (** set the bucket value of an error_desc; the boolean indicates where the bucket should be shown in the message *) let error_desc_set_bucket err_desc bucket show_in_message = let tags' = Tags.add err_desc.tags Tags.bucket bucket in let l = err_desc.descriptions in - let l' = - if not show_in_message then l - else ("[" ^ bucket ^ "]") :: l in - { err_desc with descriptions = l'; tags = tags' } + let l' = if not show_in_message then l else ("[" ^ bucket ^ "]") :: l in + {err_desc with descriptions= l'; tags= tags'} (** get the value tag, if any *) let get_value_line_tag tags = @@ -271,99 +383,89 @@ let get_value_line_tag tags = (** extract from desc a value on which to apply polymorphic hash and equality *) let desc_get_comparable err_desc = - match get_value_line_tag err_desc.tags with - | Some sl' -> sl' - | None -> err_desc.descriptions + match get_value_line_tag err_desc.tags with Some sl' -> sl' | None -> err_desc.descriptions (** hash function for error_desc *) -let error_desc_hash desc = - Hashtbl.hash (desc_get_comparable desc) +let error_desc_hash desc = Hashtbl.hash (desc_get_comparable desc) (** equality for error_desc *) let error_desc_equal desc1 desc2 = - [%compare.equal : string list] - (desc_get_comparable desc1) - (desc_get_comparable desc2) + [%compare.equal : string list] (desc_get_comparable desc1) (desc_get_comparable desc2) let _line_tag tags tag loc = let line_str = string_of_int loc.Location.line in - Tags.update tags tag line_str; + Tags.update tags tag line_str ; let s = "line " ^ line_str in - if (loc.Location.col <> -1) then + if loc.Location.col <> -1 then let col_str = string_of_int loc.Location.col in s ^ ", column " ^ col_str else s -let at_line_tag tags tag loc = - "at " ^ _line_tag tags tag loc +let at_line_tag tags tag loc = "at " ^ _line_tag tags tag loc -let _line tags loc = - _line_tag tags Tags.line loc +let _line tags loc = _line_tag tags Tags.line loc -let at_line tags loc = - at_line_tag tags Tags.line loc +let at_line tags loc = at_line_tag tags Tags.line loc let call_to tags proc_name = let proc_name_str = Typ.Procname.to_simplified_string proc_name in - Tags.update tags Tags.call_procedure proc_name_str; + Tags.update tags Tags.call_procedure proc_name_str ; "call to " ^ MF.monospaced_to_string proc_name_str let call_to_at_line tags proc_name loc = - (call_to tags proc_name) ^ " " ^ at_line_tag tags Tags.call_line loc + call_to tags proc_name ^ " " ^ at_line_tag tags Tags.call_line loc -let by_call_to tags proc_name = - "by " ^ call_to tags proc_name +let by_call_to tags proc_name = "by " ^ call_to tags proc_name -let by_call_to_ra tags ra = - "by " ^ call_to_at_line tags ra.PredSymb.ra_pname ra.PredSymb.ra_loc +let by_call_to_ra tags ra = "by " ^ call_to_at_line tags ra.PredSymb.ra_pname ra.PredSymb.ra_loc let add_by_call_to_opt problem_str tags proc_name_opt = match proc_name_opt with - | Some proc_name -> - problem_str ^ " " ^ by_call_to tags proc_name - | None -> problem_str - -let rec format_typ typ = match typ.Typ.desc with - | Typ.Tptr (t, _) when Config.curr_language_is Config.Java -> - format_typ t - | Typ.Tstruct name -> - Typ.Name.name name - | _ -> - Typ.to_string typ + | Some proc_name + -> problem_str ^ " " ^ by_call_to tags proc_name + | None + -> problem_str + +let rec format_typ typ = + match typ.Typ.desc with + | Typ.Tptr (t, _) when Config.curr_language_is Config.Java + -> format_typ t + | Typ.Tstruct name + -> Typ.Name.name name + | _ + -> Typ.to_string typ let format_field f = - if Config.curr_language_is Config.Java - then Typ.Fieldname.java_get_field f + if Config.curr_language_is Config.Java then Typ.Fieldname.java_get_field f else Typ.Fieldname.to_string f let format_method pname = match pname with - | Typ.Procname.Java pname_java -> - Typ.Procname.java_get_method pname_java - | _ -> - Typ.Procname.to_string pname + | Typ.Procname.Java pname_java + -> Typ.Procname.java_get_method pname_java + | _ + -> Typ.Procname.to_string pname let mem_dyn_allocated = "memory dynamically allocated" + let lock_acquired = "lock acquired" + let released = "released" + let reachable = "reachable" (** dereference strings used to explain a dereference action in an error message *) type deref_str = - { tags : (string * string) list ref; (** tags for the error description *) - value_pre: string option; (** string printed before the value being dereferenced *) - value_post: string option; (** string printed after the value being dereferenced *) - problem_str: string; (** description of the problem *) } + { tags: (string * string) list ref (** tags for the error description *) + ; value_pre: string option (** string printed before the value being dereferenced *) + ; value_post: string option (** string printed after the value being dereferenced *) + ; problem_str: string (** description of the problem *) } -let pointer_or_object () = - if Config.curr_language_is Config.Java then "object" else "pointer" +let pointer_or_object () = if Config.curr_language_is Config.Java then "object" else "pointer" let _deref_str_null proc_name_opt _problem_str tags = let problem_str = add_by_call_to_opt _problem_str tags proc_name_opt in - { tags = tags; - value_pre = Some (pointer_or_object ()); - value_post = None; - problem_str = problem_str; } + {tags; value_pre= Some (pointer_or_object ()); value_post= None; problem_str} (** dereference strings for null dereference *) let deref_str_null proc_name_opt = @@ -377,7 +479,7 @@ let access_str_empty proc_name_opt = (** dereference strings for null dereference due to Nullable annotation *) let deref_str_nullable proc_name_opt nullable_obj_str = let tags = Tags.create () in - Tags.update tags Tags.nullable_src nullable_obj_str; + Tags.update tags Tags.nullable_src nullable_obj_str ; (* to be completed once we know if the deref'd expression is directly or transitively @Nullable*) let problem_str = "" in _deref_str_null proc_name_opt problem_str tags @@ -385,7 +487,7 @@ let deref_str_nullable proc_name_opt nullable_obj_str = (** dereference strings for null dereference due to weak captured variable in block *) let deref_str_weak_variable_in_block proc_name_opt nullable_obj_str = let tags = Tags.create () in - Tags.update tags Tags.weak_captured_var_src nullable_obj_str; + Tags.update tags Tags.weak_captured_var_src nullable_obj_str ; let problem_str = "" in _deref_str_null proc_name_opt problem_str tags @@ -393,257 +495,302 @@ let deref_str_weak_variable_in_block proc_name_opt nullable_obj_str = let deref_str_nil_argument_in_variadic_method pn total_args arg_number = let tags = Tags.create () in let function_method, nil_null = - if Typ.Procname.is_c_method pn then ("method", "nil") else ("function", "null") in + if Typ.Procname.is_c_method pn then ("method", "nil") else ("function", "null") + in let problem_str = Printf.sprintf - "could be %s which results in a call to %s with %d arguments instead of %d \ - (%s indicates that the last argument of this variadic %s has been reached)" - nil_null (Typ.Procname.to_simplified_string pn) arg_number (total_args - 1) nil_null function_method in + "could be %s which results in a call to %s with %d arguments instead of %d (%s indicates that the last argument of this variadic %s has been reached)" + nil_null (Typ.Procname.to_simplified_string pn) arg_number (total_args - 1) nil_null + function_method + in _deref_str_null None problem_str tags (** dereference strings for an undefined value coming from the given procedure *) let deref_str_undef (proc_name, loc) = let tags = Tags.create () in let proc_name_str = Typ.Procname.to_simplified_string proc_name in - Tags.update tags Tags.call_procedure proc_name_str; - { tags = tags; - value_pre = Some (pointer_or_object ()); - value_post = None; - problem_str = "could be assigned by a call to skip function " ^ proc_name_str ^ - at_line_tag tags Tags.call_line loc ^ " and is dereferenced or freed"; } + Tags.update tags Tags.call_procedure proc_name_str ; + { tags + ; value_pre= Some (pointer_or_object ()) + ; value_post= None + ; problem_str= + "could be assigned by a call to skip function " ^ proc_name_str + ^ at_line_tag tags Tags.call_line loc ^ " and is dereferenced or freed" } (** dereference strings for a freed pointer dereference *) let deref_str_freed ra = let tags = Tags.create () in let freed_or_closed_by_call = - let freed_or_closed = match ra.PredSymb.ra_res with - | PredSymb.Rmemory _ -> "freed" - | PredSymb.Rfile -> "closed" - | PredSymb.Rignore -> "freed" - | PredSymb.Rlock -> "locked" in - freed_or_closed ^ " " ^ by_call_to_ra tags ra in - { tags = tags; - value_pre = Some (pointer_or_object ()); - value_post = None; - problem_str = "was " ^ freed_or_closed_by_call ^ " and is dereferenced or freed" } + let freed_or_closed = + match ra.PredSymb.ra_res with + | PredSymb.Rmemory _ + -> "freed" + | PredSymb.Rfile + -> "closed" + | PredSymb.Rignore + -> "freed" + | PredSymb.Rlock + -> "locked" + in + freed_or_closed ^ " " ^ by_call_to_ra tags ra + in + { tags + ; value_pre= Some (pointer_or_object ()) + ; value_post= None + ; problem_str= "was " ^ freed_or_closed_by_call ^ " and is dereferenced or freed" } (** dereference strings for a dangling pointer dereference *) let deref_str_dangling dangling_kind_opt = - let dangling_kind_prefix = match dangling_kind_opt with - | Some PredSymb.DAuninit -> "uninitialized " - | Some PredSymb.DAaddr_stack_var -> "deallocated stack " - | Some PredSymb.DAminusone -> "-1 " - | None -> "" in - { tags = Tags.create (); - value_pre = Some (dangling_kind_prefix ^ (pointer_or_object ())); - value_post = None; - problem_str = "could be dangling and is dereferenced or freed"; } + let dangling_kind_prefix = + match dangling_kind_opt with + | Some PredSymb.DAuninit + -> "uninitialized " + | Some PredSymb.DAaddr_stack_var + -> "deallocated stack " + | Some PredSymb.DAminusone + -> "-1 " + | None + -> "" + in + { tags= Tags.create () + ; value_pre= Some (dangling_kind_prefix ^ pointer_or_object ()) + ; value_post= None + ; problem_str= "could be dangling and is dereferenced or freed" } (** dereference strings for a pointer size mismatch *) let deref_str_pointer_size_mismatch typ_from_instr typ_of_object = let str_from_typ typ = let pp f = Typ.pp_full Pp.text f typ in - F.asprintf "%t" pp in - { tags = Tags.create (); - value_pre = Some (pointer_or_object ()); - value_post = Some ("of type " ^ str_from_typ typ_from_instr); - problem_str = "could be used to access an object of smaller type " ^ str_from_typ typ_of_object; } + F.asprintf "%t" pp + in + { tags= Tags.create () + ; value_pre= Some (pointer_or_object ()) + ; value_post= Some ("of type " ^ str_from_typ typ_from_instr) + ; problem_str= "could be used to access an object of smaller type " ^ str_from_typ typ_of_object + } (** dereference strings for an array out of bound access *) let deref_str_array_bound size_opt index_opt = let tags = Tags.create () in - let size_str_opt = match size_opt with - | Some n -> - let n_str = IntLit.to_string n in - Tags.update tags Tags.array_size n_str; - Some ("of size " ^ n_str) - | None -> None in - let index_str = match index_opt with - | Some n -> - let n_str = IntLit.to_string n in - Tags.update tags Tags.array_index n_str; - "index " ^ n_str - | None -> "an index" in - { tags = tags; - value_pre = Some "array"; - value_post = size_str_opt; - problem_str = "could be accessed with " ^ index_str ^ " out of bounds"; } + let size_str_opt = + match size_opt with + | Some n + -> let n_str = IntLit.to_string n in + Tags.update tags Tags.array_size n_str ; Some ("of size " ^ n_str) + | None + -> None + in + let index_str = + match index_opt with + | Some n + -> let n_str = IntLit.to_string n in + Tags.update tags Tags.array_index n_str ; "index " ^ n_str + | None + -> "an index" + in + { tags + ; value_pre= Some "array" + ; value_post= size_str_opt + ; problem_str= "could be accessed with " ^ index_str ^ " out of bounds" } (** dereference strings for an uninitialized access whose lhs has the given attribute *) let deref_str_uninitialized alloc_att_opt = let tags = Tags.create () in - let creation_str = match alloc_att_opt with - | Some (Sil.Apred (Aresource ({ ra_kind = Racquire } as ra), _)) -> - "after allocation " ^ by_call_to_ra tags ra - | _ -> "after declaration" in - { tags = tags; - value_pre = Some "value"; - value_post = None; - problem_str = "was not initialized " ^ creation_str ^ " and is used"; } + let creation_str = + match alloc_att_opt with + | Some Sil.Apred (Aresource ({ra_kind= Racquire} as ra), _) + -> "after allocation " ^ by_call_to_ra tags ra + | _ + -> "after declaration" + in + { tags + ; value_pre= Some "value" + ; value_post= None + ; problem_str= "was not initialized " ^ creation_str ^ " and is used" } (** Java unchecked exceptions errors *) let java_unchecked_exn_desc proc_name exn_name pre_str : error_desc = - { no_desc with descriptions = [ - MF.monospaced_to_string (Typ.Procname.to_string proc_name); - "can throw " ^ MF.monospaced_to_string (Typ.Name.name exn_name); - "whenever " ^ pre_str]; - } + { no_desc with + descriptions= + [ MF.monospaced_to_string (Typ.Procname.to_string proc_name) + ; ("can throw " ^ MF.monospaced_to_string (Typ.Name.name exn_name)) + ; ("whenever " ^ pre_str) ] } let desc_context_leak pname context_typ fieldname leak_path : error_desc = let fld_str = Typ.Fieldname.to_string fieldname in let leak_root = "Static field " ^ fld_str ^ " |->\n" in let leak_path_entry_to_str acc entry = - let entry_str = match entry with - | (Some fld, _) -> Typ.Fieldname.to_string fld - | (None, typ) -> Typ.to_string typ in + let entry_str = + match entry with + | Some fld, _ + -> Typ.Fieldname.to_string fld + | None, typ + -> Typ.to_string typ + in (* intentionally omit space; [typ_to_string] adds an extra space *) - acc ^ entry_str ^ " |->\n" in + acc ^ entry_str ^ " |->\n" + in let context_str = Typ.to_string context_typ in let path_str = let path_prefix = if List.is_empty leak_path then "Leaked " - else (List.fold ~f:leak_path_entry_to_str ~init:"" leak_path) ^ "Leaked " in - path_prefix ^ context_str in + else List.fold ~f:leak_path_entry_to_str ~init:"" leak_path ^ "Leaked " + in + path_prefix ^ context_str + in let preamble = - let pname_str = match pname with - | Typ.Procname.Java pname_java -> - MF.monospaced_to_string - (Printf.sprintf "%s.%s" - (Typ.Procname.java_get_class_name pname_java) + let pname_str = + match pname with + | Typ.Procname.Java pname_java + -> MF.monospaced_to_string + (Printf.sprintf "%s.%s" (Typ.Procname.java_get_class_name pname_java) (Typ.Procname.java_get_method pname_java)) - | _ -> - "" in - "Context " ^ context_str ^ " may leak during method " ^ pname_str ^ ":\n" in - { no_desc with descriptions = [preamble ^ MF.code_to_string (leak_root ^ path_str)] } + | _ + -> "" + in + "Context " ^ context_str ^ " may leak during method " ^ pname_str ^ ":\n" + in + {no_desc with descriptions= [(preamble ^ MF.code_to_string (leak_root ^ path_str))]} let desc_double_lock pname_opt object_str loc = let mutex_str = Format.sprintf "Mutex %s" object_str in let tags = Tags.create () in let msg = "could be locked and is locked again" in let msg = add_by_call_to_opt msg tags pname_opt in - Tags.update tags Tags.double_lock object_str; + Tags.update tags Tags.double_lock object_str ; let descriptions = [mutex_str; msg; at_line tags loc] in - { no_desc with descriptions; tags = !tags } + {no_desc with descriptions; tags= !tags} let desc_unsafe_guarded_by_access accessed_fld guarded_by_str loc = let line_info = at_line (Tags.create ()) loc in let accessed_fld_str = Typ.Fieldname.to_string accessed_fld in let annot_str = Printf.sprintf "@GuardedBy(\"%s\")" guarded_by_str in let syncronized_str = - MF.monospaced_to_string (Printf.sprintf "synchronized(%s)" guarded_by_str) in + MF.monospaced_to_string (Printf.sprintf "synchronized(%s)" guarded_by_str) + in let msg = Format.asprintf - "The field %a is annotated with %a, but the lock %a is not held during the access to the \ - field %s. Since the current method is non-private, it can be called from outside the \ - current class without synchronization. Consider wrapping the access in a %s block or making \ - the method private." - MF.pp_monospaced accessed_fld_str - MF.pp_monospaced annot_str - MF.pp_monospaced guarded_by_str - line_info - syncronized_str in - { no_desc with descriptions = [msg]; } - + "The field %a is annotated with %a, but the lock %a is not held during the access to the field %s. Since the current method is non-private, it can be called from outside the current class without synchronization. Consider wrapping the access in a %s block or making the method private." + MF.pp_monospaced accessed_fld_str MF.pp_monospaced annot_str MF.pp_monospaced guarded_by_str + line_info syncronized_str + in + {no_desc with descriptions= [msg]} let desc_fragment_retains_view fragment_typ fieldname fld_typ pname : error_desc = (* TODO: try advice *) let problem = Printf.sprintf "Fragment %s does not nullify View field %s (type %s) in %s." - (format_typ fragment_typ) - (format_field fieldname) - (format_typ fld_typ) - (format_method pname) in + (format_typ fragment_typ) (format_field fieldname) (format_typ fld_typ) (format_method pname) + in let consequences = - "If this Fragment is placed on the back stack, a reference to this (probably dead) View will be retained." in + "If this Fragment is placed on the back stack, a reference to this (probably dead) View will be retained." + in let advice = - "In general, it is a good idea to initialize View's in onCreateView, then nullify them in onDestroyView." in - { no_desc with descriptions = [problem; consequences; advice] } + "In general, it is a good idea to initialize View's in onCreateView, then nullify them in onDestroyView." + in + {no_desc with descriptions= [problem; consequences; advice]} let desc_custom_error loc : error_desc = - { no_desc with descriptions = ["detected"; at_line (Tags.create ()) loc] } + {no_desc with descriptions= ["detected"; at_line (Tags.create ()) loc]} (** type of access *) type access = - | Last_assigned of int * bool (* line, null_case_flag *) - | Last_accessed of int * bool (* line, is_nullable flag *) + | Last_assigned of int * bool + (* line, null_case_flag *) + | Last_accessed of int * bool + (* line, is_nullable flag *) | Initialized_automatically | Returned_from_call of int let dereference_string deref_str value_str access_opt loc = let tags = deref_str.tags in - Tags.update tags Tags.value value_str; - let is_call_access = match access_opt with - | Some (Returned_from_call _) -> true - | _ -> false in + Tags.update tags Tags.value value_str ; + let is_call_access = match access_opt with Some Returned_from_call _ -> true | _ -> false in let value_desc = - String.concat ~sep:"" [ - (match deref_str.value_pre with Some s -> s ^ " " | _ -> ""); - (if is_call_access then "returned by " else ""); - MF.monospaced_to_string value_str; - (match deref_str.value_post with Some s -> " " ^ (MF.monospaced_to_string s) | _ -> "")] in - let access_desc = match access_opt with - | None -> - [] - | Some (Last_accessed (n, _)) -> - let line_str = string_of_int n in - Tags.update tags Tags.accessed_line line_str; - ["last accessed on line " ^ line_str] - | Some (Last_assigned (n, _)) -> - let line_str = string_of_int n in - Tags.update tags Tags.assigned_line line_str; - ["last assigned on line " ^ line_str] - | Some (Returned_from_call _) -> [] - | Some Initialized_automatically -> - ["initialized automatically"] in + String.concat ~sep:"" + [ (match deref_str.value_pre with Some s -> s ^ " " | _ -> "") + ; (if is_call_access then "returned by " else "") + ; MF.monospaced_to_string value_str + ; (match deref_str.value_post with Some s -> " " ^ MF.monospaced_to_string s | _ -> "") ] + in + let access_desc = + match access_opt with + | None + -> [] + | Some Last_accessed (n, _) + -> let line_str = string_of_int n in + Tags.update tags Tags.accessed_line line_str ; [("last accessed on line " ^ line_str)] + | Some Last_assigned (n, _) + -> let line_str = string_of_int n in + Tags.update tags Tags.assigned_line line_str ; [("last assigned on line " ^ line_str)] + | Some Returned_from_call _ + -> [] + | Some Initialized_automatically + -> ["initialized automatically"] + in let problem_desc = let nullable_text = MF.monospaced_to_string - (if Config.curr_language_is Config.Java - then "@Nullable" - else "__nullable") in + (if Config.curr_language_is Config.Java then "@Nullable" else "__nullable") + in let problem_str = - match Tags.get !tags Tags.nullable_src, Tags.get !tags Tags.weak_captured_var_src with - | Some nullable_src, _ -> - if String.equal nullable_src value_str - then "is annotated with " ^ nullable_text ^ " and is dereferenced without a null check" - else "is indirectly marked " ^ nullable_text ^ - " (source: " ^ MF.monospaced_to_string nullable_src ^ - ") and is dereferenced without a null check" - | None, Some weak_var_str -> - if String.equal weak_var_str value_str then + match (Tags.get !tags Tags.nullable_src, Tags.get !tags Tags.weak_captured_var_src) with + | Some nullable_src, _ + -> if String.equal nullable_src value_str then "is annotated with " ^ nullable_text + ^ " and is dereferenced without a null check" + else "is indirectly marked " ^ nullable_text ^ " (source: " + ^ MF.monospaced_to_string nullable_src ^ ") and is dereferenced without a null check" + | None, Some weak_var_str + -> if String.equal weak_var_str value_str then "is a weak pointer captured in the block and is dereferenced without a null check" - else "is equal to the variable " ^ (MF.monospaced_to_string weak_var_str) ^ - ", a weak pointer captured in the block, and is dereferenced without a null check" - | None, None -> deref_str.problem_str in - [(problem_str ^ " " ^ at_line tags loc)] in - { no_desc with descriptions = value_desc:: access_desc @ problem_desc; tags = !tags } - -let parameter_field_not_null_checked_desc (desc : error_desc) exp = + else "is equal to the variable " ^ MF.monospaced_to_string weak_var_str + ^ ", a weak pointer captured in the block, and is dereferenced without a null check" + | None, None + -> deref_str.problem_str + in + [(problem_str ^ " " ^ at_line tags loc)] + in + {no_desc with descriptions= value_desc :: access_desc @ problem_desc; tags= !tags} + +let parameter_field_not_null_checked_desc (desc: error_desc) exp = let parameter_not_nullable_desc var = let var_s = Pvar.to_string var in let param_not_null_desc = - "Parameter " ^ (MF.monospaced_to_string var_s) ^ - " is not checked for null, there could be a null pointer dereference:" in - { desc with descriptions = param_not_null_desc :: desc.descriptions; - tags = (Tags.parameter_not_null_checked, var_s) :: desc.tags; } in + "Parameter " ^ MF.monospaced_to_string var_s + ^ " is not checked for null, there could be a null pointer dereference:" + in + { desc with + descriptions= param_not_null_desc :: desc.descriptions + ; tags= (Tags.parameter_not_null_checked, var_s) :: desc.tags } + in let field_not_nullable_desc exp = let rec exp_to_string exp = match exp with - | Exp.Lfield (exp', field, _) -> (exp_to_string exp')^" -> "^(Typ.Fieldname.to_string field) - | Exp.Lvar pvar -> Mangled.to_string (Pvar.get_name pvar) - | _ -> "" in + | Exp.Lfield (exp', field, _) + -> exp_to_string exp' ^ " -> " ^ Typ.Fieldname.to_string field + | Exp.Lvar pvar + -> Mangled.to_string (Pvar.get_name pvar) + | _ + -> "" + in let var_s = exp_to_string exp in let field_not_null_desc = - "Instance variable " ^ (MF.monospaced_to_string var_s) ^ - " is not checked for null, there could be a null pointer dereference:" in - { desc with descriptions = field_not_null_desc :: desc.descriptions; - tags = (Tags.field_not_null_checked, var_s) :: desc.tags; } in + "Instance variable " ^ MF.monospaced_to_string var_s + ^ " is not checked for null, there could be a null pointer dereference:" + in + { desc with + descriptions= field_not_null_desc :: desc.descriptions + ; tags= (Tags.field_not_null_checked, var_s) :: desc.tags } + in match exp with - | Exp.Lvar var -> parameter_not_nullable_desc var - | Exp.Lfield _ -> field_not_nullable_desc exp - | _ -> desc - -let has_tag (desc : error_desc) tag = + | Exp.Lvar var + -> parameter_not_nullable_desc var + | Exp.Lfield _ + -> field_not_nullable_desc exp + | _ + -> desc + +let has_tag (desc: error_desc) tag = List.exists ~f:(fun (tag', _) -> String.equal tag tag') desc.tags let is_parameter_not_null_checked_desc desc = has_tag desc Tags.parameter_not_null_checked @@ -651,8 +798,7 @@ let is_parameter_not_null_checked_desc desc = has_tag desc Tags.parameter_not_nu let is_field_not_null_checked_desc desc = has_tag desc Tags.field_not_null_checked let is_parameter_field_not_null_checked_desc desc = - is_parameter_not_null_checked_desc desc || - is_field_not_null_checked_desc desc + is_parameter_not_null_checked_desc desc || is_field_not_null_checked_desc desc let is_double_lock_desc desc = has_tag desc Tags.double_lock @@ -660,358 +806,361 @@ let desc_allocation_mismatch alloc dealloc = let tags = Tags.create () in let using is_alloc (primitive_pname, called_pname, loc) = let tag_fun, tag_call, tag_line = - if is_alloc then Tags.alloc_function, Tags.alloc_call, Tags.alloc_line - else Tags.dealloc_function, Tags.dealloc_call, Tags.dealloc_line in - Tags.update tags tag_fun (Typ.Procname.to_simplified_string primitive_pname); - Tags.update tags tag_call (Typ.Procname.to_simplified_string called_pname); - Tags.update tags tag_line (string_of_int loc.Location.line); + if is_alloc then (Tags.alloc_function, Tags.alloc_call, Tags.alloc_line) + else (Tags.dealloc_function, Tags.dealloc_call, Tags.dealloc_line) + in + Tags.update tags tag_fun (Typ.Procname.to_simplified_string primitive_pname) ; + Tags.update tags tag_call (Typ.Procname.to_simplified_string called_pname) ; + Tags.update tags tag_line (string_of_int loc.Location.line) ; let by_call = if Typ.Procname.equal primitive_pname called_pname then "" - else " by call to " ^ - (MF.monospaced_to_string (Typ.Procname.to_simplified_string called_pname)) in - "using " ^ (MF.monospaced_to_string (Typ.Procname.to_simplified_string primitive_pname)) ^ - by_call ^ " " ^ at_line (Tags.create ()) (* ignore the tag *) loc in - let description = Format.sprintf - "%s %s is deallocated %s" - mem_dyn_allocated - (using true alloc) - (using false dealloc) in - { no_desc with descriptions = [description]; tags = !tags } + else " by call to " + ^ MF.monospaced_to_string (Typ.Procname.to_simplified_string called_pname) + in + "using " ^ MF.monospaced_to_string (Typ.Procname.to_simplified_string primitive_pname) + ^ by_call ^ " " ^ at_line (Tags.create ()) (* ignore the tag *) loc + in + let description = + Format.sprintf "%s %s is deallocated %s" mem_dyn_allocated (using true alloc) + (using false dealloc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_comparing_floats_for_equality loc = let tags = Tags.create () in - { no_desc with descriptions = ["Comparing floats for equality " ^ at_line tags loc]; - tags = !tags } + {no_desc with descriptions= [("Comparing floats for equality " ^ at_line tags loc)]; tags= !tags} let desc_condition_is_assignment loc = let tags = Tags.create () in - { no_desc with descriptions = ["Boolean condition is an assignment " ^ at_line tags loc]; - tags = !tags } + { no_desc with + descriptions= [("Boolean condition is an assignment " ^ at_line tags loc)]; tags= !tags } let desc_condition_always_true_false i cond_str_opt loc = let tags = Tags.create () in - let value = match cond_str_opt with - | None -> "" - | Some s -> s in + let value = match cond_str_opt with None -> "" | Some s -> s in let tt_ff = if IntLit.iszero i then "false" else "true" in - Tags.update tags Tags.value value; - let description = Format.sprintf - "Boolean condition %s is always %s %s" - (if String.equal value "" then "" else " " ^ (MF.monospaced_to_string value)) - tt_ff - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.value value ; + let description = + Format.sprintf "Boolean condition %s is always %s %s" + (if String.equal value "" then "" else " " ^ MF.monospaced_to_string value) + tt_ff (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_unreachable_code_after loc = let tags = Tags.create () in let description = "Unreachable code after statement " ^ at_line tags loc in - { no_desc with descriptions = [description]} + {no_desc with descriptions= [description]} let desc_deallocate_stack_variable var_str proc_name loc = let tags = Tags.create () in - Tags.update tags Tags.value var_str; - let description = Format.asprintf - "Stack variable %a is freed by a %s" - MF.pp_monospaced var_str - (call_to_at_line tags proc_name loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.value var_str ; + let description = + Format.asprintf "Stack variable %a is freed by a %s" MF.pp_monospaced var_str + (call_to_at_line tags proc_name loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_deallocate_static_memory const_str proc_name loc = let tags = Tags.create () in - Tags.update tags Tags.value const_str; - let description = Format.asprintf - "Constant string %a is freed by a %s" - MF.pp_monospaced const_str - (call_to_at_line tags proc_name loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.value const_str ; + let description = + Format.asprintf "Constant string %a is freed by a %s" MF.pp_monospaced const_str + (call_to_at_line tags proc_name loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_class_cast_exception pname_opt typ_str1 typ_str2 exp_str_opt loc = let tags = Tags.create () in - Tags.update tags Tags.type1 typ_str1; - Tags.update tags Tags.type2 typ_str2; - let in_expression = match exp_str_opt with - | Some exp_str -> - Tags.update tags Tags.value exp_str; - " in expression " ^ (MF.monospaced_to_string exp_str) ^ " " - | None -> " " in - let at_line' () = match pname_opt with - | Some proc_name -> "in " ^ call_to_at_line tags proc_name loc - | None -> at_line tags loc in - let description = Format.asprintf - "%a cannot be cast to %a %s %s" - MF.pp_monospaced typ_str1 - MF.pp_monospaced typ_str2 - in_expression - (at_line' ()) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.type1 typ_str1 ; + Tags.update tags Tags.type2 typ_str2 ; + let in_expression = + match exp_str_opt with + | Some exp_str + -> Tags.update tags Tags.value exp_str ; + " in expression " ^ MF.monospaced_to_string exp_str ^ " " + | None + -> " " + in + let at_line' () = + match pname_opt with + | Some proc_name + -> "in " ^ call_to_at_line tags proc_name loc + | None + -> at_line tags loc + in + let description = + Format.asprintf "%a cannot be cast to %a %s %s" MF.pp_monospaced typ_str1 MF.pp_monospaced + typ_str2 in_expression (at_line' ()) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_divide_by_zero expr_str loc = let tags = Tags.create () in - Tags.update tags Tags.value expr_str; - let description = Format.asprintf - "Expression %a could be zero %s" - MF.pp_monospaced expr_str - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.value expr_str ; + let description = + Format.asprintf "Expression %a could be zero %s" MF.pp_monospaced expr_str (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_empty_vector_access pname_opt object_str loc = let vector_str = Format.asprintf "Vector %a" MF.pp_monospaced object_str in let desc = access_str_empty pname_opt in let tags = desc.tags in - Tags.update tags Tags.empty_vector_access object_str; - let descriptions = [vector_str; desc.problem_str; (at_line tags loc)] in - { no_desc with descriptions; tags = !tags } + Tags.update tags Tags.empty_vector_access object_str ; + let descriptions = [vector_str; desc.problem_str; at_line tags loc] in + {no_desc with descriptions; tags= !tags} let is_empty_vector_access_desc desc = has_tag desc Tags.empty_vector_access let desc_frontend_warning desc sugg_opt loc = let tags = Tags.create () in - let sugg = match sugg_opt with - | Some sugg -> sugg - | None -> "" in - let description = Format.sprintf - "%s %s. %s" - desc - (at_line tags loc) - sugg in - { no_desc with descriptions = [description]; tags = !tags } + let sugg = match sugg_opt with Some sugg -> sugg | None -> "" in + let description = Format.sprintf "%s %s. %s" desc (at_line tags loc) sugg in + {no_desc with descriptions= [description]; tags= !tags} let desc_leak hpred_type_opt value_str_opt resource_opt resource_action_opt loc bucket_opt = let tags = Tags.create () in - let () = match bucket_opt with - | Some bucket -> - Tags.update tags Tags.bucket bucket; - | None -> () in + let () = + match bucket_opt with Some bucket -> Tags.update tags Tags.bucket bucket | None -> () + in let xxx_allocated_to = let value_str, _to, _on = match value_str_opt with - | None -> "", "", "" - | Some s -> - Tags.update tags Tags.value s; - MF.monospaced_to_string s, " to ", " on " in + | None + -> ("", "", "") + | Some s + -> Tags.update tags Tags.value s ; (MF.monospaced_to_string s, " to ", " on ") + in let typ_str = match hpred_type_opt with - | Some (Exp.Sizeof {typ={desc=Tstruct name}}) when Typ.Name.is_class name -> - " of type " ^ MF.monospaced_to_string (Typ.Name.name name) ^ " " - | _ -> " " in + | Some Exp.Sizeof {typ= {desc= Tstruct name}} when Typ.Name.is_class name + -> " of type " ^ MF.monospaced_to_string (Typ.Name.name name) ^ " " + | _ + -> " " + in let desc_str = match resource_opt with - | Some PredSymb.Rmemory _ -> mem_dyn_allocated ^ _to ^ value_str - | Some PredSymb.Rfile -> "resource" ^ typ_str ^ "acquired" ^ _to ^ value_str - | Some PredSymb.Rlock -> lock_acquired ^ _on ^ value_str - | Some PredSymb.Rignore - | None -> if is_none value_str_opt then "memory" else value_str in - if String.equal desc_str "" then [] else [desc_str] in - let by_call_to = match resource_action_opt with - | Some ra -> [(by_call_to_ra tags ra)] - | None -> [] in - let is_not_rxxx_after = - let rxxx = match resource_opt with - | Some PredSymb.Rmemory _ -> reachable + | Some PredSymb.Rmemory _ + -> mem_dyn_allocated ^ _to ^ value_str | Some PredSymb.Rfile - | Some PredSymb.Rlock -> released - | Some PredSymb.Rignore - | None -> reachable in - [("is not " ^ rxxx ^ " after " ^ _line tags loc)] in + -> "resource" ^ typ_str ^ "acquired" ^ _to ^ value_str + | Some PredSymb.Rlock + -> lock_acquired ^ _on ^ value_str + | Some PredSymb.Rignore | None + -> if is_none value_str_opt then "memory" else value_str + in + if String.equal desc_str "" then [] else [desc_str] + in + let by_call_to = + match resource_action_opt with Some ra -> [by_call_to_ra tags ra] | None -> [] + in + let is_not_rxxx_after = + let rxxx = + match resource_opt with + | Some PredSymb.Rmemory _ + -> reachable + | Some PredSymb.Rfile | Some PredSymb.Rlock + -> released + | Some PredSymb.Rignore | None + -> reachable + in + [("is not " ^ rxxx ^ " after " ^ _line tags loc)] + in let bucket_str = - match bucket_opt with - | Some bucket when Config.show_buckets -> bucket - | _ -> "" in - { no_desc with descriptions = bucket_str :: xxx_allocated_to @ by_call_to @ is_not_rxxx_after; - tags = !tags } + match bucket_opt with Some bucket when Config.show_buckets -> bucket | _ -> "" + in + { no_desc with + descriptions= bucket_str :: xxx_allocated_to @ by_call_to @ is_not_rxxx_after; tags= !tags } let desc_buffer_overrun bucket desc = - let err_desc = { no_desc with descriptions = [desc]; } in + let err_desc = {no_desc with descriptions= [desc]} in error_desc_set_bucket err_desc bucket Config.show_buckets (** kind of precondition not met *) -type pnm_kind = - | Pnm_bounds - | Pnm_dangling +type pnm_kind = Pnm_bounds | Pnm_dangling let desc_precondition_not_met kind proc_name loc = let tags = Tags.create () in - let kind_str = match kind with - | None -> [] - | Some Pnm_bounds -> ["possible array out of bounds"] - | Some Pnm_dangling -> ["possible dangling pointer dereference"] in - { no_desc with descriptions = kind_str @ ["in " ^ call_to_at_line tags proc_name loc]; - tags = !tags } + let kind_str = + match kind with + | None + -> [] + | Some Pnm_bounds + -> ["possible array out of bounds"] + | Some Pnm_dangling + -> ["possible dangling pointer dereference"] + in + { no_desc with + descriptions= kind_str @ [("in " ^ call_to_at_line tags proc_name loc)]; tags= !tags } let desc_null_test_after_dereference expr_str line loc = let tags = Tags.create () in - Tags.update tags Tags.dereferenced_line (string_of_int line); - Tags.update tags Tags.value expr_str; - let description = Format.asprintf - "Pointer %a was dereferenced at line %d and is tested for null %s" - MF.pp_monospaced expr_str - line - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.dereferenced_line (string_of_int line) ; + Tags.update tags Tags.value expr_str ; + let description = + Format.asprintf "Pointer %a was dereferenced at line %d and is tested for null %s" + MF.pp_monospaced expr_str line (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_return_expression_required typ_str loc = let tags = Tags.create () in - Tags.update tags Tags.value typ_str; - let description = Format.sprintf - "Return statement requires an expression of type %s %s" - typ_str - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + Tags.update tags Tags.value typ_str ; + let description = + Format.sprintf "Return statement requires an expression of type %s %s" typ_str + (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_retain_cycle cycle loc cycle_dotty = - Logging.d_strln "Proposition with retain cycle:"; + Logging.d_strln "Proposition with retain cycle:" ; let ct = ref 1 in let tags = Tags.create () in let str_cycle = ref "" in let remove_old s = - match Str.split_delim (Str.regexp_string "&old_") s with - | [_; s'] -> s' - | _ -> s in + match Str.split_delim (Str.regexp_string "&old_") s with [_; s'] -> s' | _ -> s + in let do_edge ((se, _), f, _) = match se with - | Sil.Eexp(Exp.Lvar pvar, _) when Pvar.equal pvar Sil.block_pvar -> - str_cycle:=!str_cycle^" ("^(string_of_int !ct)^") a block capturing " ^ - MF.monospaced_to_string (Typ.Fieldname.to_string f)^"; "; - ct:=!ct +1; - | Sil.Eexp(Exp.Lvar pvar as e, _) -> - let e_str = Exp.to_string e in - let e_str = if Pvar.is_seed pvar then - remove_old e_str - else e_str in - str_cycle:=!str_cycle^" ("^(string_of_int !ct)^") object "^e_str^" retaining " ^ - MF.monospaced_to_string (e_str^"."^(Typ.Fieldname.to_string f))^", "; - ct:=!ct +1 - | Sil.Eexp (Exp.Sizeof {typ}, _) -> - let step = - " (" ^ (string_of_int !ct) ^ ") an object of " ^ - MF.monospaced_to_string (Typ.to_string typ) ^ - " retaining another object via instance variable " ^ - MF.monospaced_to_string (Typ.Fieldname.to_string f) ^ ", " in - str_cycle := !str_cycle ^ step; - ct:=!ct +1 - | _ -> () in - List.iter ~f:do_edge cycle; - let desc = Format.sprintf "Retain cycle involving the following objects: %s %s" - !str_cycle (at_line tags loc) in - { no_desc with descriptions = [desc]; tags = !tags; dotty = cycle_dotty } + | Sil.Eexp (Exp.Lvar pvar, _) when Pvar.equal pvar Sil.block_pvar + -> str_cycle + := !str_cycle ^ " (" ^ string_of_int !ct ^ ") a block capturing " + ^ MF.monospaced_to_string (Typ.Fieldname.to_string f) ^ "; " ; + ct := !ct + 1 + | Sil.Eexp ((Exp.Lvar pvar as e), _) + -> let e_str = Exp.to_string e in + let e_str = if Pvar.is_seed pvar then remove_old e_str else e_str in + str_cycle + := !str_cycle ^ " (" ^ string_of_int !ct ^ ") object " ^ e_str ^ " retaining " + ^ MF.monospaced_to_string (e_str ^ "." ^ Typ.Fieldname.to_string f) ^ ", " ; + ct := !ct + 1 + | Sil.Eexp (Exp.Sizeof {typ}, _) + -> let step = + " (" ^ string_of_int !ct ^ ") an object of " + ^ MF.monospaced_to_string (Typ.to_string typ) + ^ " retaining another object via instance variable " + ^ MF.monospaced_to_string (Typ.Fieldname.to_string f) ^ ", " + in + str_cycle := !str_cycle ^ step ; + ct := !ct + 1 + | _ + -> () + in + List.iter ~f:do_edge cycle ; + let desc = + Format.sprintf "Retain cycle involving the following objects: %s %s" !str_cycle + (at_line tags loc) + in + {no_desc with descriptions= [desc]; tags= !tags; dotty= cycle_dotty} let registered_observer_being_deallocated_str obj_str = - "Object " ^ obj_str ^ " is registered in a notification center but not being removed before deallocation" + "Object " ^ obj_str + ^ " is registered in a notification center but not being removed before deallocation" let desc_registered_observer_being_deallocated pvar loc = let tags = Tags.create () in let obj_str = MF.monospaced_to_string (Pvar.to_string pvar) in - { no_desc with descriptions = [ registered_observer_being_deallocated_str obj_str ^ at_line tags loc ^ - ". Being still registered as observer of the notification " ^ - "center, the deallocated object " - ^ obj_str ^ " may be notified in the future." ]; tags = !tags } + { no_desc with + descriptions= + [ ( registered_observer_being_deallocated_str obj_str ^ at_line tags loc + ^ ". Being still registered as observer of the notification " + ^ "center, the deallocated object " ^ obj_str ^ " may be notified in the future." ) ] + ; tags= !tags } let desc_return_statement_missing loc = let tags = Tags.create () in - { no_desc with descriptions = ["Return statement missing " ^ at_line tags loc]; tags = !tags } + {no_desc with descriptions= [("Return statement missing " ^ at_line tags loc)]; tags= !tags} let desc_return_value_ignored proc_name loc = let tags = Tags.create () in - { no_desc with descriptions = ["after " ^ call_to_at_line tags proc_name loc]; tags = !tags } + {no_desc with descriptions= [("after " ^ call_to_at_line tags proc_name loc)]; tags= !tags} let desc_unary_minus_applied_to_unsigned_expression expr_str_opt typ_str loc = let tags = Tags.create () in - let expression = match expr_str_opt with - | Some s -> - Tags.update tags Tags.value s; - "expression " ^ s - | None -> "an expression" in - let description = Format.asprintf - "A unary minus is applied to %a of type %s %s" - MF.pp_monospaced expression - typ_str - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + let expression = + match expr_str_opt with + | Some s + -> Tags.update tags Tags.value s ; "expression " ^ s + | None + -> "an expression" + in + let description = + Format.asprintf "A unary minus is applied to %a of type %s %s" MF.pp_monospaced expression + typ_str (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} let desc_skip_function proc_name = let tags = Tags.create () in let proc_name_str = Typ.Procname.to_string proc_name in - Tags.update tags Tags.value proc_name_str; - { no_desc with descriptions = [proc_name_str]; tags = !tags } + Tags.update tags Tags.value proc_name_str ; + {no_desc with descriptions= [proc_name_str]; tags= !tags} let desc_inherently_dangerous_function proc_name = let proc_name_str = Typ.Procname.to_string proc_name in let tags = Tags.create () in - Tags.update tags Tags.value proc_name_str; - { no_desc with descriptions = [MF.monospaced_to_string proc_name_str]; tags = !tags } + Tags.update tags Tags.value proc_name_str ; + {no_desc with descriptions= [MF.monospaced_to_string proc_name_str]; tags= !tags} let desc_stack_variable_address_escape expr_str addr_dexp_str loc = let tags = Tags.create () in - Tags.update tags Tags.value expr_str; - let escape_to_str = match addr_dexp_str with - | Some s -> - Tags.update tags Tags.escape_to s; + Tags.update tags Tags.value expr_str ; + let escape_to_str = + match addr_dexp_str with + | Some s + -> Tags.update tags Tags.escape_to s ; "to " ^ s ^ " " - | None -> "" in - let description = Format.asprintf - "Address of stack variable %a escapes %s%s" - MF.pp_monospaced expr_str - escape_to_str - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } - -let desc_tainted_value_reaching_sensitive_function - taint_kind expr_str tainting_fun sensitive_fun loc = - let tags = Tags.create () in - Tags.update tags Tags.value expr_str; + | None + -> "" + in + let description = + Format.asprintf "Address of stack variable %a escapes %s%s" MF.pp_monospaced expr_str + escape_to_str (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} + +let desc_tainted_value_reaching_sensitive_function taint_kind expr_str tainting_fun sensitive_fun + loc = + let tags = Tags.create () in + Tags.update tags Tags.value expr_str ; let description = match taint_kind with - | PredSymb.Tk_unverified_SSL_socket -> - F.asprintf + | PredSymb.Tk_unverified_SSL_socket + -> F.asprintf "The hostname of SSL socket %a (returned from %s) has not been verified! Reading from the socket via the call to %s %s is dangerous. You should verify the hostname of the socket using a HostnameVerifier before reading; otherwise, you may be vulnerable to a man-in-the-middle attack." - MF.pp_monospaced expr_str - (format_method tainting_fun) - (format_method sensitive_fun) + MF.pp_monospaced expr_str (format_method tainting_fun) (format_method sensitive_fun) (at_line tags loc) - | PredSymb.Tk_shared_preferences_data -> - F.asprintf + | PredSymb.Tk_shared_preferences_data + -> F.asprintf "%a holds sensitive data read from a SharedPreferences object (via call to %s). This data may leak via the call to %s %s." - MF.pp_monospaced expr_str - (format_method tainting_fun) - (format_method sensitive_fun) + MF.pp_monospaced expr_str (format_method tainting_fun) (format_method sensitive_fun) (at_line tags loc) - | PredSymb.Tk_privacy_annotation -> - F.asprintf + | PredSymb.Tk_privacy_annotation + -> F.asprintf "%a holds privacy-sensitive data (source: call to %s). This data may leak via the call to %s %s." - MF.pp_monospaced expr_str - (format_method tainting_fun) - (format_method sensitive_fun) + MF.pp_monospaced expr_str (format_method tainting_fun) (format_method sensitive_fun) (at_line tags loc) - | PredSymb.Tk_integrity_annotation -> - F.asprintf + | PredSymb.Tk_integrity_annotation + -> F.asprintf "%a holds untrusted user-controlled data (source: call to %s). This data may flow into a security-sensitive sink via the call to %s %s." - MF.pp_monospaced expr_str - (format_method tainting_fun) - (format_method sensitive_fun) + MF.pp_monospaced expr_str (format_method tainting_fun) (format_method sensitive_fun) (at_line tags loc) - | PredSymb.Tk_unknown -> - F.asprintf + | PredSymb.Tk_unknown + -> F.asprintf "Value %a could be insecure (tainted) due to call to function %s %s %s %s. Function %s %s" - MF.pp_monospaced expr_str - (format_method tainting_fun) - "and is reaching sensitive function" - (format_method sensitive_fun) - (at_line tags loc) - (format_method sensitive_fun) - "requires its input to be verified or sanitized." in - { no_desc with descriptions = [description]; tags = !tags } + MF.pp_monospaced expr_str (format_method tainting_fun) + "and is reaching sensitive function" (format_method sensitive_fun) (at_line tags loc) + (format_method sensitive_fun) "requires its input to be verified or sanitized." + in + {no_desc with descriptions= [description]; tags= !tags} let desc_uninitialized_dangling_pointer_deref deref expr_str loc = let tags = Tags.create () in - Tags.update tags Tags.value expr_str; - let prefix = match deref.value_pre with - | Some s -> s - | _ -> "" in + Tags.update tags Tags.value expr_str ; + let prefix = match deref.value_pre with Some s -> s | _ -> "" in let description = - Format.asprintf - "%s %a %s %s" - prefix - MF.pp_monospaced expr_str - deref.problem_str - (at_line tags loc) in - { no_desc with descriptions = [description]; tags = !tags } + Format.asprintf "%s %a %s %s" prefix MF.pp_monospaced expr_str deref.problem_str + (at_line tags loc) + in + {no_desc with descriptions= [description]; tags= !tags} diff --git a/infer/src/IR/Localise.mli b/infer/src/IR/Localise.mli index da8171c65..5282b516d 100644 --- a/infer/src/IR/Localise.mli +++ b/infer/src/IR/Localise.mli @@ -17,166 +17,244 @@ type t [@@deriving compare] val equal : t -> t -> bool -(** pretty print a localised string *) val pp : Format.formatter -> t -> unit +(** pretty print a localised string *) -(** create from an ordinary string *) val from_string : ?hum:string -> string -> t +(** create from an ordinary string *) -(** return the id of an issue *) val to_issue_id : t -> string +(** return the id of an issue *) -(** return the human-readable name of an issue *) val to_human_readable_string : t -> string +(** return the human-readable name of an issue *) val analysis_stops : t + val array_out_of_bounds_l1 : t + val array_out_of_bounds_l2 : t + val array_out_of_bounds_l3 : t + val buffer_overrun : t + val checkers_access_global : t + val checkers_immutable_cast : t + val checkers_print_c_call : t + val checkers_print_objc_method_calls : t + val checkers_printf_args : t + val checkers_repeated_calls : t + val checkers_trace_calls_sequence : t + val class_cast_exception : t + val cluster_callback : t + val comparing_floats_for_equality : t + val condition_always_false : t + val condition_always_true : t + val condition_is_assignment : t + val context_leak : t + val dangling_pointer_dereference : t + val deallocate_stack_variable : t + val deallocate_static_memory : t + val deallocation_mismatch : t + val divide_by_zero : t + val double_lock : t + val empty_vector_access : t + val eradicate_condition_redundant : t + val eradicate_condition_redundant_nonnull : t + val eradicate_field_not_initialized : t + val eradicate_field_not_mutable : t + val eradicate_field_not_nullable : t + val eradicate_field_over_annotated : t + val eradicate_field_value_absent : t + val eradicate_inconsistent_subclass_parameter_annotation : t + val eradicate_inconsistent_subclass_return_annotation : t + val eradicate_null_field_access : t + val eradicate_null_method_call : t + val eradicate_parameter_not_nullable : t + val eradicate_parameter_value_absent : t + val eradicate_return_not_nullable : t + val eradicate_return_over_annotated : t + val eradicate_return_value_not_present : t + val eradicate_value_not_present : t + val field_should_be_nullable : t + val field_not_null_checked : t + val inherently_dangerous_function : t + val memory_leak : t + val null_dereference : t + val null_test_after_dereference : t + val parameter_not_null_checked : t + val pointer_size_mismatch : t + val precondition_not_found : t + val precondition_not_met : t + val premature_nil_termination : t + val proc_callback : t + val quandary_taint_error : t + val registered_observer_being_deallocated : t + val resource_leak : t + val retain_cycle : t + val return_expression_required : t + val return_statement_missing : t + val return_value_ignored : t + val skip_function : t + val skip_pointer_dereference : t + val stack_variable_address_escape : t + val static_initialization_order_fiasco : t + val tainted_value_reaching_sensitive_function : t + val thread_safety_violation : t + val unary_minus_applied_to_unsigned_expression : t + val uninitialized_value : t + val unreachable_code_after : t + val unsafe_guarded_by_access : t + val use_after_free : t module Tags : sig type t + val tag_value_records_of_tags : t -> Jsonbug_t.tag_value_record list (** convert error description's tags to atd-serializable format *) - val tag_value_records_of_tags: t -> Jsonbug_t.tag_value_record list (* convert atd-serializable format to error description's tags *) - val tags_of_tag_value_records: Jsonbug_t.tag_value_record list -> t + + val tags_of_tag_value_records : Jsonbug_t.tag_value_record list -> t (* collect all lines from tags *) - val lines_of_tags: t -> int list + + val lines_of_tags : t -> int list end (** description field of error messages *) -type error_desc = { - descriptions : string list; - advice : string option; - tags : Tags.t; - dotty : string option; -} [@@deriving compare] +type error_desc = + {descriptions: string list; advice: string option; tags: Tags.t; dotty: string option} + [@@deriving compare] +val no_desc : error_desc (** empty error description *) -val no_desc: error_desc -(** verbatim desc from a string, not to be used for user-visible descs *) val verbatim_desc : string -> error_desc +(** verbatim desc from a string, not to be used for user-visible descs *) -(** verbatim desc with custom tags *) val custom_desc : string -> (string * string) list -> error_desc +(** verbatim desc with custom tags *) -(** verbatim desc with advice and custom tags *) val custom_desc_with_advice : string -> string -> (string * string) list -> error_desc +(** verbatim desc with advice and custom tags *) module BucketLevel : sig - val b1 : string (* highest likelyhood *) + val b1 : string + + (* highest likelyhood *) + val b2 : string + val b3 : string + val b4 : string - val b5 : string (* lowest likelyhood *) + + val b5 : string + (* lowest likelyhood *) end -(** returns the value of a tag or the empty string *) val error_desc_extract_tag_value : error_desc -> string -> string +(** returns the value of a tag or the empty string *) -(** returns all the tuples (tag, value) of an error_desc *) val error_desc_to_tag_value_pairs : error_desc -> (string * string) list +(** returns all the tuples (tag, value) of an error_desc *) -(** returns the content of the value tag of the error_desc *) val error_desc_get_tag_value : error_desc -> string +(** returns the content of the value tag of the error_desc *) -(** returns the content of the call_procedure tag of the error_desc *) val error_desc_get_tag_call_procedure : error_desc -> string +(** returns the content of the call_procedure tag of the error_desc *) -(** get the bucket value of an error_desc, if any *) val error_desc_get_bucket : error_desc -> string option +(** get the bucket value of an error_desc, if any *) +val error_desc_set_bucket : error_desc -> string -> bool -> error_desc (** set the bucket value of an error_desc. The boolean indicates where the bucket should be shown in the message *) -val error_desc_set_bucket : error_desc -> string -> bool -> error_desc -(** hash function for error_desc *) val error_desc_hash : error_desc -> int +(** hash function for error_desc *) -(** equality for error_desc *) val error_desc_equal : error_desc -> error_desc -> bool +(** equality for error_desc *) -(** pretty print an error description *) val pp_error_desc : Format.formatter -> error_desc -> unit +(** pretty print an error description *) -(** pretty print an error advice *) val pp_error_advice : Format.formatter -> error_desc -> unit +(** pretty print an error advice *) -(** get tags of error description *) val error_desc_get_tags : error_desc -> (string * string) list +(** get tags of error description *) val error_desc_get_dotty : error_desc -> string option @@ -185,40 +263,42 @@ val error_desc_get_dotty : error_desc -> string option (** dereference strings used to explain a dereference action in an error message *) type deref_str -(** dereference strings for null dereference *) val deref_str_null : Typ.Procname.t option -> deref_str +(** dereference strings for null dereference *) -(** dereference strings for null dereference due to Nullable annotation *) val deref_str_nullable : Typ.Procname.t option -> string -> deref_str +(** dereference strings for null dereference due to Nullable annotation *) -(** dereference strings for null dereference due to weak captured variable in block *) val deref_str_weak_variable_in_block : Typ.Procname.t option -> string -> deref_str +(** dereference strings for null dereference due to weak captured variable in block *) -(** dereference strings for an undefined value coming from the given procedure *) val deref_str_undef : Typ.Procname.t * Location.t -> deref_str +(** dereference strings for an undefined value coming from the given procedure *) -(** dereference strings for a freed pointer dereference *) val deref_str_freed : PredSymb.res_action -> deref_str +(** dereference strings for a freed pointer dereference *) -(** dereference strings for a dangling pointer dereference *) val deref_str_dangling : PredSymb.dangling_kind option -> deref_str +(** dereference strings for a dangling pointer dereference *) -(** dereference strings for an array out of bound access *) val deref_str_array_bound : IntLit.t option -> IntLit.t option -> deref_str +(** dereference strings for an array out of bound access *) -(** dereference strings for an uninitialized access whose lhs has the given attribute *) val deref_str_uninitialized : Sil.atom option -> deref_str +(** dereference strings for an uninitialized access whose lhs has the given attribute *) -(** dereference strings for nonterminal nil arguments in c/objc variadic methods *) val deref_str_nil_argument_in_variadic_method : Typ.Procname.t -> int -> int -> deref_str +(** dereference strings for nonterminal nil arguments in c/objc variadic methods *) -(** dereference strings for a pointer size mismatch *) val deref_str_pointer_size_mismatch : Typ.t -> Typ.t -> deref_str +(** dereference strings for a pointer size mismatch *) (** type of access *) type access = - | Last_assigned of int * bool (* line, null_case_flag *) - | Last_accessed of int * bool (* line, is_nullable flag *) + | Last_assigned of int * bool + (* line, null_case_flag *) + | Last_accessed of int * bool + (* line, is_nullable flag *) | Initialized_automatically | Returned_from_call of int @@ -233,7 +313,8 @@ val is_field_not_null_checked_desc : error_desc -> bool val is_parameter_field_not_null_checked_desc : error_desc -> bool val desc_allocation_mismatch : - Typ.Procname.t * Typ.Procname.t * Location.t -> Typ.Procname.t * Typ.Procname.t * Location.t -> error_desc + Typ.Procname.t * Typ.Procname.t * Location.t -> Typ.Procname.t * Typ.Procname.t * Location.t + -> error_desc val desc_class_cast_exception : Typ.Procname.t option -> string -> string -> string option -> Location.t -> error_desc @@ -263,8 +344,8 @@ val is_empty_vector_access_desc : error_desc -> bool val desc_frontend_warning : string -> string option -> Location.t -> error_desc val desc_leak : - Exp.t option -> string option -> PredSymb.resource option -> PredSymb.res_action option -> - Location.t -> string option -> error_desc + Exp.t option -> string option -> PredSymb.resource option -> PredSymb.res_action option + -> Location.t -> string option -> error_desc val desc_buffer_overrun : string -> string -> error_desc @@ -273,27 +354,24 @@ val desc_null_test_after_dereference : string -> int -> Location.t -> error_desc val java_unchecked_exn_desc : Typ.Procname.t -> Typ.Name.t -> string -> error_desc val desc_context_leak : - Typ.Procname.t -> Typ.t -> Typ.Fieldname.t -> - (Typ.Fieldname.t option * Typ.t) list -> error_desc + Typ.Procname.t -> Typ.t -> Typ.Fieldname.t -> (Typ.Fieldname.t option * Typ.t) list -> error_desc -val desc_fragment_retains_view : - Typ.t -> Typ.Fieldname.t -> Typ.t -> Typ.Procname.t -> error_desc +val desc_fragment_retains_view : Typ.t -> Typ.Fieldname.t -> Typ.t -> Typ.Procname.t -> error_desc (* Create human-readable error description for assertion failures *) + val desc_custom_error : Location.t -> error_desc (** kind of precondition not met *) -type pnm_kind = - | Pnm_bounds - | Pnm_dangling +type pnm_kind = Pnm_bounds | Pnm_dangling val desc_precondition_not_met : pnm_kind option -> Typ.Procname.t -> Location.t -> error_desc val desc_return_expression_required : string -> Location.t -> error_desc val desc_retain_cycle : - ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> - Location.t -> string option -> error_desc + ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> Location.t -> string option + -> error_desc val registered_observer_being_deallocated_str : string -> string diff --git a/infer/src/IR/Location.ml b/infer/src/IR/Location.ml new file mode 100644 index 000000000..634a00081 --- /dev/null +++ b/infer/src/IR/Location.ml @@ -0,0 +1,40 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module F = Format +module L = Logging + +(** Location in the original source file *) +type t = + { line: int (** The line number. -1 means "do not know" *) + ; col: int (** The column number. -1 means "do not know" *) + ; file: SourceFile.t (** The name of the source file *) } + [@@deriving compare] + +let equal = [%compare.equal : t] + +(** Dump a location *) +let d (loc: t) = L.add_print_action (L.PTloc, Obj.repr loc) + +let none file = {line= -1; col= -1; file} + +let dummy = none (SourceFile.invalid __FILE__) + +(** Pretty print a location *) +let pp f (loc: t) = F.fprintf f "[line %d]" loc.line + +let to_string loc = + let s = string_of_int loc.line in + if loc.col <> -1 then s ^ ":" ^ string_of_int loc.col else s + +(** Pretty print a file-position of a location *) +let pp_file_pos f (loc: t) = + let fname = SourceFile.to_string loc.file in + let pos = to_string loc in + F.fprintf f "%s:%s" fname pos diff --git a/infer/src/IR/Location.mli b/infer/src/IR/Location.mli new file mode 100644 index 000000000..ca4da07e1 --- /dev/null +++ b/infer/src/IR/Location.mli @@ -0,0 +1,44 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Location in the original source file *) + +type t = + { line: int (** The line number. -1 means "do not know" *) + ; col: int (** The column number. -1 means "do not know" *) + ; file: SourceFile.t (** The name of the source file *) } + [@@deriving compare] + +val equal : t -> t -> bool + +(** Dump a location. *) + +val d : t -> unit + +(** Dummy source location for the given file *) + +val none : SourceFile.t -> t + +(** Dummy location with no source file *) + +val dummy : t + +(** Pretty print a location. *) + +val pp : Format.formatter -> t -> unit + +(** String representation of a location. *) + +val to_string : t -> string + +(** Pretty print a file-position of a location *) + +val pp_file_pos : Format.formatter -> t -> unit diff --git a/infer/src/IR/Location.re b/infer/src/IR/Location.re deleted file mode 100644 index 158232b82..000000000 --- a/infer/src/IR/Location.re +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module F = Format; - -module L = Logging; - - -/** Location in the original source file */ -type t = { - line: int, /** The line number. -1 means "do not know" */ - col: int, /** The column number. -1 means "do not know" */ - file: SourceFile.t /** The name of the source file */ -} -[@@deriving compare]; - -let equal = [%compare.equal : t]; - - -/** Dump a location */ -let d (loc: t) => L.add_print_action (L.PTloc, Obj.repr loc); - -let none file => {line: (-1), col: (-1), file}; - -let dummy = none (SourceFile.invalid __FILE__); - - -/** Pretty print a location */ -let pp f (loc: t) => F.fprintf f "[line %d]" loc.line; - -let to_string loc => { - let s = string_of_int loc.line; - if (loc.col != (-1)) { - s ^ ":" ^ string_of_int loc.col - } else { - s - } -}; - - -/** Pretty print a file-position of a location */ -let pp_file_pos f (loc: t) => { - let fname = SourceFile.to_string loc.file; - let pos = to_string loc; - F.fprintf f "%s:%s" fname pos -}; diff --git a/infer/src/IR/Location.rei b/infer/src/IR/Location.rei deleted file mode 100644 index bff4787b6..000000000 --- a/infer/src/IR/Location.rei +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Location in the original source file */ -type t = { - line: int, /** The line number. -1 means "do not know" */ - col: int, /** The column number. -1 means "do not know" */ - file: SourceFile.t /** The name of the source file */ -} -[@@deriving compare]; - -let equal: t => t => bool; - - -/** Dump a location. */ -let d: t => unit; - - -/** Dummy source location for the given file */ -let none: SourceFile.t => t; - - -/** Dummy location with no source file */ -let dummy: t; - - -/** Pretty print a location. */ -let pp: Format.formatter => t => unit; - - -/** String representation of a location. */ -let to_string: t => string; - - -/** Pretty print a file-position of a location */ -let pp_file_pos: Format.formatter => t => unit; diff --git a/infer/src/IR/Mangled.ml b/infer/src/IR/Mangled.ml new file mode 100644 index 000000000..d060c484e --- /dev/null +++ b/infer/src/IR/Mangled.ml @@ -0,0 +1,48 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Module for Mangled Names *) +open! IStd +module F = Format + +type t = {plain: string; mangled: string option} [@@deriving compare] + +let equal = [%compare.equal : t] + +(** Convert a string to a mangled name *) +let from_string (s: string) = {plain= s; mangled= None} + +(** Create a mangled name from a plain and mangled string *) +let mangled (plain: string) (mangled: string) = {plain; mangled= Some (plain ^ "{" ^ mangled ^ "}")} + +(** Convert a mangled name to a string *) +let to_string (pn: t) = pn.plain + +(** Convert a full mangled name to a string *) +let to_string_full (pn: t) = + match pn.mangled with Some mangled -> pn.plain ^ "{" ^ mangled ^ "}" | None -> pn.plain + +(** Get mangled string if given *) +let get_mangled pn = match pn.mangled with Some s -> s | None -> pn.plain + +(** Pretty print a mangled name *) +let pp f pn = F.fprintf f "%s" (to_string pn) + +module Set = Caml.Set.Make (struct + type nonrec t = t + + let compare = compare +end) + +module Map = Caml.Map.Make (struct + type nonrec t = t + + let compare = compare +end) diff --git a/infer/src/IR/Mangled.mli b/infer/src/IR/Mangled.mli new file mode 100644 index 000000000..314290b9f --- /dev/null +++ b/infer/src/IR/Mangled.mli @@ -0,0 +1,53 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Module for Mangled Names *) + +(** Type of mangled names *) + +type t [@@deriving compare] + +(** Equality for mangled names *) + +val equal : t -> t -> bool + +(** Convert a string to a mangled name *) + +val from_string : string -> t + +(** Create a mangled name from a plain and mangled string *) + +val mangled : string -> string -> t + +(** Convert a mangled name to a string *) + +val to_string : t -> string + +(** Convert a full mangled name to a string *) + +val to_string_full : t -> string + +(** Get mangled string if given *) + +val get_mangled : t -> string + +(** Pretty print a mangled name *) + +val pp : Format.formatter -> t -> unit + +(** Set of Mangled. *) + +module Set : Caml.Set.S with type elt = t + +(** Map with Mangled as key *) + +module Map : Caml.Map.S with type key = t diff --git a/infer/src/IR/Mangled.re b/infer/src/IR/Mangled.re deleted file mode 100644 index 7b2363156..000000000 --- a/infer/src/IR/Mangled.re +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Module for Mangled Names */ -module F = Format; - -type t = {plain: string, mangled: option string} [@@deriving compare]; - -let equal = [%compare.equal : t]; - - -/** Convert a string to a mangled name */ -let from_string (s: string) => {plain: s, mangled: None}; - - -/** Create a mangled name from a plain and mangled string */ -let mangled (plain: string) (mangled: string) => { - plain, - mangled: Some (plain ^ "{" ^ mangled ^ "}") -}; - - -/** Convert a mangled name to a string */ -let to_string (pn: t) => pn.plain; - - -/** Convert a full mangled name to a string */ -let to_string_full (pn: t) => - switch pn.mangled { - | Some mangled => pn.plain ^ "{" ^ mangled ^ "}" - | None => pn.plain - }; - - -/** Get mangled string if given */ -let get_mangled pn => - switch pn.mangled { - | Some s => s - | None => pn.plain - }; - - -/** Pretty print a mangled name */ -let pp f pn => F.fprintf f "%s" (to_string pn); - -module Set = - Caml.Set.Make { - type nonrec t = t; - let compare = compare; - }; - -module Map = - Caml.Map.Make { - type nonrec t = t; - let compare = compare; - }; diff --git a/infer/src/IR/Mangled.rei b/infer/src/IR/Mangled.rei deleted file mode 100644 index 1d0659286..000000000 --- a/infer/src/IR/Mangled.rei +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Module for Mangled Names */ - -/** Type of mangled names */ -type t [@@deriving compare]; - - -/** Equality for mangled names */ -let equal: t => t => bool; - - -/** Convert a string to a mangled name */ -let from_string: string => t; - - -/** Create a mangled name from a plain and mangled string */ -let mangled: string => string => t; - - -/** Convert a mangled name to a string */ -let to_string: t => string; - - -/** Convert a full mangled name to a string */ -let to_string_full: t => string; - - -/** Get mangled string if given */ -let get_mangled: t => string; - - -/** Pretty print a mangled name */ -let pp: Format.formatter => t => unit; - - -/** Set of Mangled. */ -module Set: Caml.Set.S with type elt = t; - - -/** Map with Mangled as key */ -module Map: Caml.Map.S with type key = t; diff --git a/infer/src/IR/Mleak_buckets.ml b/infer/src/IR/Mleak_buckets.ml index 0278686a8..8865e89fa 100644 --- a/infer/src/IR/Mleak_buckets.ml +++ b/infer/src/IR/Mleak_buckets.ml @@ -17,57 +17,42 @@ let objc_arc_flag = "objc_arc" let bucket_to_message bucket = match bucket with - | `MLeak_cf -> "[CF]" - | `MLeak_arc -> "[ARC]" - | `MLeak_no_arc -> "[NO ARC]" - | `MLeak_cpp -> "[CPP]" - | `MLeak_unknown -> "[UNKNOWN ORIGIN]" + | `MLeak_cf + -> "[CF]" + | `MLeak_arc + -> "[ARC]" + | `MLeak_no_arc + -> "[NO ARC]" + | `MLeak_cpp + -> "[CPP]" + | `MLeak_unknown + -> "[UNKNOWN ORIGIN]" -let contains_all = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_all +let contains_all = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_all -let contains_cf = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_cf +let contains_cf = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_cf -let contains_arc = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_arc +let contains_arc = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_arc -let contains_narc = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_no_arc +let contains_narc = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_no_arc -let contains_cpp = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_cpp +let contains_cpp = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_cpp -let contains_unknown_origin = - List.mem ~equal:PVariant.(=) Config.ml_buckets `MLeak_unknown +let contains_unknown_origin = List.mem ~equal:PVariant.( = ) Config.ml_buckets `MLeak_unknown -let should_raise_leak_cf typ = - if contains_cf then - Objc_models.is_core_lib_type typ - else false +let should_raise_leak_cf typ = if contains_cf then Objc_models.is_core_lib_type typ else false -let should_raise_leak_arc () = - if contains_arc then - !Config.arc_mode - else false +let should_raise_leak_arc () = if contains_arc then !Config.arc_mode else false -let should_raise_leak_no_arc () = - if contains_narc then - not (!Config.arc_mode) - else false +let should_raise_leak_no_arc () = if contains_narc then not !Config.arc_mode else false -let should_raise_leak_unknown_origin = - contains_unknown_origin +let should_raise_leak_unknown_origin = contains_unknown_origin -let ml_bucket_unknown_origin = - bucket_to_message `MLeak_unknown +let ml_bucket_unknown_origin = bucket_to_message `MLeak_unknown (* Returns whether a memory leak should be raised for a C++ object.*) (* If ml_buckets contains cpp, then check leaks from C++ objects. *) -let should_raise_cpp_leak = - if contains_cpp then - Some (bucket_to_message `MLeak_cpp) - else None +let should_raise_cpp_leak = if contains_cpp then Some (bucket_to_message `MLeak_cpp) else None (* Returns whether a memory leak should be raised. *) (* If cf is passed, then check leaks from Core Foundation. *) diff --git a/infer/src/IR/Mleak_buckets.mli b/infer/src/IR/Mleak_buckets.mli index 08a9bda0e..77474084c 100644 --- a/infer/src/IR/Mleak_buckets.mli +++ b/infer/src/IR/Mleak_buckets.mli @@ -17,10 +17,12 @@ val objc_arc_flag : string (* If cf is passed, then check leaks from Core Foundation. *) (* If arc is passed, check leaks from code that compiles with arc*) (* If no arc is passed check the leaks from code that compiles without arc *) + val should_raise_objc_leak : Typ.t -> string option (* Returns whether a memory leak should be raised for a C++ object.*) (* If ml_buckets contains cpp, then check leaks from C++ objects. *) + val should_raise_cpp_leak : string option val should_raise_leak_unknown_origin : bool diff --git a/infer/src/IR/Objc_models.ml b/infer/src/IR/Objc_models.ml index 4ea224ab0..8cf2463c1 100644 --- a/infer/src/IR/Objc_models.ml +++ b/infer/src/IR/Objc_models.ml @@ -14,168 +14,144 @@ open! IStd (** This module models special c struct types from the Apple's Core Foundation libraries for which there are particular rules for memory management. *) -module Core_foundation_model = -struct - - let core_foundation = [ - "__CFArray"; - "__CFAttributedString"; - "__CFBag"; - "__CFNull"; - "__CFAllocator"; - "__CFBinaryHeap"; - "__CFBitVector"; - "__CFBundle"; - "__CFCalendar"; - "__CFCharacterSet"; - "__CFDate"; - "__CFDateFormatter"; - "__CFDictionary"; - "__CFError"; - "__CFFileDescriptor"; - "__CFFileSecurity"; - "__CFLocale"; - "__CFMachPort"; - "__CFMessagePort"; - "__CFNotificationCenter"; - "__CFBoolean"; - "__CFNumber"; - "__CFNumberFormatter"; - "__CFPlugInInstance"; - "__CFReadStream"; - "__CFWriteStream"; - "__CFRunLoop"; - "__CFRunLoopSource"; - "__CFRunLoopObserver"; - "__CFRunLoopTimer"; - "__CFSet"; - "__CFStringTokenizer"; - "__CFSocket"; - "__CFReadStream"; - "__CFWriteStream"; - "__CFTimeZone"; - "__CFTree"; - "__CFURLEnumerator"; - "__CFUUID" - ] - - let cf_network = [ - "_CFHTTPAuthentication"; - "__CFHTTPMessage"; - "__CFHost"; - "__CFNetDiagnostic"; - "__CFNetService"; - "__CFNetServiceMonitor"; - "__CFNetServiceBrowser" - ] - - let core_media = [ - "OpaqueCMBlockBuffer"; - "opaqueCMBufferQueue"; - "opaqueCMBufferQueueTriggerToken"; - "opaqueCMFormatDescription"; - "OpaqueCMMemoryPool"; - "opaqueCMSampleBuffer"; - "opaqueCMSimpleQueue"; - "OpaqueCMClock"; - "OpaqueCMTimebase" - ] - - let core_text = [ - "__CTFont"; - "__CTFontCollection"; - "__CTFontDescriptor"; - "__CTFrame"; - "__CTFramesetter"; - "__CTGlyphInfo"; - "__CTLine"; - "__CTParagraphStyle"; - "__CTRubyAnnotation"; - "__CTRun"; - "__CTRunDelegate"; - "__CTTextTab"; - "__CTTypesetter" - ] - - let core_video = [ - "__CVBuffer"; - "__CVMetalTextureCache"; - "__CVOpenGLESTextureCache"; - "__CVPixelBufferPool" - ] - - let image_io = [ - "CGImageDestination"; - "CGImageMetadata"; - "CGImageMetadataTag"; - "CGImageSource" - ] - - let security = [ - "__SecCertificate"; - "__SecIdentity"; - "__SecKey"; - "__SecPolicy"; - "__SecAccessControl"; - "__SecRandom"; - "__SecCode"; - "__SecTrust"; - "__SecRequirement" - ] - - let system_configuration = [ - "__SCDynamicStore"; - "__SCNetworkInterface"; - "__SCBondStatus"; - "__SCNetworkProtocol"; - "__SCNetworkService"; - "__SCNetworkSet"; - "__SCNetworkConnection"; - "__SCNetworkReachability"; - "__SCPreferences" - ] - - let core_graphics_types = [ - "CGAffineTransform"; - "CGBase"; - "CGBitmapContext"; - "CGColor"; - "CGColorSpace"; - "CGContext"; - "CGDataConsumer"; - "CGDataProvider"; - "CGError"; - "CGFont"; - "CGFunction"; - "CGGeometry"; - "CGGradient"; - "CGImage"; - "CGLayer"; - "CGPath"; - "CGPattern"; - "CGPDFArray"; - "CGPDFContentStream"; - "CGPDFContext"; - "CGPDFDictionary"; - "CGPDFDocument"; - "CGPDFObject"; - "CGPDFOperatorTable"; - "CGPDFPage"; - "CGPDFScanner"; - "CGPDFStream"; - "CGPDFString"; - "CGShading" - ] +module Core_foundation_model = struct + let core_foundation = + [ "__CFArray" + ; "__CFAttributedString" + ; "__CFBag" + ; "__CFNull" + ; "__CFAllocator" + ; "__CFBinaryHeap" + ; "__CFBitVector" + ; "__CFBundle" + ; "__CFCalendar" + ; "__CFCharacterSet" + ; "__CFDate" + ; "__CFDateFormatter" + ; "__CFDictionary" + ; "__CFError" + ; "__CFFileDescriptor" + ; "__CFFileSecurity" + ; "__CFLocale" + ; "__CFMachPort" + ; "__CFMessagePort" + ; "__CFNotificationCenter" + ; "__CFBoolean" + ; "__CFNumber" + ; "__CFNumberFormatter" + ; "__CFPlugInInstance" + ; "__CFReadStream" + ; "__CFWriteStream" + ; "__CFRunLoop" + ; "__CFRunLoopSource" + ; "__CFRunLoopObserver" + ; "__CFRunLoopTimer" + ; "__CFSet" + ; "__CFStringTokenizer" + ; "__CFSocket" + ; "__CFReadStream" + ; "__CFWriteStream" + ; "__CFTimeZone" + ; "__CFTree" + ; "__CFURLEnumerator" + ; "__CFUUID" ] + + let cf_network = + [ "_CFHTTPAuthentication" + ; "__CFHTTPMessage" + ; "__CFHost" + ; "__CFNetDiagnostic" + ; "__CFNetService" + ; "__CFNetServiceMonitor" + ; "__CFNetServiceBrowser" ] + + let core_media = + [ "OpaqueCMBlockBuffer" + ; "opaqueCMBufferQueue" + ; "opaqueCMBufferQueueTriggerToken" + ; "opaqueCMFormatDescription" + ; "OpaqueCMMemoryPool" + ; "opaqueCMSampleBuffer" + ; "opaqueCMSimpleQueue" + ; "OpaqueCMClock" + ; "OpaqueCMTimebase" ] + + let core_text = + [ "__CTFont" + ; "__CTFontCollection" + ; "__CTFontDescriptor" + ; "__CTFrame" + ; "__CTFramesetter" + ; "__CTGlyphInfo" + ; "__CTLine" + ; "__CTParagraphStyle" + ; "__CTRubyAnnotation" + ; "__CTRun" + ; "__CTRunDelegate" + ; "__CTTextTab" + ; "__CTTypesetter" ] + + let core_video = + ["__CVBuffer"; "__CVMetalTextureCache"; "__CVOpenGLESTextureCache"; "__CVPixelBufferPool"] + + let image_io = ["CGImageDestination"; "CGImageMetadata"; "CGImageMetadataTag"; "CGImageSource"] + + let security = + [ "__SecCertificate" + ; "__SecIdentity" + ; "__SecKey" + ; "__SecPolicy" + ; "__SecAccessControl" + ; "__SecRandom" + ; "__SecCode" + ; "__SecTrust" + ; "__SecRequirement" ] + + let system_configuration = + [ "__SCDynamicStore" + ; "__SCNetworkInterface" + ; "__SCBondStatus" + ; "__SCNetworkProtocol" + ; "__SCNetworkService" + ; "__SCNetworkSet" + ; "__SCNetworkConnection" + ; "__SCNetworkReachability" + ; "__SCPreferences" ] + + let core_graphics_types = + [ "CGAffineTransform" + ; "CGBase" + ; "CGBitmapContext" + ; "CGColor" + ; "CGColorSpace" + ; "CGContext" + ; "CGDataConsumer" + ; "CGDataProvider" + ; "CGError" + ; "CGFont" + ; "CGFunction" + ; "CGGeometry" + ; "CGGradient" + ; "CGImage" + ; "CGLayer" + ; "CGPath" + ; "CGPattern" + ; "CGPDFArray" + ; "CGPDFContentStream" + ; "CGPDFContext" + ; "CGPDFDictionary" + ; "CGPDFDocument" + ; "CGPDFObject" + ; "CGPDFOperatorTable" + ; "CGPDFPage" + ; "CGPDFScanner" + ; "CGPDFStream" + ; "CGPDFString" + ; "CGShading" ] let core_foundation_types = - core_foundation @ - cf_network @ - core_media @ - core_text @ - core_video @ - image_io @ - security @ - system_configuration + core_foundation @ cf_network @ core_media @ core_text @ core_video @ image_io @ security + @ system_configuration let copy = "Copy" @@ -191,64 +167,56 @@ struct let cf_type = "CFTypeRef" - type core_lib = - | Core_foundation - | Core_graphics + type core_lib = Core_foundation | Core_graphics let core_lib_to_type_list lib = match lib with - | Core_foundation -> core_foundation_types - | Core_graphics -> core_graphics_types + | Core_foundation + -> core_foundation_types + | Core_graphics + -> core_graphics_types let is_objc_memory_model_controlled o = - List.mem ~equal:String.equal core_foundation_types o || - List.mem ~equal:String.equal core_graphics_types o + List.mem ~equal:String.equal core_foundation_types o + || List.mem ~equal:String.equal core_graphics_types o let rec is_core_lib lib typ = match typ.Typ.desc with - | Typ.Tptr (styp, _ ) -> - is_core_lib lib styp - | Typ.Tstruct name -> - let core_lib_types = core_lib_to_type_list lib in + | Typ.Tptr (styp, _) + -> is_core_lib lib styp + | Typ.Tstruct name + -> let core_lib_types = core_lib_to_type_list lib in List.mem ~equal:String.equal core_lib_types (Typ.Name.name name) - | _ -> false + | _ + -> false - let is_core_foundation_type typ = - is_core_lib Core_foundation typ + let is_core_foundation_type typ = is_core_lib Core_foundation typ - let is_core_graphics_type typ = - is_core_lib Core_graphics typ + let is_core_graphics_type typ = is_core_lib Core_graphics typ - let is_core_lib_type typ = - is_core_foundation_type typ || - is_core_graphics_type typ + let is_core_lib_type typ = is_core_foundation_type typ || is_core_graphics_type typ let is_core_lib_create typ funct = - is_core_lib_type typ && - ((String.is_substring ~substring:create funct) || - (String.is_substring ~substring:copy funct )) + is_core_lib_type typ + && (String.is_substring ~substring:create funct || String.is_substring ~substring:copy funct) - let function_arg_is_cftype typ = - (String.is_substring ~substring:cf_type typ) + let function_arg_is_cftype typ = String.is_substring ~substring:cf_type typ - let is_core_lib_retain typ funct = - function_arg_is_cftype typ && String.equal funct cf_retain + let is_core_lib_retain typ funct = function_arg_is_cftype typ && String.equal funct cf_retain - let is_core_lib_release typ funct = - function_arg_is_cftype typ && String.equal funct cf_release + let is_core_lib_release typ funct = function_arg_is_cftype typ && String.equal funct cf_release let is_core_graphics_release typ funct = let f lib = - String.equal funct (lib ^ upper_release) && - String.is_substring ~substring:(lib ^ ref) typ in + String.equal funct (lib ^ upper_release) && String.is_substring ~substring:(lib ^ ref) typ + in List.exists ~f core_graphics_types -(* + (* let function_arg_is_core_pgraphics typ = let res = (String.is_substring ~substring:cf_type typ) in res *) end -let is_core_lib_type typ = - Core_foundation_model.is_core_lib_type typ +let is_core_lib_type typ = Core_foundation_model.is_core_lib_type typ diff --git a/infer/src/IR/Objc_models.mli b/infer/src/IR/Objc_models.mli index bf4025f81..c4b206319 100644 --- a/infer/src/IR/Objc_models.mli +++ b/infer/src/IR/Objc_models.mli @@ -15,9 +15,7 @@ open! IStd (** This module models special c struct types from the Apple's Core Foundation libraries for which there are particular rules for memory management. *) -module Core_foundation_model : -sig - +module Core_foundation_model : sig val is_core_lib_release : string -> string -> bool val is_core_lib_create : Typ.t -> string -> bool @@ -27,8 +25,6 @@ sig val is_core_graphics_release : string -> string -> bool val is_objc_memory_model_controlled : string -> bool - - end val is_core_lib_type : Typ.t -> bool diff --git a/infer/src/IR/PredSymb.ml b/infer/src/IR/PredSymb.ml new file mode 100644 index 000000000..83efb837c --- /dev/null +++ b/infer/src/IR/PredSymb.ml @@ -0,0 +1,259 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Predicate Symbols *) +open! IStd +module L = Logging +module F = Format + +type func_attribute = + | FA_sentinel of int * int (** __attribute__((sentinel(int, int))) *) + [@@deriving compare] + +(** Visibility modifiers. *) +type access = Default | Public | Private | Protected [@@deriving compare] + +let equal_access = [%compare.equal : access] + +(** Return the value of the FA_sentinel attribute in [attr_list] if it is found *) +let get_sentinel_func_attribute_value attr_list = + match attr_list with + | (FA_sentinel (sentinel, null_pos)) :: _ + -> Some (sentinel, null_pos) + | [] + -> None + +type mem_kind = + | Mmalloc (** memory allocated with malloc *) + | Mnew (** memory allocated with new *) + | Mnew_array (** memory allocated with new[] *) + | Mobjc (** memory allocated with objective-c alloc *) + [@@deriving compare] + +(** resource that can be allocated *) +type resource = Rmemory of mem_kind | Rfile | Rignore | Rlock [@@deriving compare] + +(** kind of resource action *) +type res_act_kind = Racquire | Rrelease [@@deriving compare] + +let equal_res_act_kind = [%compare.equal : res_act_kind] + +(** kind of dangling pointers *) +type dangling_kind = + (** pointer is dangling because it is uninitialized *) + | DAuninit + (** pointer is dangling because it is the address + of a stack variable which went out of scope *) + | DAaddr_stack_var (** pointer is -1 *) + | DAminusone + [@@deriving compare] + +(** position in a path: proc name, node id *) +type path_pos = Typ.Procname.t * int [@@deriving compare] + +let equal_path_pos = [%compare.equal : path_pos] + +type taint_kind = + | Tk_unverified_SSL_socket + | Tk_shared_preferences_data + | Tk_privacy_annotation + | Tk_integrity_annotation + | Tk_unknown + [@@deriving compare] + +type taint_info = {taint_source: Typ.Procname.t; taint_kind: taint_kind} [@@deriving compare] + +(** acquire/release action on a resource *) +type res_action = + { ra_kind: res_act_kind (** kind of action *) + ; ra_res: resource (** kind of resource *) + ; ra_pname: Typ.Procname.t (** name of the procedure used to acquire/release the resource *) + ; ra_loc: Location.t (** location of the acquire/release *) + ; ra_vpath: DecompiledExp.vpath (** vpath of the resource value *) } + +(* ignore other values beside resources: arbitrary merging into one *) +let compare_res_action {ra_kind= k1; ra_res= r1} {ra_kind= k2; ra_res= r2} = + [%compare : res_act_kind * resource] (k1, r1) (k2, r2) + +(* type aliases for components of t values that compare should ignore *) +type _annot_item = Annot.Item.t + +let compare__annot_item _ _ = 0 + +type _location = Location.t + +let compare__location _ _ = 0 + +type _path_pos = path_pos + +let compare__path_pos _ _ = 0 + +(** Attributes are nary function symbols that are applied to expression arguments in Apred and + Anpred atomic formulas. Many operations don't make much sense for nullary predicates, and are + generally treated as no-ops. The first argument is treated specially, as the "anchor" of the + predicate application. For example, adding or removing an attribute uses the anchor to identify + the atom to operate on. Also, abstraction and normalization operations treat the anchor + specially and maintain more information on it than other arguments. Therefore when attaching an + attribute to an expression, that expression should be the first argument, optionally followed by + additional related expressions. *) +type t = + | Aresource of res_action (** resource acquire/release *) + | Aautorelease + | Adangling of dangling_kind (** dangling pointer *) + (** undefined value obtained by calling the given procedure, plus its return value annots *) + | Aundef of Typ.Procname.t * _annot_item * _location * _path_pos + | Ataint of taint_info + | Auntaint of taint_info + | Alocked + | Aunlocked (** value appeared in second argument of division at given path position *) + | Adiv0 of path_pos + (** attributed exp is null due to a call to a method with given path as null receiver *) + | Aobjc_null + (** value was returned from a call to the given procedure, plus the annots of the return value *) + | Aretval of Typ.Procname.t * Annot.Item.t + (** denotes an object registered as an observers to a notification center *) + | Aobserver (** denotes an object unsubscribed from observers of a notification center *) + | Aunsubscribed_observer + [@@deriving compare] + +let equal = [%compare.equal : t] + +(** name of the allocation function for the given memory kind *) +let mem_alloc_pname = function + | Mmalloc + -> Typ.Procname.from_string_c_fun "malloc" + | Mnew + -> Typ.Procname.from_string_c_fun "new" + | Mnew_array + -> Typ.Procname.from_string_c_fun "new[]" + | Mobjc + -> Typ.Procname.from_string_c_fun "alloc" + +(** name of the deallocation function for the given memory kind *) +let mem_dealloc_pname = function + | Mmalloc + -> Typ.Procname.from_string_c_fun "free" + | Mnew + -> Typ.Procname.from_string_c_fun "delete" + | Mnew_array + -> Typ.Procname.from_string_c_fun "delete[]" + | Mobjc + -> Typ.Procname.from_string_c_fun "dealloc" + +(** Categories of attributes *) +type category = + | ACresource + | ACautorelease + | ACtaint + | AClock + | ACdiv0 + | ACobjc_null + | ACundef + | ACretval + | ACobserver + [@@deriving compare] + +let equal_category = [%compare.equal : category] + +let to_category att = + match att with + | Aresource _ | Adangling _ + -> ACresource + | Ataint _ | Auntaint _ + -> ACtaint + | Alocked | Aunlocked + -> AClock + | Aautorelease + -> ACautorelease + | Adiv0 _ + -> ACdiv0 + | Aobjc_null + -> ACobjc_null + | Aretval _ + -> ACretval + | Aundef _ + -> ACundef + | Aobserver | Aunsubscribed_observer + -> ACobserver + +let is_undef = function Aundef _ -> true | _ -> false + +(** convert the attribute to a string *) +let to_string pe = function + | Aresource ra + -> let mk_name = function + | Mmalloc + -> "ma" + | Mnew + -> "ne" + | Mnew_array + -> "na" + | Mobjc + -> "oc" + in + let name = + match (ra.ra_kind, ra.ra_res) with + | Racquire, Rmemory mk + -> "MEM" ^ mk_name mk + | Racquire, Rfile + -> "FILE" + | Rrelease, Rmemory mk + -> "FREED" ^ mk_name mk + | Rrelease, Rfile + -> "CLOSED" + | _, Rignore + -> "IGNORE" + | Racquire, Rlock + -> "LOCKED" + | Rrelease, Rlock + -> "UNLOCKED" + in + let str_vpath = + if Config.trace_error then F.asprintf "%a" (DecompiledExp.pp_vpath pe) ra.ra_vpath else "" + in + name ^ Binop.str pe Lt ^ Typ.Procname.to_string ra.ra_pname ^ ":" + ^ string_of_int ra.ra_loc.Location.line ^ Binop.str pe Gt ^ str_vpath + | Aautorelease + -> "AUTORELEASE" + | Adangling dk + -> let dks = + match dk with + | DAuninit + -> "UNINIT" + | DAaddr_stack_var + -> "ADDR_STACK" + | DAminusone + -> "MINUS1" + in + "DANGL" ^ Binop.str pe Lt ^ dks ^ Binop.str pe Gt + | Aundef (pn, _, loc, _) + -> "UND" ^ Binop.str pe Lt ^ Typ.Procname.to_string pn ^ Binop.str pe Gt ^ ":" + ^ string_of_int loc.Location.line + | Ataint {taint_source} + -> "TAINTED[" ^ Typ.Procname.to_string taint_source ^ "]" + | Auntaint _ + -> "UNTAINTED" + | Alocked + -> "LOCKED" + | Aunlocked + -> "UNLOCKED" + | Adiv0 (_, _) + -> "DIV0" + | Aobjc_null + -> "OBJC_NULL" + | Aretval (pn, _) + -> "RET" ^ Binop.str pe Lt ^ Typ.Procname.to_string pn ^ Binop.str pe Gt + | Aobserver + -> "OBSERVER" + | Aunsubscribed_observer + -> "UNSUBSCRIBED_OBSERVER" + +(** dump an attribute *) +let d_attribute (a: t) = L.add_print_action (L.PTattribute, Obj.repr a) diff --git a/infer/src/IR/PredSymb.mli b/infer/src/IR/PredSymb.mli new file mode 100644 index 000000000..5244c184b --- /dev/null +++ b/infer/src/IR/PredSymb.mli @@ -0,0 +1,147 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Predicate Symbols *) +open! IStd +module L = Logging +module F = Format + +(** {2 Programs and Types} *) + +type func_attribute = FA_sentinel of int * int [@@deriving compare] + +(** Return the value of the FA_sentinel attribute in [attr_list] if it is found *) + +val get_sentinel_func_attribute_value : func_attribute list -> (int * int) option + +(** Visibility modifiers. *) + +type access = Default | Public | Private | Protected [@@deriving compare] + +val equal_access : access -> access -> bool + +type mem_kind = + | Mmalloc (** memory allocated with malloc *) + | Mnew (** memory allocated with new *) + | Mnew_array (** memory allocated with new[] *) + | Mobjc (** memory allocated with objective-c alloc *) + [@@deriving compare] + +(** resource that can be allocated *) + +type resource = Rmemory of mem_kind | Rfile | Rignore | Rlock [@@deriving compare] + +(** kind of resource action *) + +type res_act_kind = Racquire | Rrelease [@@deriving compare] + +val equal_res_act_kind : res_act_kind -> res_act_kind -> bool + +(** kind of dangling pointers *) + +type dangling_kind = + (** pointer is dangling because it is uninitialized *) + | DAuninit + (** pointer is dangling because it is the address of a stack variable which went out of scope *) + | DAaddr_stack_var (** pointer is -1 *) + | DAminusone + +(** position in a path: proc name, node id *) + +type path_pos = Typ.Procname.t * int [@@deriving compare] + +val equal_path_pos : path_pos -> path_pos -> bool + +type taint_kind = + | Tk_unverified_SSL_socket + | Tk_shared_preferences_data + | Tk_privacy_annotation + | Tk_integrity_annotation + | Tk_unknown + +type taint_info = {taint_source: Typ.Procname.t; taint_kind: taint_kind} + +(** acquire/release action on a resource *) + +type res_action = + { ra_kind: res_act_kind (** kind of action *) + ; ra_res: resource (** kind of resource *) + ; ra_pname: Typ.Procname.t (** name of the procedure used to acquire/release the resource *) + ; ra_loc: Location.t (** location of the acquire/release *) + ; ra_vpath: DecompiledExp.vpath (** vpath of the resource value *) } + +(** Attributes are nary function symbols that are applied to expression arguments in Apred and + Anpred atomic formulas. Many operations don't make much sense for nullary predicates, and are + generally treated as no-ops. The first argument is treated specially, as the "anchor" of the + predicate application. For example, adding or removing an attribute uses the anchor to identify + the atom to operate on. Also, abstraction and normalization operations treat the anchor + specially and maintain more information on it than other arguments. Therefore when attaching an + attribute to an expression, that expression should be the first argument, optionally followed by + additional related expressions. *) + +type t = + | Aresource of res_action (** resource acquire/release *) + | Aautorelease + | Adangling of dangling_kind (** dangling pointer *) + (** undefined value obtained by calling the given procedure, plus its return value annots *) + | Aundef of Typ.Procname.t * Annot.Item.t * Location.t * path_pos + | Ataint of taint_info + | Auntaint of taint_info + | Alocked + | Aunlocked (** value appeared in second argument of division at given path position *) + | Adiv0 of path_pos + (** attributed exp is null due to a call to a method with given path as null receiver *) + | Aobjc_null + (** value was returned from a call to the given procedure, plus the annots of the return value *) + | Aretval of Typ.Procname.t * Annot.Item.t + (** denotes an object registered as an observers to a notification center *) + | Aobserver (** denotes an object unsubscribed from observers of a notification center *) + | Aunsubscribed_observer + [@@deriving compare] + +val equal : t -> t -> bool + +(** name of the allocation function for the given memory kind *) + +val mem_alloc_pname : mem_kind -> Typ.Procname.t + +(** name of the deallocation function for the given memory kind *) + +val mem_dealloc_pname : mem_kind -> Typ.Procname.t + +(** Categories of attributes *) + +type category = + | ACresource + | ACautorelease + | ACtaint + | AClock + | ACdiv0 + | ACobjc_null + | ACundef + | ACretval + | ACobserver + [@@deriving compare] + +val equal_category : category -> category -> bool + +(** Return the category to which the attribute belongs. *) + +val to_category : t -> category + +val is_undef : t -> bool + +(** convert the attribute to a string *) + +val to_string : Pp.env -> t -> string + +(** Dump an attribute. *) + +val d_attribute : t -> unit diff --git a/infer/src/IR/PredSymb.re b/infer/src/IR/PredSymb.re deleted file mode 100644 index 22dde8e6b..000000000 --- a/infer/src/IR/PredSymb.re +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Predicate Symbols */ -module L = Logging; - -module F = Format; - -type func_attribute = - | FA_sentinel int int /** __attribute__((sentinel(int, int))) */ -[@@deriving compare]; - - -/** Visibility modifiers. */ -type access = - | Default - | Public - | Private - | Protected -[@@deriving compare]; - -let equal_access = [%compare.equal : access]; - - -/** Return the value of the FA_sentinel attribute in [attr_list] if it is found */ -let get_sentinel_func_attribute_value attr_list => - switch attr_list { - | [FA_sentinel sentinel null_pos, ..._] => Some (sentinel, null_pos) - | [] => None - }; - -type mem_kind = - | Mmalloc /** memory allocated with malloc */ - | Mnew /** memory allocated with new */ - | Mnew_array /** memory allocated with new[] */ - | Mobjc /** memory allocated with objective-c alloc */ -[@@deriving compare]; - - -/** resource that can be allocated */ -type resource = - | Rmemory mem_kind - | Rfile - | Rignore - | Rlock -[@@deriving compare]; - - -/** kind of resource action */ -type res_act_kind = - | Racquire - | Rrelease -[@@deriving compare]; - -let equal_res_act_kind = [%compare.equal : res_act_kind]; - - -/** kind of dangling pointers */ -type dangling_kind = - /** pointer is dangling because it is uninitialized */ - | DAuninit - /** pointer is dangling because it is the address - of a stack variable which went out of scope */ - | DAaddr_stack_var - /** pointer is -1 */ - | DAminusone -[@@deriving compare]; - - -/** position in a path: proc name, node id */ -type path_pos = (Typ.Procname.t, int) [@@deriving compare]; - -let equal_path_pos = [%compare.equal : path_pos]; - -type taint_kind = - | Tk_unverified_SSL_socket - | Tk_shared_preferences_data - | Tk_privacy_annotation - | Tk_integrity_annotation - | Tk_unknown -[@@deriving compare]; - -type taint_info = {taint_source: Typ.Procname.t, taint_kind} [@@deriving compare]; - - -/** acquire/release action on a resource */ -type res_action = { - ra_kind: res_act_kind, /** kind of action */ - ra_res: resource, /** kind of resource */ - ra_pname: Typ.Procname.t, /** name of the procedure used to acquire/release the resource */ - ra_loc: Location.t, /** location of the acquire/release */ - ra_vpath: DecompiledExp.vpath /** vpath of the resource value */ -}; - -/* ignore other values beside resources: arbitrary merging into one */ -let compare_res_action {ra_kind: k1, ra_res: r1} {ra_kind: k2, ra_res: r2} => - [%compare : (res_act_kind, resource)] (k1, r1) (k2, r2); - -/* type aliases for components of t values that compare should ignore */ -type _annot_item = Annot.Item.t; - -let compare__annot_item _ _ => 0; - -type _location = Location.t; - -let compare__location _ _ => 0; - -type _path_pos = path_pos; - -let compare__path_pos _ _ => 0; - - -/** Attributes are nary function symbols that are applied to expression arguments in Apred and - Anpred atomic formulas. Many operations don't make much sense for nullary predicates, and are - generally treated as no-ops. The first argument is treated specially, as the "anchor" of the - predicate application. For example, adding or removing an attribute uses the anchor to identify - the atom to operate on. Also, abstraction and normalization operations treat the anchor - specially and maintain more information on it than other arguments. Therefore when attaching an - attribute to an expression, that expression should be the first argument, optionally followed by - additional related expressions. */ -type t = - | Aresource res_action /** resource acquire/release */ - | Aautorelease - | Adangling dangling_kind /** dangling pointer */ - /** undefined value obtained by calling the given procedure, plus its return value annots */ - | Aundef Typ.Procname.t _annot_item _location _path_pos - | Ataint taint_info - | Auntaint taint_info - | Alocked - | Aunlocked - /** value appeared in second argument of division at given path position */ - | Adiv0 path_pos - /** attributed exp is null due to a call to a method with given path as null receiver */ - | Aobjc_null - /** value was returned from a call to the given procedure, plus the annots of the return value */ - | Aretval Typ.Procname.t Annot.Item.t - /** denotes an object registered as an observers to a notification center */ - | Aobserver - /** denotes an object unsubscribed from observers of a notification center */ - | Aunsubscribed_observer -[@@deriving compare]; - -let equal = [%compare.equal : t]; - - -/** name of the allocation function for the given memory kind */ -let mem_alloc_pname = - fun - | Mmalloc => Typ.Procname.from_string_c_fun "malloc" - | Mnew => Typ.Procname.from_string_c_fun "new" - | Mnew_array => Typ.Procname.from_string_c_fun "new[]" - | Mobjc => Typ.Procname.from_string_c_fun "alloc"; - - -/** name of the deallocation function for the given memory kind */ -let mem_dealloc_pname = - fun - | Mmalloc => Typ.Procname.from_string_c_fun "free" - | Mnew => Typ.Procname.from_string_c_fun "delete" - | Mnew_array => Typ.Procname.from_string_c_fun "delete[]" - | Mobjc => Typ.Procname.from_string_c_fun "dealloc"; - - -/** Categories of attributes */ -type category = - | ACresource - | ACautorelease - | ACtaint - | AClock - | ACdiv0 - | ACobjc_null - | ACundef - | ACretval - | ACobserver -[@@deriving compare]; - -let equal_category = [%compare.equal : category]; - -let to_category att => - switch att { - | Aresource _ - | Adangling _ => ACresource - | Ataint _ - | Auntaint _ => ACtaint - | Alocked - | Aunlocked => AClock - | Aautorelease => ACautorelease - | Adiv0 _ => ACdiv0 - | Aobjc_null => ACobjc_null - | Aretval _ => ACretval - | Aundef _ => ACundef - | Aobserver - | Aunsubscribed_observer => ACobserver - }; - -let is_undef = - fun - | Aundef _ => true - | _ => false; - - -/** convert the attribute to a string */ -let to_string pe => - fun - | Aresource ra => { - let mk_name = ( - fun - | Mmalloc => "ma" - | Mnew => "ne" - | Mnew_array => "na" - | Mobjc => "oc" - ); - let name = - switch (ra.ra_kind, ra.ra_res) { - | (Racquire, Rmemory mk) => "MEM" ^ mk_name mk - | (Racquire, Rfile) => "FILE" - | (Rrelease, Rmemory mk) => "FREED" ^ mk_name mk - | (Rrelease, Rfile) => "CLOSED" - | (_, Rignore) => "IGNORE" - | (Racquire, Rlock) => "LOCKED" - | (Rrelease, Rlock) => "UNLOCKED" - }; - let str_vpath = - if Config.trace_error { - F.asprintf "%a" (DecompiledExp.pp_vpath pe) ra.ra_vpath - } else { - "" - }; - name ^ - Binop.str pe Lt ^ - Typ.Procname.to_string ra.ra_pname ^ - ":" ^ string_of_int ra.ra_loc.Location.line ^ Binop.str pe Gt ^ str_vpath - } - | Aautorelease => "AUTORELEASE" - | Adangling dk => { - let dks = - switch dk { - | DAuninit => "UNINIT" - | DAaddr_stack_var => "ADDR_STACK" - | DAminusone => "MINUS1" - }; - "DANGL" ^ Binop.str pe Lt ^ dks ^ Binop.str pe Gt - } - | Aundef pn _ loc _ => - "UND" ^ - Binop.str pe Lt ^ - Typ.Procname.to_string pn ^ Binop.str pe Gt ^ ":" ^ string_of_int loc.Location.line - | Ataint {taint_source} => "TAINTED[" ^ Typ.Procname.to_string taint_source ^ "]" - | Auntaint _ => "UNTAINTED" - | Alocked => "LOCKED" - | Aunlocked => "UNLOCKED" - | Adiv0 (_, _) => "DIV0" - | Aobjc_null => "OBJC_NULL" - | Aretval pn _ => "RET" ^ Binop.str pe Lt ^ Typ.Procname.to_string pn ^ Binop.str pe Gt - | Aobserver => "OBSERVER" - | Aunsubscribed_observer => "UNSUBSCRIBED_OBSERVER"; - - -/** dump an attribute */ -let d_attribute (a: t) => L.add_print_action (L.PTattribute, Obj.repr a); diff --git a/infer/src/IR/PredSymb.rei b/infer/src/IR/PredSymb.rei deleted file mode 100644 index 8276f11d8..000000000 --- a/infer/src/IR/PredSymb.rei +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Predicate Symbols */ -module L = Logging; - -module F = Format; - - -/** {2 Programs and Types} */ -type func_attribute = - | FA_sentinel int int -[@@deriving compare]; - - -/** Return the value of the FA_sentinel attribute in [attr_list] if it is found */ -let get_sentinel_func_attribute_value: list func_attribute => option (int, int); - - -/** Visibility modifiers. */ -type access = - | Default - | Public - | Private - | Protected -[@@deriving compare]; - -let equal_access: access => access => bool; - -type mem_kind = - | Mmalloc /** memory allocated with malloc */ - | Mnew /** memory allocated with new */ - | Mnew_array /** memory allocated with new[] */ - | Mobjc /** memory allocated with objective-c alloc */ -[@@deriving compare]; - - -/** resource that can be allocated */ -type resource = - | Rmemory mem_kind - | Rfile - | Rignore - | Rlock -[@@deriving compare]; - - -/** kind of resource action */ -type res_act_kind = - | Racquire - | Rrelease -[@@deriving compare]; - -let equal_res_act_kind: res_act_kind => res_act_kind => bool; - - -/** kind of dangling pointers */ -type dangling_kind = - /** pointer is dangling because it is uninitialized */ - | DAuninit - /** pointer is dangling because it is the address of a stack variable which went out of scope */ - | DAaddr_stack_var - /** pointer is -1 */ - | DAminusone; - - -/** position in a path: proc name, node id */ -type path_pos = (Typ.Procname.t, int) [@@deriving compare]; - -let equal_path_pos: path_pos => path_pos => bool; - -type taint_kind = - | Tk_unverified_SSL_socket - | Tk_shared_preferences_data - | Tk_privacy_annotation - | Tk_integrity_annotation - | Tk_unknown; - -type taint_info = {taint_source: Typ.Procname.t, taint_kind}; - - -/** acquire/release action on a resource */ -type res_action = { - ra_kind: res_act_kind, /** kind of action */ - ra_res: resource, /** kind of resource */ - ra_pname: Typ.Procname.t, /** name of the procedure used to acquire/release the resource */ - ra_loc: Location.t, /** location of the acquire/release */ - ra_vpath: DecompiledExp.vpath /** vpath of the resource value */ -}; - - -/** Attributes are nary function symbols that are applied to expression arguments in Apred and - Anpred atomic formulas. Many operations don't make much sense for nullary predicates, and are - generally treated as no-ops. The first argument is treated specially, as the "anchor" of the - predicate application. For example, adding or removing an attribute uses the anchor to identify - the atom to operate on. Also, abstraction and normalization operations treat the anchor - specially and maintain more information on it than other arguments. Therefore when attaching an - attribute to an expression, that expression should be the first argument, optionally followed by - additional related expressions. */ -type t = - | Aresource res_action /** resource acquire/release */ - | Aautorelease - | Adangling dangling_kind /** dangling pointer */ - /** undefined value obtained by calling the given procedure, plus its return value annots */ - | Aundef Typ.Procname.t Annot.Item.t Location.t path_pos - | Ataint taint_info - | Auntaint taint_info - | Alocked - | Aunlocked - /** value appeared in second argument of division at given path position */ - | Adiv0 path_pos - /** attributed exp is null due to a call to a method with given path as null receiver */ - | Aobjc_null - /** value was returned from a call to the given procedure, plus the annots of the return value */ - | Aretval Typ.Procname.t Annot.Item.t - /** denotes an object registered as an observers to a notification center */ - | Aobserver - /** denotes an object unsubscribed from observers of a notification center */ - | Aunsubscribed_observer -[@@deriving compare]; - -let equal: t => t => bool; - - -/** name of the allocation function for the given memory kind */ -let mem_alloc_pname: mem_kind => Typ.Procname.t; - - -/** name of the deallocation function for the given memory kind */ -let mem_dealloc_pname: mem_kind => Typ.Procname.t; - - -/** Categories of attributes */ -type category = - | ACresource - | ACautorelease - | ACtaint - | AClock - | ACdiv0 - | ACobjc_null - | ACundef - | ACretval - | ACobserver -[@@deriving compare]; - -let equal_category: category => category => bool; - - -/** Return the category to which the attribute belongs. */ -let to_category: t => category; - -let is_undef: t => bool; - - -/** convert the attribute to a string */ -let to_string: Pp.env => t => string; - - -/** Dump an attribute. */ -let d_attribute: t => unit; diff --git a/infer/src/IR/ProcAttributes.ml b/infer/src/IR/ProcAttributes.ml new file mode 100644 index 000000000..a215999a8 --- /dev/null +++ b/infer/src/IR/ProcAttributes.ml @@ -0,0 +1,98 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Attributes of a procedure. *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +(** flags for a procedure *) +type proc_flags = (string, string) Hashtbl.t + +let compare_proc_flags x y = + let bindings x = Hashtbl.fold (fun k d l -> (k, d) :: l) x [] in + [%compare : (string * string) list] (bindings x) (bindings y) + +let proc_flags_empty () : proc_flags = Hashtbl.create 1 + +let proc_flag_skip = "skip" + +let proc_flag_ignore_return = "ignore_return" + +let proc_flags_add proc_flags key value = Hashtbl.replace proc_flags key value + +let proc_flags_find proc_flags key = Hashtbl.find proc_flags key + +(** Type for ObjC accessors *) +type objc_accessor_type = + | Objc_getter of Typ.Fieldname.t + | Objc_setter of Typ.Fieldname.t + [@@deriving compare] + +type t = + { access: PredSymb.access (** visibility access *) + ; captured: (Mangled.t * Typ.t) list (** name and type of variables captured in blocks *) + ; mutable changed: bool (** true if proc has changed since last analysis *) + ; mutable did_preanalysis: bool (** true if we performed preanalysis on the CFG for this proc *) + ; err_log: Errlog.t (** Error log for the procedure *) + ; exceptions: string list (** exceptions thrown by the procedure *) + ; formals: (Mangled.t * Typ.t) list (** name and type of formal parameters *) + ; const_formals: int list (** list of indices of formals that are const-qualified *) + ; func_attributes: PredSymb.func_attribute list + ; is_abstract: bool (** the procedure is abstract *) + ; is_bridge_method: bool (** the procedure is a bridge method *) + ; is_defined: bool (** true if the procedure is defined, and not just declared *) + ; is_objc_instance_method: bool (** the procedure is an objective-C instance method *) + ; is_cpp_instance_method: bool (** the procedure is an C++ instance method *) + ; is_cpp_noexcept_method: bool (** the procedure is an C++ method annotated with "noexcept" *) + ; is_java_synchronized_method: bool (** the procedure is a Java synchronized method *) + ; is_model: bool (** the procedure is a model *) + ; is_synthetic_method: bool (** the procedure is a synthetic method *) + ; language: Config.language (** language of the procedure *) + ; loc: Location.t (** location of this procedure in the source code *) + ; translation_unit: SourceFile.t option (** translation unit to which the procedure belongs *) + ; mutable locals: (Mangled.t * Typ.t) list (** name and type of local variables *) + ; method_annotation: Annot.Method.t (** annotations for java methods *) + ; objc_accessor: objc_accessor_type option (** type of ObjC accessor, if any *) + ; proc_flags: proc_flags (** flags of the procedure *) + ; proc_name: Typ.Procname.t (** name of the procedure *) + ; ret_type: Typ.t (** return type *) + ; source_file_captured: SourceFile.t (** source file where the procedure was captured *) } + [@@deriving compare] + +let default proc_name language = + { access= PredSymb.Default + ; captured= [] + ; changed= true + ; did_preanalysis= false + ; err_log= Errlog.empty () + ; exceptions= [] + ; formals= [] + ; const_formals= [] + ; func_attributes= [] + ; is_abstract= false + ; is_bridge_method= false + ; is_cpp_instance_method= false + ; is_cpp_noexcept_method= false + ; is_java_synchronized_method= false + ; is_defined= false + ; is_objc_instance_method= false + ; is_model= false + ; is_synthetic_method= false + ; language + ; loc= Location.dummy + ; translation_unit= None + ; locals= [] + ; method_annotation= Annot.Method.empty + ; objc_accessor= None + ; proc_flags= proc_flags_empty () + ; proc_name + ; ret_type= Typ.mk Typ.Tvoid + ; source_file_captured= SourceFile.invalid __FILE__ } diff --git a/infer/src/IR/ProcAttributes.mli b/infer/src/IR/ProcAttributes.mli new file mode 100644 index 000000000..f5c329ec1 --- /dev/null +++ b/infer/src/IR/ProcAttributes.mli @@ -0,0 +1,78 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Attributes of a procedure. *) + +(** flags for a procedure *) + +type proc_flags = (string, string) Caml.Hashtbl.t [@@deriving compare] + +(** keys for proc_flags *) + +val proc_flag_skip : string + +(** key to specify that a function should be treated as a skip function *) + +val proc_flag_ignore_return : string + +(** key to specify that it is OK to ignore the return value *) + +(** empty proc flags *) + +val proc_flags_empty : unit -> proc_flags + +(** add a key value pair to a proc flags *) + +val proc_flags_add : proc_flags -> string -> string -> unit + +(** find a value for a key in the proc flags *) + +val proc_flags_find : proc_flags -> string -> string + +type objc_accessor_type = + | Objc_getter of Typ.Fieldname.t + | Objc_setter of Typ.Fieldname.t + [@@deriving compare] + +type t = + { access: PredSymb.access (** visibility access *) + ; captured: (Mangled.t * Typ.t) list (** name and type of variables captured in blocks *) + ; mutable changed: bool (** true if proc has changed since last analysis *) + ; mutable did_preanalysis: bool (** true if we performed preanalysis on the CFG for this proc *) + ; err_log: Errlog.t (** Error log for the procedure *) + ; exceptions: string list (** exceptions thrown by the procedure *) + ; formals: (Mangled.t * Typ.t) list (** name and type of formal parameters *) + ; const_formals: int list (** list of indices of formals that are const-qualified *) + ; func_attributes: PredSymb.func_attribute list + ; is_abstract: bool (** the procedure is abstract *) + ; is_bridge_method: bool (** the procedure is a bridge method *) + ; is_defined: bool (** true if the procedure is defined, and not just declared *) + ; is_objc_instance_method: bool (** the procedure is an objective-C instance method *) + ; is_cpp_instance_method: bool (** the procedure is an C++ instance method *) + ; is_cpp_noexcept_method: bool (** the procedure is an C++ method annotated with "noexcept" *) + ; is_java_synchronized_method: bool (** the procedure is a Java synchronized method *) + ; is_model: bool (** the procedure is a model *) + ; is_synthetic_method: bool (** the procedure is a synthetic method *) + ; language: Config.language (** language of the procedure *) + ; loc: Location.t (** location of this procedure in the source code *) + ; translation_unit: SourceFile.t option (** translation unit to which the procedure belongs *) + ; mutable locals: (Mangled.t * Typ.t) list (** name and type of local variables *) + ; method_annotation: Annot.Method.t (** annotations for java methods *) + ; objc_accessor: objc_accessor_type option (** type of ObjC accessor, if any *) + ; proc_flags: proc_flags (** flags of the procedure *) + ; proc_name: Typ.Procname.t (** name of the procedure *) + ; ret_type: Typ.t (** return type *) + ; source_file_captured: SourceFile.t (** source file where the procedure was captured *) } + [@@deriving compare] + +(** Create a proc_attributes with default values. *) + +val default : Typ.Procname.t -> Config.language -> t diff --git a/infer/src/IR/ProcAttributes.re b/infer/src/IR/ProcAttributes.re deleted file mode 100644 index 6b8ae1d5c..000000000 --- a/infer/src/IR/ProcAttributes.re +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** Attributes of a procedure. */ -module L = Logging; - -module F = Format; - - -/** flags for a procedure */ -type proc_flags = Hashtbl.t string string; - -let compare_proc_flags x y => { - let bindings x => Hashtbl.fold (fun k d l => [(k, d), ...l]) x []; - [%compare : list (string, string)] (bindings x) (bindings y) -}; - -let proc_flags_empty () :proc_flags => Hashtbl.create 1; - -let proc_flag_skip = "skip"; - -let proc_flag_ignore_return = "ignore_return"; - -let proc_flags_add proc_flags key value => Hashtbl.replace proc_flags key value; - -let proc_flags_find proc_flags key => Hashtbl.find proc_flags key; - - -/** Type for ObjC accessors */ -type objc_accessor_type = - | Objc_getter Typ.Fieldname.t - | Objc_setter Typ.Fieldname.t -[@@deriving compare]; - -type t = { - access: PredSymb.access, /** visibility access */ - captured: list (Mangled.t, Typ.t), /** name and type of variables captured in blocks */ - mutable changed: bool, /** true if proc has changed since last analysis */ - mutable did_preanalysis: bool, /** true if we performed preanalysis on the CFG for this proc */ - err_log: Errlog.t, /** Error log for the procedure */ - exceptions: list string, /** exceptions thrown by the procedure */ - formals: list (Mangled.t, Typ.t), /** name and type of formal parameters */ - const_formals: list int, /** list of indices of formals that are const-qualified */ - func_attributes: list PredSymb.func_attribute, - is_abstract: bool, /** the procedure is abstract */ - is_bridge_method: bool, /** the procedure is a bridge method */ - is_defined: bool, /** true if the procedure is defined, and not just declared */ - is_objc_instance_method: bool, /** the procedure is an objective-C instance method */ - is_cpp_instance_method: bool, /** the procedure is an C++ instance method */ - is_cpp_noexcept_method: bool, /** the procedure is an C++ method annotated with "noexcept" */ - is_java_synchronized_method: bool, /** the procedure is a Java synchronized method */ - is_model: bool, /** the procedure is a model */ - is_synthetic_method: bool, /** the procedure is a synthetic method */ - language: Config.language, /** language of the procedure */ - loc: Location.t, /** location of this procedure in the source code */ - translation_unit: option SourceFile.t, /** translation unit to which the procedure belongs */ - mutable locals: list (Mangled.t, Typ.t), /** name and type of local variables */ - method_annotation: Annot.Method.t, /** annotations for java methods */ - objc_accessor: option objc_accessor_type, /** type of ObjC accessor, if any */ - proc_flags, /** flags of the procedure */ - proc_name: Typ.Procname.t, /** name of the procedure */ - ret_type: Typ.t, /** return type */ - source_file_captured: SourceFile.t /** source file where the procedure was captured */ -} -[@@deriving compare]; - -let default proc_name language => { - access: PredSymb.Default, - captured: [], - changed: true, - did_preanalysis: false, - err_log: Errlog.empty (), - exceptions: [], - formals: [], - const_formals: [], - func_attributes: [], - is_abstract: false, - is_bridge_method: false, - is_cpp_instance_method: false, - is_cpp_noexcept_method: false, - is_java_synchronized_method: false, - is_defined: false, - is_objc_instance_method: false, - is_model: false, - is_synthetic_method: false, - language, - loc: Location.dummy, - translation_unit: None, - locals: [], - method_annotation: Annot.Method.empty, - objc_accessor: None, - proc_flags: proc_flags_empty (), - proc_name, - ret_type: Typ.mk Typ.Tvoid, - source_file_captured: SourceFile.invalid __FILE__ -}; diff --git a/infer/src/IR/ProcAttributes.rei b/infer/src/IR/ProcAttributes.rei deleted file mode 100644 index 48d304d88..000000000 --- a/infer/src/IR/ProcAttributes.rei +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Attributes of a procedure. */ - -/** flags for a procedure */ -type proc_flags = Caml.Hashtbl.t string string [@@deriving compare]; - - -/** keys for proc_flags */ -let proc_flag_skip: string; /** key to specify that a function should be treated as a skip function */ - -let proc_flag_ignore_return: string; /** key to specify that it is OK to ignore the return value */ - - -/** empty proc flags */ -let proc_flags_empty: unit => proc_flags; - - -/** add a key value pair to a proc flags */ -let proc_flags_add: proc_flags => string => string => unit; - - -/** find a value for a key in the proc flags */ -let proc_flags_find: proc_flags => string => string; - -type objc_accessor_type = - | Objc_getter Typ.Fieldname.t - | Objc_setter Typ.Fieldname.t -[@@deriving compare]; - -type t = { - access: PredSymb.access, /** visibility access */ - captured: list (Mangled.t, Typ.t), /** name and type of variables captured in blocks */ - mutable changed: bool, /** true if proc has changed since last analysis */ - mutable did_preanalysis: bool, /** true if we performed preanalysis on the CFG for this proc */ - err_log: Errlog.t, /** Error log for the procedure */ - exceptions: list string, /** exceptions thrown by the procedure */ - formals: list (Mangled.t, Typ.t), /** name and type of formal parameters */ - const_formals: list int, /** list of indices of formals that are const-qualified */ - func_attributes: list PredSymb.func_attribute, - is_abstract: bool, /** the procedure is abstract */ - is_bridge_method: bool, /** the procedure is a bridge method */ - is_defined: bool, /** true if the procedure is defined, and not just declared */ - is_objc_instance_method: bool, /** the procedure is an objective-C instance method */ - is_cpp_instance_method: bool, /** the procedure is an C++ instance method */ - is_cpp_noexcept_method: bool, /** the procedure is an C++ method annotated with "noexcept" */ - is_java_synchronized_method: bool, /** the procedure is a Java synchronized method */ - is_model: bool, /** the procedure is a model */ - is_synthetic_method: bool, /** the procedure is a synthetic method */ - language: Config.language, /** language of the procedure */ - loc: Location.t, /** location of this procedure in the source code */ - translation_unit: option SourceFile.t, /** translation unit to which the procedure belongs */ - mutable locals: list (Mangled.t, Typ.t), /** name and type of local variables */ - method_annotation: Annot.Method.t, /** annotations for java methods */ - objc_accessor: option objc_accessor_type, /** type of ObjC accessor, if any */ - proc_flags, /** flags of the procedure */ - proc_name: Typ.Procname.t, /** name of the procedure */ - ret_type: Typ.t, /** return type */ - source_file_captured: SourceFile.t /** source file where the procedure was captured */ -} -[@@deriving compare]; - - -/** Create a proc_attributes with default values. */ -let default: Typ.Procname.t => Config.language => t; diff --git a/infer/src/IR/Procdesc.ml b/infer/src/IR/Procdesc.ml new file mode 100644 index 000000000..3bd0646d3 --- /dev/null +++ b/infer/src/IR/Procdesc.ml @@ -0,0 +1,505 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +(* =============== START of module Node =============== *) +module Node = struct + type id = int [@@deriving compare] + + let equal_id = [%compare.equal : id] + + type nodekind = + | Start_node of Typ.Procname.t + | Exit_node of Typ.Procname.t + | Stmt_node of string + | Join_node + | Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *) + | Skip_node of string + [@@deriving compare] + + let equal_nodekind = [%compare.equal : nodekind] + + (** a node *) + type t = + { (** unique id of the node *) + id: id (** distance to the exit node *) + ; mutable dist_exit: int option (** exception nodes in the cfg *) + ; mutable exn: t list (** instructions for symbolic execution *) + ; mutable instrs: Sil.instr list (** kind of node *) + ; kind: nodekind (** location in the source code *) + ; loc: Location.t (** predecessor nodes in the cfg *) + ; mutable preds: t list (** name of the procedure the node belongs to *) + ; pname_opt: Typ.Procname.t option (** successor nodes in the cfg *) + ; mutable succs: t list } + + let exn_handler_kind = Stmt_node "exception handler" + + let exn_sink_kind = Stmt_node "exceptions sink" + + let throw_kind = Stmt_node "throw" + + let dummy pname_opt = + { id= 0 + ; dist_exit= None + ; instrs= [] + ; kind= Skip_node "dummy" + ; loc= Location.dummy + ; pname_opt + ; succs= [] + ; preds= [] + ; exn= [] } + + let compare node1 node2 = Int.compare node1.id node2.id + + let hash node = Hashtbl.hash node.id + + let equal = [%compare.equal : t] + + (** Get the unique id of the node *) + let get_id node = node.id + + let get_succs node = node.succs + + type node = t + + module NodeSet = Caml.Set.Make (struct + type t = node + + let compare = compare + end) + + module IdMap = Caml.Map.Make (struct + type t = id + + let compare = compare_id + end) + + let get_sliced_succs node f = + let visited = ref NodeSet.empty in + let rec slice_nodes nodes : NodeSet.t = + let do_node acc n = + visited := NodeSet.add n !visited ; + if f n then NodeSet.singleton n + else + NodeSet.union acc + (slice_nodes (List.filter ~f:(fun s -> not (NodeSet.mem s !visited)) n.succs)) + in + List.fold ~f:do_node ~init:NodeSet.empty nodes + in + NodeSet.elements (slice_nodes node.succs) + + let get_sliced_preds node f = + let visited = ref NodeSet.empty in + let rec slice_nodes nodes : NodeSet.t = + let do_node acc n = + visited := NodeSet.add n !visited ; + if f n then NodeSet.singleton n + else + NodeSet.union acc + (slice_nodes (List.filter ~f:(fun s -> not (NodeSet.mem s !visited)) n.preds)) + in + List.fold ~f:do_node ~init:NodeSet.empty nodes + in + NodeSet.elements (slice_nodes node.preds) + + let get_exn node = node.exn + + (** Get the name of the procedure the node belongs to *) + let get_proc_name node = + match node.pname_opt with + | None + -> L.internal_error "get_proc_name: at node %d@\n" node.id ; + assert false + | Some pname + -> pname + + (** Get the predecessors of the node *) + let get_preds node = node.preds + + (** Generates a list of nodes starting at a given node + and recursively adding the results of the generator *) + let get_generated_slope start_node generator = + let visited = ref NodeSet.empty in + let rec nodes n = + visited := NodeSet.add n !visited ; + let succs = List.filter ~f:(fun n -> not (NodeSet.mem n !visited)) (generator n) in + match succs with [hd] -> n :: nodes hd | _ -> [n] + in + nodes start_node + + (** Get the node kind *) + let get_kind node = node.kind + + (** Get the instructions to be executed *) + let get_instrs node = node.instrs + + (** Get the list of callee procnames from the node *) + let get_callees node = + let collect callees instr = + match instr with + | Sil.Call (_, exp, _, _, _) -> ( + match exp with Exp.Const Const.Cfun procname -> procname :: callees | _ -> callees ) + | _ + -> callees + in + List.fold ~f:collect ~init:[] (get_instrs node) + + (** Get the location of the node *) + let get_loc n = n.loc + + (** Get the source location of the last instruction in the node *) + let get_last_loc n = + match List.rev (get_instrs n) with instr :: _ -> Sil.instr_get_loc instr | [] -> n.loc + + let pp_id f id = F.fprintf f "%d" id + + let pp f node = pp_id f (get_id node) + + let get_distance_to_exit node = node.dist_exit + + (** Append the instructions to the list of instructions to execute *) + let append_instrs node instrs = node.instrs <- node.instrs @ instrs + + (** Add the instructions at the beginning of the list of instructions to execute *) + let prepend_instrs node instrs = node.instrs <- instrs @ node.instrs + + (** Replace the instructions to be executed. *) + let replace_instrs node instrs = node.instrs <- instrs + + (** Add declarations for local variables and return variable to the node *) + let add_locals_ret_declaration node (proc_attributes: ProcAttributes.t) locals = + let loc = get_loc node in + let pname = proc_attributes.proc_name in + let ret_var = + let ret_type = proc_attributes.ret_type in + (Pvar.get_ret_pvar pname, ret_type) + in + let construct_decl (x, typ) = (Pvar.mk x pname, typ) in + let ptl = ret_var :: List.map ~f:construct_decl locals in + let instr = Sil.Declare_locals (ptl, loc) in + prepend_instrs node [instr] + + (** Print extended instructions for the node, + highlighting the given subinstruction if present *) + let pp_instrs pe0 ~sub_instrs instro fmt node = + let pe = + match instro with None -> pe0 | Some instr -> Pp.extend_colormap pe0 (Obj.repr instr) Red + in + let instrs = get_instrs node in + let pp_loc fmt () = F.fprintf fmt " %a " Location.pp (get_loc node) in + let print_sub_instrs () = F.fprintf fmt "%a" (Sil.pp_instr_list pe) instrs in + match get_kind node with + | Stmt_node s + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "statements (%s) %a" s pp_loc () + | Prune_node (_, _, descr) + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "assume %s %a" descr pp_loc () + | Exit_node _ + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "exit %a" pp_loc () + | Skip_node s + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "skip (%s) %a" s pp_loc () + | Start_node _ + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "start %a" pp_loc () + | Join_node + -> if sub_instrs then print_sub_instrs () else F.fprintf fmt "join %a" pp_loc () + + (** Dump extended instructions for the node *) + let d_instrs ~(sub_instrs: bool) (curr_instr: Sil.instr option) (node: t) = + L.add_print_action (L.PTnode_instrs, Obj.repr (sub_instrs, curr_instr, node)) + + (** Return a description of the cfg node *) + let get_description pe node = + let str = + match get_kind node with + | Stmt_node _ + -> "Instructions" + | Prune_node (_, _, descr) + -> "Conditional" ^ " " ^ descr + | Exit_node _ + -> "Exit" + | Skip_node _ + -> "Skip" + | Start_node _ + -> "Start" + | Join_node + -> "Join" + in + let pp fmt = F.fprintf fmt "%s@\n%a@?" str (pp_instrs pe None ~sub_instrs:true) node in + F.asprintf "%t" pp +end + +(* =============== END of module Node =============== *) + +(** Map over nodes *) +module NodeMap = Caml.Map.Make (Node) (** Hash table with nodes as keys. *) +(** Hash table with nodes as keys. *) +module NodeHash = Hashtbl.Make (Node) (** Set of nodes. *) +(** Set of nodes. *) +module NodeSet = Node.NodeSet (** Map with node id keys. *) +(** Map with node id keys. *) +module IdMap = Node.IdMap + +(** procedure description *) +type t = + { attributes: ProcAttributes.t (** attributes of the procedure *) + ; mutable nodes: Node.t list (** list of nodes of this procedure *) + ; mutable nodes_num: int (** number of nodes *) + ; mutable start_node: Node.t (** start node of this procedure *) + ; mutable exit_node: Node.t (** exit node of ths procedure *) + ; mutable loop_heads: (** loop head nodes of this procedure *) NodeSet.t option } + [@@deriving compare] + +(** Only call from Cfg *) +let from_proc_attributes ~called_from_cfg attributes = + if not called_from_cfg then assert false ; + let pname_opt = Some attributes.ProcAttributes.proc_name in + let start_node = Node.dummy pname_opt in + let exit_node = Node.dummy pname_opt in + {attributes; nodes= []; nodes_num= 0; start_node; exit_node; loop_heads= None} + +(** Compute the distance of each node to the exit node, if not computed already *) +let compute_distance_to_exit_node pdesc = + let exit_node = pdesc.exit_node in + let rec mark_distance dist nodes = + let next_nodes = ref [] in + let do_node (node: Node.t) = + match node.dist_exit with + | Some _ + -> () + | None + -> node.dist_exit <- Some dist ; + next_nodes := node.preds @ !next_nodes + in + List.iter ~f:do_node nodes ; + if !next_nodes <> [] then mark_distance (dist + 1) !next_nodes + in + mark_distance 0 [exit_node] + +(** check or indicate if we have performed preanalysis on the CFG *) +let did_preanalysis pdesc = pdesc.attributes.did_preanalysis + +let signal_did_preanalysis pdesc = (pdesc.attributes).did_preanalysis <- true + +let get_attributes pdesc = pdesc.attributes + +let get_err_log pdesc = pdesc.attributes.err_log + +let get_exit_node pdesc = pdesc.exit_node + +(** Get flags for the proc desc *) +let get_flags pdesc = pdesc.attributes.proc_flags + +(** Return name and type of formal parameters *) +let get_formals pdesc = pdesc.attributes.formals + +let get_loc pdesc = pdesc.attributes.loc + +(** Return name and type of local variables *) +let get_locals pdesc = pdesc.attributes.locals + +(** Return name and type of captured variables *) +let get_captured pdesc = pdesc.attributes.captured + +(** Return the visibility attribute *) +let get_access pdesc = pdesc.attributes.access + +let get_nodes pdesc = pdesc.nodes + +let get_proc_name pdesc = pdesc.attributes.proc_name + +(** Return the return type of the procedure *) +let get_ret_type pdesc = pdesc.attributes.ret_type + +let get_ret_var pdesc = Pvar.mk Ident.name_return (get_proc_name pdesc) + +let get_start_node pdesc = pdesc.start_node + +(** List of nodes in the procedure sliced by a predicate up to the first branching *) +let get_sliced_slope pdesc f = + Node.get_generated_slope (get_start_node pdesc) (fun n -> Node.get_sliced_succs n f) + +(** List of nodes in the procedure up to the first branching *) +let get_slope pdesc = Node.get_generated_slope (get_start_node pdesc) Node.get_succs + +(** Return [true] iff the procedure is defined, and not just declared *) +let is_defined pdesc = pdesc.attributes.is_defined + +let is_body_empty pdesc = List.is_empty (Node.get_succs (get_start_node pdesc)) + +let is_java_synchronized pdesc = pdesc.attributes.is_java_synchronized_method + +let iter_nodes f pdesc = List.iter ~f (List.rev (get_nodes pdesc)) + +let fold_calls f acc pdesc = + let do_node a node = + List.fold + ~f:(fun b callee_pname -> f b (callee_pname, Node.get_loc node)) + ~init:a (Node.get_callees node) + in + List.fold ~f:do_node ~init:acc (get_nodes pdesc) + +(** iterate over the calls from the procedure: (callee,location) pairs *) +let iter_calls f pdesc = fold_calls (fun _ call -> f call) () pdesc + +let iter_instrs f pdesc = + let do_node node = List.iter ~f:(fun i -> f node i) (Node.get_instrs node) in + iter_nodes do_node pdesc + +let fold_nodes f acc pdesc = List.fold ~f ~init:acc (List.rev (get_nodes pdesc)) + +let fold_instrs f acc pdesc = + let fold_node acc node = + List.fold ~f:(fun acc instr -> f acc node instr) ~init:acc (Node.get_instrs node) + in + fold_nodes fold_node acc pdesc + +let iter_slope f pdesc = + let visited = ref NodeSet.empty in + let rec do_node node = + visited := NodeSet.add node !visited ; + f node ; + match Node.get_succs node with + | [n] + -> if not (NodeSet.mem n !visited) then do_node n + | _ + -> () + in + do_node (get_start_node pdesc) + +let iter_slope_calls f pdesc = + let do_node node = List.iter ~f:(fun callee_pname -> f callee_pname) (Node.get_callees node) in + iter_slope do_node pdesc + +(** iterate between two nodes or until we reach a branching structure *) +let iter_slope_range f src_node dst_node = + let visited = ref NodeSet.empty in + let rec do_node node = + visited := NodeSet.add node !visited ; + f node ; + match Node.get_succs node with + | [n] + -> if not (NodeSet.mem n !visited) && not (Node.equal node dst_node) then do_node n + | _ + -> () + in + do_node src_node + +(** Set the exit node of the proc desc *) +let set_exit_node pdesc node = pdesc.exit_node <- node + +(** Set a flag for the proc desc *) +let set_flag pdesc key value = ProcAttributes.proc_flags_add pdesc.attributes.proc_flags key value + +(** Set the start node of the proc desc *) +let set_start_node pdesc node = pdesc.start_node <- node + +(** Append the locals to the list of local variables *) +let append_locals pdesc new_locals = + (pdesc.attributes).locals <- pdesc.attributes.locals @ new_locals + +(** Set the successor nodes and exception nodes, and build predecessor links *) +let set_succs_exn_base (node: Node.t) succs exn = + node.succs <- succs ; + node.exn <- exn ; + List.iter ~f:(fun (n: Node.t) -> n.preds <- node :: n.preds) succs + +(** Create a new cfg node *) +let create_node pdesc loc kind instrs = + pdesc.nodes_num <- pdesc.nodes_num + 1 ; + let node_id = pdesc.nodes_num in + let node = + { Node.id= node_id + ; dist_exit= None + ; instrs + ; kind + ; loc + ; preds= [] + ; pname_opt= Some pdesc.attributes.proc_name + ; succs= [] + ; exn= [] } + in + pdesc.nodes <- node :: pdesc.nodes ; + node + +(** Set the successor and exception nodes. + If this is a join node right before the exit node, add an extra node in the middle, + otherwise nullify and abstract instructions cannot be added after a conditional. *) +let node_set_succs_exn pdesc (node: Node.t) succs exn = + match (node.kind, succs) with + | Join_node, [({Node.kind= Exit_node _} as exit_node)] + -> let kind = Node.Stmt_node "between_join_and_exit" in + let node' = create_node pdesc node.loc kind node.instrs in + set_succs_exn_base node [node'] exn ; + set_succs_exn_base node' [exit_node] exn + | _ + -> set_succs_exn_base node succs exn + +(** Get loop heads for widening. + It collects all target nodes of back-edges in a depth-first + traversal. + *) +let get_loop_heads pdesc = + let rec set_loop_head_rec visited heads wl = + match wl with + | [] + -> heads + | (n, ancester) :: wl' + -> if NodeSet.mem n visited then + if NodeSet.mem n ancester then set_loop_head_rec visited (NodeSet.add n heads) wl' + else set_loop_head_rec visited heads wl' + else + let ancester = NodeSet.add n ancester in + let succs = List.append (Node.get_succs n) (Node.get_exn n) in + let works = List.map ~f:(fun m -> (m, ancester)) succs in + set_loop_head_rec (NodeSet.add n visited) heads (List.append works wl') + in + let start_wl = [(get_start_node pdesc, NodeSet.empty)] in + let lh = set_loop_head_rec NodeSet.empty NodeSet.empty start_wl in + pdesc.loop_heads <- Some lh ; + lh + +let is_loop_head pdesc (node: Node.t) = + let lh = match pdesc.loop_heads with Some lh -> lh | None -> get_loop_heads pdesc in + NodeSet.mem node lh + +let pp_variable_list fmt etl = + if List.is_empty etl then Format.fprintf fmt "None" + else + List.iter + ~f:(fun (id, ty) -> Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) + etl + +let pp_objc_accessor fmt accessor = + match accessor with + | Some ProcAttributes.Objc_getter name + -> Format.fprintf fmt "Getter of %a, " Typ.Fieldname.pp name + | Some ProcAttributes.Objc_setter name + -> Format.fprintf fmt "Setter of %a, " Typ.Fieldname.pp name + | None + -> () + +let pp_signature fmt pdesc = + let attributes = get_attributes pdesc in + let pname = get_proc_name pdesc in + let pname_string = Typ.Procname.to_string pname in + let defined_string = match is_defined pdesc with true -> "defined" | false -> "undefined" in + Format.fprintf fmt "%s [%s, Return type: %s, %aFormals: %a, Locals: %a" pname_string + defined_string + (Typ.to_string (get_ret_type pdesc)) + pp_objc_accessor attributes.ProcAttributes.objc_accessor pp_variable_list (get_formals pdesc) + pp_variable_list (get_locals pdesc) ; + if not (List.is_empty (get_captured pdesc)) then + Format.fprintf fmt ", Captured: %a" pp_variable_list (get_captured pdesc) ; + let method_annotation = attributes.ProcAttributes.method_annotation in + if not (Annot.Method.is_empty method_annotation) then + Format.fprintf fmt ", Annotation: %a" (Annot.Method.pp pname_string) method_annotation ; + Format.fprintf fmt "]@\n" diff --git a/infer/src/IR/Procdesc.mli b/infer/src/IR/Procdesc.mli new file mode 100644 index 000000000..6ac5adc87 --- /dev/null +++ b/infer/src/IR/Procdesc.mli @@ -0,0 +1,317 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** node of the control flow graph *) + +module Node : sig + (** type of nodes *) + + type t [@@deriving compare] + + (** node id *) + + type id = private int [@@deriving compare] + + val equal_id : id -> id -> bool + + (** kind of cfg node *) + + type nodekind = + | Start_node of Typ.Procname.t + | Exit_node of Typ.Procname.t + | Stmt_node of string + | Join_node + | Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *) + | Skip_node of string + [@@deriving compare] + + val equal_nodekind : nodekind -> nodekind -> bool + + (** kind of Stmt_node for an exception handler. *) + + val exn_handler_kind : nodekind + + (** kind of Stmt_node for an exceptions sink. *) + + val exn_sink_kind : nodekind + + (** kind of Stmt_node for a throw instruction. *) + + val throw_kind : nodekind + + (** Add declarations for local variables and return variable to the node *) + + val add_locals_ret_declaration : t -> ProcAttributes.t -> (Mangled.t * Typ.t) list -> unit + + (** Append the instructions to the list of instructions to execute *) + + val append_instrs : t -> Sil.instr list -> unit + + (** Dump extended instructions for the node *) + + val d_instrs : sub_instrs:bool -> Sil.instr option -> t -> unit + + (** Create a dummy node *) + + val dummy : Typ.Procname.t option -> t + + (** Check if two nodes are equal *) + + val equal : t -> t -> bool + + (** Get the list of callee procnames from the node *) + + val get_callees : t -> Typ.Procname.t list + + (** Return a description of the node *) + + val get_description : Pp.env -> t -> string + + (** Get the distance to the exit node, if it has been computed *) + + val get_distance_to_exit : t -> int option + + (** Get the exception nodes from the current node *) + + val get_exn : t -> t list + + (** Get a list of unique nodes until the first branch starting + from a node with subsequent applications of a generator function *) + + val get_generated_slope : t -> (t -> t list) -> t list + + (** Get the unique id of the node *) + + val get_id : t -> id + + (** Get the instructions to be executed *) + + val get_instrs : t -> Sil.instr list + + (** Get the kind of the current node *) + + val get_kind : t -> nodekind + + (** Get the source location of the last instruction in the node *) + + val get_last_loc : t -> Location.t + + (** Get the source location of the node *) + + val get_loc : t -> Location.t + + (** Get the predecessor nodes of the current node *) + + val get_preds : t -> t list + + (** Get the name of the procedure the node belongs to *) + + val get_proc_name : t -> Typ.Procname.t + + (** Get the predecessor nodes of a node where the given predicate evaluates to true *) + + val get_sliced_preds : t -> (t -> bool) -> t list + + (** Get the successor nodes of a node where the given predicate evaluates to true *) + + val get_sliced_succs : t -> (t -> bool) -> t list + + (** Get the successor nodes of the current node *) + + val get_succs : t -> t list + + (** Hash function for nodes *) + + val hash : t -> int + + (** Pretty print the node *) + + val pp : Format.formatter -> t -> unit + + (** Pretty print a node id *) + + val pp_id : Format.formatter -> id -> unit + + (** Print extended instructions for the node, + highlighting the given subinstruction if present *) + + val pp_instrs : Pp.env -> sub_instrs:bool -> Sil.instr option -> Format.formatter -> t -> unit + + (** Replace the instructions to be executed. *) + + val replace_instrs : t -> Sil.instr list -> unit +end + +(** Map with node id keys. *) + +module IdMap : Caml.Map.S with type key = Node.id + +(** Hash table with nodes as keys. *) + +module NodeHash : Caml.Hashtbl.S with type key = Node.t + +(** Map over nodes. *) + +module NodeMap : Caml.Map.S with type key = Node.t + +(** Set of nodes. *) + +module NodeSet : Caml.Set.S with type elt = Node.t + +(** procedure descriptions *) + +(** proc description *) + +type t [@@deriving compare] + +(** append a list of new local variables to the existing list of local variables *) + +val append_locals : t -> (Mangled.t * Typ.t) list -> unit + +(** Compute the distance of each node to the exit node, if not computed already *) + +val compute_distance_to_exit_node : t -> unit + +(** Create a new cfg node with the given location, kind, list of instructions, + and add it to the procdesc. *) + +val create_node : t -> Location.t -> Node.nodekind -> Sil.instr list -> Node.t + +(** true if we ran the preanalysis on the CFG associated with [t] *) + +val did_preanalysis : t -> bool + +(** fold over the calls from the procedure: (callee, location) pairs *) + +val fold_calls : ('a -> Typ.Procname.t * Location.t -> 'a) -> 'a -> t -> 'a + +(** fold over all nodes and their instructions *) + +val fold_instrs : ('a -> Node.t -> Sil.instr -> 'a) -> 'a -> t -> 'a + +(** fold over all nodes *) + +val fold_nodes : ('a -> Node.t -> 'a) -> 'a -> t -> 'a + +(** Only call from Cfg. *) + +val from_proc_attributes : called_from_cfg:bool -> ProcAttributes.t -> t + +(** Return the visibility attribute *) + +val get_access : t -> PredSymb.access + +(** Get the attributes of the procedure. *) + +val get_attributes : t -> ProcAttributes.t + +(** Return name and type of block's captured variables *) + +val get_captured : t -> (Mangled.t * Typ.t) list + +val get_err_log : t -> Errlog.t + +val get_exit_node : t -> Node.t + +(** Get flags for the proc desc *) + +val get_flags : t -> ProcAttributes.proc_flags + +(** Return name and type of formal parameters *) + +val get_formals : t -> (Mangled.t * Typ.t) list + +(** Return loc information for the procedure *) + +val get_loc : t -> Location.t + +(** Return name and type of local variables *) + +val get_locals : t -> (Mangled.t * Typ.t) list + +val get_nodes : t -> Node.t list + +val get_proc_name : t -> Typ.Procname.t + +(** Return the return type of the procedure and type string *) + +val get_ret_type : t -> Typ.t + +val get_ret_var : t -> Pvar.t + +(** Get the sliced procedure's nodes up until the first branching *) + +val get_sliced_slope : t -> (Node.t -> bool) -> Node.t list + +(** Get the procedure's nodes up until the first branching *) + +val get_slope : t -> Node.t list + +val get_start_node : t -> Node.t + +(** Return [true] iff the procedure is defined, and not just declared *) + +val is_defined : t -> bool + +(** Return [true] if the body of the procdesc is empty (no instructions) *) + +val is_body_empty : t -> bool + +(** Return [true] if the procedure signature has the Java synchronized keyword *) + +val is_java_synchronized : t -> bool + +(** iterate over the calls from the procedure: (callee, location) pairs *) + +val iter_calls : (Typ.Procname.t * Location.t -> unit) -> t -> unit + +(** iterate over all nodes and their instructions *) + +val iter_instrs : (Node.t -> Sil.instr -> unit) -> t -> unit + +(** iterate over all the nodes of a procedure *) + +val iter_nodes : (Node.t -> unit) -> t -> unit + +(** iterate over all nodes until we reach a branching structure *) + +val iter_slope : (Node.t -> unit) -> t -> unit + +(** iterate over all calls until we reach a branching structure *) + +val iter_slope_calls : (Typ.Procname.t -> unit) -> t -> unit + +(** iterate between two nodes or until we reach a branching structure *) + +val iter_slope_range : (Node.t -> unit) -> Node.t -> Node.t -> unit + +(** Set the successor nodes and exception nodes, and build predecessor links *) + +val node_set_succs_exn : t -> Node.t -> Node.t list -> Node.t list -> unit + +(** Set the exit node of the procedure *) + +val set_exit_node : t -> Node.t -> unit + +(** Set a flag for the proc desc *) + +val set_flag : t -> string -> string -> unit + +val set_start_node : t -> Node.t -> unit + +(** indicate that we have performed preanalysis on the CFG assoociated with [t] *) + +val signal_did_preanalysis : t -> unit + +val is_loop_head : t -> Node.t -> bool + +val pp_signature : Format.formatter -> t -> unit diff --git a/infer/src/IR/Procdesc.re b/infer/src/IR/Procdesc.re deleted file mode 100644 index ce947dcff..000000000 --- a/infer/src/IR/Procdesc.re +++ /dev/null @@ -1,605 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - -module L = Logging; - -module F = Format; - -/* =============== START of module Node =============== */ -module Node = { - type id = int [@@deriving compare]; - let equal_id = [%compare.equal : id]; - type nodekind = - | Start_node Typ.Procname.t - | Exit_node Typ.Procname.t - | Stmt_node string - | Join_node - | Prune_node bool Sil.if_kind string /** (true/false branch, if_kind, comment) */ - | Skip_node string - [@@deriving compare]; - let equal_nodekind = [%compare.equal : nodekind]; - - /** a node */ - type t = { - /** unique id of the node */ - id, - /** distance to the exit node */ - mutable dist_exit: option int, - /** exception nodes in the cfg */ - mutable exn: list t, - /** instructions for symbolic execution */ - mutable instrs: list Sil.instr, - /** kind of node */ - kind: nodekind, - /** location in the source code */ - loc: Location.t, - /** predecessor nodes in the cfg */ - mutable preds: list t, - /** name of the procedure the node belongs to */ - pname_opt: option Typ.Procname.t, - /** successor nodes in the cfg */ - mutable succs: list t - }; - let exn_handler_kind = Stmt_node "exception handler"; - let exn_sink_kind = Stmt_node "exceptions sink"; - let throw_kind = Stmt_node "throw"; - let dummy pname_opt => { - id: 0, - dist_exit: None, - instrs: [], - kind: Skip_node "dummy", - loc: Location.dummy, - pname_opt, - succs: [], - preds: [], - exn: [] - }; - let compare node1 node2 => Int.compare node1.id node2.id; - let hash node => Hashtbl.hash node.id; - let equal = [%compare.equal : t]; - - /** Get the unique id of the node */ - let get_id node => node.id; - let get_succs node => node.succs; - type node = t; - module NodeSet = - Caml.Set.Make { - type t = node; - let compare = compare; - }; - module IdMap = - Caml.Map.Make { - type t = id; - let compare = compare_id; - }; - let get_sliced_succs node f => { - let visited = ref NodeSet.empty; - let rec slice_nodes nodes :NodeSet.t => { - let do_node acc n => { - visited := NodeSet.add n !visited; - if (f n) { - NodeSet.singleton n - } else { - NodeSet.union - acc (slice_nodes (List.filter f::(fun s => not (NodeSet.mem s !visited)) n.succs)) - } - }; - List.fold f::do_node init::NodeSet.empty nodes - }; - NodeSet.elements (slice_nodes node.succs) - }; - let get_sliced_preds node f => { - let visited = ref NodeSet.empty; - let rec slice_nodes nodes :NodeSet.t => { - let do_node acc n => { - visited := NodeSet.add n !visited; - if (f n) { - NodeSet.singleton n - } else { - NodeSet.union - acc (slice_nodes (List.filter f::(fun s => not (NodeSet.mem s !visited)) n.preds)) - } - }; - List.fold f::do_node init::NodeSet.empty nodes - }; - NodeSet.elements (slice_nodes node.preds) - }; - let get_exn node => node.exn; - - /** Get the name of the procedure the node belongs to */ - let get_proc_name node => - switch node.pname_opt { - | None => - L.internal_error "get_proc_name: at node %d@\n" node.id; - assert false - | Some pname => pname - }; - - /** Get the predecessors of the node */ - let get_preds node => node.preds; - - /** Generates a list of nodes starting at a given node - and recursively adding the results of the generator */ - let get_generated_slope start_node generator => { - let visited = ref NodeSet.empty; - let rec nodes n => { - visited := NodeSet.add n !visited; - let succs = List.filter f::(fun n => not (NodeSet.mem n !visited)) (generator n); - switch succs { - | [hd] => [n, ...nodes hd] - | _ => [n] - } - }; - nodes start_node - }; - - /** Get the node kind */ - let get_kind node => node.kind; - - /** Get the instructions to be executed */ - let get_instrs node => node.instrs; - - /** Get the list of callee procnames from the node */ - let get_callees node => { - let collect callees instr => - switch instr { - | Sil.Call _ exp _ _ _ => - switch exp { - | Exp.Const (Const.Cfun procname) => [procname, ...callees] - | _ => callees - } - | _ => callees - }; - List.fold f::collect init::[] (get_instrs node) - }; - - /** Get the location of the node */ - let get_loc n => n.loc; - - /** Get the source location of the last instruction in the node */ - let get_last_loc n => - switch (List.rev (get_instrs n)) { - | [instr, ..._] => Sil.instr_get_loc instr - | [] => n.loc - }; - let pp_id f id => F.fprintf f "%d" id; - let pp f node => pp_id f (get_id node); - let get_distance_to_exit node => node.dist_exit; - - /** Append the instructions to the list of instructions to execute */ - let append_instrs node instrs => node.instrs = node.instrs @ instrs; - - /** Add the instructions at the beginning of the list of instructions to execute */ - let prepend_instrs node instrs => node.instrs = instrs @ node.instrs; - - /** Replace the instructions to be executed. */ - let replace_instrs node instrs => node.instrs = instrs; - - /** Add declarations for local variables and return variable to the node */ - let add_locals_ret_declaration node (proc_attributes: ProcAttributes.t) locals => { - let loc = get_loc node; - let pname = proc_attributes.proc_name; - let ret_var = { - let ret_type = proc_attributes.ret_type; - (Pvar.get_ret_pvar pname, ret_type) - }; - let construct_decl (x, typ) => (Pvar.mk x pname, typ); - let ptl = [ret_var, ...List.map f::construct_decl locals]; - let instr = Sil.Declare_locals ptl loc; - prepend_instrs node [instr] - }; - - /** Print extended instructions for the node, - highlighting the given subinstruction if present */ - let pp_instrs pe0 ::sub_instrs instro fmt node => { - let pe = - switch instro { - | None => pe0 - | Some instr => Pp.extend_colormap pe0 (Obj.repr instr) Red - }; - let instrs = get_instrs node; - let pp_loc fmt () => F.fprintf fmt " %a " Location.pp (get_loc node); - let print_sub_instrs () => F.fprintf fmt "%a" (Sil.pp_instr_list pe) instrs; - switch (get_kind node) { - | Stmt_node s => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "statements (%s) %a" s pp_loc () - } - | Prune_node _ _ descr => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "assume %s %a" descr pp_loc () - } - | Exit_node _ => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "exit %a" pp_loc () - } - | Skip_node s => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "skip (%s) %a" s pp_loc () - } - | Start_node _ => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "start %a" pp_loc () - } - | Join_node => - if sub_instrs { - print_sub_instrs () - } else { - F.fprintf fmt "join %a" pp_loc () - } - } - }; - - /** Dump extended instructions for the node */ - let d_instrs sub_instrs::(sub_instrs: bool) (curr_instr: option Sil.instr) (node: t) => - L.add_print_action (L.PTnode_instrs, Obj.repr (sub_instrs, curr_instr, node)); - - /** Return a description of the cfg node */ - let get_description pe node => { - let str = - switch (get_kind node) { - | Stmt_node _ => "Instructions" - | Prune_node _ _ descr => "Conditional" ^ " " ^ descr - | Exit_node _ => "Exit" - | Skip_node _ => "Skip" - | Start_node _ => "Start" - | Join_node => "Join" - }; - let pp fmt => F.fprintf fmt "%s@\n%a@?" str (pp_instrs pe None sub_instrs::true) node; - F.asprintf "%t" pp - }; -}; - -/* =============== END of module Node =============== */ - -/** Map over nodes */ -module NodeMap = Caml.Map.Make Node; - - -/** Hash table with nodes as keys. */ -module NodeHash = Hashtbl.Make Node; - - -/** Set of nodes. */ -module NodeSet = Node.NodeSet; - - -/** Map with node id keys. */ -module IdMap = Node.IdMap; - - -/** procedure description */ -type t = { - attributes: ProcAttributes.t, /** attributes of the procedure */ - mutable nodes: list Node.t, /** list of nodes of this procedure */ - mutable nodes_num: int, /** number of nodes */ - mutable start_node: Node.t, /** start node of this procedure */ - mutable exit_node: Node.t, /** exit node of ths procedure */ - mutable loop_heads: option NodeSet.t /** loop head nodes of this procedure */ -} -[@@deriving compare]; - - -/** Only call from Cfg */ -let from_proc_attributes ::called_from_cfg attributes => { - if (not called_from_cfg) { - assert false - }; - let pname_opt = Some attributes.ProcAttributes.proc_name; - let start_node = Node.dummy pname_opt; - let exit_node = Node.dummy pname_opt; - {attributes, nodes: [], nodes_num: 0, start_node, exit_node, loop_heads: None} -}; - - -/** Compute the distance of each node to the exit node, if not computed already */ -let compute_distance_to_exit_node pdesc => { - let exit_node = pdesc.exit_node; - let rec mark_distance dist nodes => { - let next_nodes = ref []; - let do_node (node: Node.t) => - switch node.dist_exit { - | Some _ => () - | None => - node.dist_exit = Some dist; - next_nodes := node.preds @ !next_nodes - }; - List.iter f::do_node nodes; - if (!next_nodes != []) { - mark_distance (dist + 1) !next_nodes - } - }; - mark_distance 0 [exit_node] -}; - - -/** check or indicate if we have performed preanalysis on the CFG */ -let did_preanalysis pdesc => pdesc.attributes.did_preanalysis; - -let signal_did_preanalysis pdesc => pdesc.attributes.did_preanalysis = true; - -let get_attributes pdesc => pdesc.attributes; - -let get_err_log pdesc => pdesc.attributes.err_log; - -let get_exit_node pdesc => pdesc.exit_node; - - -/** Get flags for the proc desc */ -let get_flags pdesc => pdesc.attributes.proc_flags; - - -/** Return name and type of formal parameters */ -let get_formals pdesc => pdesc.attributes.formals; - -let get_loc pdesc => pdesc.attributes.loc; - - -/** Return name and type of local variables */ -let get_locals pdesc => pdesc.attributes.locals; - - -/** Return name and type of captured variables */ -let get_captured pdesc => pdesc.attributes.captured; - - -/** Return the visibility attribute */ -let get_access pdesc => pdesc.attributes.access; - -let get_nodes pdesc => pdesc.nodes; - -let get_proc_name pdesc => pdesc.attributes.proc_name; - - -/** Return the return type of the procedure */ -let get_ret_type pdesc => pdesc.attributes.ret_type; - -let get_ret_var pdesc => Pvar.mk Ident.name_return (get_proc_name pdesc); - -let get_start_node pdesc => pdesc.start_node; - - -/** List of nodes in the procedure sliced by a predicate up to the first branching */ -let get_sliced_slope pdesc f => - Node.get_generated_slope (get_start_node pdesc) (fun n => Node.get_sliced_succs n f); - - -/** List of nodes in the procedure up to the first branching */ -let get_slope pdesc => Node.get_generated_slope (get_start_node pdesc) Node.get_succs; - - -/** Return [true] iff the procedure is defined, and not just declared */ -let is_defined pdesc => pdesc.attributes.is_defined; - -let is_body_empty pdesc => List.is_empty (Node.get_succs (get_start_node pdesc)); - -let is_java_synchronized pdesc => pdesc.attributes.is_java_synchronized_method; - -let iter_nodes f pdesc => List.iter ::f (List.rev (get_nodes pdesc)); - -let fold_calls f acc pdesc => { - let do_node a node => - List.fold - f::(fun b callee_pname => f b (callee_pname, Node.get_loc node)) - init::a - (Node.get_callees node); - List.fold f::do_node init::acc (get_nodes pdesc) -}; - - -/** iterate over the calls from the procedure: (callee,location) pairs */ -let iter_calls f pdesc => fold_calls (fun _ call => f call) () pdesc; - -let iter_instrs f pdesc => { - let do_node node => List.iter f::(fun i => f node i) (Node.get_instrs node); - iter_nodes do_node pdesc -}; - -let fold_nodes f acc pdesc => List.fold ::f init::acc (List.rev (get_nodes pdesc)); - -let fold_instrs f acc pdesc => { - let fold_node acc node => - List.fold f::(fun acc instr => f acc node instr) init::acc (Node.get_instrs node); - fold_nodes fold_node acc pdesc -}; - -let iter_slope f pdesc => { - let visited = ref NodeSet.empty; - let rec do_node node => { - visited := NodeSet.add node !visited; - f node; - switch (Node.get_succs node) { - | [n] => - if (not (NodeSet.mem n !visited)) { - do_node n - } - | _ => () - } - }; - do_node (get_start_node pdesc) -}; - -let iter_slope_calls f pdesc => { - let do_node node => List.iter f::(fun callee_pname => f callee_pname) (Node.get_callees node); - iter_slope do_node pdesc -}; - - -/** iterate between two nodes or until we reach a branching structure */ -let iter_slope_range f src_node dst_node => { - let visited = ref NodeSet.empty; - let rec do_node node => { - visited := NodeSet.add node !visited; - f node; - switch (Node.get_succs node) { - | [n] => - if (not (NodeSet.mem n !visited) && not (Node.equal node dst_node)) { - do_node n - } - | _ => () - } - }; - do_node src_node -}; - - -/** Set the exit node of the proc desc */ -let set_exit_node pdesc node => pdesc.exit_node = node; - - -/** Set a flag for the proc desc */ -let set_flag pdesc key value => - ProcAttributes.proc_flags_add pdesc.attributes.proc_flags key value; - - -/** Set the start node of the proc desc */ -let set_start_node pdesc node => pdesc.start_node = node; - - -/** Append the locals to the list of local variables */ -let append_locals pdesc new_locals => - pdesc.attributes.locals = pdesc.attributes.locals @ new_locals; - - -/** Set the successor nodes and exception nodes, and build predecessor links */ -let set_succs_exn_base (node: Node.t) succs exn => { - node.succs = succs; - node.exn = exn; - List.iter f::(fun (n: Node.t) => n.preds = [node, ...n.preds]) succs -}; - - -/** Create a new cfg node */ -let create_node pdesc loc kind instrs => { - pdesc.nodes_num = pdesc.nodes_num + 1; - let node_id = pdesc.nodes_num; - let node = { - Node.id: node_id, - dist_exit: None, - instrs, - kind, - loc, - preds: [], - pname_opt: Some pdesc.attributes.proc_name, - succs: [], - exn: [] - }; - pdesc.nodes = [node, ...pdesc.nodes]; - node -}; - - -/** Set the successor and exception nodes. - If this is a join node right before the exit node, add an extra node in the middle, - otherwise nullify and abstract instructions cannot be added after a conditional. */ -let node_set_succs_exn pdesc (node: Node.t) succs exn => - switch (node.kind, succs) { - | (Join_node, [{Node.kind: Exit_node _} as exit_node]) => - let kind = Node.Stmt_node "between_join_and_exit"; - let node' = create_node pdesc node.loc kind node.instrs; - set_succs_exn_base node [node'] exn; - set_succs_exn_base node' [exit_node] exn - | _ => set_succs_exn_base node succs exn - }; - - -/** Get loop heads for widening. - It collects all target nodes of back-edges in a depth-first - traversal. - */ -let get_loop_heads pdesc => { - let rec set_loop_head_rec visited heads wl => - switch wl { - | [] => heads - | [(n, ancester), ...wl'] => - if (NodeSet.mem n visited) { - if (NodeSet.mem n ancester) { - set_loop_head_rec visited (NodeSet.add n heads) wl' - } else { - set_loop_head_rec visited heads wl' - } - } else { - let ancester = NodeSet.add n ancester; - let succs = List.append (Node.get_succs n) (Node.get_exn n); - let works = List.map f::(fun m => (m, ancester)) succs; - set_loop_head_rec (NodeSet.add n visited) heads (List.append works wl') - } - }; - let start_wl = [(get_start_node pdesc, NodeSet.empty)]; - let lh = set_loop_head_rec NodeSet.empty NodeSet.empty start_wl; - pdesc.loop_heads = Some lh; - lh -}; - -let is_loop_head pdesc (node: Node.t) => { - let lh = - switch pdesc.loop_heads { - | Some lh => lh - | None => get_loop_heads pdesc - }; - NodeSet.mem node lh -}; - -let pp_variable_list fmt etl => - if (List.is_empty etl) { - Format.fprintf fmt "None" - } else { - List.iter - f::(fun (id, ty) => Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) etl - }; - -let pp_objc_accessor fmt accessor => - switch accessor { - | Some (ProcAttributes.Objc_getter name) => - Format.fprintf fmt "Getter of %a, " Typ.Fieldname.pp name - | Some (ProcAttributes.Objc_setter name) => - Format.fprintf fmt "Setter of %a, " Typ.Fieldname.pp name - | None => () - }; - -let pp_signature fmt pdesc => { - let attributes = get_attributes pdesc; - let pname = get_proc_name pdesc; - let pname_string = Typ.Procname.to_string pname; - let defined_string = is_defined pdesc ? "defined" : "undefined"; - Format.fprintf - fmt - "%s [%s, Return type: %s, %aFormals: %a, Locals: %a" - pname_string - defined_string - (Typ.to_string (get_ret_type pdesc)) - pp_objc_accessor - attributes.ProcAttributes.objc_accessor - pp_variable_list - (get_formals pdesc) - pp_variable_list - (get_locals pdesc); - if (not (List.is_empty (get_captured pdesc))) { - Format.fprintf fmt ", Captured: %a" pp_variable_list (get_captured pdesc) - }; - let method_annotation = attributes.ProcAttributes.method_annotation; - if (not (Annot.Method.is_empty method_annotation)) { - Format.fprintf fmt ", Annotation: %a" (Annot.Method.pp pname_string) method_annotation - }; - Format.fprintf fmt "]@\n" -}; diff --git a/infer/src/IR/Procdesc.rei b/infer/src/IR/Procdesc.rei deleted file mode 100644 index e82774ccb..000000000 --- a/infer/src/IR/Procdesc.rei +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** node of the control flow graph */ -module Node: { - - /** type of nodes */ - type t [@@deriving compare]; - - /** node id */ - type id = pri int [@@deriving compare]; - let equal_id: id => id => bool; - - /** kind of cfg node */ - type nodekind = - | Start_node Typ.Procname.t - | Exit_node Typ.Procname.t - | Stmt_node string - | Join_node - | Prune_node bool Sil.if_kind string /** (true/false branch, if_kind, comment) */ - | Skip_node string - [@@deriving compare]; - let equal_nodekind: nodekind => nodekind => bool; - - /** kind of Stmt_node for an exception handler. */ - let exn_handler_kind: nodekind; - - /** kind of Stmt_node for an exceptions sink. */ - let exn_sink_kind: nodekind; - - /** kind of Stmt_node for a throw instruction. */ - let throw_kind: nodekind; - - /** Add declarations for local variables and return variable to the node */ - let add_locals_ret_declaration: t => ProcAttributes.t => list (Mangled.t, Typ.t) => unit; - - /** Append the instructions to the list of instructions to execute */ - let append_instrs: t => list Sil.instr => unit; - - /** Dump extended instructions for the node */ - let d_instrs: sub_instrs::bool => option Sil.instr => t => unit; - - /** Create a dummy node */ - let dummy: option Typ.Procname.t => t; - - /** Check if two nodes are equal */ - let equal: t => t => bool; - - /** Get the list of callee procnames from the node */ - let get_callees: t => list Typ.Procname.t; - - /** Return a description of the node */ - let get_description: Pp.env => t => string; - - /** Get the distance to the exit node, if it has been computed */ - let get_distance_to_exit: t => option int; - - /** Get the exception nodes from the current node */ - let get_exn: t => list t; - - /** Get a list of unique nodes until the first branch starting - from a node with subsequent applications of a generator function */ - let get_generated_slope: t => (t => list t) => list t; - - /** Get the unique id of the node */ - let get_id: t => id; - - /** Get the instructions to be executed */ - let get_instrs: t => list Sil.instr; - - /** Get the kind of the current node */ - let get_kind: t => nodekind; - - /** Get the source location of the last instruction in the node */ - let get_last_loc: t => Location.t; - - /** Get the source location of the node */ - let get_loc: t => Location.t; - - /** Get the predecessor nodes of the current node */ - let get_preds: t => list t; - - /** Get the name of the procedure the node belongs to */ - let get_proc_name: t => Typ.Procname.t; - - /** Get the predecessor nodes of a node where the given predicate evaluates to true */ - let get_sliced_preds: t => (t => bool) => list t; - - /** Get the successor nodes of a node where the given predicate evaluates to true */ - let get_sliced_succs: t => (t => bool) => list t; - - /** Get the successor nodes of the current node */ - let get_succs: t => list t; - - /** Hash function for nodes */ - let hash: t => int; - - /** Pretty print the node */ - let pp: Format.formatter => t => unit; - - /** Pretty print a node id */ - let pp_id: Format.formatter => id => unit; - - /** Print extended instructions for the node, - highlighting the given subinstruction if present */ - let pp_instrs: Pp.env => sub_instrs::bool => option Sil.instr => Format.formatter => t => unit; - - /** Replace the instructions to be executed. */ - let replace_instrs: t => list Sil.instr => unit; -}; - - -/** Map with node id keys. */ -module IdMap: Caml.Map.S with type key = Node.id; - - -/** Hash table with nodes as keys. */ -module NodeHash: Caml.Hashtbl.S with type key = Node.t; - - -/** Map over nodes. */ -module NodeMap: Caml.Map.S with type key = Node.t; - - -/** Set of nodes. */ -module NodeSet: Caml.Set.S with type elt = Node.t; - - -/** procedure descriptions */ - -/** proc description */ -type t [@@deriving compare]; - - -/** append a list of new local variables to the existing list of local variables */ -let append_locals: t => list (Mangled.t, Typ.t) => unit; - - -/** Compute the distance of each node to the exit node, if not computed already */ -let compute_distance_to_exit_node: t => unit; - - -/** Create a new cfg node with the given location, kind, list of instructions, - and add it to the procdesc. */ -let create_node: t => Location.t => Node.nodekind => list Sil.instr => Node.t; - - -/** true if we ran the preanalysis on the CFG associated with [t] */ -let did_preanalysis: t => bool; - - -/** fold over the calls from the procedure: (callee, location) pairs */ -let fold_calls: ('a => (Typ.Procname.t, Location.t) => 'a) => 'a => t => 'a; - - -/** fold over all nodes and their instructions */ -let fold_instrs: ('a => Node.t => Sil.instr => 'a) => 'a => t => 'a; - - -/** fold over all nodes */ -let fold_nodes: ('a => Node.t => 'a) => 'a => t => 'a; - - -/** Only call from Cfg. */ -let from_proc_attributes: called_from_cfg::bool => ProcAttributes.t => t; - - -/** Return the visibility attribute */ -let get_access: t => PredSymb.access; - - -/** Get the attributes of the procedure. */ -let get_attributes: t => ProcAttributes.t; - - -/** Return name and type of block's captured variables */ -let get_captured: t => list (Mangled.t, Typ.t); - -let get_err_log: t => Errlog.t; - -let get_exit_node: t => Node.t; - - -/** Get flags for the proc desc */ -let get_flags: t => ProcAttributes.proc_flags; - - -/** Return name and type of formal parameters */ -let get_formals: t => list (Mangled.t, Typ.t); - - -/** Return loc information for the procedure */ -let get_loc: t => Location.t; - - -/** Return name and type of local variables */ -let get_locals: t => list (Mangled.t, Typ.t); - -let get_nodes: t => list Node.t; - -let get_proc_name: t => Typ.Procname.t; - - -/** Return the return type of the procedure and type string */ -let get_ret_type: t => Typ.t; - -let get_ret_var: t => Pvar.t; - - -/** Get the sliced procedure's nodes up until the first branching */ -let get_sliced_slope: t => (Node.t => bool) => list Node.t; - - -/** Get the procedure's nodes up until the first branching */ -let get_slope: t => list Node.t; - -let get_start_node: t => Node.t; - - -/** Return [true] iff the procedure is defined, and not just declared */ -let is_defined: t => bool; - - -/** Return [true] if the body of the procdesc is empty (no instructions) */ -let is_body_empty: t => bool; - - -/** Return [true] if the procedure signature has the Java synchronized keyword */ -let is_java_synchronized: t => bool; - - -/** iterate over the calls from the procedure: (callee, location) pairs */ -let iter_calls: ((Typ.Procname.t, Location.t) => unit) => t => unit; - - -/** iterate over all nodes and their instructions */ -let iter_instrs: (Node.t => Sil.instr => unit) => t => unit; - - -/** iterate over all the nodes of a procedure */ -let iter_nodes: (Node.t => unit) => t => unit; - - -/** iterate over all nodes until we reach a branching structure */ -let iter_slope: (Node.t => unit) => t => unit; - - -/** iterate over all calls until we reach a branching structure */ -let iter_slope_calls: (Typ.Procname.t => unit) => t => unit; - - -/** iterate between two nodes or until we reach a branching structure */ -let iter_slope_range: (Node.t => unit) => Node.t => Node.t => unit; - - -/** Set the successor nodes and exception nodes, and build predecessor links */ -let node_set_succs_exn: t => Node.t => list Node.t => list Node.t => unit; - - -/** Set the exit node of the procedure */ -let set_exit_node: t => Node.t => unit; - - -/** Set a flag for the proc desc */ -let set_flag: t => string => string => unit; - -let set_start_node: t => Node.t => unit; - - -/** indicate that we have performed preanalysis on the CFG assoociated with [t] */ -let signal_did_preanalysis: t => unit; - -let is_loop_head: t => Node.t => bool; - -let pp_signature: Format.formatter => t => unit; diff --git a/infer/src/IR/Pvar.ml b/infer/src/IR/Pvar.ml new file mode 100644 index 000000000..8630f8854 --- /dev/null +++ b/infer/src/IR/Pvar.ml @@ -0,0 +1,258 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language *) +open! IStd +module L = Logging +module F = Format + +type translation_unit = TUFile of SourceFile.t | TUExtern [@@deriving compare] + +(** Kind of global variables *) +type pvar_kind = + | Local_var of Typ.Procname.t (** local variable belonging to a function *) + | Callee_var of Typ.Procname.t (** local variable belonging to a callee *) + | Abduced_retvar of Typ.Procname.t * Location.t + (** synthetic variable to represent return value *) + | Abduced_ref_param of Typ.Procname.t * t * Location.t + | Abduced_ref_param_val of Typ.Procname.t * Ident.t * Location.t + (** synthetic variable to represent param passed by reference *) + | Global_var of (translation_unit * bool * bool * bool) + (** global variable: translation unit + is it compile constant? + is it POD? + is it a static + local? *) + | Seed_var (** variable used to store the initial value of formal parameters *) + [@@deriving compare] + +(** Names for program variables. *) +and t = {pv_hash: int; pv_name: Mangled.t; pv_kind: pvar_kind} [@@deriving compare] + +let equal = [%compare.equal : t] + +let pp_translation_unit fmt = function + | TUFile fname + -> SourceFile.pp fmt fname + | TUExtern + -> Format.fprintf fmt "EXTERN" + +let rec _pp f pv = + let name = pv.pv_name in + match pv.pv_kind with + | Local_var n + -> if !Config.pp_simple then F.fprintf f "%a" Mangled.pp name + else F.fprintf f "%a$%a" Typ.Procname.pp n Mangled.pp name + | Callee_var n + -> if !Config.pp_simple then F.fprintf f "%a|callee" Mangled.pp name + else F.fprintf f "%a$%a|callee" Typ.Procname.pp n Mangled.pp name + | Abduced_retvar (n, l) + -> if !Config.pp_simple then F.fprintf f "%a|abducedRetvar" Mangled.pp name + else F.fprintf f "%a$%a%a|abducedRetvar" Typ.Procname.pp n Location.pp l Mangled.pp name + | Abduced_ref_param (n, pv, l) + -> if !Config.pp_simple then F.fprintf f "%a|%a|abducedRefParam" _pp pv Mangled.pp name + else F.fprintf f "%a$%a%a|abducedRefParam" Typ.Procname.pp n Location.pp l Mangled.pp name + | Abduced_ref_param_val (n, id, l) + -> if !Config.pp_simple then + F.fprintf f "%a|%a|abducedRefParamVal" (Ident.pp Pp.text) id Mangled.pp name + else F.fprintf f "%a$%a%a|abducedRefParamVal" Typ.Procname.pp n Location.pp l Mangled.pp name + | Global_var (translation_unit, is_const, is_pod, _) + -> F.fprintf f "#GB<%a%s%s>$%a" pp_translation_unit translation_unit + (if is_const then "|const" else "") + (if not is_pod then "|!pod" else "") + Mangled.pp name + | Seed_var + -> F.fprintf f "old_%a" Mangled.pp name + +(** Pretty print a program variable in latex. *) +let pp_latex f pv = + let name = pv.pv_name in + match pv.pv_kind with + | Local_var _ + -> Latex.pp_string Latex.Roman f (Mangled.to_string name) + | Callee_var _ + -> F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "callee" + | Abduced_retvar _ + -> F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "abducedRetvar" + | Abduced_ref_param _ + -> F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "abducedRefParam" + | Abduced_ref_param_val _ + -> F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "abducedRefParamVal" + | Global_var _ + -> Latex.pp_string Latex.Boldface f (Mangled.to_string name) + | Seed_var + -> F.fprintf f "%a^{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "old" + +(** Pretty print a pvar which denotes a value, not an address *) +let pp_value pe f pv = + match pe.Pp.kind with TEXT -> _pp f pv | HTML -> _pp f pv | LATEX -> pp_latex f pv + +(** Pretty print a program variable. *) +let pp pe f pv = + let ampersand = match pe.Pp.kind with TEXT -> "&" | HTML -> "&" | LATEX -> "\\&" in + F.fprintf f "%s%a" ampersand (pp_value pe) pv + +(** Dump a program variable. *) +let d (pvar: t) = L.add_print_action (L.PTpvar, Obj.repr pvar) + +(** Pretty print a list of program variables. *) +let pp_list pe f pvl = F.fprintf f "%a" (Pp.seq (fun f e -> F.fprintf f "%a" (pp pe) e)) pvl + +(** Dump a list of program variables. *) +let d_list pvl = List.iter ~f:(fun pv -> d pv ; L.d_str " ") pvl + +let get_name pv = pv.pv_name + +let to_string pv = Mangled.to_string pv.pv_name + +let get_simplified_name pv = + let s = Mangled.to_string pv.pv_name in + match String.rsplit2 s ~on:'.' with + | Some (s1, s2) -> ( + match String.rsplit2 s1 ~on:'.' with Some (_, s4) -> s4 ^ "." ^ s2 | _ -> s ) + | _ + -> s + +(** Check if the pvar is an abucted return var or param passed by ref *) +let is_abduced pv = + match pv.pv_kind with + | Abduced_retvar _ | Abduced_ref_param _ | Abduced_ref_param_val _ + -> true + | _ + -> false + +(** Turn a pvar into a seed pvar (which stored the initial value) *) +let to_seed pv = {pv with pv_kind= Seed_var} + +(** Check if the pvar is a local var *) +let is_local pv = match pv.pv_kind with Local_var _ -> true | _ -> false + +(** Check if the pvar is a callee var *) +let is_callee pv = match pv.pv_kind with Callee_var _ -> true | _ -> false + +(** Check if the pvar is a seed var *) +let is_seed pv = match pv.pv_kind with Seed_var -> true | _ -> false + +(** Check if the pvar is a global var *) +let is_global pv = match pv.pv_kind with Global_var _ -> true | _ -> false + +let is_static_local pv = match pv.pv_kind with Global_var (_, _, _, true) -> true | _ -> false + +(** Check if a pvar is the special "this" var *) +let is_this pvar = Mangled.equal (get_name pvar) (Mangled.from_string "this") + +(** Check if a pvar is the special "self" var *) +let is_self pvar = Mangled.equal (get_name pvar) (Mangled.from_string "self") + +(** Check if the pvar is a return var *) +let is_return pv = Mangled.equal (get_name pv) Ident.name_return + +(** something that can't be part of a legal identifier in any conceivable language *) +let tmp_prefix = "0$?%__sil_tmp" + +(** return true if [pvar] is a temporary variable generated by the frontend *) +let is_frontend_tmp pvar = + (* Check whether the program variable is a temporary one generated by Sawja, javac, or some other + bytecode/name generation pass. valid java identifiers cannot contain `$` *) + let is_bytecode_tmp name = + String.contains name '$' && not (String.contains name '_') + || String.is_prefix ~prefix:"CatchVar" name + in + (* Check whether the program variable is generated by [mk_tmp] *) + let is_sil_tmp name = String.is_prefix ~prefix:tmp_prefix name in + let name = to_string pvar in + is_sil_tmp name + || + match pvar.pv_kind with + | Local_var pname + -> Typ.Procname.is_java pname && is_bytecode_tmp name + | _ + -> false + +(* in Sawja, variables like $T0_18 are temporaries, but not SSA vars. *) +let is_ssa_frontend_tmp pvar = + is_frontend_tmp pvar + && + let name = to_string pvar in + not (String.contains name '_' && String.contains name '$') + +(** Turn an ordinary program variable into a callee program variable *) +let to_callee pname pvar = + match pvar.pv_kind with + | Local_var _ + -> {pvar with pv_kind= Callee_var pname} + | Global_var _ + -> pvar + | Callee_var _ | Abduced_retvar _ | Abduced_ref_param _ | Abduced_ref_param_val _ | Seed_var + -> L.d_str "Cannot convert pvar to callee: " ; + d pvar ; + L.d_ln () ; + assert false + +let name_hash (name: Mangled.t) = Hashtbl.hash name + +(** [mk name proc_name] creates a program var with the given function name *) +let mk (name: Mangled.t) (proc_name: Typ.Procname.t) : t = + {pv_hash= name_hash name; pv_name= name; pv_kind= Local_var proc_name} + +let get_ret_pvar pname = mk Ident.name_return pname + +(** [mk_callee name proc_name] creates a program var + for a callee function with the given function name *) +let mk_callee (name: Mangled.t) (proc_name: Typ.Procname.t) : t = + {pv_hash= name_hash name; pv_name= name; pv_kind= Callee_var proc_name} + +(** create a global variable with the given name *) +let mk_global ?(is_constexpr= false) ?(is_pod= true) ?(is_static_local= false) (name: Mangled.t) + translation_unit : t = + { pv_hash= name_hash name + ; pv_name= name + ; pv_kind= Global_var (translation_unit, is_constexpr, is_pod, is_static_local) } + +(** create a fresh temporary variable local to procedure [pname]. for use in the frontends only! *) +let mk_tmp name pname = + let id = Ident.create_fresh Ident.knormal in + let pvar_mangled = Mangled.from_string (tmp_prefix ^ name ^ Ident.to_string id) in + mk pvar_mangled pname + +(** create an abduced return variable for a call to [proc_name] at [loc] *) +let mk_abduced_ret (proc_name: Typ.Procname.t) (loc: Location.t) : t = + let name = Mangled.from_string ("$RET_" ^ Typ.Procname.to_unique_id proc_name) in + {pv_hash= name_hash name; pv_name= name; pv_kind= Abduced_retvar (proc_name, loc)} + +let mk_abduced_ref_param (proc_name: Typ.Procname.t) (pv: t) (loc: Location.t) : t = + let name = Mangled.from_string ("$REF_PARAM_" ^ Typ.Procname.to_unique_id proc_name) in + {pv_hash= name_hash name; pv_name= name; pv_kind= Abduced_ref_param (proc_name, pv, loc)} + +let mk_abduced_ref_param_val (proc_name: Typ.Procname.t) (id: Ident.t) (loc: Location.t) : t = + let name = Mangled.from_string ("$REF_PARAM_VAL_" ^ Typ.Procname.to_unique_id proc_name) in + {pv_hash= name_hash name; pv_name= name; pv_kind= Abduced_ref_param_val (proc_name, id, loc)} + +let get_translation_unit pvar = + match pvar.pv_kind with + | Global_var (tu, _, _, _) + -> tu + | _ + -> invalid_argf "Expected a global variable" + +let is_compile_constant pvar = match pvar.pv_kind with Global_var (_, b, _, _) -> b | _ -> false + +let is_pod pvar = match pvar.pv_kind with Global_var (_, _, b, _) -> b | _ -> true + +let get_initializer_pname {pv_name; pv_kind} = + match pv_kind with + | Global_var _ + -> Some + (Typ.Procname.from_string_c_fun + (Config.clang_initializer_prefix ^ Mangled.to_string_full pv_name)) + | _ + -> None diff --git a/infer/src/IR/Pvar.mli b/infer/src/IR/Pvar.mli new file mode 100644 index 000000000..3993f7406 --- /dev/null +++ b/infer/src/IR/Pvar.mli @@ -0,0 +1,167 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Program variables. *) +open! IStd +module F = Format + +type translation_unit = TUFile of SourceFile.t | TUExtern [@@deriving compare] + +(** Type for program variables. There are 4 kinds of variables: + 1) local variables, used for local variables and formal parameters + 2) callee program variables, used to handle recursion ([x | callee] is distinguished from [x]) + 3) global variables + 4) seed variables, used to store the initial value of formal parameters + *) + +type t [@@deriving compare] + +(** Equality for pvar's *) + +val equal : t -> t -> bool + +(** Dump a program variable. *) + +val d : t -> unit + +(** Dump a list of program variables. *) + +val d_list : t list -> unit + +(** Get the name component of a program variable. *) + +val get_name : t -> Mangled.t + +(** [get_ret_pvar proc_name] retuns the return pvar associated with the procedure name *) + +val get_ret_pvar : Typ.Procname.t -> t + +(** Get a simplified version of the name component of a program variable. *) + +val get_simplified_name : t -> string + +(** Check if the pvar is an abduced return var or param passed by ref *) + +val is_abduced : t -> bool + +(** Check if the pvar is a callee var *) + +val is_callee : t -> bool + +(** Check if the pvar is a global var or a static local var *) + +val is_global : t -> bool + +(** Check if the pvar is a static variable declared inside a function *) + +val is_static_local : t -> bool + +(** Check if the pvar is a (non-static) local var *) + +val is_local : t -> bool + +(** Check if the pvar is a seed var *) + +val is_seed : t -> bool + +(** Check if the pvar is a return var *) + +val is_return : t -> bool + +(** Check if a pvar is the special "this" var *) + +val is_this : t -> bool + +(** Check if a pvar is the special "self" var *) + +val is_self : t -> bool + +(** return true if [pvar] is a temporary variable generated by the frontend *) + +val is_frontend_tmp : t -> bool + +(** return true if [pvar] is a temporary variable generated by the frontend and is only assigned + once on a non-looping control-flow path *) + +val is_ssa_frontend_tmp : t -> bool + +(** [mk name proc_name suffix] creates a program var with the given function name and suffix *) + +val mk : Mangled.t -> Typ.Procname.t -> t + +(** create an abduced variable for a parameter passed by reference *) + +val mk_abduced_ref_param : Typ.Procname.t -> t -> Location.t -> t + +(** create an abduced variable for a parameter passed by reference *) + +val mk_abduced_ref_param_val : Typ.Procname.t -> Ident.t -> Location.t -> t + +(** create an abduced return variable for a call to [proc_name] at [loc] *) + +val mk_abduced_ret : Typ.Procname.t -> Location.t -> t + +(** [mk_callee name proc_name] creates a program var + for a callee function with the given function name *) + +val mk_callee : Mangled.t -> Typ.Procname.t -> t + +(** create a global variable with the given name *) + +val mk_global : + ?is_constexpr:bool -> ?is_pod:bool -> ?is_static_local:bool -> Mangled.t -> translation_unit -> t + +(** create a fresh temporary variable local to procedure [pname]. for use in the frontends only! *) + +val mk_tmp : string -> Typ.Procname.t -> t + +(** Pretty print a program variable. *) + +val pp : Pp.env -> F.formatter -> t -> unit + +(** Pretty print a list of program variables. *) + +val pp_list : Pp.env -> F.formatter -> t list -> unit + +(** Pretty print a pvar which denotes a value, not an address *) + +val pp_value : Pp.env -> F.formatter -> t -> unit + +val pp_translation_unit : F.formatter -> translation_unit -> unit + +(** Turn an ordinary program variable into a callee program variable *) + +val to_callee : Typ.Procname.t -> t -> t + +(** Turn a pvar into a seed pvar (which stores the initial value of a stack var) *) + +val to_seed : t -> t + +(** Convert a pvar to string. *) + +val to_string : t -> string + +(** Get the translation unit corresponding to a global. Raises Invalid_arg if not a global. *) + +val get_translation_unit : t -> translation_unit + +(** Is the variable's value a compile-time constant? Always (potentially incorrectly) returns + [false] for non-globals. *) + +val is_compile_constant : t -> bool + +(** Is the variable's type a "Plain Old Data" type (C++)? Always (potentially incorrectly) returns + [true] for non-globals. *) + +val is_pod : t -> bool + +(** Get the procname of the initializer function for the given global variable *) + +val get_initializer_pname : t -> Typ.Procname.t option diff --git a/infer/src/IR/Pvar.re b/infer/src/IR/Pvar.re deleted file mode 100644 index ed4f53a4f..000000000 --- a/infer/src/IR/Pvar.re +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language */ -module L = Logging; - -module F = Format; - -type translation_unit = - | TUFile SourceFile.t - | TUExtern -[@@deriving compare]; - - -/** Kind of global variables */ -type pvar_kind = - | Local_var Typ.Procname.t /** local variable belonging to a function */ - | Callee_var Typ.Procname.t /** local variable belonging to a callee */ - | Abduced_retvar Typ.Procname.t Location.t /** synthetic variable to represent return value */ - | Abduced_ref_param Typ.Procname.t t Location.t - | Abduced_ref_param_val Typ.Procname.t Ident.t Location.t - /** synthetic variable to represent param passed by reference */ - | Global_var (translation_unit, bool, bool, bool) - /** global variable: translation unit + is it compile constant? + is it POD? + is it a static - local? */ - | Seed_var /** variable used to store the initial value of formal parameters */ -[@@deriving compare] -/** Names for program variables. */ -and t = {pv_hash: int, pv_name: Mangled.t, pv_kind: pvar_kind} [@@deriving compare]; - -let equal = [%compare.equal : t]; - -let pp_translation_unit fmt => - fun - | TUFile fname => SourceFile.pp fmt fname - | TUExtern => Format.fprintf fmt "EXTERN"; - -let rec _pp f pv => { - let name = pv.pv_name; - switch pv.pv_kind { - | Local_var n => - if !Config.pp_simple { - F.fprintf f "%a" Mangled.pp name - } else { - F.fprintf f "%a$%a" Typ.Procname.pp n Mangled.pp name - } - | Callee_var n => - if !Config.pp_simple { - F.fprintf f "%a|callee" Mangled.pp name - } else { - F.fprintf f "%a$%a|callee" Typ.Procname.pp n Mangled.pp name - } - | Abduced_retvar n l => - if !Config.pp_simple { - F.fprintf f "%a|abducedRetvar" Mangled.pp name - } else { - F.fprintf f "%a$%a%a|abducedRetvar" Typ.Procname.pp n Location.pp l Mangled.pp name - } - | Abduced_ref_param n pv l => - if !Config.pp_simple { - F.fprintf f "%a|%a|abducedRefParam" _pp pv Mangled.pp name - } else { - F.fprintf f "%a$%a%a|abducedRefParam" Typ.Procname.pp n Location.pp l Mangled.pp name - } - | Abduced_ref_param_val n id l => - if !Config.pp_simple { - F.fprintf f "%a|%a|abducedRefParamVal" (Ident.pp Pp.text) id Mangled.pp name - } else { - F.fprintf f "%a$%a%a|abducedRefParamVal" Typ.Procname.pp n Location.pp l Mangled.pp name - } - | Global_var (translation_unit, is_const, is_pod, _) => - F.fprintf - f - "#GB<%a%s%s>$%a" - pp_translation_unit - translation_unit - (if is_const {"|const"} else {""}) - ( - if (not is_pod) { - "|!pod" - } else { - "" - } - ) - Mangled.pp - name - | Seed_var => F.fprintf f "old_%a" Mangled.pp name - } -}; - - -/** Pretty print a program variable in latex. */ -let pp_latex f pv => { - let name = pv.pv_name; - switch pv.pv_kind { - | Local_var _ => Latex.pp_string Latex.Roman f (Mangled.to_string name) - | Callee_var _ => - F.fprintf - f - "%a_{%a}" - (Latex.pp_string Latex.Roman) - (Mangled.to_string name) - (Latex.pp_string Latex.Roman) - "callee" - | Abduced_retvar _ => - F.fprintf - f - "%a_{%a}" - (Latex.pp_string Latex.Roman) - (Mangled.to_string name) - (Latex.pp_string Latex.Roman) - "abducedRetvar" - | Abduced_ref_param _ => - F.fprintf - f - "%a_{%a}" - (Latex.pp_string Latex.Roman) - (Mangled.to_string name) - (Latex.pp_string Latex.Roman) - "abducedRefParam" - | Abduced_ref_param_val _ => - F.fprintf - f - "%a_{%a}" - (Latex.pp_string Latex.Roman) - (Mangled.to_string name) - (Latex.pp_string Latex.Roman) - "abducedRefParamVal" - | Global_var _ => Latex.pp_string Latex.Boldface f (Mangled.to_string name) - | Seed_var => - F.fprintf - f - "%a^{%a}" - (Latex.pp_string Latex.Roman) - (Mangled.to_string name) - (Latex.pp_string Latex.Roman) - "old" - } -}; - - -/** Pretty print a pvar which denotes a value, not an address */ -let pp_value pe f pv => - switch pe.Pp.kind { - | TEXT => _pp f pv - | HTML => _pp f pv - | LATEX => pp_latex f pv - }; - - -/** Pretty print a program variable. */ -let pp pe f pv => { - let ampersand = - switch pe.Pp.kind { - | TEXT => "&" - | HTML => "&" - | LATEX => "\\&" - }; - F.fprintf f "%s%a" ampersand (pp_value pe) pv -}; - - -/** Dump a program variable. */ -let d (pvar: t) => L.add_print_action (L.PTpvar, Obj.repr pvar); - - -/** Pretty print a list of program variables. */ -let pp_list pe f pvl => F.fprintf f "%a" (Pp.seq (fun f e => F.fprintf f "%a" (pp pe) e)) pvl; - - -/** Dump a list of program variables. */ -let d_list pvl => - List.iter - f::( - fun pv => { - d pv; - L.d_str " " - } - ) - pvl; - -let get_name pv => pv.pv_name; - -let to_string pv => Mangled.to_string pv.pv_name; - -let get_simplified_name pv => { - let s = Mangled.to_string pv.pv_name; - switch (String.rsplit2 s on::'.') { - | Some (s1, s2) => - switch (String.rsplit2 s1 on::'.') { - | Some (_, s4) => s4 ^ "." ^ s2 - | _ => s - } - | _ => s - } -}; - - -/** Check if the pvar is an abucted return var or param passed by ref */ -let is_abduced pv => - switch pv.pv_kind { - | Abduced_retvar _ - | Abduced_ref_param _ - | Abduced_ref_param_val _ => true - | _ => false - }; - - -/** Turn a pvar into a seed pvar (which stored the initial value) */ -let to_seed pv => {...pv, pv_kind: Seed_var}; - - -/** Check if the pvar is a local var */ -let is_local pv => - switch pv.pv_kind { - | Local_var _ => true - | _ => false - }; - - -/** Check if the pvar is a callee var */ -let is_callee pv => - switch pv.pv_kind { - | Callee_var _ => true - | _ => false - }; - - -/** Check if the pvar is a seed var */ -let is_seed pv => - switch pv.pv_kind { - | Seed_var => true - | _ => false - }; - - -/** Check if the pvar is a global var */ -let is_global pv => - switch pv.pv_kind { - | Global_var _ => true - | _ => false - }; - -let is_static_local pv => - switch pv.pv_kind { - | Global_var (_, _, _, true) => true - | _ => false - }; - - -/** Check if a pvar is the special "this" var */ -let is_this pvar => Mangled.equal (get_name pvar) (Mangled.from_string "this"); - - -/** Check if a pvar is the special "self" var */ -let is_self pvar => Mangled.equal (get_name pvar) (Mangled.from_string "self"); - - -/** Check if the pvar is a return var */ -let is_return pv => Mangled.equal (get_name pv) Ident.name_return; - - -/** something that can't be part of a legal identifier in any conceivable language */ -let tmp_prefix = "0$?%__sil_tmp"; - - -/** return true if [pvar] is a temporary variable generated by the frontend */ -let is_frontend_tmp pvar => { - /* Check whether the program variable is a temporary one generated by Sawja, javac, or some other - bytecode/name generation pass. valid java identifiers cannot contain `$` */ - let is_bytecode_tmp name => - String.contains name '$' && not (String.contains name '_') || - String.is_prefix prefix::"CatchVar" name; - /* Check whether the program variable is generated by [mk_tmp] */ - let is_sil_tmp name => String.is_prefix prefix::tmp_prefix name; - let name = to_string pvar; - is_sil_tmp name || ( - switch pvar.pv_kind { - | Local_var pname => Typ.Procname.is_java pname && is_bytecode_tmp name - | _ => false - } - ) -}; - -/* in Sawja, variables like $T0_18 are temporaries, but not SSA vars. */ -let is_ssa_frontend_tmp pvar => - is_frontend_tmp pvar && { - let name = to_string pvar; - not (String.contains name '_' && String.contains name '$') - }; - - -/** Turn an ordinary program variable into a callee program variable */ -let to_callee pname pvar => - switch pvar.pv_kind { - | Local_var _ => {...pvar, pv_kind: Callee_var pname} - | Global_var _ => pvar - | Callee_var _ - | Abduced_retvar _ - | Abduced_ref_param _ - | Abduced_ref_param_val _ - | Seed_var => - L.d_str "Cannot convert pvar to callee: "; - d pvar; - L.d_ln (); - assert false - }; - -let name_hash (name: Mangled.t) => Hashtbl.hash name; - - -/** [mk name proc_name] creates a program var with the given function name */ -let mk (name: Mangled.t) (proc_name: Typ.Procname.t) :t => { - pv_hash: name_hash name, - pv_name: name, - pv_kind: Local_var proc_name -}; - -let get_ret_pvar pname => mk Ident.name_return pname; - - -/** [mk_callee name proc_name] creates a program var - for a callee function with the given function name */ -let mk_callee (name: Mangled.t) (proc_name: Typ.Procname.t) :t => { - pv_hash: name_hash name, - pv_name: name, - pv_kind: Callee_var proc_name -}; - - -/** create a global variable with the given name */ -let mk_global - ::is_constexpr=false - ::is_pod=true - ::is_static_local=false - (name: Mangled.t) - translation_unit - :t => { - pv_hash: name_hash name, - pv_name: name, - pv_kind: Global_var (translation_unit, is_constexpr, is_pod, is_static_local) -}; - - -/** create a fresh temporary variable local to procedure [pname]. for use in the frontends only! */ -let mk_tmp name pname => { - let id = Ident.create_fresh Ident.knormal; - let pvar_mangled = Mangled.from_string (tmp_prefix ^ name ^ Ident.to_string id); - mk pvar_mangled pname -}; - - -/** create an abduced return variable for a call to [proc_name] at [loc] */ -let mk_abduced_ret (proc_name: Typ.Procname.t) (loc: Location.t) :t => { - let name = Mangled.from_string ("$RET_" ^ Typ.Procname.to_unique_id proc_name); - {pv_hash: name_hash name, pv_name: name, pv_kind: Abduced_retvar proc_name loc} -}; - -let mk_abduced_ref_param (proc_name: Typ.Procname.t) (pv: t) (loc: Location.t) :t => { - let name = Mangled.from_string ("$REF_PARAM_" ^ Typ.Procname.to_unique_id proc_name); - {pv_hash: name_hash name, pv_name: name, pv_kind: Abduced_ref_param proc_name pv loc} -}; - -let mk_abduced_ref_param_val (proc_name: Typ.Procname.t) (id: Ident.t) (loc: Location.t) :t => { - let name = Mangled.from_string ("$REF_PARAM_VAL_" ^ Typ.Procname.to_unique_id proc_name); - {pv_hash: name_hash name, pv_name: name, pv_kind: Abduced_ref_param_val proc_name id loc} -}; - -let get_translation_unit pvar => - switch pvar.pv_kind { - | Global_var (tu, _, _, _) => tu - | _ => invalid_argf "Expected a global variable" - }; - -let is_compile_constant pvar => - switch pvar.pv_kind { - | Global_var (_, b, _, _) => b - | _ => false - }; - -let is_pod pvar => - switch pvar.pv_kind { - | Global_var (_, _, b, _) => b - | _ => true - }; - -let get_initializer_pname {pv_name, pv_kind} => - switch pv_kind { - | Global_var _ => - Some ( - Typ.Procname.from_string_c_fun ( - Config.clang_initializer_prefix ^ Mangled.to_string_full pv_name - ) - ) - | _ => None - }; diff --git a/infer/src/IR/Pvar.rei b/infer/src/IR/Pvar.rei deleted file mode 100644 index 22fec3e49..000000000 --- a/infer/src/IR/Pvar.rei +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Program variables. */ -module F = Format; - -type translation_unit = - | TUFile SourceFile.t - | TUExtern -[@@deriving compare]; - - -/** Type for program variables. There are 4 kinds of variables: - 1) local variables, used for local variables and formal parameters - 2) callee program variables, used to handle recursion ([x | callee] is distinguished from [x]) - 3) global variables - 4) seed variables, used to store the initial value of formal parameters - */ -type t [@@deriving compare]; - - -/** Equality for pvar's */ -let equal: t => t => bool; - - -/** Dump a program variable. */ -let d: t => unit; - - -/** Dump a list of program variables. */ -let d_list: list t => unit; - - -/** Get the name component of a program variable. */ -let get_name: t => Mangled.t; - - -/** [get_ret_pvar proc_name] retuns the return pvar associated with the procedure name */ -let get_ret_pvar: Typ.Procname.t => t; - - -/** Get a simplified version of the name component of a program variable. */ -let get_simplified_name: t => string; - - -/** Check if the pvar is an abduced return var or param passed by ref */ -let is_abduced: t => bool; - - -/** Check if the pvar is a callee var */ -let is_callee: t => bool; - - -/** Check if the pvar is a global var or a static local var */ -let is_global: t => bool; - - -/** Check if the pvar is a static variable declared inside a function */ -let is_static_local: t => bool; - - -/** Check if the pvar is a (non-static) local var */ -let is_local: t => bool; - - -/** Check if the pvar is a seed var */ -let is_seed: t => bool; - - -/** Check if the pvar is a return var */ -let is_return: t => bool; - - -/** Check if a pvar is the special "this" var */ -let is_this: t => bool; - - -/** Check if a pvar is the special "self" var */ -let is_self: t => bool; - - -/** return true if [pvar] is a temporary variable generated by the frontend */ -let is_frontend_tmp: t => bool; - - -/** return true if [pvar] is a temporary variable generated by the frontend and is only assigned - once on a non-looping control-flow path */ -let is_ssa_frontend_tmp: t => bool; - - -/** [mk name proc_name suffix] creates a program var with the given function name and suffix */ -let mk: Mangled.t => Typ.Procname.t => t; - - -/** create an abduced variable for a parameter passed by reference */ -let mk_abduced_ref_param: Typ.Procname.t => t => Location.t => t; - - -/** create an abduced variable for a parameter passed by reference */ -let mk_abduced_ref_param_val: Typ.Procname.t => Ident.t => Location.t => t; - - -/** create an abduced return variable for a call to [proc_name] at [loc] */ -let mk_abduced_ret: Typ.Procname.t => Location.t => t; - - -/** [mk_callee name proc_name] creates a program var - for a callee function with the given function name */ -let mk_callee: Mangled.t => Typ.Procname.t => t; - - -/** create a global variable with the given name */ -let mk_global: - is_constexpr::bool? => - is_pod::bool? => - is_static_local::bool? => - Mangled.t => - translation_unit => - t; - - -/** create a fresh temporary variable local to procedure [pname]. for use in the frontends only! */ -let mk_tmp: string => Typ.Procname.t => t; - - -/** Pretty print a program variable. */ -let pp: Pp.env => F.formatter => t => unit; - - -/** Pretty print a list of program variables. */ -let pp_list: Pp.env => F.formatter => list t => unit; - - -/** Pretty print a pvar which denotes a value, not an address */ -let pp_value: Pp.env => F.formatter => t => unit; - -let pp_translation_unit: F.formatter => translation_unit => unit; - - -/** Turn an ordinary program variable into a callee program variable */ -let to_callee: Typ.Procname.t => t => t; - - -/** Turn a pvar into a seed pvar (which stores the initial value of a stack var) */ -let to_seed: t => t; - - -/** Convert a pvar to string. */ -let to_string: t => string; - - -/** Get the translation unit corresponding to a global. Raises Invalid_arg if not a global. */ -let get_translation_unit: t => translation_unit; - - -/** Is the variable's value a compile-time constant? Always (potentially incorrectly) returns - [false] for non-globals. */ -let is_compile_constant: t => bool; - - -/** Is the variable's type a "Plain Old Data" type (C++)? Always (potentially incorrectly) returns - [true] for non-globals. */ -let is_pod: t => bool; - - -/** Get the procname of the initializer function for the given global variable */ -let get_initializer_pname: t => option Typ.Procname.t; diff --git a/infer/src/IR/QualifiedCppName.ml b/infer/src/IR/QualifiedCppName.ml new file mode 100644 index 000000000..a01790dc6 --- /dev/null +++ b/infer/src/IR/QualifiedCppName.ml @@ -0,0 +1,90 @@ +(* + * Copyright (c) 2017 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd + +(* internally it uses reversed list to store qualified name, for example: ["get", "shared_ptr", "std"]*) +type t = string list [@@deriving compare] + +let equal = [%compare.equal : t] + +let empty = [] + +let append_qualifier quals ~qual = List.cons qual quals + +let extract_last = function last :: rest -> Some (last, rest) | [] -> None + +let strip_template_args quals = + let no_template_name s = List.hd_exn (String.split ~on:'<' s) in + List.map ~f:no_template_name quals + +let append_template_args_to_last quals ~args = + match quals with + | [last; _] when String.contains last '<' + -> failwith "expected qualified name without template args" + | last :: rest + -> (last ^ args) :: rest + | [] + -> failwith "expected non-empty qualified name" + +let to_list = List.rev + +let to_rev_list = ident + +let of_list = List.rev + +let of_rev_list = ident + +let cpp_separator = "::" + +(* define [cpp_separator_regex] here to compute it once *) +let cpp_separator_regex = Str.regexp_string cpp_separator + +(* This is simplistic and will give the wrong answer in some cases, eg + "foo>::someMethod" will get parsed as ["foo>", + "someMethod"]. Avoid using it if possible *) +let of_qual_string str = Str.split cpp_separator_regex str |> List.rev + +let to_separated_string quals ~sep = List.rev quals |> String.concat ~sep + +let to_qual_string = to_separated_string ~sep:cpp_separator + +let pp fmt quals = Format.fprintf fmt "%s" (to_qual_string quals) + +module Match = struct + type quals_matcher = Str.regexp + + let matching_separator = "#" + + let regexp_string_of_qualifiers quals = + Str.quote (to_separated_string ~sep:matching_separator quals) ^ "$" + + let qualifiers_list_matcher quals_list = + ( if List.is_empty quals_list then "a^" + else + (* regexp that does not match anything *) + List.map ~f:regexp_string_of_qualifiers quals_list |> String.concat ~sep:"\\|" ) + |> Str.regexp + + let qualifiers_of_fuzzy_qual_name qual_name = + (* Fail if we detect templates in the fuzzy name. Template instantiations are not taken into + account when fuzzy matching, and templates may produce wrong results when parsing qualified + names. *) + if String.contains qual_name '<' then + failwithf "Unexpected template in fuzzy qualified name %s." qual_name ; + of_qual_string qual_name + + let of_fuzzy_qual_names fuzzy_qual_names = + List.map fuzzy_qual_names ~f:qualifiers_of_fuzzy_qual_name |> qualifiers_list_matcher + + let match_qualifiers matcher quals = + (* qual_name may have qualifiers with template parameters - drop them to whitelist all + instantiations *) + let normalized_qualifiers = strip_template_args quals in + Str.string_match matcher (to_separated_string ~sep:matching_separator normalized_qualifiers) 0 +end diff --git a/infer/src/IR/QualifiedCppName.rei b/infer/src/IR/QualifiedCppName.mli similarity index 53% rename from infer/src/IR/QualifiedCppName.rei rename to infer/src/IR/QualifiedCppName.mli index 2043dbf17..0c10e357d 100644 --- a/infer/src/IR/QualifiedCppName.rei +++ b/infer/src/IR/QualifiedCppName.mli @@ -1,67 +1,68 @@ -/* +(* * Copyright (c) 2017 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) -type t [@@deriving compare]; +open! IStd +type t [@@deriving compare] -/** empty qualified name */ -let empty: t; +(** empty qualified name *) -let equal: t => t => bool; +val empty : t +val equal : t -> t -> bool -/** attempts to parse the argument into a list::of::possibly::templated::qualifiers */ -let of_qual_string: string => t; +(** attempts to parse the argument into a list::of::possibly::templated::qualifiers *) +val of_qual_string : string -> t -/** returns qualified name as a string with "::" as a separator between qualifiers */ -let to_qual_string: t => string; +(** returns qualified name as a string with "::" as a separator between qualifiers *) +val to_qual_string : t -> string -/** append qualifier to the end (innermost scope) of the qualified name */ -let append_qualifier: t => qual::string => t; +(** append qualifier to the end (innermost scope) of the qualified name *) +val append_qualifier : t -> qual:string -> t -/** returns last (innermost scope) qualifier and qualified name without last qualifier */ -let extract_last: t => option (string, t); +(** returns last (innermost scope) qualifier and qualified name without last qualifier *) +val extract_last : t -> (string * t) option -/** returns qualified name without template arguments. For example: +(** returns qualified name without template arguments. For example: input: std::shared_ptr::shared_ptr - output: std::shared_ptr::shared_ptr */ -let strip_template_args: t => t; + output: std::shared_ptr::shared_ptr *) +val strip_template_args : t -> t -/** append template arguments to the last qualifier. Fails if qualified name is empty or it already has - template args */ -let append_template_args_to_last: t => args::string => t; +(** append template arguments to the last qualifier. Fails if qualified name is empty or it already has + template args *) +val append_template_args_to_last : t -> args:string -> t -/** returns list of qualifers */ -let to_list: t => list string; +(** returns list of qualifers *) +val to_list : t -> string list -/** returns reversed list of qualifiers, ie innermost scope is the first element */ -let to_rev_list: t => list string; +(** returns reversed list of qualifiers, ie innermost scope is the first element *) +val to_rev_list : t -> string list -/** given list of qualifiers in normal order produce qualified name ["std", "move"] */ -let of_list: list string => t; +(** given list of qualifiers in normal order produce qualified name ["std", "move"] *) +val of_list : string list -> t -/** given reversed list of qualifiers, produce qualified name (ie. ["move", "std"] for std::move )*/ -let of_rev_list: list string => t; +(** given reversed list of qualifiers, produce qualified name (ie. ["move", "std"] for std::move )*) -let pp: Format.formatter => t => unit; +val of_rev_list : string list -> t -/* Module to match qualified C++ procnames "fuzzily", that is up to namescapes and templating. In +val pp : Format.formatter -> t -> unit + +(* Module to match qualified C++ procnames "fuzzily", that is up to namescapes and templating. In particular, this deals with the following issues: 1. 'std::' namespace may have inline namespace afterwards: std::move becomes std::__1::move. This @@ -87,9 +88,12 @@ let pp: Format.formatter => t => unit; does not match: ["folly", "BAD", "someFunction"] - unlike 'std' any other namespace needs all qualifiers to match does not match: ["folly","someFunction", "BAD"] - same as previous example - */ -module Match: { - type quals_matcher; - let of_fuzzy_qual_names: list string => quals_matcher; - let match_qualifiers: quals_matcher => t => bool; -}; + *) + +module Match : sig + type quals_matcher + + val of_fuzzy_qual_names : string list -> quals_matcher + + val match_qualifiers : quals_matcher -> t -> bool +end diff --git a/infer/src/IR/QualifiedCppName.re b/infer/src/IR/QualifiedCppName.re deleted file mode 100644 index 3aaaf839a..000000000 --- a/infer/src/IR/QualifiedCppName.re +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2017 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -/* internally it uses reversed list to store qualified name, for example: ["get", "shared_ptr", "std"]*/ -type t = list string [@@deriving compare]; - -let equal = [%compare.equal : t]; - -let empty = []; - -let append_qualifier quals ::qual => List.cons qual quals; - -let extract_last = - fun - | [last, ...rest] => Some (last, rest) - | [] => None; - -let strip_template_args quals => { - let no_template_name s => List.hd_exn (String.split on::'<' s); - List.map f::no_template_name quals -}; - -let append_template_args_to_last quals ::args => - switch quals { - | [last, _] when String.contains last '<' => - failwith "expected qualified name without template args" - | [last, ...rest] => [last ^ args, ...rest] - | [] => failwith "expected non-empty qualified name" - }; - -let to_list = List.rev; - -let to_rev_list = ident; - -let of_list = List.rev; - -let of_rev_list = ident; - -let cpp_separator = "::"; - -/* define [cpp_separator_regex] here to compute it once */ -let cpp_separator_regex = Str.regexp_string cpp_separator; - -/* This is simplistic and will give the wrong answer in some cases, eg - "foo>::someMethod" will get parsed as ["foo>", - "someMethod"]. Avoid using it if possible */ -let of_qual_string str => Str.split cpp_separator_regex str |> List.rev; - -let to_separated_string quals ::sep => List.rev quals |> String.concat ::sep; - -let to_qual_string = to_separated_string sep::cpp_separator; - -let pp fmt quals => Format.fprintf fmt "%s" (to_qual_string quals); - -module Match = { - type quals_matcher = Str.regexp; - let matching_separator = "#"; - let regexp_string_of_qualifiers quals => - Str.quote (to_separated_string sep::matching_separator quals) ^ "$"; - let qualifiers_list_matcher quals_list => - ( - if (List.is_empty quals_list) { - "a^" /* regexp that does not match anything */ - } else { - List.map f::regexp_string_of_qualifiers quals_list |> String.concat sep::"\\|" - } - ) |> Str.regexp; - let qualifiers_of_fuzzy_qual_name qual_name => { - /* Fail if we detect templates in the fuzzy name. Template instantiations are not taken into - account when fuzzy matching, and templates may produce wrong results when parsing qualified - names. */ - if (String.contains qual_name '<') { - failwithf "Unexpected template in fuzzy qualified name %s." qual_name - }; - of_qual_string qual_name - }; - let of_fuzzy_qual_names fuzzy_qual_names => - List.map fuzzy_qual_names f::qualifiers_of_fuzzy_qual_name |> qualifiers_list_matcher; - let match_qualifiers matcher quals => { - /* qual_name may have qualifiers with template parameters - drop them to whitelist all - instantiations */ - let normalized_qualifiers = strip_template_args quals; - Str.string_match matcher (to_separated_string sep::matching_separator normalized_qualifiers) 0 - }; -}; diff --git a/infer/src/IR/README.md b/infer/src/IR/README.md index 3a6acabea..6a28bfcff 100644 --- a/infer/src/IR/README.md +++ b/infer/src/IR/README.md @@ -2,11 +2,11 @@ The Intermediate Representation is a format used by the back-end for analysis. It is produced by one of the front-ends, one for each program analyzed. -The main entry point is the intermediate language in [Sil](Sil.rei). +The main entry point is the intermediate language in [Sil](Sil.mli). -The control flow graph module is [Cfg](Cfg.rei). +The control flow graph module is [Cfg](Cfg.mli). -The call graph module is [Cg](Cg.rei). +The call graph module is [Cg](Cg.mli). -The type environment module is [Tenv](Tenv.rei). +The type environment module is [Tenv](Tenv.mli). diff --git a/infer/src/IR/Sil.ml b/infer/src/IR/Sil.ml new file mode 100644 index 000000000..f0def4eeb --- /dev/null +++ b/infer/src/IR/Sil.ml @@ -0,0 +1,2035 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +(** {2 Programs and Types} *) + +(** Kind of prune instruction *) +type if_kind = + | Ik_bexp + (* boolean expressions, and exp ? exp : exp *) + | Ik_dowhile + | Ik_for + | Ik_if + | Ik_land_lor + (* obtained from translation of && or || *) + | Ik_while + | Ik_switch + [@@deriving compare] + +(** An instruction. *) +type instr = + (** Load a value from the heap into an identifier. + [x = *lexp:typ] where + [lexp] is an expression denoting a heap address + [typ] is the root type of [lexp]. *) + (* Note for frontend writers: + [x] must be used in a subsequent instruction, otherwise the entire + `Load` instruction may be eliminated by copy-propagation. *) + | Load of Ident.t * Exp.t * Typ.t * Location.t + (** Store the value of an expression into the heap. + [*lexp1:typ = exp2] where + [lexp1] is an expression denoting a heap address + [typ] is the root type of [lexp1] + [exp2] is the expression whose value is store. *) + | Store of Exp.t * Typ.t * Exp.t * Location.t + (** prune the state based on [exp=1], the boolean indicates whether true branch *) + | Prune of Exp.t * Location.t * bool * if_kind + (** [Call (ret_id, e_fun, arg_ts, loc, call_flags)] represents an instruction + [ret_id = e_fun(arg_ts);]. The return value is ignored when [ret_id = None]. *) + | Call of (Ident.t * Typ.t) option * Exp.t * (Exp.t * Typ.t) list * Location.t * CallFlags.t + (** nullify stack variable *) + | Nullify of Pvar.t * Location.t + | Abstract of Location.t (** apply abstraction *) + | Remove_temps of Ident.t list * Location.t (** remove temporaries *) + | Declare_locals of (Pvar.t * Typ.t) list * Location.t (** declare local variables *) + [@@deriving compare] + +let equal_instr = [%compare.equal : instr] + +let skip_instr = Remove_temps ([], Location.dummy) + +(** Check if an instruction is auxiliary, or if it comes from source instructions. *) +let instr_is_auxiliary = function + | Load _ | Store _ | Prune _ | Call _ + -> false + | Nullify _ | Abstract _ | Remove_temps _ | Declare_locals _ + -> true + +(** offset for an lvalue *) +type offset = Off_fld of Typ.Fieldname.t * Typ.t | Off_index of Exp.t + +(** {2 Components of Propositions} *) + +(** an atom is a pure atomic formula *) +type atom = + | Aeq of Exp.t * Exp.t (** equality *) + | Aneq of Exp.t * Exp.t (** disequality *) + | Apred of PredSymb.t * (** predicate symbol applied to exps *) Exp.t list + | Anpred of PredSymb.t * (** negated predicate symbol applied to exps *) Exp.t list + [@@deriving compare] + +let equal_atom = [%compare.equal : atom] + +let atom_has_local_addr a = + match a with + | Aeq (e0, e1) | Aneq (e0, e1) + -> Exp.has_local_addr e0 || Exp.has_local_addr e1 + | Apred _ | Anpred _ + -> false + +(** kind of lseg or dllseg predicates *) +type lseg_kind = + | Lseg_NE (** nonempty (possibly circular) listseg *) + | Lseg_PE (** possibly empty (possibly circular) listseg *) + [@@deriving compare] + +let equal_lseg_kind = [%compare.equal : lseg_kind] + +(** The boolean is true when the pointer was dereferenced without testing for zero. *) +type zero_flag = bool option [@@deriving compare] + +(** True when the value was obtained by doing case analysis on null in a procedure call. *) +type null_case_flag = bool [@@deriving compare] + +(** instrumentation of heap values *) +type inst = + | Iabstraction + | Iactual_precondition + | Ialloc + | Iformal of zero_flag * null_case_flag + | Iinitial + | Ilookup + | Inone + | Inullify + | Irearrange of zero_flag * null_case_flag * int * PredSymb.path_pos + | Itaint + | Iupdate of zero_flag * null_case_flag * int * PredSymb.path_pos + | Ireturn_from_call of int + [@@deriving compare] + +let equal_inst = [%compare.equal : inst] + +(** structured expressions represent a value of structured type, such as an array or a struct. *) +type 'inst strexp0 = + | Eexp of Exp.t * 'inst (** Base case: expression with instrumentation *) + | Estruct of (Typ.Fieldname.t * 'inst strexp0) list * 'inst (** C structure *) + (** Array of given length + There are two conditions imposed / used in the array case. + First, if some index and value pair appears inside an array + in a strexp, then the index is less than the length of the array. + For instance, x |->[10 | e1: v1] implies that e1 <= 9. + Second, if two indices appear in an array, they should be different. + For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. *) + | Earray of Exp.t * (Exp.t * 'inst strexp0) list * 'inst + [@@deriving compare] + +type strexp = inst strexp0 + +let compare_strexp ?(inst= false) se1 se2 = + compare_strexp0 (match inst with true -> compare_inst | false -> fun _ _ -> 0) se1 se2 + +let equal_strexp ?(inst= false) se1 se2 = Int.equal (compare_strexp ~inst se1 se2) 0 + +(** an atomic heap predicate *) +type 'inst hpred0 = + | Hpointsto of Exp.t * 'inst strexp0 * Exp.t + (** represents [exp|->strexp:typexp] where [typexp] + is an expression representing a type, e.h. [sizeof(t)]. *) + | Hlseg of lseg_kind * 'inst hpara0 * Exp.t * Exp.t * Exp.t list + (** higher - order predicate for singly - linked lists. + Should ensure that exp1!= exp2 implies that exp1 is allocated. + This assumption is used in the rearrangement. The last [exp list] parameter + is used to denote the shared links by all the nodes in the list. *) + | Hdllseg of lseg_kind * 'inst hpara_dll0 * Exp.t * Exp.t * Exp.t * Exp.t * Exp.t list + (** higher-order predicate for doubly-linked lists. + Parameter for the higher-order singly-linked list predicate. + Means "lambda (root,next,svars). Exists evars. body". + Assume that root, next, svars, evars are disjoint sets of + primed identifiers, and include all the free primed identifiers in body. + body should not contain any non - primed identifiers or program + variables (i.e. pvars). *) + [@@deriving compare] + +and 'inst hpara0 = + {root: Ident.t; next: Ident.t; svars: Ident.t list; evars: Ident.t list; body: 'inst hpred0 list} + [@@deriving compare] + +(** parameter for the higher-order doubly-linked list predicates. + Assume that all the free identifiers in body_dll should belong to + cell, blink, flink, svars_dll, evars_dll. *) +and 'inst hpara_dll0 = + { cell: Ident.t (** address cell *) + ; blink: Ident.t (** backward link *) + ; flink: Ident.t (** forward link *) + ; svars_dll: Ident.t list + ; evars_dll: Ident.t list + ; body_dll: 'inst hpred0 list } + [@@deriving compare] + +type hpred = inst hpred0 + +(** Comparsion between heap predicates. Reverse natural order, and order first by anchor exp. *) +let compare_hpred ?(inst= false) hpred1 hpred2 = + compare_hpred0 (match inst with true -> compare_inst | false -> fun _ _ -> 0) hpred1 hpred2 + +let equal_hpred ?(inst= false) hpred1 hpred2 = Int.equal (compare_hpred ~inst hpred1 hpred2) 0 + +type hpara = inst hpara0 + +let compare_hpara = compare_hpara0 (fun _ _ -> 0) + +let equal_hpara = [%compare.equal : hpara] + +type hpara_dll = inst hpara_dll0 + +let compare_hpara_dll = compare_hpara_dll0 (fun _ _ -> 0) + +let equal_hpara_dll = [%compare.equal : hpara_dll] + +(** Return the lhs expression of a hpred *) +let hpred_get_lhs h = + match h + with Hpointsto (e, _, _) | Hlseg (_, _, e, _, _) | Hdllseg (_, _, e, _, _, _, _) -> e + +(** {2 Comparision and Inspection Functions} *) +let has_objc_ref_counter tenv hpred = + match hpred with + | Hpointsto (_, _, Sizeof {typ= {desc= Tstruct name}}) -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> List.exists ~f:Typ.Struct.is_objc_ref_counter_field fields + | _ + -> false ) + | _ + -> false + +(** Returns the zero value of a type, for int, float and ptr types, None othwewise *) +let zero_value_of_numerical_type_option typ = + match typ.Typ.desc with + | Typ.Tint _ + -> Some (Exp.Const (Cint IntLit.zero)) + | Typ.Tfloat _ + -> Some (Exp.Const (Cfloat 0.0)) + | Typ.Tptr _ + -> Some (Exp.Const (Cint IntLit.null)) + | _ + -> None + +(** Returns the zero value of a type, for int, float and ptr types, fail otherwise *) +let zero_value_of_numerical_type typ = Option.value_exn (zero_value_of_numerical_type_option typ) + +(** Make a static local name in objc *) +let mk_static_local_name pname vname = pname ^ "_" ^ vname + +(** Check if a pvar is a local static in objc *) +let is_static_local_name pname pvar = + (* local static name is of the form procname_varname *) + let var_name = Mangled.to_string (Pvar.get_name pvar) in + match Str.split_delim (Str.regexp_string pname) var_name with [_; _] -> true | _ -> false + +(** {2 Sets of expressions} *) +let elist_to_eset es = List.fold ~f:(fun set e -> Exp.Set.add e set) ~init:Exp.Set.empty es + +(** {2 Sets of heap predicates} *) +module HpredSet = Caml.Set.Make (struct + type t = hpred + + let compare = compare_hpred ~inst:false +end) + +(** {2 Pretty Printing} *) + +(** Begin change color if using diff printing, return updated printenv and change status *) +let color_pre_wrapper pe f x = + if Config.print_using_diff && pe.Pp.kind <> Pp.TEXT then + let color = pe.Pp.cmap_norm (Obj.repr x) in + if color <> pe.Pp.color then ( + ( if Pp.equal_print_kind pe.Pp.kind Pp.HTML then Io_infer.Html.pp_start_color + else Latex.pp_color ) + f color ; + if Pp.equal_color color Pp.Red then + (Pp.{(** All subexpressiona red *) + pe with cmap_norm= colormap_red; color= Red}, true) + else (Pp.{pe with color}, true) ) + else (pe, false) + else (pe, false) + +(** Close color annotation if changed *) +let color_post_wrapper changed pe f = + if changed then + if Pp.equal_print_kind pe.Pp.kind Pp.HTML then Io_infer.Html.pp_end_color f () + else Latex.pp_color f pe.Pp.color + +(** Print a sequence with difference mode if enabled. *) +let pp_seq_diff pp pe0 f = + if not Config.print_using_diff then Pp.comma_seq pp f + else + let rec doit = function + | [] + -> () + | [x] + -> let _, changed = color_pre_wrapper pe0 f x in + F.fprintf f "%a" pp x ; color_post_wrapper changed pe0 f + | x :: l + -> let _, changed = color_pre_wrapper pe0 f x in + F.fprintf f "%a" pp x ; color_post_wrapper changed pe0 f ; F.fprintf f ", " ; doit l + in + doit + +(** Pretty print an expression. *) +let pp_exp_printenv pe0 f e0 = + let pe, changed = color_pre_wrapper pe0 f e0 in + let e = + match pe.Pp.obj_sub with + | Some sub + -> Obj.obj (sub (Obj.repr e0) (* apply object substitution to expression *)) + | None + -> e0 + in + if not (Exp.equal e0 e) then + match e with Exp.Lvar pvar -> Pvar.pp_value pe f pvar | _ -> assert false + else Exp.pp_printenv pe Typ.pp f e ; + color_post_wrapper changed pe0 f + +(** dump an expression. *) +let d_exp (e: Exp.t) = L.add_print_action (L.PTexp, Obj.repr e) + +(** Pretty print a list of expressions. *) +let pp_exp_list pe f expl = Pp.seq (pp_exp_printenv pe) f expl + +(** dump a list of expressions. *) +let d_exp_list (el: Exp.t list) = L.add_print_action (L.PTexp_list, Obj.repr el) + +let pp_texp pe f = function + | Exp.Sizeof {typ; nbytes; dynamic_length; subtype} + -> let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_exp_printenv pe)) l in + let pp_size f size = Option.iter ~f:(Int.pp f) size in + F.fprintf f "%a%a%a%a" (Typ.pp pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp + subtype + | e + -> pp_exp_printenv pe f e + +(** Pretty print a type with all the details. *) +let pp_texp_full pe f = function + | Exp.Sizeof {typ; nbytes; dynamic_length; subtype} + -> let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_exp_printenv pe)) l in + let pp_size f size = Option.iter ~f:(Int.pp f) size in + F.fprintf f "%a%a%a%a" (Typ.pp_full pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp + subtype + | e + -> Exp.pp_printenv pe Typ.pp_full f e + +(** Dump a type expression with all the details. *) +let d_texp_full (te: Exp.t) = L.add_print_action (L.PTtexp_full, Obj.repr te) + +(** Pretty print an offset *) +let pp_offset pe f = function + | Off_fld (fld, _) + -> F.fprintf f "%a" Typ.Fieldname.pp fld + | Off_index exp + -> F.fprintf f "%a" (pp_exp_printenv pe) exp + +(** Convert an offset to a string *) +let offset_to_string e = F.asprintf "%a" (pp_offset Pp.text) e + +(** dump an offset. *) +let d_offset (off: offset) = L.add_print_action (L.PToff, Obj.repr off) + +(** Pretty print a list of offsets *) +let rec pp_offset_list pe f = function + | [] + -> () + | [off1; off2] + -> F.fprintf f "%a.%a" (pp_offset pe) off1 (pp_offset pe) off2 + | off :: off_list + -> F.fprintf f "%a.%a" (pp_offset pe) off (pp_offset_list pe) off_list + +(** Dump a list of offsets *) +let d_offset_list (offl: offset list) = L.add_print_action (L.PToff_list, Obj.repr offl) + +let pp_exp_typ pe f (e, t) = F.fprintf f "%a:%a" (pp_exp_printenv pe) e (Typ.pp pe) t + +(** Get the location of the instruction *) +let instr_get_loc = function + | Load (_, _, _, loc) + | Store (_, _, _, loc) + | Prune (_, loc, _, _) + | Call (_, _, _, loc, _) + | Nullify (_, loc) + | Abstract loc + | Remove_temps (_, loc) + | Declare_locals (_, loc) + -> loc + +(** get the expressions occurring in the instruction *) +let instr_get_exps = function + | Load (id, e, _, _) + -> [Exp.Var id; e] + | Store (e1, _, e2, _) + -> [e1; e2] + | Prune (cond, _, _, _) + -> [cond] + | Call (ret_id, e, _, _, _) + -> e :: Option.value_map ~f:(fun (id, _) -> [Exp.Var id]) ~default:[] ret_id + | Nullify (pvar, _) + -> [Exp.Lvar pvar] + | Abstract _ + -> [] + | Remove_temps (temps, _) + -> List.map ~f:(fun id -> Exp.Var id) temps + | Declare_locals _ + -> [] + +(** Pretty print an instruction. *) +let pp_instr pe0 f instr = + let pe, changed = color_pre_wrapper pe0 f instr in + ( match instr with + | Load (id, e, t, loc) + -> F.fprintf f "%a=*%a:%a %a" (Ident.pp pe) id (pp_exp_printenv pe) e (Typ.pp pe) t Location.pp + loc + | Store (e1, t, e2, loc) + -> F.fprintf f "*%a:%a=%a %a" (pp_exp_printenv pe) e1 (Typ.pp pe) t (pp_exp_printenv pe) e2 + Location.pp loc + | Prune (cond, loc, true_branch, _) + -> F.fprintf f "PRUNE(%a, %b); %a" (pp_exp_printenv pe) cond true_branch Location.pp loc + | Call (ret_id, e, arg_ts, loc, cf) + -> (match ret_id with None -> () | Some (id, _) -> F.fprintf f "%a=" (Ident.pp pe) id) ; + F.fprintf f "%a(%a)%a %a" (pp_exp_printenv pe) e + (Pp.comma_seq (pp_exp_typ pe)) + arg_ts CallFlags.pp cf Location.pp loc + | Nullify (pvar, loc) + -> F.fprintf f "NULLIFY(%a); %a" (Pvar.pp pe) pvar Location.pp loc + | Abstract loc + -> F.fprintf f "APPLY_ABSTRACTION; %a" Location.pp loc + | Remove_temps (temps, loc) + -> F.fprintf f "REMOVE_TEMPS(%a); %a" (Ident.pp_list pe) temps Location.pp loc + | Declare_locals (ptl, loc) + -> let pp_typ fmt (pvar, _) = F.fprintf fmt "%a" (Pvar.pp pe) pvar in + F.fprintf f "DECLARE_LOCALS(%a); %a" (Pp.comma_seq pp_typ) ptl Location.pp loc ) ; + color_post_wrapper changed pe0 f + +(** Check if a pvar is a local pointing to a block in objc *) +let is_block_pvar pvar = Typ.has_block_prefix (Mangled.to_string (Pvar.get_name pvar)) + +(* A block pvar used to explain retain cycles *) +let block_pvar = Pvar.mk (Mangled.from_string "block") (Typ.Procname.from_string_c_fun "") + +(** Dump an instruction. *) +let d_instr (i: instr) = L.add_print_action (L.PTinstr, Obj.repr i) + +let rec pp_instr_list pe f = function + | [] + -> F.fprintf f "" + | i :: is + -> F.fprintf f "%a;@\n%a" (pp_instr pe) i (pp_instr_list pe) is + +(** Dump a list of instructions. *) +let d_instr_list (il: instr list) = L.add_print_action (L.PTinstr_list, Obj.repr il) + +let pp_atom pe0 f a = + let pe, changed = color_pre_wrapper pe0 f a in + ( match a with + | Aeq (BinOp (op, e1, e2), Const Cint i) when IntLit.isone i -> ( + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "%a" (pp_exp_printenv pe) (Exp.BinOp (op, e1, e2)) + | LATEX + -> F.fprintf f "%a" (pp_exp_printenv pe) (Exp.BinOp (op, e1, e2)) ) + | Aeq (e1, e2) -> ( + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "%a = %a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 + | LATEX + -> F.fprintf f "%a{=}%a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 ) + | Aneq (e1, e2) -> ( + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "%a != %a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 + | LATEX + -> F.fprintf f "%a{\\neq}%a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 ) + | Apred (a, es) + -> F.fprintf f "%s(%a)" (PredSymb.to_string pe a) (Pp.comma_seq (pp_exp_printenv pe)) es + | Anpred (a, es) + -> F.fprintf f "!%s(%a)" (PredSymb.to_string pe a) (Pp.comma_seq (pp_exp_printenv pe)) es ) ; + color_post_wrapper changed pe0 f + +(** dump an atom *) +let d_atom (a: atom) = L.add_print_action (L.PTatom, Obj.repr a) + +let pp_lseg_kind f = function Lseg_NE -> F.fprintf f "ne" | Lseg_PE -> F.fprintf f "" + +(** Print a *-separated sequence. *) +let rec pp_star_seq pp f = function + | [] + -> () + | [x] + -> F.fprintf f "%a" pp x + | x :: l + -> F.fprintf f "%a * %a" pp x (pp_star_seq pp) l + +(********* START OF MODULE Predicates **********) + +(** Module Predicates records the occurrences of predicates as parameters + of (doubly -)linked lists and Epara. Provides unique numbering + for predicates and an iterator. *) +module Predicates : sig + (** predicate environment *) + + type env + + (** create an empty predicate environment *) + + val empty_env : unit -> env + + (** return true if the environment is empty *) + + val is_empty : env -> bool + + (** return the id of the hpara *) + + val get_hpara_id : env -> hpara -> int + + (** return the id of the hpara_dll *) + + val get_hpara_dll_id : env -> hpara_dll -> int + + (** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, + passing the unique id to the functions. The iterator can only be used once. *) + + val iter : env -> (int -> hpara -> unit) -> (int -> hpara_dll -> unit) -> unit + + (** Process one hpred, updating the predicate environment *) + + val process_hpred : env -> hpred -> unit +end = struct + (** hash tables for hpara *) + module HparaHash = Hashtbl.Make (struct + type t = hpara + + let equal = equal_hpara + + let hash = Hashtbl.hash + end) + + (** hash tables for hpara_dll *) + module HparaDllHash = Hashtbl.Make (struct + type t = hpara_dll + + let equal = equal_hpara_dll + + let hash = Hashtbl.hash + end) + + (** Map each visited hpara to a unique number and a boolean denoting whether it has been emitted, + also keep a list of hparas still to be emitted. Same for hpara_dll. *) + type env = + { mutable num: int + ; hash: (int * bool) HparaHash.t + ; mutable todo: hpara list + ; hash_dll: (int * bool) HparaDllHash.t + ; mutable todo_dll: hpara_dll list } + + (** return true if the environment is empty *) + let is_empty env = Int.equal env.num 0 + + (** return the id of the hpara *) + let get_hpara_id env hpara = fst (HparaHash.find env.hash hpara) + + (** return the id of the hpara_dll *) + let get_hpara_dll_id env hpara_dll = fst (HparaDllHash.find env.hash_dll hpara_dll) + + (** Process one hpara, updating the map from hparas to numbers, and the todo list *) + let process_hpara env hpara = + if not (HparaHash.mem env.hash hpara) then ( + HparaHash.add env.hash hpara (env.num, false) ; + env.num <- env.num + 1 ; + env.todo <- env.todo @ [hpara] ) + + (** Process one hpara_dll, updating the map from hparas to numbers, and the todo list *) + let process_hpara_dll env hpara_dll = + if not (HparaDllHash.mem env.hash_dll hpara_dll) then ( + HparaDllHash.add env.hash_dll hpara_dll (env.num, false) ; + env.num <- env.num + 1 ; + env.todo_dll <- env.todo_dll @ [hpara_dll] ) + + (** Process a sexp, updating env *) + let rec process_sexp env = function + | Eexp _ + -> () + | Earray (_, esel, _) + -> List.iter ~f:(fun (_, se) -> process_sexp env se) esel + | Estruct (fsel, _) + -> List.iter ~f:(fun (_, se) -> process_sexp env se) fsel + + (** Process one hpred, updating env *) + let rec process_hpred env = function + | Hpointsto (_, se, _) + -> process_sexp env se + | Hlseg (_, hpara, _, _, _) + -> List.iter ~f:(process_hpred env) hpara.body ; + process_hpara env hpara + | Hdllseg (_, hpara_dll, _, _, _, _, _) + -> List.iter ~f:(process_hpred env) hpara_dll.body_dll ; + process_hpara_dll env hpara_dll + + (** create an empty predicate environment *) + let empty_env () = + {num= 0; hash= HparaHash.create 3; todo= []; hash_dll= HparaDllHash.create 3; todo_dll= []} + + (** iterator for predicates which are marked as todo in env, + unless they have been visited already. + This can in turn extend the todo list for the nested predicates, + which are then visited as well. + Can be applied only once, as it destroys the todo list *) + let iter (env: env) f f_dll = + while env.todo <> [] || env.todo_dll <> [] do + match env.todo with + | hpara :: todo' + -> env.todo <- todo' ; + let n, emitted = HparaHash.find env.hash hpara in + if not emitted then f n hpara + | [] -> + match env.todo_dll with + | hpara_dll :: todo_dll' + -> env.todo_dll <- todo_dll' ; + let n, emitted = HparaDllHash.find env.hash_dll hpara_dll in + if not emitted then f_dll n hpara_dll + | [] + -> () + done +end + +(********* END OF MODULE Predicates **********) +let pp_texp_simple pe = + match pe.Pp.opt with SIM_DEFAULT -> pp_texp pe | SIM_WITH_TYP -> pp_texp_full pe + +let inst_abstraction = Iabstraction + +let inst_actual_precondition = Iactual_precondition + +let inst_alloc = Ialloc + +(** for formal parameters *) +let inst_formal = Iformal (None, false) + +(** for initial values *) +let inst_initial = Iinitial + +let inst_lookup = Ilookup + +let inst_none = Inone + +let inst_nullify = Inullify + +let inst_rearrange b loc pos = Irearrange (Some b, false, loc.Location.line, pos) + +let inst_taint = Itaint + +let inst_update loc pos = Iupdate (None, false, loc.Location.line, pos) + +(** update the location of the instrumentation *) +let inst_new_loc loc inst = + match inst with + | Iabstraction + -> inst + | Iactual_precondition + -> inst + | Ialloc + -> inst + | Iformal _ + -> inst + | Iinitial + -> inst + | Ilookup + -> inst + | Inone + -> inst + | Inullify + -> inst + | Irearrange (zf, ncf, _, pos) + -> Irearrange (zf, ncf, loc.Location.line, pos) + | Itaint + -> inst + | Iupdate (zf, ncf, _, pos) + -> Iupdate (zf, ncf, loc.Location.line, pos) + | Ireturn_from_call _ + -> Ireturn_from_call loc.Location.line + +(** return a string representing the inst *) +let inst_to_string inst = + let zero_flag_to_string = function Some true -> "(z)" | _ -> "" in + let null_case_flag_to_string ncf = if ncf then "(ncf)" else "" in + match inst with + | Iabstraction + -> "abstraction" + | Iactual_precondition + -> "actual_precondition" + | Ialloc + -> "alloc" + | Iformal (zf, ncf) + -> "formal" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf + | Iinitial + -> "initial" + | Ilookup + -> "lookup" + | Inone + -> "none" + | Inullify + -> "nullify" + | Irearrange (zf, ncf, n, _) + -> "rearrange:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n + | Itaint + -> "taint" + | Iupdate (zf, ncf, n, _) + -> "update:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n + | Ireturn_from_call n + -> "return_from_call: " ^ string_of_int n + +exception JoinFail + +(** join of instrumentations, can raise JoinFail *) +let inst_partial_join inst1 inst2 = + let fail () = + L.d_strln ("inst_partial_join failed on " ^ inst_to_string inst1 ^ " " ^ inst_to_string inst2) ; + raise JoinFail + in + if equal_inst inst1 inst2 then inst1 + else + match (inst1, inst2) with + | _, Inone | Inone, _ + -> inst_none + | _, Ialloc | Ialloc, _ + -> fail () + | _, Iinitial | Iinitial, _ + -> fail () + | _, Iupdate _ | Iupdate _, _ + -> fail () + | _ + -> inst_none + +(** meet of instrumentations *) +let inst_partial_meet inst1 inst2 = if equal_inst inst1 inst2 then inst1 else inst_none + +(** Return the zero flag of the inst *) +let inst_zero_flag = function + | Iabstraction + -> None + | Iactual_precondition + -> None + | Ialloc + -> None + | Iformal (zf, _) + -> zf + | Iinitial + -> None + | Ilookup + -> None + | Inone + -> None + | Inullify + -> None + | Irearrange (zf, _, _, _) + -> zf + | Itaint + -> None + | Iupdate (zf, _, _, _) + -> zf + | Ireturn_from_call _ + -> None + +(** Set the null case flag of the inst. *) +let inst_set_null_case_flag = function + | Iformal (zf, false) + -> Iformal (zf, true) + | Irearrange (zf, false, n, pos) + -> Irearrange (zf, true, n, pos) + | Iupdate (zf, false, n, pos) + -> Iupdate (zf, true, n, pos) + | inst + -> inst + +(** Get the null case flag of the inst. *) +let inst_get_null_case_flag = function Iupdate (_, ncf, _, _) -> Some ncf | _ -> None + +(** Update [inst_old] to [inst_new] preserving the zero flag *) +let update_inst inst_old inst_new = + let combine_zero_flags z1 z2 = + match (z1, z2) with + | Some b1, Some b2 + -> Some (b1 || b2) + | Some b, None + -> Some b + | None, Some b + -> Some b + | None, None + -> None + in + match inst_new with + | Iabstraction + -> inst_new + | Iactual_precondition + -> inst_new + | Ialloc + -> inst_new + | Iformal (zf, ncf) + -> let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Iformal (zf', ncf) + | Iinitial + -> inst_new + | Ilookup + -> inst_new + | Inone + -> inst_new + | Inullify + -> inst_new + | Irearrange (zf, ncf, n, pos) + -> let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Irearrange (zf', ncf, n, pos) + | Itaint + -> inst_new + | Iupdate (zf, ncf, n, pos) + -> let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Iupdate (zf', ncf, n, pos) + | Ireturn_from_call _ + -> inst_new + +(** describe an instrumentation with a string *) +let pp_inst pe f inst = + let str = inst_to_string inst in + if Pp.equal_print_kind pe.Pp.kind Pp.HTML then + F.fprintf f " %a%s%a" Io_infer.Html.pp_start_color Pp.Orange str Io_infer.Html.pp_end_color () + else F.fprintf f "%s%s%s" (Binop.str pe Lt) str (Binop.str pe Gt) + +let pp_inst_if_trace pe f inst = if Config.trace_error then pp_inst pe f inst + +(** pretty print a strexp with an optional predicate env *) +let rec pp_sexp_env pe0 envo f se = + let pe, changed = color_pre_wrapper pe0 f se in + ( match se with + | Eexp (e, inst) + -> F.fprintf f "%a%a" (pp_exp_printenv pe) e (pp_inst_if_trace pe) inst + | Estruct (fel, inst) -> ( + match pe.Pp.kind with + | TEXT | HTML + -> let pp_diff f (n, se) = F.fprintf f "%a:%a" Typ.Fieldname.pp n (pp_sexp_env pe envo) se in + F.fprintf f "{%a}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst + | LATEX + -> let pp_diff f (n, se) = + F.fprintf f "%a:%a" (Typ.Fieldname.pp_latex Latex.Boldface) n (pp_sexp_env pe envo) se + in + F.fprintf f "\\{%a\\}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst ) + | Earray (len, nel, inst) + -> let pp_diff f (i, se) = + F.fprintf f "%a:%a" (pp_exp_printenv pe) i (pp_sexp_env pe envo) se + in + F.fprintf f "[%a|%a]%a" (pp_exp_printenv pe) len (pp_seq_diff pp_diff pe) nel + (pp_inst_if_trace pe) inst ) ; + color_post_wrapper changed pe0 f + +(** Pretty print an hpred with an optional predicate env *) +let rec pp_hpred_env pe0 envo f hpred = + let pe, changed = color_pre_wrapper pe0 f hpred in + ( match hpred with + | Hpointsto (e, se, te) + -> ( + let pe' = + match (e, se) with + | Lvar pvar, Eexp (Var _, _) when not (Pvar.is_global pvar) + -> Pp.{pe with obj_sub= None (* dont use obj sub on the var defining it *)} + | _ + -> pe + in + match pe'.Pp.kind with + | TEXT | HTML + -> F.fprintf f "%a|->%a:%a" (pp_exp_printenv pe') e (pp_sexp_env pe' envo) se + (pp_texp_simple pe') te + | LATEX + -> F.fprintf f "%a\\mapsto %a" (pp_exp_printenv pe') e (pp_sexp_env pe' envo) se ) + | Hlseg (k, hpara, e1, e2, elist) -> ( + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "lseg%a(%a,%a,[%a],%a)" pp_lseg_kind k (pp_exp_printenv pe) e1 + (pp_exp_printenv pe) e2 + (Pp.comma_seq (pp_exp_printenv pe)) + elist (pp_hpara_env pe envo) hpara + | LATEX + -> F.fprintf f "\\textsf{lseg}_{%a}(%a,%a,[%a],%a)" pp_lseg_kind k (pp_exp_printenv pe) e1 + (pp_exp_printenv pe) e2 + (Pp.comma_seq (pp_exp_printenv pe)) + elist (pp_hpara_env pe envo) hpara ) + | Hdllseg (k, hpara_dll, iF, oB, oF, iB, elist) -> + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "dllseg%a(%a,%a,%a,%a,[%a],%a)" pp_lseg_kind k (pp_exp_printenv pe) iF + (pp_exp_printenv pe) oB (pp_exp_printenv pe) oF (pp_exp_printenv pe) iB + (Pp.comma_seq (pp_exp_printenv pe)) + elist (pp_hpara_dll_env pe envo) hpara_dll + | LATEX + -> F.fprintf f "\\textsf{dllseg}_{%a}(%a,%a,%a,%a,[%a],%a)" pp_lseg_kind k + (pp_exp_printenv pe) iF (pp_exp_printenv pe) oB (pp_exp_printenv pe) oF + (pp_exp_printenv pe) iB + (Pp.comma_seq (pp_exp_printenv pe)) + elist (pp_hpara_dll_env pe envo) hpara_dll ) ; + color_post_wrapper changed pe0 f + +and pp_hpara_env pe envo f hpara = + match envo with + | None + -> let r, n, svars, evars, b = (hpara.root, hpara.next, hpara.svars, hpara.evars, hpara.body) in + F.fprintf f "lam [%a,%a,%a]. exists [%a]. %a" (Ident.pp pe) r (Ident.pp pe) n + (Pp.seq (Ident.pp pe)) + svars + (Pp.seq (Ident.pp pe)) + evars + (pp_star_seq (pp_hpred_env pe envo)) + b + | Some env + -> F.fprintf f "P%d" (Predicates.get_hpara_id env hpara) + +and pp_hpara_dll_env pe envo f hpara_dll = + match envo with + | None + -> let iF, oB, oF, svars, evars, b = + ( hpara_dll.cell + , hpara_dll.blink + , hpara_dll.flink + , hpara_dll.svars_dll + , hpara_dll.evars_dll + , hpara_dll.body_dll ) + in + F.fprintf f "lam [%a,%a,%a,%a]. exists [%a]. %a" (Ident.pp pe) iF (Ident.pp pe) oB + (Ident.pp pe) oF + (Pp.seq (Ident.pp pe)) + svars + (Pp.seq (Ident.pp pe)) + evars + (pp_star_seq (pp_hpred_env pe envo)) + b + | Some env + -> F.fprintf f "P%d" (Predicates.get_hpara_dll_id env hpara_dll) + +(** pretty print a strexp *) +let pp_sexp pe f = pp_sexp_env pe None f + +(** pretty print a hpara *) +let pp_hpara pe f = pp_hpara_env pe None f + +(** pretty print a hpara_dll *) +let pp_hpara_dll pe f = pp_hpara_dll_env pe None f + +(** pretty print a hpred *) +let pp_hpred pe f = pp_hpred_env pe None f + +(** dump a strexp. *) +let d_sexp (se: strexp) = L.add_print_action (L.PTsexp, Obj.repr se) + +(** Pretty print a list of expressions. *) +let pp_sexp_list pe f sel = + F.fprintf f "%a" (Pp.seq (fun f se -> F.fprintf f "%a" (pp_sexp pe) se)) sel + +(** dump a list of expressions. *) +let d_sexp_list (sel: strexp list) = L.add_print_action (L.PTsexp_list, Obj.repr sel) + +let rec pp_hpara_list pe f = function + | [] + -> () + | [para] + -> F.fprintf f "PRED: %a" (pp_hpara pe) para + | para :: paras + -> F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara pe) para (pp_hpara_list pe) paras + +let rec pp_hpara_dll_list pe f = function + | [] + -> () + | [para] + -> F.fprintf f "PRED: %a" (pp_hpara_dll pe) para + | para :: paras + -> F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara_dll pe) para (pp_hpara_dll_list pe) paras + +(** dump a hpred. *) +let d_hpred (hpred: hpred) = L.add_print_action (L.PThpred, Obj.repr hpred) + +(** {2 Functions for traversing SIL data types} *) +let rec strexp_expmap (f: Exp.t * inst option -> Exp.t * inst option) = + let fe e = fst (f (e, None)) in + let fei (e, inst) = + match f (e, Some inst) with e', None -> (e', inst) | e', Some inst' -> (e', inst') + in + function + | Eexp (e, inst) + -> let e', inst' = fei (e, inst) in + Eexp (e', inst') + | Estruct (fld_se_list, inst) + -> let f_fld_se (fld, se) = (fld, strexp_expmap f se) in + Estruct (List.map ~f:f_fld_se fld_se_list, inst) + | Earray (len, idx_se_list, inst) + -> let len' = fe len in + let f_idx_se (idx, se) = + let idx' = fe idx in + (idx', strexp_expmap f se) + in + Earray (len', List.map ~f:f_idx_se idx_se_list, inst) + +let hpred_expmap (f: Exp.t * inst option -> Exp.t * inst option) = + let fe e = fst (f (e, None)) in + function + | Hpointsto (e, se, te) + -> let e' = fe e in + let se' = strexp_expmap f se in + let te' = fe te in + Hpointsto (e', se', te') + | Hlseg (k, hpara, root, next, shared) + -> let root' = fe root in + let next' = fe next in + let shared' = List.map ~f:fe shared in + Hlseg (k, hpara, root', next', shared') + | Hdllseg (k, hpara, iF, oB, oF, iB, shared) + -> let iF' = fe iF in + let oB' = fe oB in + let oF' = fe oF in + let iB' = fe iB in + let shared' = List.map ~f:fe shared in + Hdllseg (k, hpara, iF', oB', oF', iB', shared') + +let rec strexp_instmap (f: inst -> inst) strexp = + match strexp with + | Eexp (e, inst) + -> Eexp (e, f inst) + | Estruct (fld_se_list, inst) + -> let f_fld_se (fld, se) = (fld, strexp_instmap f se) in + Estruct (List.map ~f:f_fld_se fld_se_list, f inst) + | Earray (len, idx_se_list, inst) + -> let f_idx_se (idx, se) = (idx, strexp_instmap f se) in + Earray (len, List.map ~f:f_idx_se idx_se_list, f inst) + +let rec hpara_instmap (f: inst -> inst) hpara = + {hpara with body= List.map ~f:(hpred_instmap f) hpara.body} + +and hpara_dll_instmap (f: inst -> inst) hpara_dll = + {hpara_dll with body_dll= List.map ~f:(hpred_instmap f) hpara_dll.body_dll} + +and hpred_instmap (fn: inst -> inst) (hpred: hpred) : hpred = + match hpred with + | Hpointsto (e, se, te) + -> let se' = strexp_instmap fn se in + Hpointsto (e, se', te) + | Hlseg (k, hpara, e, f, el) + -> Hlseg (k, hpara_instmap fn hpara, e, f, el) + | Hdllseg (k, hpar_dll, e, f, g, h, el) + -> Hdllseg (k, hpara_dll_instmap fn hpar_dll, e, f, g, h, el) + +let hpred_list_expmap (f: Exp.t * inst option -> Exp.t * inst option) (hlist: hpred list) = + List.map ~f:(hpred_expmap f) hlist + +let atom_expmap (f: Exp.t -> Exp.t) = function + | Aeq (e1, e2) + -> Aeq (f e1, f e2) + | Aneq (e1, e2) + -> Aneq (f e1, f e2) + | Apred (a, es) + -> Apred (a, List.map ~f es) + | Anpred (a, es) + -> Anpred (a, List.map ~f es) + +let atom_list_expmap (f: Exp.t -> Exp.t) (alist: atom list) = List.map ~f:(atom_expmap f) alist + +(** {2 Function for computing lexps in sigma} *) +let hpred_get_lexp acc = function + | Hpointsto (e, _, _) + -> e :: acc + | Hlseg (_, _, e, _, _) + -> e :: acc + | Hdllseg (_, _, e1, _, _, e2, _) + -> e1 :: e2 :: acc + +let hpred_list_get_lexps (filter: Exp.t -> bool) (hlist: hpred list) : Exp.t list = + let lexps = List.fold ~f:hpred_get_lexp ~init:[] hlist in + List.filter ~f:filter lexps + +(** {2 Functions for computing program variables} *) +let rec exp_fpv e = + match (e : Exp.t) with + | Var _ + -> [] + | Exn e + -> exp_fpv e + | Closure {captured_vars} + -> List.map ~f:(fun (_, pvar, _) -> pvar) captured_vars + | Const _ + -> [] + | Cast (_, e) | UnOp (_, e, _) + -> exp_fpv e + | BinOp (_, e1, e2) + -> exp_fpv e1 @ exp_fpv e2 + | Lvar name + -> [name] + | Lfield (e, _, _) + -> exp_fpv e + | Lindex (e1, e2) + -> exp_fpv e1 @ exp_fpv e2 + (* TODO: Sizeof length expressions may contain variables, do not ignore them. *) + | Sizeof _ + -> [] + +let exp_list_fpv el = List.concat_map ~f:exp_fpv el + +let atom_fpv = function + | Aeq (e1, e2) + -> exp_fpv e1 @ exp_fpv e2 + | Aneq (e1, e2) + -> exp_fpv e1 @ exp_fpv e2 + | Apred (_, es) | Anpred (_, es) + -> List.fold ~f:(fun fpv e -> List.rev_append (exp_fpv e) fpv) ~init:[] es + +let rec strexp_fpv = function + | Eexp (e, _) + -> exp_fpv e + | Estruct (fld_se_list, _) + -> let f (_, se) = strexp_fpv se in + List.concat_map ~f fld_se_list + | Earray (len, idx_se_list, _) + -> let fpv_in_len = exp_fpv len in + let f (idx, se) = exp_fpv idx @ strexp_fpv se in + fpv_in_len @ List.concat_map ~f idx_se_list + +let rec hpred_fpv = function + | Hpointsto (base, se, te) + -> exp_fpv base @ strexp_fpv se @ exp_fpv te + | Hlseg (_, para, e1, e2, elist) + -> let fpvars_in_elist = exp_list_fpv elist in + hpara_fpv para @ exp_fpv (* This set has to be empty. *) e1 @ exp_fpv e2 @ fpvars_in_elist + | Hdllseg (_, para, e1, e2, e3, e4, elist) + -> let fpvars_in_elist = exp_list_fpv elist in + hpara_dll_fpv para (* This set has to be empty. *) + @ exp_fpv e1 @ exp_fpv e2 @ exp_fpv e3 @ exp_fpv e4 @ fpvars_in_elist + +(** hpara should not contain any program variables. + This is because it might cause problems when we do interprocedural + analysis. In interprocedural analysis, we should consider the issue + of scopes of program variables. *) +and hpara_fpv para = + let fpvars_in_body = List.concat_map ~f:hpred_fpv para.body in + match fpvars_in_body with [] -> [] | _ -> assert false + +(** hpara_dll should not contain any program variables. + This is because it might cause problems when we do interprocedural + analysis. In interprocedural analysis, we should consider the issue + of scopes of program variables. *) +and hpara_dll_fpv para = + let fpvars_in_body = List.concat_map ~f:hpred_fpv para.body_dll in + match fpvars_in_body with [] -> [] | _ -> assert false + +(** {2 Functions for computing free non-program variables} *) + +(** Type of free variables. These include primed, normal and footprint variables. + We keep a count of how many types the variables appear. *) +type fav = Ident.t list ref + +let fav_new () = ref [] + +(** Emptyness check. *) +let fav_is_empty fav = match !fav with [] -> true | _ -> false + +(** Check whether a predicate holds for all elements. *) +let fav_for_all fav predicate = List.for_all ~f:predicate !fav + +(** Check whether a predicate holds for some elements. *) +let fav_exists fav predicate = List.exists ~f:predicate !fav + +(** flag to indicate whether fav's are stored in duplicate form. + Only to be used with fav_to_list *) +let fav_duplicates = ref false + +(** extend [fav] with a [id] *) +let ( ++ ) fav id = + if !fav_duplicates || not (List.exists ~f:(Ident.equal id) !fav) then fav := id :: !fav + +(** extend [fav] with ident list [idl] *) +let ( +++ ) fav idl = List.iter ~f:(fun id -> fav ++ id) idl + +(** add identity lists to fav *) +let ident_list_fav_add idl fav = fav +++ idl + +(** Convert a list to a fav. *) +let fav_from_list l = + let fav = fav_new () in + let _ = List.iter ~f:(fun id -> fav ++ id) l in + fav + +let rec remove_duplicates_from_sorted special_equal = function + | [] + -> [] + | [x] + -> [x] + | x :: y :: l + -> if special_equal x y then remove_duplicates_from_sorted special_equal (y :: l) + else x :: remove_duplicates_from_sorted special_equal (y :: l) + +(** Convert a [fav] to a list of identifiers while preserving the order + that the identifiers were added to [fav]. *) +let fav_to_list fav = List.rev !fav + +(** Pretty print a fav. *) +let pp_fav pe f fav = Pp.seq (Ident.pp pe) f (fav_to_list fav) + +(** Copy a [fav]. *) +let fav_copy fav = ref (List.map ~f:(fun x -> x) !fav) + +(** Turn a xxx_fav_add function into a xxx_fav function *) +let fav_imperative_to_functional f x = + let fav = fav_new () in + let _ = f fav x in + fav + +(** [fav_filter_ident fav f] only keeps [id] if [f id] is true. *) +let fav_filter_ident fav filter = fav := List.filter ~f:filter !fav + +(** Like [fav_filter_ident] but return a copy. *) +let fav_copy_filter_ident fav filter = ref (List.filter ~f:filter !fav) + +(** checks whether every element in l1 appears l2 **) +let rec ident_sorted_list_subset l1 l2 = + match (l1, l2) with + | [], _ + -> true + | _ :: _, [] + -> false + | id1 :: l1, id2 :: l2 + -> let n = Ident.compare id1 id2 in + if Int.equal n 0 then ident_sorted_list_subset l1 (id2 :: l2) + else if n > 0 then ident_sorted_list_subset (id1 :: l1) l2 + else false + +(** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] + is in [fav2].*) +let fav_subset_ident fav1 fav2 = ident_sorted_list_subset (fav_to_list fav1) (fav_to_list fav2) + +let fav_mem fav id = List.exists ~f:(Ident.equal id) !fav + +let rec exp_fav_add fav e = + match (e : Exp.t) with + | Var id + -> fav ++ id + | Exn e + -> exp_fav_add fav e + | Closure {captured_vars} + -> List.iter ~f:(fun (e, _, _) -> exp_fav_add fav e) captured_vars + | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) + -> () + | Cast (_, e) | UnOp (_, e, _) + -> exp_fav_add fav e + | BinOp (_, e1, e2) + -> exp_fav_add fav e1 ; exp_fav_add fav e2 + | Lvar _ + -> () + | Lfield (* do nothing since we only count non-program variables *) + (e, _, _) + -> exp_fav_add fav e + | Lindex (e1, e2) + -> exp_fav_add fav e1 ; exp_fav_add fav e2 + (* TODO: Sizeof length expressions may contain variables, do not ignore them. *) + | Sizeof _ + -> () + +let exp_fav = fav_imperative_to_functional exp_fav_add + +let exp_fav_list e = fav_to_list (exp_fav e) + +let ident_in_exp id e = + let fav = fav_new () in + exp_fav_add fav e ; fav_mem fav id + +let atom_fav_add fav = function + | Aeq (e1, e2) | Aneq (e1, e2) + -> exp_fav_add fav e1 ; exp_fav_add fav e2 + | Apred (_, es) | Anpred (_, es) + -> List.iter ~f:(fun e -> exp_fav_add fav e) es + +let atom_fav = fav_imperative_to_functional atom_fav_add + +(** Atoms do not contain binders *) +let atom_av_add = atom_fav_add + +let rec strexp_fav_add fav = function + | Eexp (e, _) + -> exp_fav_add fav e + | Estruct (fld_se_list, _) + -> List.iter ~f:(fun (_, se) -> strexp_fav_add fav se) fld_se_list + | Earray (len, idx_se_list, _) + -> exp_fav_add fav len ; + List.iter ~f:(fun (e, se) -> exp_fav_add fav e ; strexp_fav_add fav se) idx_se_list + +let hpred_fav_add fav = function + | Hpointsto (base, sexp, te) + -> exp_fav_add fav base ; strexp_fav_add fav sexp ; exp_fav_add fav te + | Hlseg (_, _, e1, e2, elist) + -> exp_fav_add fav e1 ; + exp_fav_add fav e2 ; + List.iter ~f:(exp_fav_add fav) elist + | Hdllseg (_, _, e1, e2, e3, e4, elist) + -> exp_fav_add fav e1 ; + exp_fav_add fav e2 ; + exp_fav_add fav e3 ; + exp_fav_add fav e4 ; + List.iter ~f:(exp_fav_add fav) elist + +let hpred_fav = fav_imperative_to_functional hpred_fav_add + +(** This function should be used before adding a new + index to Earray. The [exp] is the newly created + index. This function "cleans" [exp] according to whether it is + the footprint or current part of the prop. + The function faults in the re - execution mode, as an internal check of the tool. *) +let array_clean_new_index footprint_part new_idx = + if footprint_part && not !Config.footprint then assert false ; + let fav = exp_fav new_idx in + if footprint_part && fav_exists fav (fun id -> not (Ident.is_footprint id)) then ( + L.d_warning + ( "Array index " ^ Exp.to_string new_idx + ^ " has non-footprint vars: replaced by fresh footprint var" ) ; + L.d_ln () ; + let id = Ident.create_fresh Ident.kfootprint in + Exp.Var id ) + else new_idx + +(** {2 Functions for computing all free or bound non-program variables} *) +let exp_av_add = exp_fav_add (** Expressions do not bind variables *) + +(** Structured expressions do not bind variables *) +let strexp_av_add = strexp_fav_add + +let rec hpara_av_add fav para = + List.iter ~f:(hpred_av_add fav) para.body ; + fav ++ para.root ; + fav ++ para.next ; + fav +++ para.svars ; + fav +++ para.evars + +and hpara_dll_av_add fav para = + List.iter ~f:(hpred_av_add fav) para.body_dll ; + fav ++ para.cell ; + fav ++ para.blink ; + fav ++ para.flink ; + fav +++ para.svars_dll ; + fav +++ para.evars_dll + +and hpred_av_add fav = function + | Hpointsto (base, se, te) + -> exp_av_add fav base ; strexp_av_add fav se ; exp_av_add fav te + | Hlseg (_, para, e1, e2, elist) + -> hpara_av_add fav para ; + exp_av_add fav e1 ; + exp_av_add fav e2 ; + List.iter ~f:(exp_av_add fav) elist + | Hdllseg (_, para, e1, e2, e3, e4, elist) + -> hpara_dll_av_add fav para ; + exp_av_add fav e1 ; + exp_av_add fav e2 ; + exp_av_add fav e3 ; + exp_av_add fav e4 ; + List.iter ~f:(exp_av_add fav) elist + +let hpara_shallow_av_add fav para = + List.iter ~f:(hpred_fav_add fav) para.body ; + fav ++ para.root ; + fav ++ para.next ; + fav +++ para.svars ; + fav +++ para.evars + +let hpara_dll_shallow_av_add fav para = + List.iter ~f:(hpred_fav_add fav) para.body_dll ; + fav ++ para.cell ; + fav ++ para.blink ; + fav ++ para.flink ; + fav +++ para.svars_dll ; + fav +++ para.evars_dll + +(** Variables in hpara, excluding bound vars in the body *) +let hpara_shallow_av = fav_imperative_to_functional hpara_shallow_av_add + +(** Variables in hpara_dll, excluding bound vars in the body *) +let hpara_dll_shallow_av = fav_imperative_to_functional hpara_dll_shallow_av_add + +(** {2 Functions for Substitution} *) +let rec reverse_with_base base = function [] -> base | x :: l -> reverse_with_base (x :: base) l + +let sorted_list_merge compare l1_in l2_in = + let rec merge acc l1 l2 = + match (l1, l2) with + | [], l2 + -> reverse_with_base l2 acc + | l1, [] + -> reverse_with_base l1 acc + | x1 :: l1', x2 :: l2' + -> if compare x1 x2 <= 0 then merge (x1 :: acc) l1' l2 else merge (x2 :: acc) l1 l2' + in + merge [] l1_in l2_in + +let rec sorted_list_check_consecutives f = function + | [] | [_] + -> false + | x1 :: (x2 :: _ as l) + -> if f x1 x2 then true else sorted_list_check_consecutives f l + +(** substitution *) +type ident_exp = Ident.t * Exp.t [@@deriving compare] + +let equal_ident_exp = [%compare.equal : ident_exp] + +type exp_subst = ident_exp list [@@deriving compare] + +type subst = [`Exp of exp_subst | `Typ of Typ.type_subst_t] [@@deriving compare] + +type subst_fun = [`Exp of Ident.t -> Exp.t | `Typ of (Typ.t -> Typ.t) * (Typ.Name.t -> Typ.Name.t)] + +(** Equality for substitutions. *) +let equal_exp_subst = [%compare.equal : exp_subst] + +let sub_check_duplicated_ids sub = + let f (id1, _) (id2, _) = Ident.equal id1 id2 in + sorted_list_check_consecutives f sub + +(** Create a substitution from a list of pairs. + For all (id1, e1), (id2, e2) in the input list, + if id1 = id2, then e1 = e2. *) +let exp_subst_of_list sub = + let sub' = List.sort ~cmp:compare_ident_exp sub in + let sub'' = remove_duplicates_from_sorted equal_ident_exp sub' in + if sub_check_duplicated_ids sub'' then assert false ; + sub' + +let subst_of_list sub = `Exp (exp_subst_of_list sub) + +(** like exp_subst_of_list, but allow duplicate ids and only keep the first occurrence *) +let exp_subst_of_list_duplicates sub = + let sub' = List.sort ~cmp:compare_ident_exp sub in + let rec remove_duplicate_ids = function + | (id1, e1) :: (id2, e2) :: l + -> if Ident.equal id1 id2 then remove_duplicate_ids ((id1, e1) :: l) + else (id1, e1) :: remove_duplicate_ids ((id2, e2) :: l) + | l + -> l + in + remove_duplicate_ids sub' + +(** Convert a subst to a list of pairs. *) +let sub_to_list sub = sub + +(** The empty substitution. *) +let exp_sub_empty = exp_subst_of_list [] + +let sub_empty = `Exp exp_sub_empty + +let is_sub_empty = function + | `Exp [] + -> true + | `Exp _ + -> false + | `Typ sub + -> Typ.is_type_subst_empty sub + +(** Join two substitutions into one. + For all id in dom(sub1) cap dom(sub2), sub1(id) = sub2(id). *) +let sub_join sub1 sub2 = + let sub = sorted_list_merge compare_ident_exp sub1 sub2 in + let sub' = remove_duplicates_from_sorted equal_ident_exp sub in + if sub_check_duplicated_ids sub' then assert false ; + sub + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. + The first component of the output is this common part. + The second and third components are the remainder of [subst1] + and [subst2], respectively. *) +let sub_symmetric_difference sub1_in sub2_in = + let rec diff sub_common sub1_only sub2_only sub1 sub2 = + match (sub1, sub2) with + | [], _ | _, [] + -> let sub1_only' = reverse_with_base sub1 sub1_only in + let sub2_only' = reverse_with_base sub2 sub2_only in + let sub_common = reverse_with_base [] sub_common in + (sub_common, sub1_only', sub2_only') + | id_e1 :: sub1', id_e2 :: sub2' + -> let n = compare_ident_exp id_e1 id_e2 in + if Int.equal n 0 then diff (id_e1 :: sub_common) sub1_only sub2_only sub1' sub2' + else if n < 0 then diff sub_common (id_e1 :: sub1_only) sub2_only sub1' sub2 + else diff sub_common sub1_only (id_e2 :: sub2_only) sub1 sub2' + in + diff [] [] [] sub1_in sub2_in + +(** [sub_find filter sub] returns the expression associated to the first identifier + that satisfies [filter]. Raise [Not_found] if there isn't one. *) +let sub_find filter (sub: exp_subst) = snd (List.find_exn ~f:(fun (i, _) -> filter i) sub) + +(** [sub_filter filter sub] restricts the domain of [sub] to the + identifiers satisfying [filter]. *) +let sub_filter filter (sub: exp_subst) = List.filter ~f:(fun (i, _) -> filter i) sub + +(** [sub_filter_pair filter sub] restricts the domain of [sub] to the + identifiers satisfying [filter(id, sub(id))]. *) +let sub_filter_pair = List.filter + +(** [sub_range_partition filter sub] partitions [sub] according to + whether range expressions satisfy [filter]. *) +let sub_range_partition filter (sub: exp_subst) = List.partition_tf ~f:(fun (_, e) -> filter e) sub + +(** [sub_domain_partition filter sub] partitions [sub] according to + whether domain identifiers satisfy [filter]. *) +let sub_domain_partition filter (sub: exp_subst) = + List.partition_tf ~f:(fun (i, _) -> filter i) sub + +(** Return the list of identifiers in the domain of the substitution. *) +let sub_domain sub = List.map ~f:fst sub + +(** Return the list of expressions in the range of the substitution. *) +let sub_range sub = List.map ~f:snd sub + +(** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. *) +let sub_range_map f sub = exp_subst_of_list (List.map ~f:(fun (i, e) -> (i, f e)) sub) + +(** [sub_map f g sub] applies the renaming [f] to identifiers in the domain + of [sub] and the substitution [g] to the expressions in the range of [sub]. *) +let sub_map f g sub = exp_subst_of_list (List.map ~f:(fun (i, e) -> (f i, g e)) sub) + +let mem_sub id sub = List.exists ~f:(fun (id1, _) -> Ident.equal id id1) sub + +(** Extend substitution and return [None] if not possible. *) +let extend_sub sub id exp : exp_subst option = + let compare (id1, _) (id2, _) = Ident.compare id1 id2 in + if mem_sub id sub then None else Some (sorted_list_merge compare sub [(id, exp)]) + +(** Free auxilary variables in the domain and range of the + substitution. *) +let sub_fav_add fav (sub: exp_subst) = + List.iter ~f:(fun (id, e) -> fav ++ id ; exp_fav_add fav e) sub + +(** Substitutions do not contain binders *) +let sub_av_add = sub_fav_add + +let rec exp_sub_ids (f: subst_fun) exp = + let f_typ x = match f with `Exp _ -> x | `Typ (f, _) -> f x in + let f_tname x = match f with `Exp _ -> x | `Typ (_, f) -> f x in + match (exp : Exp.t) with + | Var id -> ( + match f with + | `Exp f_exp -> ( + match f_exp id with + | Exp.Var id' when Ident.equal id id' + -> exp + (* it will preserve physical equality when needed *) | exp' + -> exp' ) + | _ + -> exp ) + | Lvar _ + -> exp + | Exn e + -> let e' = exp_sub_ids f e in + if phys_equal e' e then exp else Exp.Exn e' + | Closure c + -> let captured_vars = + IList.map_changed + (fun (e, pvar, typ as captured) -> + let e' = exp_sub_ids f e in + let typ' = f_typ typ in + if phys_equal e' e && phys_equal typ typ' then captured else (e', pvar, typ')) + c.captured_vars + in + if phys_equal captured_vars c.captured_vars then exp else Exp.Closure {c with captured_vars} + | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) + -> exp + | Cast (t, e) + -> let e' = exp_sub_ids f e in + let t' = f_typ t in + if phys_equal e' e && phys_equal t' t then exp else Exp.Cast (t', e') + | UnOp (op, e, typ_opt) + -> let e' = exp_sub_ids f e in + let typ_opt' = + match typ_opt with + | Some t + -> let t' = f_typ t in + if phys_equal t t' then typ_opt else Some t' + | None + -> typ_opt + in + if phys_equal e' e && phys_equal typ_opt typ_opt' then exp else Exp.UnOp (op, e', typ_opt') + | BinOp (op, e1, e2) + -> let e1' = exp_sub_ids f e1 in + let e2' = exp_sub_ids f e2 in + if phys_equal e1' e1 && phys_equal e2' e2 then exp else Exp.BinOp (op, e1', e2') + | Lfield (e, fld, typ) + -> let e' = exp_sub_ids f e in + let typ' = f_typ typ in + let fld' = Typ.Fieldname.class_name_replace ~f:f_tname fld in + if phys_equal e' e && phys_equal typ typ' && phys_equal fld fld' then exp + else Exp.Lfield (e', fld', typ') + | Lindex (e1, e2) + -> let e1' = exp_sub_ids f e1 in + let e2' = exp_sub_ids f e2 in + if phys_equal e1' e1 && phys_equal e2' e2 then exp else Exp.Lindex (e1', e2') + | Sizeof ({typ; dynamic_length= Some l; subtype} as sizeof_data) + -> let l' = exp_sub_ids f l in + let typ' = f_typ typ in + let subtype' = Subtype.sub_type f_tname subtype in + if phys_equal l' l && phys_equal typ typ' && phys_equal subtype subtype' then exp + else Exp.Sizeof {sizeof_data with typ= typ'; dynamic_length= Some l'; subtype= subtype'} + | Sizeof ({typ; dynamic_length= None; subtype} as sizeof_data) + -> let typ' = f_typ typ in + let subtype' = Subtype.sub_type f_tname subtype in + if phys_equal typ typ' then exp + else Exp.Sizeof {sizeof_data with typ= typ'; subtype= subtype'} + +let apply_sub subst : subst_fun = + match subst with + | `Exp l + -> `Exp + (fun id -> + match List.Assoc.find l ~equal:Ident.equal id with Some x -> x | None -> Exp.Var id) + | `Typ typ_subst + -> `Typ (Typ.sub_type typ_subst, Typ.sub_tname typ_subst) + +let exp_sub (subst: subst) e = exp_sub_ids (apply_sub subst) e + +(** apply [f] to id's in [instr]. if [sub_id_binders] is false, [f] is only applied to bound id's *) +let instr_sub_ids ~sub_id_binders f instr = + let sub_id id = + match exp_sub_ids f (Var id) with Var id' when not (Ident.equal id id') -> id' | _ -> id + in + let sub_typ x = match f with `Exp _ -> x | `Typ (f, _) -> f x in + match instr with + | Load (id, rhs_exp, typ, loc) + -> let id' = if sub_id_binders then sub_id id else id in + let rhs_exp' = exp_sub_ids f rhs_exp in + let typ' = sub_typ typ in + if phys_equal id' id && phys_equal rhs_exp' rhs_exp && phys_equal typ typ' then instr + else Load (id', rhs_exp', typ', loc) + | Store (lhs_exp, typ, rhs_exp, loc) + -> let lhs_exp' = exp_sub_ids f lhs_exp in + let typ' = sub_typ typ in + let rhs_exp' = exp_sub_ids f rhs_exp in + if phys_equal lhs_exp' lhs_exp && phys_equal typ typ' && phys_equal rhs_exp' rhs_exp then + instr + else Store (lhs_exp', typ', rhs_exp', loc) + | Call (ret_id, fun_exp, actuals, call_flags, loc) + -> let ret_id' = + if sub_id_binders then + match ret_id with + | Some (id, typ) + -> let id' = sub_id id in + let typ' = sub_typ typ in + if Ident.equal id id' && phys_equal typ typ' then ret_id else Some (id', typ') + | None + -> None + else ret_id + in + let fun_exp' = exp_sub_ids f fun_exp in + let actuals' = + IList.map_changed + (fun (actual, typ as actual_pair) -> + let actual' = exp_sub_ids f actual in + let typ' = sub_typ typ in + if phys_equal actual' actual && phys_equal typ typ' then actual_pair + else (actual', typ')) + actuals + in + if phys_equal ret_id' ret_id && phys_equal fun_exp' fun_exp && phys_equal actuals' actuals + then instr + else Call (ret_id', fun_exp', actuals', call_flags, loc) + | Prune (exp, loc, true_branch, if_kind) + -> let exp' = exp_sub_ids f exp in + if phys_equal exp' exp then instr else Prune (exp', loc, true_branch, if_kind) + | Remove_temps (ids, loc) + -> let ids' = IList.map_changed sub_id ids in + if phys_equal ids' ids then instr else Remove_temps (ids', loc) + | Declare_locals (locals, loc) + -> let locals' = + IList.map_changed + (fun (name, typ as local_var) -> + let typ' = sub_typ typ in + if phys_equal typ typ' then local_var else (name, typ')) + locals + in + if phys_equal locals locals' then instr else Declare_locals (locals', loc) + | Nullify _ | Abstract _ + -> instr + +(** apply [subst] to all id's in [instr], including binder id's *) +let instr_sub (subst: subst) instr = instr_sub_ids ~sub_id_binders:true (apply_sub subst) instr + +(** compare expressions from different procedures without considering loc's, ident's, and pvar's. + the [exp_map] param gives a mapping of names used in the procedure of [e1] to names used in the + procedure of [e2] *) +let rec exp_compare_structural e1 e2 exp_map = + let compare_exps_with_map e1 e2 exp_map = + try + let e1_mapping = Exp.Map.find e1 exp_map in + (Exp.compare e1_mapping e2, exp_map) + with Not_found -> + (* assume e1 and e2 equal, enforce by adding to [exp_map] *) + (0, Exp.Map.add e1 e2 exp_map) + in + match ((e1 : Exp.t), (e2 : Exp.t)) with + | Var _, Var _ + -> compare_exps_with_map e1 e2 exp_map + | UnOp (o1, e1, to1), UnOp (o2, e2, to2) + -> let n = Unop.compare o1 o2 in + if n <> 0 then (n, exp_map) + else + let n, exp_map = exp_compare_structural e1 e2 exp_map in + ((if n <> 0 then n else [%compare : Typ.t option] to1 to2), exp_map) + | BinOp (o1, e1, f1), BinOp (o2, e2, f2) + -> let n = Binop.compare o1 o2 in + if n <> 0 then (n, exp_map) + else + let n, exp_map = exp_compare_structural e1 e2 exp_map in + if n <> 0 then (n, exp_map) else exp_compare_structural f1 f2 exp_map + | Cast (t1, e1), Cast (t2, e2) + -> let n, exp_map = exp_compare_structural e1 e2 exp_map in + ((if n <> 0 then n else Typ.compare t1 t2), exp_map) + | Lvar _, Lvar _ + -> compare_exps_with_map e1 e2 exp_map + | Lfield (e1, f1, t1), Lfield (e2, f2, t2) + -> let n, exp_map = exp_compare_structural e1 e2 exp_map in + ( ( if n <> 0 then n + else + let n = Typ.Fieldname.compare f1 f2 in + if n <> 0 then n else Typ.compare t1 t2 ) + , exp_map ) + | Lindex (e1, f1), Lindex (e2, f2) + -> let n, exp_map = exp_compare_structural e1 e2 exp_map in + if n <> 0 then (n, exp_map) else exp_compare_structural f1 f2 exp_map + | _ + -> (Exp.compare e1 e2, exp_map) + +let exp_typ_compare_structural (e1, t1) (e2, t2) exp_map = + let n, exp_map = exp_compare_structural e1 e2 exp_map in + ((if n <> 0 then n else Typ.compare t1 t2), exp_map) + +(** compare instructions from different procedures without considering loc's, ident's, and pvar's. + the [exp_map] param gives a mapping of names used in the procedure of [instr1] to identifiers + used in the procedure of [instr2] *) +let compare_structural_instr instr1 instr2 exp_map = + let id_typ_opt_compare_structural id_typ1 id_typ2 exp_map = + let id_typ_compare_structural (id1, typ1) (id2, typ2) = + let n, exp_map = exp_compare_structural (Var id1) (Var id2) exp_map in + if n <> 0 then (n, exp_map) else (Typ.compare typ1 typ2, exp_map) + in + match (id_typ1, id_typ2) with + | Some it1, Some it2 + -> id_typ_compare_structural it1 it2 + | None, None + -> (0, exp_map) + | None, _ + -> (-1, exp_map) + | _, None + -> (1, exp_map) + in + let id_list_compare_structural ids1 ids2 exp_map = + let n = Int.compare (List.length ids1) (List.length ids2) in + if n <> 0 then (n, exp_map) + else + List.fold2_exn + ~f:(fun (n, exp_map) id1 id2 -> + if n <> 0 then (n, exp_map) else exp_compare_structural (Var id1) (Var id2) exp_map) + ~init:(0, exp_map) ids1 ids2 + in + match (instr1, instr2) with + | Load (id1, e1, t1, _), Load (id2, e2, t2, _) + -> let n, exp_map = exp_compare_structural (Var id1) (Var id2) exp_map in + if n <> 0 then (n, exp_map) + else + let n, exp_map = exp_compare_structural e1 e2 exp_map in + ((if n <> 0 then n else Typ.compare t1 t2), exp_map) + | Store (e11, t1, e21, _), Store (e12, t2, e22, _) + -> let n, exp_map = exp_compare_structural e11 e12 exp_map in + if n <> 0 then (n, exp_map) + else + let n = Typ.compare t1 t2 in + if n <> 0 then (n, exp_map) else exp_compare_structural e21 e22 exp_map + | Prune (cond1, _, true_branch1, ik1), Prune (cond2, _, true_branch2, ik2) + -> let n, exp_map = exp_compare_structural cond1 cond2 exp_map in + ( ( if n <> 0 then n + else + let n = Bool.compare true_branch1 true_branch2 in + if n <> 0 then n else compare_if_kind ik1 ik2 ) + , exp_map ) + | Call (ret_id1, e1, arg_ts1, _, cf1), Call (ret_id2, e2, arg_ts2, _, cf2) + -> let args_compare_structural args1 args2 exp_map = + let n = Int.compare (List.length args1) (List.length args2) in + if n <> 0 then (n, exp_map) + else + List.fold2_exn + ~f:(fun (n, exp_map) arg1 arg2 -> + if n <> 0 then (n, exp_map) else exp_typ_compare_structural arg1 arg2 exp_map) + ~init:(0, exp_map) args1 args2 + in + let n, exp_map = id_typ_opt_compare_structural ret_id1 ret_id2 exp_map in + if n <> 0 then (n, exp_map) + else + let n, exp_map = exp_compare_structural e1 e2 exp_map in + if n <> 0 then (n, exp_map) + else + let n, exp_map = args_compare_structural arg_ts1 arg_ts2 exp_map in + ((if n <> 0 then n else CallFlags.compare cf1 cf2), exp_map) + | Nullify (pvar1, _), Nullify (pvar2, _) + -> exp_compare_structural (Lvar pvar1) (Lvar pvar2) exp_map + | Abstract _, Abstract _ + -> (0, exp_map) + | Remove_temps (temps1, _), Remove_temps (temps2, _) + -> id_list_compare_structural temps1 temps2 exp_map + | Declare_locals (ptl1, _), Declare_locals (ptl2, _) + -> let n = Int.compare (List.length ptl1) (List.length ptl2) in + if n <> 0 then (n, exp_map) + else + List.fold2_exn + ~f:(fun (n, exp_map) (pv1, t1) (pv2, t2) -> + if n <> 0 then (n, exp_map) + else + let n, exp_map = exp_compare_structural (Lvar pv1) (Lvar pv2) exp_map in + if n <> 0 then (n, exp_map) else (Typ.compare t1 t2, exp_map)) + ~init:(0, exp_map) ptl1 ptl2 + | _ + -> (compare_instr instr1 instr2, exp_map) + +let atom_sub subst = atom_expmap (exp_sub subst) + +let hpred_sub subst = + let f (e, inst_opt) = (exp_sub subst e, inst_opt) in + hpred_expmap f + +(** {2 Functions for replacing occurrences of expressions.} *) +let rec exp_replace_exp epairs e = + (* First we check if there is an exact match *) + match List.find ~f:(fun (e1, _) -> Exp.equal e e1) epairs with + | Some (_, e2) + -> e2 + | None -> + (* If e is a compound expression, we need to check for its subexpressions as well *) + match e with + | Exp.UnOp (op, e0, ty) + -> let e0' = exp_replace_exp epairs e0 in + if phys_equal e0 e0' then e else Exp.UnOp (op, e0', ty) + | Exp.BinOp (op, lhs, rhs) + -> let lhs' = exp_replace_exp epairs lhs in + let rhs' = exp_replace_exp epairs rhs in + if phys_equal lhs lhs' && phys_equal rhs rhs' then e else Exp.BinOp (op, lhs', rhs') + | Exp.Cast (ty, e0) + -> let e0' = exp_replace_exp epairs e0 in + if phys_equal e0 e0' then e else Exp.Cast (ty, e0') + | Exp.Lfield (e0, fname, ty) + -> let e0' = exp_replace_exp epairs e0 in + if phys_equal e0 e0' then e else Exp.Lfield (e0', fname, ty) + | Exp.Lindex (base, index) + -> let base' = exp_replace_exp epairs base in + let index' = exp_replace_exp epairs index in + if phys_equal base base' && phys_equal index index' then e else Exp.Lindex (base', index') + | _ + -> e + +let atom_replace_exp epairs atom = atom_expmap (fun e -> exp_replace_exp epairs e) atom + +let rec strexp_replace_exp epairs = function + | Eexp (e, inst) + -> Eexp (exp_replace_exp epairs e, inst) + | Estruct (fsel, inst) + -> let f (fld, se) = (fld, strexp_replace_exp epairs se) in + Estruct (List.map ~f fsel, inst) + | Earray (len, isel, inst) + -> let len' = exp_replace_exp epairs len in + let f (idx, se) = + let idx' = exp_replace_exp epairs idx in + (idx', strexp_replace_exp epairs se) + in + Earray (len', List.map ~f isel, inst) + +let hpred_replace_exp epairs = function + | Hpointsto (root, se, te) + -> let root_repl = exp_replace_exp epairs root in + let strexp_repl = strexp_replace_exp epairs se in + let te_repl = exp_replace_exp epairs te in + Hpointsto (root_repl, strexp_repl, te_repl) + | Hlseg (k, para, root, next, shared) + -> let root_repl = exp_replace_exp epairs root in + let next_repl = exp_replace_exp epairs next in + let shared_repl = List.map ~f:(exp_replace_exp epairs) shared in + Hlseg (k, para, root_repl, next_repl, shared_repl) + | Hdllseg (k, para, e1, e2, e3, e4, shared) + -> let e1' = exp_replace_exp epairs e1 in + let e2' = exp_replace_exp epairs e2 in + let e3' = exp_replace_exp epairs e3 in + let e4' = exp_replace_exp epairs e4 in + let shared_repl = List.map ~f:(exp_replace_exp epairs) shared in + Hdllseg (k, para, e1', e2', e3', e4', shared_repl) + +(** {2 Compaction} *) +module HpredInstHash = Hashtbl.Make (struct + type t = hpred + + let equal = equal_hpred ~inst:true + + let hash = Hashtbl.hash +end) + +type sharing_env = {exph: Exp.t Exp.Hash.t; hpredh: hpred HpredInstHash.t} + +(** Create a sharing env to store canonical representations *) +let create_sharing_env () = {exph= Exp.Hash.create 3; hpredh= HpredInstHash.create 3} + +(** Return a canonical representation of the exp *) +let exp_compact sh e = + try Exp.Hash.find sh.exph e + with Not_found -> Exp.Hash.add sh.exph e e ; e + +let rec sexp_compact sh se = + match se with + | Eexp (e, inst) + -> Eexp (exp_compact sh e, inst) + | Estruct (fsel, inst) + -> Estruct (List.map ~f:(fun (f, se) -> (f, sexp_compact sh se)) fsel, inst) + | Earray _ + -> se + +(** Return a compact representation of the hpred *) +let _hpred_compact sh hpred = + match hpred with + | Hpointsto (e1, se, e2) + -> let e1' = exp_compact sh e1 in + let e2' = exp_compact sh e2 in + let se' = sexp_compact sh se in + Hpointsto (e1', se', e2') + | Hlseg _ + -> hpred + | Hdllseg _ + -> hpred + +let hpred_compact sh hpred = + try HpredInstHash.find sh.hpredh hpred + with Not_found -> + let hpred' = _hpred_compact sh hpred in + HpredInstHash.add sh.hpredh hpred' hpred' ; hpred' + +(** {2 Functions for constructing or destructing entities in this module} *) + +(** Compute the offset list of an expression *) +let exp_get_offsets exp = + let rec f offlist_past e = + match (e : Exp.t) with + | Var _ + | Const _ + | UnOp _ + | BinOp _ + | Exn _ + | Closure _ + | Lvar _ + | Sizeof {dynamic_length= None} + -> offlist_past + | Sizeof {dynamic_length= Some l} + -> f offlist_past l + | Cast (_, sub_exp) + -> f offlist_past sub_exp + | Lfield (sub_exp, fldname, typ) + -> f (Off_fld (fldname, typ) :: offlist_past) sub_exp + | Lindex (sub_exp, e) + -> f (Off_index e :: offlist_past) sub_exp + in + f [] exp + +let exp_add_offsets exp offsets = + let rec f acc = function + | [] + -> acc + | (Off_fld (fld, typ)) :: offs' + -> f (Exp.Lfield (acc, fld, typ)) offs' + | (Off_index e) :: offs' + -> f (Exp.Lindex (acc, e)) offs' + in + f exp offsets + +(** Convert all the lseg's in sigma to nonempty lsegs. *) +let sigma_to_sigma_ne sigma : (atom list * hpred list) list = + if Config.nelseg then + let f eqs_sigma_list hpred = + match hpred with + | Hpointsto _ | Hlseg (Lseg_NE, _, _, _, _) | Hdllseg (Lseg_NE, _, _, _, _, _, _) + -> let g (eqs, sigma) = (eqs, hpred :: sigma) in + List.map ~f:g eqs_sigma_list + | Hlseg (Lseg_PE, para, e1, e2, el) + -> let g (eqs, sigma) = + [(Aeq (e1, e2) :: eqs, sigma); (eqs, Hlseg (Lseg_NE, para, e1, e2, el) :: sigma)] + in + List.concat_map ~f:g eqs_sigma_list + | Hdllseg (Lseg_PE, para_dll, e1, e2, e3, e4, el) + -> let g (eqs, sigma) = + [ (Aeq (e1, e3) :: Aeq (e2, e4) :: eqs, sigma) + ; (eqs, Hdllseg (Lseg_NE, para_dll, e1, e2, e3, e4, el) :: sigma) ] + in + List.concat_map ~f:g eqs_sigma_list + in + List.fold ~f ~init:[([], [])] sigma + else [([], sigma)] + +(** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], + [e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], + then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] + for some fresh [_zs'].*) +let hpara_instantiate para e1 e2 elist = + let subst_for_svars = + let g id e = (id, e) in + try List.map2_exn ~f:g para.svars elist + with Invalid_argument _ -> assert false + in + let ids_evars = + let g _ = Ident.create_fresh Ident.kprimed in + List.map ~f:g para.evars + in + let subst_for_evars = + let g id id' = (id, Exp.Var id') in + try List.map2_exn ~f:g para.evars ids_evars + with Invalid_argument _ -> assert false + in + let subst = + `Exp + (exp_subst_of_list ((para.root, e1) :: (para.next, e2) :: subst_for_svars @ subst_for_evars)) + in + (ids_evars, List.map ~f:(hpred_sub subst) para.body) + +(** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], + [blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], + then the result of the instantiation is + [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] + for some fresh [_zs'].*) +let hpara_dll_instantiate (para: hpara_dll) cell blink flink elist = + let subst_for_svars = + let g id e = (id, e) in + try List.map2_exn ~f:g para.svars_dll elist + with Invalid_argument _ -> assert false + in + let ids_evars = + let g _ = Ident.create_fresh Ident.kprimed in + List.map ~f:g para.evars_dll + in + let subst_for_evars = + let g id id' = (id, Exp.Var id') in + try List.map2_exn ~f:g para.evars_dll ids_evars + with Invalid_argument _ -> assert false + in + let subst = + `Exp + (exp_subst_of_list + ( (para.cell, cell) :: (para.blink, blink) :: (para.flink, flink) :: subst_for_svars + @ subst_for_evars )) + in + (ids_evars, List.map ~f:(hpred_sub subst) para.body_dll) + +let custom_error = Pvar.mk_global (Mangled.from_string "INFER_CUSTOM_ERROR") Pvar.TUExtern diff --git a/infer/src/IR/Sil.mli b/infer/src/IR/Sil.mli new file mode 100644 index 000000000..5c61b211b --- /dev/null +++ b/infer/src/IR/Sil.mli @@ -0,0 +1,847 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language *) +open! IStd +module F = Format + +(** {2 Programs and Types} *) + +(** Convert expression lists to expression sets. *) + +val elist_to_eset : Exp.t list -> Exp.Set.t + +(** Kind of prune instruction *) + +type if_kind = + | Ik_bexp + (* boolean expressions, and exp ? exp : exp *) + | Ik_dowhile + | Ik_for + | Ik_if + | Ik_land_lor + (* obtained from translation of && or || *) + | Ik_while + | Ik_switch + [@@deriving compare] + +(** An instruction. *) + +type instr = + (** Load a value from the heap into an identifier. + [x = *lexp:typ] where + [lexp] is an expression denoting a heap address + [typ] is the root type of [lexp]. *) + (* Note for frontend writers: + [x] must be used in a subsequent instruction, otherwise the entire + `Load` instruction may be eliminated by copy-propagation. *) + | Load of Ident.t * Exp.t * Typ.t * Location.t + (** Store the value of an expression into the heap. + [*lexp1:typ = exp2] where + [lexp1] is an expression denoting a heap address + [typ] is the root type of [lexp1] + [exp2] is the expression whose value is store. *) + | Store of Exp.t * Typ.t * Exp.t * Location.t + (** prune the state based on [exp=1], the boolean indicates whether true branch *) + | Prune of Exp.t * Location.t * bool * if_kind + (** [Call (ret_id, e_fun, arg_ts, loc, call_flags)] represents an instruction + [ret_id = e_fun(arg_ts);]. The return value is ignored when [ret_id = None]. *) + | Call of (Ident.t * Typ.t) option * Exp.t * (Exp.t * Typ.t) list * Location.t * CallFlags.t + (** nullify stack variable *) + | Nullify of Pvar.t * Location.t + | Abstract of Location.t (** apply abstraction *) + | Remove_temps of Ident.t list * Location.t (** remove temporaries *) + | Declare_locals of (Pvar.t * Typ.t) list * Location.t (** declare local variables *) + [@@deriving compare] + +val equal_instr : instr -> instr -> bool + +(** compare instructions from different procedures without considering loc's, ident's, and pvar's. + the [exp_map] param gives a mapping of names used in the procedure of [instr1] to identifiers + used in the procedure of [instr2] *) + +val compare_structural_instr : instr -> instr -> Exp.t Exp.Map.t -> int * Exp.t Exp.Map.t + +val skip_instr : instr + +(** Check if an instruction is auxiliary, or if it comes from source instructions. *) + +val instr_is_auxiliary : instr -> bool + +(** Offset for an lvalue. *) + +type offset = Off_fld of Typ.Fieldname.t * Typ.t | Off_index of Exp.t + +(** {2 Components of Propositions} *) + +(** an atom is a pure atomic formula *) + +type atom = + | Aeq of Exp.t * Exp.t (** equality *) + | Aneq of Exp.t * Exp.t (** disequality *) + | Apred of PredSymb.t * (** predicate symbol applied to exps *) Exp.t list + | Anpred of PredSymb.t * (** negated predicate symbol applied to exps *) Exp.t list + [@@deriving compare] + +val equal_atom : atom -> atom -> bool + +val atom_has_local_addr : atom -> bool + +(** kind of lseg or dllseg predicates *) + +type lseg_kind = + | Lseg_NE (** nonempty (possibly circular) listseg *) + | Lseg_PE (** possibly empty (possibly circular) listseg *) + [@@deriving compare] + +val equal_lseg_kind : lseg_kind -> lseg_kind -> bool + +(** The boolean is true when the pointer was dereferenced without testing for zero. *) + +type zero_flag = bool option + +(** True when the value was obtained by doing case analysis on null in a procedure call. *) + +type null_case_flag = bool + +(** instrumentation of heap values *) + +type inst = + | Iabstraction + | Iactual_precondition + | Ialloc + | Iformal of zero_flag * null_case_flag + | Iinitial + | Ilookup + | Inone + | Inullify + | Irearrange of zero_flag * null_case_flag * int * PredSymb.path_pos + | Itaint + | Iupdate of zero_flag * null_case_flag * int * PredSymb.path_pos + | Ireturn_from_call of int + [@@deriving compare] + +val equal_inst : inst -> inst -> bool + +val inst_abstraction : inst + +val inst_actual_precondition : inst + +val inst_alloc : inst + +val inst_formal : inst + +(** for formal parameters and heap values at the beginning of the function *) + +val inst_initial : inst + +(** for initial values *) + +val inst_lookup : inst + +val inst_none : inst + +val inst_nullify : inst + +(** the boolean indicates whether the pointer is known nonzero *) + +val inst_rearrange : bool -> Location.t -> PredSymb.path_pos -> inst + +val inst_taint : inst + +val inst_update : Location.t -> PredSymb.path_pos -> inst + +(** Get the null case flag of the inst. *) + +val inst_get_null_case_flag : inst -> bool option + +(** Set the null case flag of the inst. *) + +val inst_set_null_case_flag : inst -> inst + +(** update the location of the instrumentation *) + +val inst_new_loc : Location.t -> inst -> inst + +(** Update [inst_old] to [inst_new] preserving the zero flag *) + +val update_inst : inst -> inst -> inst + +exception JoinFail + +(** join of instrumentations, can raise JoinFail *) + +val inst_partial_join : inst -> inst -> inst + +(** meet of instrumentations *) + +val inst_partial_meet : inst -> inst -> inst + +(** structured expressions represent a value of structured type, such as an array or a struct. *) + +type 'inst strexp0 = + | Eexp of Exp.t * 'inst (** Base case: expression with instrumentation *) + | Estruct of (Typ.Fieldname.t * 'inst strexp0) list * 'inst (** C structure *) + (** Array of given length + There are two conditions imposed / used in the array case. + First, if some index and value pair appears inside an array + in a strexp, then the index is less than the length of the array. + For instance, x |->[10 | e1: v1] implies that e1 <= 9. + Second, if two indices appear in an array, they should be different. + For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. *) + | Earray of Exp.t * (Exp.t * 'inst strexp0) list * 'inst + [@@deriving compare] + +type strexp = inst strexp0 + +(** Comparison function for strexp. + The inst:: parameter specifies whether instumentations should also + be considered (false by default). *) + +val compare_strexp : ?inst:bool -> strexp -> strexp -> int + +(** Equality function for strexp. + The inst:: parameter specifies whether instumentations should also + be considered (false by default). *) + +val equal_strexp : ?inst:bool -> strexp -> strexp -> bool + +(** an atomic heap predicate *) + +type 'inst hpred0 = + | Hpointsto of Exp.t * 'inst strexp0 * Exp.t + (** represents [exp|->strexp:typexp] where [typexp] + is an expression representing a type, e.h. [sizeof(t)]. *) + | Hlseg of lseg_kind * 'inst hpara0 * Exp.t * Exp.t * Exp.t list + (** higher - order predicate for singly - linked lists. + Should ensure that exp1!= exp2 implies that exp1 is allocated. + This assumption is used in the rearrangement. The last [exp list] parameter + is used to denote the shared links by all the nodes in the list. *) + | Hdllseg of lseg_kind * 'inst hpara_dll0 * Exp.t * Exp.t * Exp.t * Exp.t * Exp.t list + (** higher-order predicate for doubly-linked lists. + Parameter for the higher-order singly-linked list predicate. + Means "lambda (root,next,svars). Exists evars. body". + Assume that root, next, svars, evars are disjoint sets of + primed identifiers, and include all the free primed identifiers in body. + body should not contain any non - primed identifiers or program + variables (i.e. pvars). *) + [@@deriving compare] + +and 'inst hpara0 = + {root: Ident.t; next: Ident.t; svars: Ident.t list; evars: Ident.t list; body: 'inst hpred0 list} + [@@deriving compare] + +(** parameter for the higher-order doubly-linked list predicates. + Assume that all the free identifiers in body_dll should belong to + cell, blink, flink, svars_dll, evars_dll. *) +and 'inst hpara_dll0 = + { cell: Ident.t (** address cell *) + ; blink: Ident.t (** backward link *) + ; flink: Ident.t (** forward link *) + ; svars_dll: Ident.t list + ; evars_dll: Ident.t list + ; body_dll: 'inst hpred0 list } + [@@deriving compare] + +type hpred = inst hpred0 + +type hpara = inst hpara0 + +type hpara_dll = inst hpara_dll0 + +(** Comparison function for hpred. + The inst:: parameter specifies whether instumentations should also + be considered (false by default). *) + +val compare_hpred : ?inst:bool -> hpred -> hpred -> int + +(** Equality function for hpred. + The inst:: parameter specifies whether instumentations should also + be considered (false by default). *) + +val equal_hpred : ?inst:bool -> hpred -> hpred -> bool + +(** Sets of heap predicates *) + +module HpredSet : Caml.Set.S with type elt = hpred + +(** {2 Compaction} *) + +type sharing_env + +(** Create a sharing env to store canonical representations *) + +val create_sharing_env : unit -> sharing_env + +(** Return a canonical representation of the exp *) + +val exp_compact : sharing_env -> Exp.t -> Exp.t + +(** Return a compact representation of the exp *) + +val hpred_compact : sharing_env -> hpred -> hpred + +(** {2 Comparision And Inspection Functions} *) + +val has_objc_ref_counter : Tenv.t -> hpred -> bool + +(** Returns the zero value of a type, for int, float and ptr types, None othwewise *) + +val zero_value_of_numerical_type_option : Typ.t -> Exp.t option + +(** Returns the zero value of a type, for int, float and ptr types, fail otherwise *) + +val zero_value_of_numerical_type : Typ.t -> Exp.t + +(** Make a static local name in objc *) + +val mk_static_local_name : string -> string -> string + +(** Check if a pvar is a local static in objc *) + +val is_static_local_name : string -> Pvar.t -> bool + +(* A block pvar used to explain retain cycles *) + +val block_pvar : Pvar.t + +(** Check if a pvar is a local pointing to a block in objc *) + +val is_block_pvar : Pvar.t -> bool + +(** Return the lhs expression of a hpred *) + +val hpred_get_lhs : hpred -> Exp.t + +(** {2 Pretty Printing} *) + +(** Begin change color if using diff printing, return updated printenv and change status *) + +val color_pre_wrapper : Pp.env -> F.formatter -> 'a -> Pp.env * bool + +(** Close color annotation if changed *) + +val color_post_wrapper : bool -> Pp.env -> F.formatter -> unit + +(** Pretty print an expression. *) + +val pp_exp_printenv : Pp.env -> F.formatter -> Exp.t -> unit + +(** Pretty print an expression with type. *) + +val pp_exp_typ : Pp.env -> F.formatter -> Exp.t * Typ.t -> unit + +(** dump an expression. *) + +val d_exp : Exp.t -> unit + +(** Pretty print a type. *) + +val pp_texp : Pp.env -> F.formatter -> Exp.t -> unit + +(** Pretty print a type with all the details. *) + +val pp_texp_full : Pp.env -> F.formatter -> Exp.t -> unit + +(** Dump a type expression with all the details. *) + +val d_texp_full : Exp.t -> unit + +(** Pretty print a list of expressions. *) + +val pp_exp_list : Pp.env -> F.formatter -> Exp.t list -> unit + +(** Dump a list of expressions. *) + +val d_exp_list : Exp.t list -> unit + +(** Pretty print an offset *) + +val pp_offset : Pp.env -> F.formatter -> offset -> unit + +(** Convert an offset to a string *) + +val offset_to_string : offset -> string + +(** Dump an offset *) + +val d_offset : offset -> unit + +(** Pretty print a list of offsets *) + +val pp_offset_list : Pp.env -> F.formatter -> offset list -> unit + +(** Dump a list of offsets *) + +val d_offset_list : offset list -> unit + +(** Get the location of the instruction *) + +val instr_get_loc : instr -> Location.t + +(** get the expressions occurring in the instruction *) + +val instr_get_exps : instr -> Exp.t list + +(** Pretty print an instruction. *) + +val pp_instr : Pp.env -> F.formatter -> instr -> unit + +(** Dump an instruction. *) + +val d_instr : instr -> unit + +(** Pretty print a list of instructions. *) + +val pp_instr_list : Pp.env -> F.formatter -> instr list -> unit + +(** Dump a list of instructions. *) + +val d_instr_list : instr list -> unit + +(** Pretty print an atom. *) + +val pp_atom : Pp.env -> F.formatter -> atom -> unit + +(** Dump an atom. *) + +val d_atom : atom -> unit + +(** return a string representing the inst *) + +val inst_to_string : inst -> string + +(** Pretty print a strexp. *) + +val pp_sexp : Pp.env -> F.formatter -> strexp -> unit + +(** Dump a strexp. *) + +val d_sexp : strexp -> unit + +(** Pretty print a strexp list. *) + +val pp_sexp_list : Pp.env -> F.formatter -> strexp list -> unit + +(** Dump a strexp. *) + +val d_sexp_list : strexp list -> unit + +(** Pretty print a hpred. *) + +val pp_hpred : Pp.env -> F.formatter -> hpred -> unit + +(** Dump a hpred. *) + +val d_hpred : hpred -> unit + +(** Pretty print a hpara. *) + +val pp_hpara : Pp.env -> F.formatter -> hpara -> unit + +(** Pretty print a list of hparas. *) + +val pp_hpara_list : Pp.env -> F.formatter -> hpara list -> unit + +(** Pretty print a hpara_dll. *) + +val pp_hpara_dll : Pp.env -> F.formatter -> hpara_dll -> unit + +(** Pretty print a list of hpara_dlls. *) + +val pp_hpara_dll_list : Pp.env -> F.formatter -> hpara_dll list -> unit + +(** Module Predicates records the occurrences of predicates as parameters + of (doubly -)linked lists and Epara. + Provides unique numbering for predicates and an iterator. *) + +module Predicates : sig + (** predicate environment *) + + type env + + (** create an empty predicate environment *) + + val empty_env : unit -> env + + (** return true if the environment is empty *) + + val is_empty : env -> bool + + (** return the id of the hpara *) + + val get_hpara_id : env -> hpara -> int + + (** return the id of the hpara_dll *) + + val get_hpara_dll_id : env -> hpara_dll -> int + + (** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, + passing the unique id to the functions. The iterator can only be used once. *) + + val iter : env -> (int -> hpara -> unit) -> (int -> hpara_dll -> unit) -> unit + + (** Process one hpred, updating the predicate environment *) + + val process_hpred : env -> hpred -> unit +end + +(** Pretty print a hpred with optional predicate env *) + +val pp_hpred_env : Pp.env -> Predicates.env option -> F.formatter -> hpred -> unit + +(** {2 Functions for traversing SIL data types} *) + +(** This function should be used before adding a new + index to Earray. The [exp] is the newly created + index. This function "cleans" [exp] according to whether it is the + footprint or current part of the prop. + The function faults in the re - execution mode, as an internal check of the tool. *) + +val array_clean_new_index : bool -> Exp.t -> Exp.t + +(** Change exps in strexp using [f]. *) + +(** WARNING: the result might not be normalized. *) + +val strexp_expmap : (Exp.t * inst option -> Exp.t * inst option) -> strexp -> strexp + +(** Change exps in hpred by [f]. *) + +(** WARNING: the result might not be normalized. *) + +val hpred_expmap : (Exp.t * inst option -> Exp.t * inst option) -> hpred -> hpred + +(** Change instrumentations in hpred using [f]. *) + +val hpred_instmap : (inst -> inst) -> hpred -> hpred + +(** Change exps in hpred list by [f]. *) + +(** WARNING: the result might not be normalized. *) + +val hpred_list_expmap : (Exp.t * inst option -> Exp.t * inst option) -> hpred list -> hpred list + +(** Change exps in atom by [f]. *) + +(** WARNING: the result might not be normalized. *) + +val atom_expmap : (Exp.t -> Exp.t) -> atom -> atom + +(** Change exps in atom list by [f]. *) + +(** WARNING: the result might not be normalized. *) + +val atom_list_expmap : (Exp.t -> Exp.t) -> atom list -> atom list + +(** {2 Function for computing lexps in sigma} *) + +val hpred_list_get_lexps : (Exp.t -> bool) -> hpred list -> Exp.t list + +(** {2 Functions for computing program variables} *) + +val exp_fpv : Exp.t -> Pvar.t list + +val strexp_fpv : strexp -> Pvar.t list + +val atom_fpv : atom -> Pvar.t list + +val hpred_fpv : hpred -> Pvar.t list + +val hpara_fpv : hpara -> Pvar.t list + +(** {2 Functions for computing free non-program variables} *) + +(** Type of free variables. These include primed, normal and footprint variables. + We remember the order in which variables are added. *) + +type fav + +(** flag to indicate whether fav's are stored in duplicate form. + Only to be used with fav_to_list *) + +val fav_duplicates : bool ref + +(** Pretty print a fav. *) + +val pp_fav : Pp.env -> F.formatter -> fav -> unit + +(** Create a new [fav]. *) + +val fav_new : unit -> fav + +(** Emptyness check. *) + +val fav_is_empty : fav -> bool + +(** Check whether a predicate holds for all elements. *) + +val fav_for_all : fav -> (Ident.t -> bool) -> bool + +(** Check whether a predicate holds for some elements. *) + +val fav_exists : fav -> (Ident.t -> bool) -> bool + +(** Membership test fot [fav] *) + +val fav_mem : fav -> Ident.t -> bool + +(** Convert a list to a fav. *) + +val fav_from_list : Ident.t list -> fav + +(** Convert a [fav] to a list of identifiers while preserving the order + that identifiers were added to [fav]. *) + +val fav_to_list : fav -> Ident.t list + +(** Copy a [fav]. *) + +val fav_copy : fav -> fav + +(** Turn a xxx_fav_add function into a xxx_fav function *) + +val fav_imperative_to_functional : (fav -> 'a -> unit) -> 'a -> fav + +(** [fav_filter_ident fav f] only keeps [id] if [f id] is true. *) + +val fav_filter_ident : fav -> (Ident.t -> bool) -> unit + +(** Like [fav_filter_ident] but return a copy. *) + +val fav_copy_filter_ident : fav -> (Ident.t -> bool) -> fav + +(** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] + is in [fav2].*) + +val fav_subset_ident : fav -> fav -> bool + +(** add identifier list to fav *) + +val ident_list_fav_add : Ident.t list -> fav -> unit + +(** [exp_fav_add fav exp] extends [fav] with the free variables of [exp] *) + +val exp_fav_add : fav -> Exp.t -> unit + +val exp_fav : Exp.t -> fav + +val exp_fav_list : Exp.t -> Ident.t list + +val ident_in_exp : Ident.t -> Exp.t -> bool + +val strexp_fav_add : fav -> strexp -> unit + +val atom_fav_add : fav -> atom -> unit + +val atom_fav : atom -> fav + +val hpred_fav_add : fav -> hpred -> unit + +val hpred_fav : hpred -> fav + +(** Variables in hpara, excluding bound vars in the body *) + +val hpara_shallow_av : hpara -> fav + +(** Variables in hpara_dll, excluding bound vars in the body *) + +val hpara_dll_shallow_av : hpara_dll -> fav + +(** {2 Functions for computing all free or bound non-program variables} *) + +(** Non-program variables include all of primed, normal and footprint + variables. Thus, the functions essentially compute all the + identifiers occuring in a parameter. Some variables can appear more + than once in the result. *) + +val exp_av_add : fav -> Exp.t -> unit + +val strexp_av_add : fav -> strexp -> unit + +val atom_av_add : fav -> atom -> unit + +val hpred_av_add : fav -> hpred -> unit + +val hpara_av_add : fav -> hpara -> unit + +(** {2 Substitution} *) + +type exp_subst [@@deriving compare] + +type subst = [`Exp of exp_subst | `Typ of Typ.type_subst_t] [@@deriving compare] + +type subst_fun = [`Exp of Ident.t -> Exp.t | `Typ of (Typ.t -> Typ.t) * (Typ.Name.t -> Typ.Name.t)] + +(** Equality for substitutions. *) + +val equal_exp_subst : exp_subst -> exp_subst -> bool + +(** Create a substitution from a list of pairs. + For all (id1, e1), (id2, e2) in the input list, + if id1 = id2, then e1 = e2. *) + +val exp_subst_of_list : (Ident.t * Exp.t) list -> exp_subst + +val subst_of_list : (Ident.t * Exp.t) list -> subst + +(** like exp_subst_of_list, but allow duplicate ids and only keep the first occurrence *) + +val exp_subst_of_list_duplicates : (Ident.t * Exp.t) list -> exp_subst + +(** Convert a subst to a list of pairs. *) + +val sub_to_list : exp_subst -> (Ident.t * Exp.t) list + +(** The empty substitution. *) + +val sub_empty : subst + +val exp_sub_empty : exp_subst + +val is_sub_empty : subst -> bool + +(* let to_exp_subst : [< `Exp exp_subst] => exp_subst; *) + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. + The first component of the output is this common part. + The second and third components are the remainder of [subst1] + and [subst2], respectively. *) + +val sub_join : exp_subst -> exp_subst -> exp_subst + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. + The first component of the output is this common part. + The second and third components are the remainder of [subst1] + and [subst2], respectively. *) + +val sub_symmetric_difference : exp_subst -> exp_subst -> exp_subst * exp_subst * exp_subst + +(** [sub_find filter sub] returns the expression associated to the first identifier + that satisfies [filter]. + Raise [Not_found] if there isn't one. *) + +val sub_find : (Ident.t -> bool) -> exp_subst -> Exp.t + +(** [sub_filter filter sub] restricts the domain of [sub] to the + identifiers satisfying [filter]. *) + +val sub_filter : (Ident.t -> bool) -> exp_subst -> exp_subst + +(** [sub_filter_exp filter sub] restricts the domain of [sub] to the + identifiers satisfying [filter(id, sub(id))]. *) + +val sub_filter_pair : exp_subst -> f:(Ident.t * Exp.t -> bool) -> exp_subst + +(** [sub_range_partition filter sub] partitions [sub] according to + whether range expressions satisfy [filter]. *) + +val sub_range_partition : (Exp.t -> bool) -> exp_subst -> exp_subst * exp_subst + +(** [sub_domain_partition filter sub] partitions [sub] according to + whether domain identifiers satisfy [filter]. *) + +val sub_domain_partition : (Ident.t -> bool) -> exp_subst -> exp_subst * exp_subst + +(** Return the list of identifiers in the domain of the substitution. *) + +val sub_domain : exp_subst -> Ident.t list + +(** Return the list of expressions in the range of the substitution. *) + +val sub_range : exp_subst -> Exp.t list + +(** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. *) + +val sub_range_map : (Exp.t -> Exp.t) -> exp_subst -> exp_subst + +(** [sub_map f g sub] applies the renaming [f] to identifiers in the domain + of [sub] and the substitution [g] to the expressions in the range of [sub]. *) + +val sub_map : (Ident.t -> Ident.t) -> (Exp.t -> Exp.t) -> exp_subst -> exp_subst + +(** Checks whether [id] belongs to the domain of [subst]. *) + +val mem_sub : Ident.t -> exp_subst -> bool + +(** Extend substitution and return [None] if not possible. *) + +val extend_sub : exp_subst -> Ident.t -> Exp.t -> exp_subst option + +(** Free auxilary variables in the domain and range of the + substitution. *) + +val sub_fav_add : fav -> exp_subst -> unit + +(** Free or bound auxilary variables in the domain and range of the + substitution. *) + +val sub_av_add : fav -> exp_subst -> unit + +(** substitution functions *) + +(** WARNING: these functions do not ensure that the results are normalized. *) + +val exp_sub : subst -> Exp.t -> Exp.t + +val atom_sub : subst -> atom -> atom + +(** apply [subst] to all id's in [instr], including LHS id's *) + +val instr_sub : subst -> instr -> instr + +val hpred_sub : subst -> hpred -> hpred + +(** apply [f] to id's in [instr]. if [sub_id_binders] is false, [f] is only applied to bound id's *) + +val instr_sub_ids : sub_id_binders:bool -> subst_fun -> instr -> instr + +(** {2 Functions for replacing occurrences of expressions.} *) + +(** The first parameter should define a partial function. + No parts of hpara are replaced by these functions. *) + +val exp_replace_exp : (Exp.t * Exp.t) list -> Exp.t -> Exp.t + +val strexp_replace_exp : (Exp.t * Exp.t) list -> strexp -> strexp + +val atom_replace_exp : (Exp.t * Exp.t) list -> atom -> atom + +val hpred_replace_exp : (Exp.t * Exp.t) list -> hpred -> hpred + +(** {2 Functions for constructing or destructing entities in this module} *) + +(** Compute the offset list of an expression *) + +val exp_get_offsets : Exp.t -> offset list + +(** Add the offset list to an expression *) + +val exp_add_offsets : Exp.t -> offset list -> Exp.t + +val sigma_to_sigma_ne : hpred list -> (atom list * hpred list) list + +(** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], + [e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], + then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] + for some fresh [_zs'].*) + +val hpara_instantiate : hpara -> Exp.t -> Exp.t -> Exp.t list -> Ident.t list * hpred list + +(** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], + [blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], + then the result of the instantiation is + [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] + for some fresh [_zs'].*) + +val hpara_dll_instantiate : + hpara_dll -> Exp.t -> Exp.t -> Exp.t -> Exp.t list -> Ident.t list * hpred list + +val custom_error : Pvar.t diff --git a/infer/src/IR/Sil.re b/infer/src/IR/Sil.re deleted file mode 100644 index 5ea2b3dc1..000000000 --- a/infer/src/IR/Sil.re +++ /dev/null @@ -1,2600 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** The Smallfoot Intermediate Language */ -module L = Logging; - -module F = Format; - - -/** {2 Programs and Types} */ - -/** Kind of prune instruction */ -type if_kind = - | Ik_bexp /* boolean expressions, and exp ? exp : exp */ - | Ik_dowhile - | Ik_for - | Ik_if - | Ik_land_lor /* obtained from translation of && or || */ - | Ik_while - | Ik_switch -[@@deriving compare]; - - -/** An instruction. */ -type instr = - /** Load a value from the heap into an identifier. - [x = *lexp:typ] where - [lexp] is an expression denoting a heap address - [typ] is the root type of [lexp]. */ - /* Note for frontend writers: - [x] must be used in a subsequent instruction, otherwise the entire - `Load` instruction may be eliminated by copy-propagation. */ - | Load Ident.t Exp.t Typ.t Location.t - /** Store the value of an expression into the heap. - [*lexp1:typ = exp2] where - [lexp1] is an expression denoting a heap address - [typ] is the root type of [lexp1] - [exp2] is the expression whose value is store. */ - | Store Exp.t Typ.t Exp.t Location.t - /** prune the state based on [exp=1], the boolean indicates whether true branch */ - | Prune Exp.t Location.t bool if_kind - /** [Call (ret_id, e_fun, arg_ts, loc, call_flags)] represents an instruction - [ret_id = e_fun(arg_ts);]. The return value is ignored when [ret_id = None]. */ - | Call (option (Ident.t, Typ.t)) Exp.t (list (Exp.t, Typ.t)) Location.t CallFlags.t - /** nullify stack variable */ - | Nullify Pvar.t Location.t - | Abstract Location.t /** apply abstraction */ - | Remove_temps (list Ident.t) Location.t /** remove temporaries */ - | Declare_locals (list (Pvar.t, Typ.t)) Location.t /** declare local variables */ -[@@deriving compare]; - -let equal_instr = [%compare.equal : instr]; - -let skip_instr = Remove_temps [] Location.dummy; - - -/** Check if an instruction is auxiliary, or if it comes from source instructions. */ -let instr_is_auxiliary = - fun - | Load _ - | Store _ - | Prune _ - | Call _ => false - | Nullify _ - | Abstract _ - | Remove_temps _ - | Declare_locals _ => true; - - -/** offset for an lvalue */ -type offset = - | Off_fld Typ.Fieldname.t Typ.t - | Off_index Exp.t; - - -/** {2 Components of Propositions} */ - -/** an atom is a pure atomic formula */ -type atom = - | Aeq Exp.t Exp.t /** equality */ - | Aneq Exp.t Exp.t /** disequality */ - | Apred PredSymb.t (list Exp.t) /** predicate symbol applied to exps */ - | Anpred PredSymb.t (list Exp.t) /** negated predicate symbol applied to exps */ -[@@deriving compare]; - -let equal_atom = [%compare.equal : atom]; - -let atom_has_local_addr a => - switch a { - | Aeq e0 e1 - | Aneq e0 e1 => Exp.has_local_addr e0 || Exp.has_local_addr e1 - | Apred _ - | Anpred _ => false - }; - - -/** kind of lseg or dllseg predicates */ -type lseg_kind = - | Lseg_NE /** nonempty (possibly circular) listseg */ - | Lseg_PE /** possibly empty (possibly circular) listseg */ -[@@deriving compare]; - -let equal_lseg_kind = [%compare.equal : lseg_kind]; - - -/** The boolean is true when the pointer was dereferenced without testing for zero. */ -type zero_flag = option bool [@@deriving compare]; - - -/** True when the value was obtained by doing case analysis on null in a procedure call. */ -type null_case_flag = bool [@@deriving compare]; - - -/** instrumentation of heap values */ -type inst = - | Iabstraction - | Iactual_precondition - | Ialloc - | Iformal zero_flag null_case_flag - | Iinitial - | Ilookup - | Inone - | Inullify - | Irearrange zero_flag null_case_flag int PredSymb.path_pos - | Itaint - | Iupdate zero_flag null_case_flag int PredSymb.path_pos - | Ireturn_from_call int -[@@deriving compare]; - -let equal_inst = [%compare.equal : inst]; - - -/** structured expressions represent a value of structured type, such as an array or a struct. */ -type strexp0 'inst = - | Eexp Exp.t 'inst /** Base case: expression with instrumentation */ - | Estruct (list (Typ.Fieldname.t, strexp0 'inst)) 'inst /** C structure */ - /** Array of given length - There are two conditions imposed / used in the array case. - First, if some index and value pair appears inside an array - in a strexp, then the index is less than the length of the array. - For instance, x |->[10 | e1: v1] implies that e1 <= 9. - Second, if two indices appear in an array, they should be different. - For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. */ - | Earray Exp.t (list (Exp.t, strexp0 'inst)) 'inst -[@@deriving compare]; - -type strexp = strexp0 inst; - -let compare_strexp ::inst=false se1 se2 => - compare_strexp0 (inst ? compare_inst : (fun _ _ => 0)) se1 se2; - -let equal_strexp ::inst=false se1 se2 => Int.equal (compare_strexp ::inst se1 se2) 0; - - -/** an atomic heap predicate */ -type hpred0 'inst = - | Hpointsto Exp.t (strexp0 'inst) Exp.t - /** represents [exp|->strexp:typexp] where [typexp] - is an expression representing a type, e.h. [sizeof(t)]. */ - | Hlseg lseg_kind (hpara0 'inst) Exp.t Exp.t (list Exp.t) - /** higher - order predicate for singly - linked lists. - Should ensure that exp1!= exp2 implies that exp1 is allocated. - This assumption is used in the rearrangement. The last [exp list] parameter - is used to denote the shared links by all the nodes in the list. */ - | Hdllseg lseg_kind (hpara_dll0 'inst) Exp.t Exp.t Exp.t Exp.t (list Exp.t) - /** higher-order predicate for doubly-linked lists. - Parameter for the higher-order singly-linked list predicate. - Means "lambda (root,next,svars). Exists evars. body". - Assume that root, next, svars, evars are disjoint sets of - primed identifiers, and include all the free primed identifiers in body. - body should not contain any non - primed identifiers or program - variables (i.e. pvars). */ -[@@deriving compare] -and hpara0 'inst = { - root: Ident.t, - next: Ident.t, - svars: list Ident.t, - evars: list Ident.t, - body: list (hpred0 'inst) -} -[@@deriving compare] -/** parameter for the higher-order doubly-linked list predicates. - Assume that all the free identifiers in body_dll should belong to - cell, blink, flink, svars_dll, evars_dll. */ -and hpara_dll0 'inst = { - cell: Ident.t, /** address cell */ - blink: Ident.t, /** backward link */ - flink: Ident.t, /** forward link */ - svars_dll: list Ident.t, - evars_dll: list Ident.t, - body_dll: list (hpred0 'inst) -} -[@@deriving compare]; - -type hpred = hpred0 inst; - - -/** Comparsion between heap predicates. Reverse natural order, and order first by anchor exp. */ -let compare_hpred ::inst=false hpred1 hpred2 => - compare_hpred0 (inst ? compare_inst : (fun _ _ => 0)) hpred1 hpred2; - -let equal_hpred ::inst=false hpred1 hpred2 => Int.equal (compare_hpred ::inst hpred1 hpred2) 0; - -type hpara = hpara0 inst; - -let compare_hpara = compare_hpara0 (fun _ _ => 0); - -let equal_hpara = [%compare.equal : hpara]; - -type hpara_dll = hpara_dll0 inst; - -let compare_hpara_dll = compare_hpara_dll0 (fun _ _ => 0); - -let equal_hpara_dll = [%compare.equal : hpara_dll]; - - -/** Return the lhs expression of a hpred */ -let hpred_get_lhs h => - switch h { - | Hpointsto e _ _ - | Hlseg _ _ e _ _ - | Hdllseg _ _ e _ _ _ _ => e - }; - - -/** {2 Comparision and Inspection Functions} */ -let has_objc_ref_counter tenv hpred => - switch hpred { - | Hpointsto _ _ (Sizeof {typ: {desc: Tstruct name}}) => - switch (Tenv.lookup tenv name) { - | Some {fields} => List.exists f::Typ.Struct.is_objc_ref_counter_field fields - | _ => false - } - | _ => false - }; - - -/** Returns the zero value of a type, for int, float and ptr types, None othwewise */ -let zero_value_of_numerical_type_option typ => - switch typ.Typ.desc { - | Typ.Tint _ => Some (Exp.Const (Cint IntLit.zero)) - | Typ.Tfloat _ => Some (Exp.Const (Cfloat 0.0)) - | Typ.Tptr _ => Some (Exp.Const (Cint IntLit.null)) - | _ => None - }; - - -/** Returns the zero value of a type, for int, float and ptr types, fail otherwise */ -let zero_value_of_numerical_type typ => Option.value_exn (zero_value_of_numerical_type_option typ); - - -/** Make a static local name in objc */ -let mk_static_local_name pname vname => pname ^ "_" ^ vname; - - -/** Check if a pvar is a local static in objc */ -let is_static_local_name pname pvar => { - /* local static name is of the form procname_varname */ - let var_name = Mangled.to_string (Pvar.get_name pvar); - switch (Str.split_delim (Str.regexp_string pname) var_name) { - | [_, _] => true - | _ => false - } -}; - - -/** {2 Sets of expressions} */ -let elist_to_eset es => List.fold f::(fun set e => Exp.Set.add e set) init::Exp.Set.empty es; - - -/** {2 Sets of heap predicates} */ -module HpredSet = - Caml.Set.Make { - type t = hpred; - let compare = compare_hpred inst::false; - }; - - -/** {2 Pretty Printing} */ - -/** Begin change color if using diff printing, return updated printenv and change status */ -let color_pre_wrapper pe f x => - if (Config.print_using_diff && pe.Pp.kind != Pp.TEXT) { - let color = pe.Pp.cmap_norm (Obj.repr x); - if (color != pe.Pp.color) { - ( - if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) { - Io_infer.Html.pp_start_color - } else { - Latex.pp_color - } - ) - f color; - if (Pp.equal_color color Pp.Red) { - ( - Pp.{ - /** All subexpressiona red */ - ...pe, - cmap_norm: colormap_red, - color: Red - }, - true - ) - } else { - (Pp.{...pe, color}, true) - } - } else { - (pe, false) - } - } else { - (pe, false) - }; - - -/** Close color annotation if changed */ -let color_post_wrapper changed pe f => - if changed { - if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) { - Io_infer.Html.pp_end_color f () - } else { - Latex.pp_color f pe.Pp.color - } - }; - - -/** Print a sequence with difference mode if enabled. */ -let pp_seq_diff pp pe0 f => - if (not Config.print_using_diff) { - Pp.comma_seq pp f - } else { - let rec doit = - fun - | [] => () - | [x] => { - let (_, changed) = color_pre_wrapper pe0 f x; - F.fprintf f "%a" pp x; - color_post_wrapper changed pe0 f - } - | [x, ...l] => { - let (_, changed) = color_pre_wrapper pe0 f x; - F.fprintf f "%a" pp x; - color_post_wrapper changed pe0 f; - F.fprintf f ", "; - doit l - }; - doit - }; - - -/** Pretty print an expression. */ -let pp_exp_printenv pe0 f e0 => { - let (pe, changed) = color_pre_wrapper pe0 f e0; - let e = - switch pe.Pp.obj_sub { - | Some sub => Obj.obj (sub (Obj.repr e0)) /* apply object substitution to expression */ - | None => e0 - }; - if (not (Exp.equal e0 e)) { - switch e { - | Exp.Lvar pvar => Pvar.pp_value pe f pvar - | _ => assert false - } - } else { - Exp.pp_printenv pe Typ.pp f e - }; - color_post_wrapper changed pe0 f -}; - - -/** dump an expression. */ -let d_exp (e: Exp.t) => L.add_print_action (L.PTexp, Obj.repr e); - - -/** Pretty print a list of expressions. */ -let pp_exp_list pe f expl => (Pp.seq (pp_exp_printenv pe)) f expl; - - -/** dump a list of expressions. */ -let d_exp_list (el: list Exp.t) => L.add_print_action (L.PTexp_list, Obj.repr el); - -let pp_texp pe f => - fun - | Exp.Sizeof {typ, nbytes, dynamic_length, subtype} => { - let pp_len f l => Option.iter f::(F.fprintf f "[%a]" (pp_exp_printenv pe)) l; - let pp_size f size => Option.iter f::(Int.pp f) size; - F.fprintf - f "%a%a%a%a" (Typ.pp pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp subtype - } - | e => (pp_exp_printenv pe) f e; - - -/** Pretty print a type with all the details. */ -let pp_texp_full pe f => - fun - | Exp.Sizeof {typ, nbytes, dynamic_length, subtype} => { - let pp_len f l => Option.iter f::(F.fprintf f "[%a]" (pp_exp_printenv pe)) l; - let pp_size f size => Option.iter f::(Int.pp f) size; - F.fprintf - f "%a%a%a%a" (Typ.pp_full pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp subtype - } - | e => Exp.pp_printenv pe Typ.pp_full f e; - - -/** Dump a type expression with all the details. */ -let d_texp_full (te: Exp.t) => L.add_print_action (L.PTtexp_full, Obj.repr te); - - -/** Pretty print an offset */ -let pp_offset pe f => - fun - | Off_fld fld _ => F.fprintf f "%a" Typ.Fieldname.pp fld - | Off_index exp => F.fprintf f "%a" (pp_exp_printenv pe) exp; - - -/** Convert an offset to a string */ -let offset_to_string e => F.asprintf "%a" (pp_offset Pp.text) e; - - -/** dump an offset. */ -let d_offset (off: offset) => L.add_print_action (L.PToff, Obj.repr off); - - -/** Pretty print a list of offsets */ -let rec pp_offset_list pe f => - fun - | [] => () - | [off1, off2] => F.fprintf f "%a.%a" (pp_offset pe) off1 (pp_offset pe) off2 - | [off, ...off_list] => F.fprintf f "%a.%a" (pp_offset pe) off (pp_offset_list pe) off_list; - - -/** Dump a list of offsets */ -let d_offset_list (offl: list offset) => L.add_print_action (L.PToff_list, Obj.repr offl); - -let pp_exp_typ pe f (e, t) => F.fprintf f "%a:%a" (pp_exp_printenv pe) e (Typ.pp pe) t; - - -/** Get the location of the instruction */ -let instr_get_loc = - fun - | Load _ _ _ loc - | Store _ _ _ loc - | Prune _ loc _ _ - | Call _ _ _ loc _ - | Nullify _ loc - | Abstract loc - | Remove_temps _ loc - | Declare_locals _ loc => loc; - - -/** get the expressions occurring in the instruction */ -let instr_get_exps = - fun - | Load id e _ _ => [Exp.Var id, e] - | Store e1 _ e2 _ => [e1, e2] - | Prune cond _ _ _ => [cond] - | Call ret_id e _ _ _ => [ - e, - ...Option.value_map f::(fun (id, _) => [Exp.Var id]) default::[] ret_id - ] - | Nullify pvar _ => [Exp.Lvar pvar] - | Abstract _ => [] - | Remove_temps temps _ => List.map f::(fun id => Exp.Var id) temps - | Declare_locals _ => []; - - -/** Pretty print an instruction. */ -let pp_instr pe0 f instr => { - let (pe, changed) = color_pre_wrapper pe0 f instr; - switch instr { - | Load id e t loc => - F.fprintf - f "%a=*%a:%a %a" (Ident.pp pe) id (pp_exp_printenv pe) e (Typ.pp pe) t Location.pp loc - | Store e1 t e2 loc => - F.fprintf - f - "*%a:%a=%a %a" - (pp_exp_printenv pe) - e1 - (Typ.pp pe) - t - (pp_exp_printenv pe) - e2 - Location.pp - loc - | Prune cond loc true_branch _ => - F.fprintf f "PRUNE(%a, %b); %a" (pp_exp_printenv pe) cond true_branch Location.pp loc - | Call ret_id e arg_ts loc cf => - switch ret_id { - | None => () - | Some (id, _) => F.fprintf f "%a=" (Ident.pp pe) id - }; - F.fprintf - f - "%a(%a)%a %a" - (pp_exp_printenv pe) - e - (Pp.comma_seq (pp_exp_typ pe)) - arg_ts - CallFlags.pp - cf - Location.pp - loc - | Nullify pvar loc => F.fprintf f "NULLIFY(%a); %a" (Pvar.pp pe) pvar Location.pp loc - | Abstract loc => F.fprintf f "APPLY_ABSTRACTION; %a" Location.pp loc - | Remove_temps temps loc => - F.fprintf f "REMOVE_TEMPS(%a); %a" (Ident.pp_list pe) temps Location.pp loc - | Declare_locals ptl loc => - let pp_typ fmt (pvar, _) => F.fprintf fmt "%a" (Pvar.pp pe) pvar; - F.fprintf f "DECLARE_LOCALS(%a); %a" (Pp.comma_seq pp_typ) ptl Location.pp loc - }; - color_post_wrapper changed pe0 f -}; - - -/** Check if a pvar is a local pointing to a block in objc */ -let is_block_pvar pvar => Typ.has_block_prefix (Mangled.to_string (Pvar.get_name pvar)); - -/* A block pvar used to explain retain cycles */ -let block_pvar = Pvar.mk (Mangled.from_string "block") (Typ.Procname.from_string_c_fun ""); - - -/** Dump an instruction. */ -let d_instr (i: instr) => L.add_print_action (L.PTinstr, Obj.repr i); - -let rec pp_instr_list pe f => - fun - | [] => F.fprintf f "" - | [i, ...is] => F.fprintf f "%a;@\n%a" (pp_instr pe) i (pp_instr_list pe) is; - - -/** Dump a list of instructions. */ -let d_instr_list (il: list instr) => L.add_print_action (L.PTinstr_list, Obj.repr il); - -let pp_atom pe0 f a => { - let (pe, changed) = color_pre_wrapper pe0 f a; - switch a { - | Aeq (BinOp op e1 e2) (Const (Cint i)) when IntLit.isone i => - switch pe.Pp.kind { - | TEXT - | HTML => F.fprintf f "%a" (pp_exp_printenv pe) (Exp.BinOp op e1 e2) - | LATEX => F.fprintf f "%a" (pp_exp_printenv pe) (Exp.BinOp op e1 e2) - } - | Aeq e1 e2 => - switch pe.Pp.kind { - | TEXT - | HTML => F.fprintf f "%a = %a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 - | LATEX => F.fprintf f "%a{=}%a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 - } - | Aneq e1 e2 => - switch pe.Pp.kind { - | TEXT - | HTML => F.fprintf f "%a != %a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 - | LATEX => F.fprintf f "%a{\\neq}%a" (pp_exp_printenv pe) e1 (pp_exp_printenv pe) e2 - } - | Apred a es => - F.fprintf f "%s(%a)" (PredSymb.to_string pe a) (Pp.comma_seq (pp_exp_printenv pe)) es - | Anpred a es => - F.fprintf f "!%s(%a)" (PredSymb.to_string pe a) (Pp.comma_seq (pp_exp_printenv pe)) es - }; - color_post_wrapper changed pe0 f -}; - - -/** dump an atom */ -let d_atom (a: atom) => L.add_print_action (L.PTatom, Obj.repr a); - -let pp_lseg_kind f => - fun - | Lseg_NE => F.fprintf f "ne" - | Lseg_PE => F.fprintf f ""; - - -/** Print a *-separated sequence. */ -let rec pp_star_seq pp f => - fun - | [] => () - | [x] => F.fprintf f "%a" pp x - | [x, ...l] => F.fprintf f "%a * %a" pp x (pp_star_seq pp) l; - - -/********* START OF MODULE Predicates **********/ - -/** Module Predicates records the occurrences of predicates as parameters - of (doubly -)linked lists and Epara. Provides unique numbering - for predicates and an iterator. */ -module Predicates: { - - /** predicate environment */ - type env; - - /** create an empty predicate environment */ - let empty_env: unit => env; - - /** return true if the environment is empty */ - let is_empty: env => bool; - - /** return the id of the hpara */ - let get_hpara_id: env => hpara => int; - - /** return the id of the hpara_dll */ - let get_hpara_dll_id: env => hpara_dll => int; - - /** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, - passing the unique id to the functions. The iterator can only be used once. */ - let iter: env => (int => hpara => unit) => (int => hpara_dll => unit) => unit; - - /** Process one hpred, updating the predicate environment */ - let process_hpred: env => hpred => unit; -} = { - - /** hash tables for hpara */ - module HparaHash = - Hashtbl.Make { - type t = hpara; - let equal = equal_hpara; - let hash = Hashtbl.hash; - }; - - /** hash tables for hpara_dll */ - module HparaDllHash = - Hashtbl.Make { - type t = hpara_dll; - let equal = equal_hpara_dll; - let hash = Hashtbl.hash; - }; - - /** Map each visited hpara to a unique number and a boolean denoting whether it has been emitted, - also keep a list of hparas still to be emitted. Same for hpara_dll. */ - type env = { - mutable num: int, - hash: HparaHash.t (int, bool), - mutable todo: list hpara, - hash_dll: HparaDllHash.t (int, bool), - mutable todo_dll: list hpara_dll - }; - - /** return true if the environment is empty */ - let is_empty env => Int.equal env.num 0; - - /** return the id of the hpara */ - let get_hpara_id env hpara => fst (HparaHash.find env.hash hpara); - - /** return the id of the hpara_dll */ - let get_hpara_dll_id env hpara_dll => fst (HparaDllHash.find env.hash_dll hpara_dll); - - /** Process one hpara, updating the map from hparas to numbers, and the todo list */ - let process_hpara env hpara => - if (not (HparaHash.mem env.hash hpara)) { - HparaHash.add env.hash hpara (env.num, false); - env.num = env.num + 1; - env.todo = env.todo @ [hpara] - }; - - /** Process one hpara_dll, updating the map from hparas to numbers, and the todo list */ - let process_hpara_dll env hpara_dll => - if (not (HparaDllHash.mem env.hash_dll hpara_dll)) { - HparaDllHash.add env.hash_dll hpara_dll (env.num, false); - env.num = env.num + 1; - env.todo_dll = env.todo_dll @ [hpara_dll] - }; - - /** Process a sexp, updating env */ - let rec process_sexp env => - fun - | Eexp _ => () - | Earray _ esel _ => List.iter f::(fun (_, se) => process_sexp env se) esel - | Estruct fsel _ => List.iter f::(fun (_, se) => process_sexp env se) fsel; - - /** Process one hpred, updating env */ - let rec process_hpred env => - fun - | Hpointsto _ se _ => process_sexp env se - | Hlseg _ hpara _ _ _ => { - List.iter f::(process_hpred env) hpara.body; - process_hpara env hpara - } - | Hdllseg _ hpara_dll _ _ _ _ _ => { - List.iter f::(process_hpred env) hpara_dll.body_dll; - process_hpara_dll env hpara_dll - }; - - /** create an empty predicate environment */ - let empty_env () => { - num: 0, - hash: HparaHash.create 3, - todo: [], - hash_dll: HparaDllHash.create 3, - todo_dll: [] - }; - - /** iterator for predicates which are marked as todo in env, - unless they have been visited already. - This can in turn extend the todo list for the nested predicates, - which are then visited as well. - Can be applied only once, as it destroys the todo list */ - let iter (env: env) f f_dll => - while (env.todo != [] || env.todo_dll != []) { - switch env.todo { - | [hpara, ...todo'] => - env.todo = todo'; - let (n, emitted) = HparaHash.find env.hash hpara; - if (not emitted) { - f n hpara - } - | [] => - switch env.todo_dll { - | [hpara_dll, ...todo_dll'] => - env.todo_dll = todo_dll'; - let (n, emitted) = HparaDllHash.find env.hash_dll hpara_dll; - if (not emitted) { - f_dll n hpara_dll - } - | [] => () - } - } - }; -}; - - -/********* END OF MODULE Predicates **********/ -let pp_texp_simple pe => - switch pe.Pp.opt { - | SIM_DEFAULT => pp_texp pe - | SIM_WITH_TYP => pp_texp_full pe - }; - -let inst_abstraction = Iabstraction; - -let inst_actual_precondition = Iactual_precondition; - -let inst_alloc = Ialloc; - -let inst_formal = Iformal None false; /** for formal parameters */ - -let inst_initial = Iinitial; /** for initial values */ - -let inst_lookup = Ilookup; - -let inst_none = Inone; - -let inst_nullify = Inullify; - -let inst_rearrange b loc pos => Irearrange (Some b) false loc.Location.line pos; - -let inst_taint = Itaint; - -let inst_update loc pos => Iupdate None false loc.Location.line pos; - - -/** update the location of the instrumentation */ -let inst_new_loc loc inst => - switch inst { - | Iabstraction => inst - | Iactual_precondition => inst - | Ialloc => inst - | Iformal _ => inst - | Iinitial => inst - | Ilookup => inst - | Inone => inst - | Inullify => inst - | Irearrange zf ncf _ pos => Irearrange zf ncf loc.Location.line pos - | Itaint => inst - | Iupdate zf ncf _ pos => Iupdate zf ncf loc.Location.line pos - | Ireturn_from_call _ => Ireturn_from_call loc.Location.line - }; - - -/** return a string representing the inst */ -let inst_to_string inst => { - let zero_flag_to_string = - fun - | Some true => "(z)" - | _ => ""; - let null_case_flag_to_string ncf => if ncf {"(ncf)"} else {""}; - switch inst { - | Iabstraction => "abstraction" - | Iactual_precondition => "actual_precondition" - | Ialloc => "alloc" - | Iformal zf ncf => "formal" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf - | Iinitial => "initial" - | Ilookup => "lookup" - | Inone => "none" - | Inullify => "nullify" - | Irearrange zf ncf n _ => - "rearrange:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n - | Itaint => "taint" - | Iupdate zf ncf n _ => - "update:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n - | Ireturn_from_call n => "return_from_call: " ^ string_of_int n - } -}; - -exception JoinFail; - - -/** join of instrumentations, can raise JoinFail */ -let inst_partial_join inst1 inst2 => { - let fail () => { - L.d_strln ("inst_partial_join failed on " ^ inst_to_string inst1 ^ " " ^ inst_to_string inst2); - raise JoinFail - }; - if (equal_inst inst1 inst2) { - inst1 - } else { - switch (inst1, inst2) { - | (_, Inone) - | (Inone, _) => inst_none - | (_, Ialloc) - | (Ialloc, _) => fail () - | (_, Iinitial) - | (Iinitial, _) => fail () - | (_, Iupdate _) - | (Iupdate _, _) => fail () - | _ => inst_none - } - } -}; - - -/** meet of instrumentations */ -let inst_partial_meet inst1 inst2 => - if (equal_inst inst1 inst2) { - inst1 - } else { - inst_none - }; - - -/** Return the zero flag of the inst */ -let inst_zero_flag = - fun - | Iabstraction => None - | Iactual_precondition => None - | Ialloc => None - | Iformal zf _ => zf - | Iinitial => None - | Ilookup => None - | Inone => None - | Inullify => None - | Irearrange zf _ _ _ => zf - | Itaint => None - | Iupdate zf _ _ _ => zf - | Ireturn_from_call _ => None; - - -/** Set the null case flag of the inst. */ -let inst_set_null_case_flag = - fun - | Iformal zf false => Iformal zf true - | Irearrange zf false n pos => Irearrange zf true n pos - | Iupdate zf false n pos => Iupdate zf true n pos - | inst => inst; - - -/** Get the null case flag of the inst. */ -let inst_get_null_case_flag = - fun - | Iupdate _ ncf _ _ => Some ncf - | _ => None; - - -/** Update [inst_old] to [inst_new] preserving the zero flag */ -let update_inst inst_old inst_new => { - let combine_zero_flags z1 z2 => - switch (z1, z2) { - | (Some b1, Some b2) => Some (b1 || b2) - | (Some b, None) => Some b - | (None, Some b) => Some b - | (None, None) => None - }; - switch inst_new { - | Iabstraction => inst_new - | Iactual_precondition => inst_new - | Ialloc => inst_new - | Iformal zf ncf => - let zf' = combine_zero_flags (inst_zero_flag inst_old) zf; - Iformal zf' ncf - | Iinitial => inst_new - | Ilookup => inst_new - | Inone => inst_new - | Inullify => inst_new - | Irearrange zf ncf n pos => - let zf' = combine_zero_flags (inst_zero_flag inst_old) zf; - Irearrange zf' ncf n pos - | Itaint => inst_new - | Iupdate zf ncf n pos => - let zf' = combine_zero_flags (inst_zero_flag inst_old) zf; - Iupdate zf' ncf n pos - | Ireturn_from_call _ => inst_new - } -}; - - -/** describe an instrumentation with a string */ -let pp_inst pe f inst => { - let str = inst_to_string inst; - if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) { - F.fprintf f " %a%s%a" Io_infer.Html.pp_start_color Pp.Orange str Io_infer.Html.pp_end_color () - } else { - F.fprintf f "%s%s%s" (Binop.str pe Lt) str (Binop.str pe Gt) - } -}; - -let pp_inst_if_trace pe f inst => - if Config.trace_error { - pp_inst pe f inst - }; - - -/** pretty print a strexp with an optional predicate env */ -let rec pp_sexp_env pe0 envo f se => { - let (pe, changed) = color_pre_wrapper pe0 f se; - switch se { - | Eexp e inst => F.fprintf f "%a%a" (pp_exp_printenv pe) e (pp_inst_if_trace pe) inst - | Estruct fel inst => - switch pe.Pp.kind { - | TEXT - | HTML => - let pp_diff f (n, se) => F.fprintf f "%a:%a" Typ.Fieldname.pp n (pp_sexp_env pe envo) se; - F.fprintf f "{%a}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst - | LATEX => - let pp_diff f (n, se) => - F.fprintf f "%a:%a" (Typ.Fieldname.pp_latex Latex.Boldface) n (pp_sexp_env pe envo) se; - F.fprintf f "\\{%a\\}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst - } - | Earray len nel inst => - let pp_diff f (i, se) => F.fprintf f "%a:%a" (pp_exp_printenv pe) i (pp_sexp_env pe envo) se; - F.fprintf - f - "[%a|%a]%a" - (pp_exp_printenv pe) - len - (pp_seq_diff pp_diff pe) - nel - (pp_inst_if_trace pe) - inst - }; - color_post_wrapper changed pe0 f -}; - - -/** Pretty print an hpred with an optional predicate env */ -let rec pp_hpred_env pe0 envo f hpred => { - let (pe, changed) = color_pre_wrapper pe0 f hpred; - switch hpred { - | Hpointsto e se te => - let pe' = - switch (e, se) { - | (Lvar pvar, Eexp (Var _) _) when not (Pvar.is_global pvar) => - Pp.{...pe, obj_sub: None} /* dont use obj sub on the var defining it */ - | _ => pe - }; - switch pe'.Pp.kind { - | TEXT - | HTML => - F.fprintf - f "%a|->%a:%a" (pp_exp_printenv pe') e (pp_sexp_env pe' envo) se (pp_texp_simple pe') te - | LATEX => F.fprintf f "%a\\mapsto %a" (pp_exp_printenv pe') e (pp_sexp_env pe' envo) se - } - | Hlseg k hpara e1 e2 elist => - switch pe.Pp.kind { - | TEXT - | HTML => - F.fprintf - f - "lseg%a(%a,%a,[%a],%a)" - pp_lseg_kind - k - (pp_exp_printenv pe) - e1 - (pp_exp_printenv pe) - e2 - (Pp.comma_seq (pp_exp_printenv pe)) - elist - (pp_hpara_env pe envo) - hpara - | LATEX => - F.fprintf - f - "\\textsf{lseg}_{%a}(%a,%a,[%a],%a)" - pp_lseg_kind - k - (pp_exp_printenv pe) - e1 - (pp_exp_printenv pe) - e2 - (Pp.comma_seq (pp_exp_printenv pe)) - elist - (pp_hpara_env pe envo) - hpara - } - | Hdllseg k hpara_dll iF oB oF iB elist => - switch pe.Pp.kind { - | TEXT - | HTML => - F.fprintf - f - "dllseg%a(%a,%a,%a,%a,[%a],%a)" - pp_lseg_kind - k - (pp_exp_printenv pe) - iF - (pp_exp_printenv pe) - oB - (pp_exp_printenv pe) - oF - (pp_exp_printenv pe) - iB - (Pp.comma_seq (pp_exp_printenv pe)) - elist - (pp_hpara_dll_env pe envo) - hpara_dll - | LATEX => - F.fprintf - f - "\\textsf{dllseg}_{%a}(%a,%a,%a,%a,[%a],%a)" - pp_lseg_kind - k - (pp_exp_printenv pe) - iF - (pp_exp_printenv pe) - oB - (pp_exp_printenv pe) - oF - (pp_exp_printenv pe) - iB - (Pp.comma_seq (pp_exp_printenv pe)) - elist - (pp_hpara_dll_env pe envo) - hpara_dll - } - }; - color_post_wrapper changed pe0 f -} -and pp_hpara_env pe envo f hpara => - switch envo { - | None => - let (r, n, svars, evars, b) = (hpara.root, hpara.next, hpara.svars, hpara.evars, hpara.body); - F.fprintf - f - "lam [%a,%a,%a]. exists [%a]. %a" - (Ident.pp pe) - r - (Ident.pp pe) - n - (Pp.seq (Ident.pp pe)) - svars - (Pp.seq (Ident.pp pe)) - evars - (pp_star_seq (pp_hpred_env pe envo)) - b - | Some env => F.fprintf f "P%d" (Predicates.get_hpara_id env hpara) - } -and pp_hpara_dll_env pe envo f hpara_dll => - switch envo { - | None => - let (iF, oB, oF, svars, evars, b) = ( - hpara_dll.cell, - hpara_dll.blink, - hpara_dll.flink, - hpara_dll.svars_dll, - hpara_dll.evars_dll, - hpara_dll.body_dll - ); - F.fprintf - f - "lam [%a,%a,%a,%a]. exists [%a]. %a" - (Ident.pp pe) - iF - (Ident.pp pe) - oB - (Ident.pp pe) - oF - (Pp.seq (Ident.pp pe)) - svars - (Pp.seq (Ident.pp pe)) - evars - (pp_star_seq (pp_hpred_env pe envo)) - b - | Some env => F.fprintf f "P%d" (Predicates.get_hpara_dll_id env hpara_dll) - }; - - -/** pretty print a strexp */ -let pp_sexp pe f => pp_sexp_env pe None f; - - -/** pretty print a hpara */ -let pp_hpara pe f => pp_hpara_env pe None f; - - -/** pretty print a hpara_dll */ -let pp_hpara_dll pe f => pp_hpara_dll_env pe None f; - - -/** pretty print a hpred */ -let pp_hpred pe f => pp_hpred_env pe None f; - - -/** dump a strexp. */ -let d_sexp (se: strexp) => L.add_print_action (L.PTsexp, Obj.repr se); - - -/** Pretty print a list of expressions. */ -let pp_sexp_list pe f sel => - F.fprintf f "%a" (Pp.seq (fun f se => F.fprintf f "%a" (pp_sexp pe) se)) sel; - - -/** dump a list of expressions. */ -let d_sexp_list (sel: list strexp) => L.add_print_action (L.PTsexp_list, Obj.repr sel); - -let rec pp_hpara_list pe f => - fun - | [] => () - | [para] => F.fprintf f "PRED: %a" (pp_hpara pe) para - | [para, ...paras] => F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara pe) para (pp_hpara_list pe) paras; - -let rec pp_hpara_dll_list pe f => - fun - | [] => () - | [para] => F.fprintf f "PRED: %a" (pp_hpara_dll pe) para - | [para, ...paras] => - F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara_dll pe) para (pp_hpara_dll_list pe) paras; - - -/** dump a hpred. */ -let d_hpred (hpred: hpred) => L.add_print_action (L.PThpred, Obj.repr hpred); - - -/** {2 Functions for traversing SIL data types} */ -let rec strexp_expmap (f: (Exp.t, option inst) => (Exp.t, option inst)) => { - let fe e => fst (f (e, None)); - let fei (e, inst) => - switch (f (e, Some inst)) { - | (e', None) => (e', inst) - | (e', Some inst') => (e', inst') - }; - fun - | Eexp e inst => { - let (e', inst') = fei (e, inst); - Eexp e' inst' - } - | Estruct fld_se_list inst => { - let f_fld_se (fld, se) => (fld, strexp_expmap f se); - Estruct (List.map f::f_fld_se fld_se_list) inst - } - | Earray len idx_se_list inst => { - let len' = fe len; - let f_idx_se (idx, se) => { - let idx' = fe idx; - (idx', strexp_expmap f se) - }; - Earray len' (List.map f::f_idx_se idx_se_list) inst - } -}; - -let hpred_expmap (f: (Exp.t, option inst) => (Exp.t, option inst)) => { - let fe e => fst (f (e, None)); - fun - | Hpointsto e se te => { - let e' = fe e; - let se' = strexp_expmap f se; - let te' = fe te; - Hpointsto e' se' te' - } - | Hlseg k hpara root next shared => { - let root' = fe root; - let next' = fe next; - let shared' = List.map f::fe shared; - Hlseg k hpara root' next' shared' - } - | Hdllseg k hpara iF oB oF iB shared => { - let iF' = fe iF; - let oB' = fe oB; - let oF' = fe oF; - let iB' = fe iB; - let shared' = List.map f::fe shared; - Hdllseg k hpara iF' oB' oF' iB' shared' - } -}; - -let rec strexp_instmap (f: inst => inst) strexp => - switch strexp { - | Eexp e inst => Eexp e (f inst) - | Estruct fld_se_list inst => - let f_fld_se (fld, se) => (fld, strexp_instmap f se); - Estruct (List.map f::f_fld_se fld_se_list) (f inst) - | Earray len idx_se_list inst => - let f_idx_se (idx, se) => (idx, strexp_instmap f se); - Earray len (List.map f::f_idx_se idx_se_list) (f inst) - }; - -let rec hpara_instmap (f: inst => inst) hpara => { - ...hpara, - body: List.map f::(hpred_instmap f) hpara.body -} -and hpara_dll_instmap (f: inst => inst) hpara_dll => { - ...hpara_dll, - body_dll: List.map f::(hpred_instmap f) hpara_dll.body_dll -} -and hpred_instmap (fn: inst => inst) (hpred: hpred) :hpred => - switch hpred { - | Hpointsto e se te => - let se' = strexp_instmap fn se; - Hpointsto e se' te - | Hlseg k hpara e f el => Hlseg k (hpara_instmap fn hpara) e f el - | Hdllseg k hpar_dll e f g h el => Hdllseg k (hpara_dll_instmap fn hpar_dll) e f g h el - }; - -let hpred_list_expmap (f: (Exp.t, option inst) => (Exp.t, option inst)) (hlist: list hpred) => - List.map f::(hpred_expmap f) hlist; - -let atom_expmap (f: Exp.t => Exp.t) => - fun - | Aeq e1 e2 => Aeq (f e1) (f e2) - | Aneq e1 e2 => Aneq (f e1) (f e2) - | Apred a es => Apred a (List.map ::f es) - | Anpred a es => Anpred a (List.map ::f es); - -let atom_list_expmap (f: Exp.t => Exp.t) (alist: list atom) => List.map f::(atom_expmap f) alist; - - -/** {2 Function for computing lexps in sigma} */ -let hpred_get_lexp acc => - fun - | Hpointsto e _ _ => [e, ...acc] - | Hlseg _ _ e _ _ => [e, ...acc] - | Hdllseg _ _ e1 _ _ e2 _ => [e1, e2, ...acc]; - -let hpred_list_get_lexps (filter: Exp.t => bool) (hlist: list hpred) :list Exp.t => { - let lexps = List.fold f::hpred_get_lexp init::[] hlist; - List.filter f::filter lexps -}; - - -/** {2 Functions for computing program variables} */ -let rec exp_fpv e => - switch (e: Exp.t) { - | Var _ => [] - | Exn e => exp_fpv e - | Closure {captured_vars} => List.map f::(fun (_, pvar, _) => pvar) captured_vars - | Const _ => [] - | Cast _ e - | UnOp _ e _ => exp_fpv e - | BinOp _ e1 e2 => exp_fpv e1 @ exp_fpv e2 - | Lvar name => [name] - | Lfield e _ _ => exp_fpv e - | Lindex e1 e2 => exp_fpv e1 @ exp_fpv e2 - /* TODO: Sizeof length expressions may contain variables, do not ignore them. */ - | Sizeof _ => [] - }; - -let exp_list_fpv el => List.concat_map f::exp_fpv el; - -let atom_fpv = - fun - | Aeq e1 e2 => exp_fpv e1 @ exp_fpv e2 - | Aneq e1 e2 => exp_fpv e1 @ exp_fpv e2 - | Apred _ es - | Anpred _ es => List.fold f::(fun fpv e => List.rev_append (exp_fpv e) fpv) init::[] es; - -let rec strexp_fpv = - fun - | Eexp e _ => exp_fpv e - | Estruct fld_se_list _ => { - let f (_, se) => strexp_fpv se; - List.concat_map ::f fld_se_list - } - | Earray len idx_se_list _ => { - let fpv_in_len = exp_fpv len; - let f (idx, se) => exp_fpv idx @ strexp_fpv se; - fpv_in_len @ List.concat_map ::f idx_se_list - }; - -let rec hpred_fpv = - fun - | Hpointsto base se te => exp_fpv base @ strexp_fpv se @ exp_fpv te - | Hlseg _ para e1 e2 elist => { - let fpvars_in_elist = exp_list_fpv elist; - hpara_fpv para @ /* This set has to be empty. */ exp_fpv e1 @ exp_fpv e2 @ fpvars_in_elist - } - | Hdllseg _ para e1 e2 e3 e4 elist => { - let fpvars_in_elist = exp_list_fpv elist; - hpara_dll_fpv para @ - /* This set has to be empty. */ - exp_fpv e1 @ exp_fpv e2 @ exp_fpv e3 @ exp_fpv e4 @ fpvars_in_elist - } -/** hpara should not contain any program variables. - This is because it might cause problems when we do interprocedural - analysis. In interprocedural analysis, we should consider the issue - of scopes of program variables. */ -and hpara_fpv para => { - let fpvars_in_body = List.concat_map f::hpred_fpv para.body; - switch fpvars_in_body { - | [] => [] - | _ => assert false - } -} -/** hpara_dll should not contain any program variables. - This is because it might cause problems when we do interprocedural - analysis. In interprocedural analysis, we should consider the issue - of scopes of program variables. */ -and hpara_dll_fpv para => { - let fpvars_in_body = List.concat_map f::hpred_fpv para.body_dll; - switch fpvars_in_body { - | [] => [] - | _ => assert false - } -}; - - -/** {2 Functions for computing free non-program variables} */ - -/** Type of free variables. These include primed, normal and footprint variables. - We keep a count of how many types the variables appear. */ -type fav = ref (list Ident.t); - -let fav_new () => ref []; - - -/** Emptyness check. */ -let fav_is_empty fav => - switch !fav { - | [] => true - | _ => false - }; - - -/** Check whether a predicate holds for all elements. */ -let fav_for_all fav predicate => List.for_all f::predicate !fav; - - -/** Check whether a predicate holds for some elements. */ -let fav_exists fav predicate => List.exists f::predicate !fav; - - -/** flag to indicate whether fav's are stored in duplicate form. - Only to be used with fav_to_list */ -let fav_duplicates = ref false; - - -/** extend [fav] with a [id] */ -let (++) fav id => - if (!fav_duplicates || not (List.exists f::(Ident.equal id) !fav)) { - fav := [id, ...!fav] - }; - - -/** extend [fav] with ident list [idl] */ -let (+++) fav idl => List.iter f::(fun id => fav ++ id) idl; - - -/** add identity lists to fav */ -let ident_list_fav_add idl fav => fav +++ idl; - - -/** Convert a list to a fav. */ -let fav_from_list l => { - let fav = fav_new (); - let _ = List.iter f::(fun id => fav ++ id) l; - fav -}; - -let rec remove_duplicates_from_sorted special_equal => - fun - | [] => [] - | [x] => [x] - | [x, y, ...l] => - if (special_equal x y) { - remove_duplicates_from_sorted special_equal [y, ...l] - } else { - [x, ...remove_duplicates_from_sorted special_equal [y, ...l]] - }; - - -/** Convert a [fav] to a list of identifiers while preserving the order - that the identifiers were added to [fav]. */ -let fav_to_list fav => List.rev !fav; - - -/** Pretty print a fav. */ -let pp_fav pe f fav => (Pp.seq (Ident.pp pe)) f (fav_to_list fav); - - -/** Copy a [fav]. */ -let fav_copy fav => ref (List.map f::(fun x => x) !fav); - - -/** Turn a xxx_fav_add function into a xxx_fav function */ -let fav_imperative_to_functional f x => { - let fav = fav_new (); - let _ = f fav x; - fav -}; - - -/** [fav_filter_ident fav f] only keeps [id] if [f id] is true. */ -let fav_filter_ident fav filter => fav := List.filter f::filter !fav; - - -/** Like [fav_filter_ident] but return a copy. */ -let fav_copy_filter_ident fav filter => ref (List.filter f::filter !fav); - - -/** checks whether every element in l1 appears l2 **/ -let rec ident_sorted_list_subset l1 l2 => - switch (l1, l2) { - | ([], _) => true - | ([_, ..._], []) => false - | ([id1, ...l1], [id2, ...l2]) => - let n = Ident.compare id1 id2; - if (Int.equal n 0) { - ident_sorted_list_subset l1 [id2, ...l2] - } else if (n > 0) { - ident_sorted_list_subset [id1, ...l1] l2 - } else { - false - } - }; - - -/** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] - is in [fav2].*/ -let fav_subset_ident fav1 fav2 => ident_sorted_list_subset (fav_to_list fav1) (fav_to_list fav2); - -let fav_mem fav id => List.exists f::(Ident.equal id) !fav; - -let rec exp_fav_add fav e => - switch (e: Exp.t) { - | Var id => fav ++ id - | Exn e => exp_fav_add fav e - | Closure {captured_vars} => List.iter f::(fun (e, _, _) => exp_fav_add fav e) captured_vars - | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) => () - | Cast _ e - | UnOp _ e _ => exp_fav_add fav e - | BinOp _ e1 e2 => - exp_fav_add fav e1; - exp_fav_add fav e2 - | Lvar _ => () /* do nothing since we only count non-program variables */ - | Lfield e _ _ => exp_fav_add fav e - | Lindex e1 e2 => - exp_fav_add fav e1; - exp_fav_add fav e2 - /* TODO: Sizeof length expressions may contain variables, do not ignore them. */ - | Sizeof _ => () - }; - -let exp_fav = fav_imperative_to_functional exp_fav_add; - -let exp_fav_list e => fav_to_list (exp_fav e); - -let ident_in_exp id e => { - let fav = fav_new (); - exp_fav_add fav e; - fav_mem fav id -}; - -let atom_fav_add fav => - fun - | Aeq e1 e2 - | Aneq e1 e2 => { - exp_fav_add fav e1; - exp_fav_add fav e2 - } - | Apred _ es - | Anpred _ es => List.iter f::(fun e => exp_fav_add fav e) es; - -let atom_fav = fav_imperative_to_functional atom_fav_add; - - -/** Atoms do not contain binders */ -let atom_av_add = atom_fav_add; - -let rec strexp_fav_add fav => - fun - | Eexp e _ => exp_fav_add fav e - | Estruct fld_se_list _ => List.iter f::(fun (_, se) => strexp_fav_add fav se) fld_se_list - | Earray len idx_se_list _ => { - exp_fav_add fav len; - List.iter - f::( - fun (e, se) => { - exp_fav_add fav e; - strexp_fav_add fav se - } - ) - idx_se_list - }; - -let hpred_fav_add fav => - fun - | Hpointsto base sexp te => { - exp_fav_add fav base; - strexp_fav_add fav sexp; - exp_fav_add fav te - } - | Hlseg _ _ e1 e2 elist => { - exp_fav_add fav e1; - exp_fav_add fav e2; - List.iter f::(exp_fav_add fav) elist - } - | Hdllseg _ _ e1 e2 e3 e4 elist => { - exp_fav_add fav e1; - exp_fav_add fav e2; - exp_fav_add fav e3; - exp_fav_add fav e4; - List.iter f::(exp_fav_add fav) elist - }; - -let hpred_fav = fav_imperative_to_functional hpred_fav_add; - - -/** This function should be used before adding a new - index to Earray. The [exp] is the newly created - index. This function "cleans" [exp] according to whether it is - the footprint or current part of the prop. - The function faults in the re - execution mode, as an internal check of the tool. */ -let array_clean_new_index footprint_part new_idx => { - if (footprint_part && not !Config.footprint) { - assert false - }; - let fav = exp_fav new_idx; - if (footprint_part && fav_exists fav (fun id => not (Ident.is_footprint id))) { - L.d_warning ( - "Array index " ^ - Exp.to_string new_idx ^ " has non-footprint vars: replaced by fresh footprint var" - ); - L.d_ln (); - let id = Ident.create_fresh Ident.kfootprint; - Exp.Var id - } else { - new_idx - } -}; - - -/** {2 Functions for computing all free or bound non-program variables} */ -let exp_av_add = exp_fav_add; /** Expressions do not bind variables */ - -let strexp_av_add = strexp_fav_add; /** Structured expressions do not bind variables */ - -let rec hpara_av_add fav para => { - List.iter f::(hpred_av_add fav) para.body; - fav ++ para.root; - fav ++ para.next; - fav +++ para.svars; - fav +++ para.evars -} -and hpara_dll_av_add fav para => { - List.iter f::(hpred_av_add fav) para.body_dll; - fav ++ para.cell; - fav ++ para.blink; - fav ++ para.flink; - fav +++ para.svars_dll; - fav +++ para.evars_dll -} -and hpred_av_add fav => - fun - | Hpointsto base se te => { - exp_av_add fav base; - strexp_av_add fav se; - exp_av_add fav te - } - | Hlseg _ para e1 e2 elist => { - hpara_av_add fav para; - exp_av_add fav e1; - exp_av_add fav e2; - List.iter f::(exp_av_add fav) elist - } - | Hdllseg _ para e1 e2 e3 e4 elist => { - hpara_dll_av_add fav para; - exp_av_add fav e1; - exp_av_add fav e2; - exp_av_add fav e3; - exp_av_add fav e4; - List.iter f::(exp_av_add fav) elist - }; - -let hpara_shallow_av_add fav para => { - List.iter f::(hpred_fav_add fav) para.body; - fav ++ para.root; - fav ++ para.next; - fav +++ para.svars; - fav +++ para.evars -}; - -let hpara_dll_shallow_av_add fav para => { - List.iter f::(hpred_fav_add fav) para.body_dll; - fav ++ para.cell; - fav ++ para.blink; - fav ++ para.flink; - fav +++ para.svars_dll; - fav +++ para.evars_dll -}; - - -/** Variables in hpara, excluding bound vars in the body */ -let hpara_shallow_av = fav_imperative_to_functional hpara_shallow_av_add; - - -/** Variables in hpara_dll, excluding bound vars in the body */ -let hpara_dll_shallow_av = fav_imperative_to_functional hpara_dll_shallow_av_add; - - -/** {2 Functions for Substitution} */ -let rec reverse_with_base base => - fun - | [] => base - | [x, ...l] => reverse_with_base [x, ...base] l; - -let sorted_list_merge compare l1_in l2_in => { - let rec merge acc l1 l2 => - switch (l1, l2) { - | ([], l2) => reverse_with_base l2 acc - | (l1, []) => reverse_with_base l1 acc - | ([x1, ...l1'], [x2, ...l2']) => - if (compare x1 x2 <= 0) { - merge [x1, ...acc] l1' l2 - } else { - merge [x2, ...acc] l1 l2' - } - }; - merge [] l1_in l2_in -}; - -let rec sorted_list_check_consecutives f => - fun - | [] - | [_] => false - | [x1, ...[x2, ..._] as l] => - if (f x1 x2) { - true - } else { - sorted_list_check_consecutives f l - }; - - -/** substitution */ -type ident_exp = (Ident.t, Exp.t) [@@deriving compare]; - -let equal_ident_exp = [%compare.equal : ident_exp]; - -type exp_subst = list ident_exp [@@deriving compare]; - -type subst = [ | `Exp exp_subst | `Typ Typ.type_subst_t] [@@deriving compare]; - -type subst_fun = [ | `Exp (Ident.t => Exp.t) | `Typ (Typ.t => Typ.t, Typ.Name.t => Typ.Name.t)]; - - -/** Equality for substitutions. */ -let equal_exp_subst = [%compare.equal : exp_subst]; - -let sub_check_duplicated_ids sub => { - let f (id1, _) (id2, _) => Ident.equal id1 id2; - sorted_list_check_consecutives f sub -}; - - -/** Create a substitution from a list of pairs. - For all (id1, e1), (id2, e2) in the input list, - if id1 = id2, then e1 = e2. */ -let exp_subst_of_list sub => { - let sub' = List.sort cmp::compare_ident_exp sub; - let sub'' = remove_duplicates_from_sorted equal_ident_exp sub'; - if (sub_check_duplicated_ids sub'') { - assert false - }; - sub' -}; - -let subst_of_list sub => `Exp (exp_subst_of_list sub); - - -/** like exp_subst_of_list, but allow duplicate ids and only keep the first occurrence */ -let exp_subst_of_list_duplicates sub => { - let sub' = List.sort cmp::compare_ident_exp sub; - let rec remove_duplicate_ids = - fun - | [(id1, e1), (id2, e2), ...l] => - if (Ident.equal id1 id2) { - remove_duplicate_ids [(id1, e1), ...l] - } else { - [(id1, e1), ...remove_duplicate_ids [(id2, e2), ...l]] - } - | l => l; - remove_duplicate_ids sub' -}; - - -/** Convert a subst to a list of pairs. */ -let sub_to_list sub => sub; - - -/** The empty substitution. */ -let exp_sub_empty = exp_subst_of_list []; - -let sub_empty = `Exp exp_sub_empty; - -let is_sub_empty = - fun - | `Exp [] => true - | `Exp _ => false - | `Typ sub => Typ.is_type_subst_empty sub; - - -/** Join two substitutions into one. - For all id in dom(sub1) cap dom(sub2), sub1(id) = sub2(id). */ -let sub_join sub1 sub2 => { - let sub = sorted_list_merge compare_ident_exp sub1 sub2; - let sub' = remove_duplicates_from_sorted equal_ident_exp sub; - if (sub_check_duplicated_ids sub') { - assert false - }; - sub -}; - - -/** Compute the common id-exp part of two inputs [subst1] and [subst2]. - The first component of the output is this common part. - The second and third components are the remainder of [subst1] - and [subst2], respectively. */ -let sub_symmetric_difference sub1_in sub2_in => { - let rec diff sub_common sub1_only sub2_only sub1 sub2 => - switch (sub1, sub2) { - | ([], _) - | (_, []) => - let sub1_only' = reverse_with_base sub1 sub1_only; - let sub2_only' = reverse_with_base sub2 sub2_only; - let sub_common = reverse_with_base [] sub_common; - (sub_common, sub1_only', sub2_only') - | ([id_e1, ...sub1'], [id_e2, ...sub2']) => - let n = compare_ident_exp id_e1 id_e2; - if (Int.equal n 0) { - diff [id_e1, ...sub_common] sub1_only sub2_only sub1' sub2' - } else if ( - n < 0 - ) { - diff sub_common [id_e1, ...sub1_only] sub2_only sub1' sub2 - } else { - diff sub_common sub1_only [id_e2, ...sub2_only] sub1 sub2' - } - }; - diff [] [] [] sub1_in sub2_in -}; - - -/** [sub_find filter sub] returns the expression associated to the first identifier - that satisfies [filter]. Raise [Not_found] if there isn't one. */ -let sub_find filter (sub: exp_subst) => snd (List.find_exn f::(fun (i, _) => filter i) sub); - - -/** [sub_filter filter sub] restricts the domain of [sub] to the - identifiers satisfying [filter]. */ -let sub_filter filter (sub: exp_subst) => List.filter f::(fun (i, _) => filter i) sub; - - -/** [sub_filter_pair filter sub] restricts the domain of [sub] to the - identifiers satisfying [filter(id, sub(id))]. */ -let sub_filter_pair = List.filter; - - -/** [sub_range_partition filter sub] partitions [sub] according to - whether range expressions satisfy [filter]. */ -let sub_range_partition filter (sub: exp_subst) => - List.partition_tf f::(fun (_, e) => filter e) sub; - - -/** [sub_domain_partition filter sub] partitions [sub] according to - whether domain identifiers satisfy [filter]. */ -let sub_domain_partition filter (sub: exp_subst) => - List.partition_tf f::(fun (i, _) => filter i) sub; - - -/** Return the list of identifiers in the domain of the substitution. */ -let sub_domain sub => List.map f::fst sub; - - -/** Return the list of expressions in the range of the substitution. */ -let sub_range sub => List.map f::snd sub; - - -/** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. */ -let sub_range_map f sub => exp_subst_of_list (List.map f::(fun (i, e) => (i, f e)) sub); - - -/** [sub_map f g sub] applies the renaming [f] to identifiers in the domain - of [sub] and the substitution [g] to the expressions in the range of [sub]. */ -let sub_map f g sub => exp_subst_of_list (List.map f::(fun (i, e) => (f i, g e)) sub); - -let mem_sub id sub => List.exists f::(fun (id1, _) => Ident.equal id id1) sub; - - -/** Extend substitution and return [None] if not possible. */ -let extend_sub sub id exp :option exp_subst => { - let compare (id1, _) (id2, _) => Ident.compare id1 id2; - if (mem_sub id sub) { - None - } else { - Some (sorted_list_merge compare sub [(id, exp)]) - } -}; - - -/** Free auxilary variables in the domain and range of the - substitution. */ -let sub_fav_add fav (sub: exp_subst) => - List.iter - f::( - fun (id, e) => { - fav ++ id; - exp_fav_add fav e - } - ) - sub; - - -/** Substitutions do not contain binders */ -let sub_av_add = sub_fav_add; - -let rec exp_sub_ids (f: subst_fun) exp => { - let f_typ x => - switch f { - | `Exp _ => x - | `Typ (f, _) => f x - }; - let f_tname x => - switch f { - | `Exp _ => x - | `Typ (_, f) => f x - }; - switch (exp: Exp.t) { - | Var id => - switch f { - | `Exp f_exp => - switch (f_exp id) { - | Exp.Var id' when Ident.equal id id' => /* it will preserve physical equality when needed */ exp - | exp' => exp' - } - | _ => exp - } - | Lvar _ => exp - | Exn e => - let e' = exp_sub_ids f e; - if (phys_equal e' e) { - exp - } else { - Exp.Exn e' - } - | Closure c => - let captured_vars = - IList.map_changed - ( - fun ((e, pvar, typ) as captured) => { - let e' = exp_sub_ids f e; - let typ' = f_typ typ; - if (phys_equal e' e && phys_equal typ typ') { - captured - } else { - (e', pvar, typ') - } - } - ) - c.captured_vars; - if (phys_equal captured_vars c.captured_vars) { - exp - } else { - Exp.Closure {...c, captured_vars} - } - | Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) => exp - | Cast t e => - let e' = exp_sub_ids f e; - let t' = f_typ t; - if (phys_equal e' e && phys_equal t' t) { - exp - } else { - Exp.Cast t' e' - } - | UnOp op e typ_opt => - let e' = exp_sub_ids f e; - let typ_opt' = - switch typ_opt { - | Some t => - let t' = f_typ t; - if (phys_equal t t') { - typ_opt - } else { - Some t' - } - | None => typ_opt - }; - if (phys_equal e' e && phys_equal typ_opt typ_opt') { - exp - } else { - Exp.UnOp op e' typ_opt' - } - | BinOp op e1 e2 => - let e1' = exp_sub_ids f e1; - let e2' = exp_sub_ids f e2; - if (phys_equal e1' e1 && phys_equal e2' e2) { - exp - } else { - Exp.BinOp op e1' e2' - } - | Lfield e fld typ => - let e' = exp_sub_ids f e; - let typ' = f_typ typ; - let fld' = Typ.Fieldname.class_name_replace f::f_tname fld; - if (phys_equal e' e && phys_equal typ typ' && phys_equal fld fld') { - exp - } else { - Exp.Lfield e' fld' typ' - } - | Lindex e1 e2 => - let e1' = exp_sub_ids f e1; - let e2' = exp_sub_ids f e2; - if (phys_equal e1' e1 && phys_equal e2' e2) { - exp - } else { - Exp.Lindex e1' e2' - } - | Sizeof ({typ, dynamic_length: Some l, subtype} as sizeof_data) => - let l' = exp_sub_ids f l; - let typ' = f_typ typ; - let subtype' = Subtype.sub_type f_tname subtype; - if (phys_equal l' l && phys_equal typ typ' && phys_equal subtype subtype') { - exp - } else { - Exp.Sizeof {...sizeof_data, typ: typ', dynamic_length: Some l', subtype: subtype'} - } - | Sizeof ({typ, dynamic_length: None, subtype} as sizeof_data) => - let typ' = f_typ typ; - let subtype' = Subtype.sub_type f_tname subtype; - if (phys_equal typ typ') { - exp - } else { - Exp.Sizeof {...sizeof_data, typ: typ', subtype: subtype'} - } - } -}; - -let apply_sub subst :subst_fun => - switch subst { - | `Exp l => - `Exp ( - fun id => - switch (List.Assoc.find l equal::Ident.equal id) { - | Some x => x - | None => Exp.Var id - } - ) - | `Typ typ_subst => `Typ (Typ.sub_type typ_subst, Typ.sub_tname typ_subst) - }; - -let exp_sub (subst: subst) e => exp_sub_ids (apply_sub subst) e; - - -/** apply [f] to id's in [instr]. if [sub_id_binders] is false, [f] is only applied to bound id's */ -let instr_sub_ids ::sub_id_binders f instr => { - let sub_id id => - switch (exp_sub_ids f (Var id)) { - | Var id' when not (Ident.equal id id') => id' - | _ => id - }; - let sub_typ x => - switch f { - | `Exp _ => x - | `Typ (f, _) => f x - }; - switch instr { - | Load id rhs_exp typ loc => - let id' = - if sub_id_binders { - sub_id id - } else { - id - }; - let rhs_exp' = exp_sub_ids f rhs_exp; - let typ' = sub_typ typ; - if (phys_equal id' id && phys_equal rhs_exp' rhs_exp && phys_equal typ typ') { - instr - } else { - Load id' rhs_exp' typ' loc - } - | Store lhs_exp typ rhs_exp loc => - let lhs_exp' = exp_sub_ids f lhs_exp; - let typ' = sub_typ typ; - let rhs_exp' = exp_sub_ids f rhs_exp; - if (phys_equal lhs_exp' lhs_exp && phys_equal typ typ' && phys_equal rhs_exp' rhs_exp) { - instr - } else { - Store lhs_exp' typ' rhs_exp' loc - } - | Call ret_id fun_exp actuals call_flags loc => - let ret_id' = - if sub_id_binders { - switch ret_id { - | Some (id, typ) => - let id' = sub_id id; - let typ' = sub_typ typ; - if (Ident.equal id id' && phys_equal typ typ') { - ret_id - } else { - Some (id', typ') - } - | None => None - } - } else { - ret_id - }; - let fun_exp' = exp_sub_ids f fun_exp; - let actuals' = - IList.map_changed - ( - fun ((actual, typ) as actual_pair) => { - let actual' = exp_sub_ids f actual; - let typ' = sub_typ typ; - if (phys_equal actual' actual && phys_equal typ typ') { - actual_pair - } else { - (actual', typ') - } - } - ) - actuals; - if (phys_equal ret_id' ret_id && phys_equal fun_exp' fun_exp && phys_equal actuals' actuals) { - instr - } else { - Call ret_id' fun_exp' actuals' call_flags loc - } - | Prune exp loc true_branch if_kind => - let exp' = exp_sub_ids f exp; - if (phys_equal exp' exp) { - instr - } else { - Prune exp' loc true_branch if_kind - } - | Remove_temps ids loc => - let ids' = IList.map_changed sub_id ids; - if (phys_equal ids' ids) { - instr - } else { - Remove_temps ids' loc - } - | Declare_locals locals loc => - let locals' = - IList.map_changed - ( - fun ((name, typ) as local_var) => { - let typ' = sub_typ typ; - if (phys_equal typ typ') { - local_var - } else { - (name, typ') - } - } - ) - locals; - if (phys_equal locals locals') { - instr - } else { - Declare_locals locals' loc - } - | Nullify _ - | Abstract _ => instr - } -}; - - -/** apply [subst] to all id's in [instr], including binder id's */ -let instr_sub (subst: subst) instr => instr_sub_ids sub_id_binders::true (apply_sub subst) instr; - - -/** compare expressions from different procedures without considering loc's, ident's, and pvar's. - the [exp_map] param gives a mapping of names used in the procedure of [e1] to names used in the - procedure of [e2] */ -let rec exp_compare_structural e1 e2 exp_map => { - let compare_exps_with_map e1 e2 exp_map => - try { - let e1_mapping = Exp.Map.find e1 exp_map; - (Exp.compare e1_mapping e2, exp_map) - } { - | Not_found => - /* assume e1 and e2 equal, enforce by adding to [exp_map] */ - (0, Exp.Map.add e1 e2 exp_map) - }; - switch (e1: Exp.t, e2: Exp.t) { - | (Var _, Var _) => compare_exps_with_map e1 e2 exp_map - | (UnOp o1 e1 to1, UnOp o2 e2 to2) => - let n = Unop.compare o1 o2; - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - ( - if (n != 0) { - n - } else { - [%compare : option Typ.t] to1 to2 - }, - exp_map - ) - } - | (BinOp o1 e1 f1, BinOp o2 e2 f2) => - let n = Binop.compare o1 o2; - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - if (n != 0) { - (n, exp_map) - } else { - exp_compare_structural f1 f2 exp_map - } - } - | (Cast t1 e1, Cast t2 e2) => - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - ( - if (n != 0) { - n - } else { - Typ.compare t1 t2 - }, - exp_map - ) - | (Lvar _, Lvar _) => compare_exps_with_map e1 e2 exp_map - | (Lfield e1 f1 t1, Lfield e2 f2 t2) => - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - ( - if (n != 0) { - n - } else { - let n = Typ.Fieldname.compare f1 f2; - if (n != 0) { - n - } else { - Typ.compare t1 t2 - } - }, - exp_map - ) - | (Lindex e1 f1, Lindex e2 f2) => - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - if (n != 0) { - (n, exp_map) - } else { - exp_compare_structural f1 f2 exp_map - } - | _ => (Exp.compare e1 e2, exp_map) - } -}; - -let exp_typ_compare_structural (e1, t1) (e2, t2) exp_map => { - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - ( - if (n != 0) { - n - } else { - Typ.compare t1 t2 - }, - exp_map - ) -}; - - -/** compare instructions from different procedures without considering loc's, ident's, and pvar's. - the [exp_map] param gives a mapping of names used in the procedure of [instr1] to identifiers - used in the procedure of [instr2] */ -let compare_structural_instr instr1 instr2 exp_map => { - let id_typ_opt_compare_structural id_typ1 id_typ2 exp_map => { - let id_typ_compare_structural (id1, typ1) (id2, typ2) => { - let (n, exp_map) = exp_compare_structural (Var id1) (Var id2) exp_map; - if (n != 0) { - (n, exp_map) - } else { - (Typ.compare typ1 typ2, exp_map) - } - }; - switch (id_typ1, id_typ2) { - | (Some it1, Some it2) => id_typ_compare_structural it1 it2 - | (None, None) => (0, exp_map) - | (None, _) => ((-1), exp_map) - | (_, None) => (1, exp_map) - } - }; - let id_list_compare_structural ids1 ids2 exp_map => { - let n = Int.compare (List.length ids1) (List.length ids2); - if (n != 0) { - (n, exp_map) - } else { - List.fold2_exn - f::( - fun (n, exp_map) id1 id2 => - if (n != 0) { - (n, exp_map) - } else { - exp_compare_structural (Var id1) (Var id2) exp_map - } - ) - init::(0, exp_map) - ids1 - ids2 - } - }; - switch (instr1, instr2) { - | (Load id1 e1 t1 _, Load id2 e2 t2 _) => - let (n, exp_map) = exp_compare_structural (Var id1) (Var id2) exp_map; - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - ( - if (n != 0) { - n - } else { - Typ.compare t1 t2 - }, - exp_map - ) - } - | (Store e11 t1 e21 _, Store e12 t2 e22 _) => - let (n, exp_map) = exp_compare_structural e11 e12 exp_map; - if (n != 0) { - (n, exp_map) - } else { - let n = Typ.compare t1 t2; - if (n != 0) { - (n, exp_map) - } else { - exp_compare_structural e21 e22 exp_map - } - } - | (Prune cond1 _ true_branch1 ik1, Prune cond2 _ true_branch2 ik2) => - let (n, exp_map) = exp_compare_structural cond1 cond2 exp_map; - ( - if (n != 0) { - n - } else { - let n = Bool.compare true_branch1 true_branch2; - if (n != 0) { - n - } else { - compare_if_kind ik1 ik2 - } - }, - exp_map - ) - | (Call ret_id1 e1 arg_ts1 _ cf1, Call ret_id2 e2 arg_ts2 _ cf2) => - let args_compare_structural args1 args2 exp_map => { - let n = Int.compare (List.length args1) (List.length args2); - if (n != 0) { - (n, exp_map) - } else { - List.fold2_exn - f::( - fun (n, exp_map) arg1 arg2 => - if (n != 0) { - (n, exp_map) - } else { - exp_typ_compare_structural arg1 arg2 exp_map - } - ) - init::(0, exp_map) - args1 - args2 - } - }; - let (n, exp_map) = id_typ_opt_compare_structural ret_id1 ret_id2 exp_map; - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = exp_compare_structural e1 e2 exp_map; - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = args_compare_structural arg_ts1 arg_ts2 exp_map; - ( - if (n != 0) { - n - } else { - CallFlags.compare cf1 cf2 - }, - exp_map - ) - } - } - | (Nullify pvar1 _, Nullify pvar2 _) => exp_compare_structural (Lvar pvar1) (Lvar pvar2) exp_map - | (Abstract _, Abstract _) => (0, exp_map) - | (Remove_temps temps1 _, Remove_temps temps2 _) => - id_list_compare_structural temps1 temps2 exp_map - | (Declare_locals ptl1 _, Declare_locals ptl2 _) => - let n = Int.compare (List.length ptl1) (List.length ptl2); - if (n != 0) { - (n, exp_map) - } else { - List.fold2_exn - f::( - fun (n, exp_map) (pv1, t1) (pv2, t2) => - if (n != 0) { - (n, exp_map) - } else { - let (n, exp_map) = exp_compare_structural (Lvar pv1) (Lvar pv2) exp_map; - if (n != 0) { - (n, exp_map) - } else { - (Typ.compare t1 t2, exp_map) - } - } - ) - init::(0, exp_map) - ptl1 - ptl2 - } - | _ => (compare_instr instr1 instr2, exp_map) - } -}; - -let atom_sub subst => atom_expmap (exp_sub subst); - -let hpred_sub subst => { - let f (e, inst_opt) => (exp_sub subst e, inst_opt); - hpred_expmap f -}; - - -/** {2 Functions for replacing occurrences of expressions.} */ -let rec exp_replace_exp epairs e => - /* First we check if there is an exact match */ - switch (List.find f::(fun (e1, _) => Exp.equal e e1) epairs) { - | Some (_, e2) => e2 - | None => - /* If e is a compound expression, we need to check for its subexpressions as well */ - switch e { - | Exp.UnOp op e0 ty => - let e0' = exp_replace_exp epairs e0; - if (phys_equal e0 e0') { - e - } else { - Exp.UnOp op e0' ty - } - | Exp.BinOp op lhs rhs => - let lhs' = exp_replace_exp epairs lhs; - let rhs' = exp_replace_exp epairs rhs; - if (phys_equal lhs lhs' && phys_equal rhs rhs') { - e - } else { - Exp.BinOp op lhs' rhs' - } - | Exp.Cast ty e0 => - let e0' = exp_replace_exp epairs e0; - if (phys_equal e0 e0') { - e - } else { - Exp.Cast ty e0' - } - | Exp.Lfield e0 fname ty => - let e0' = exp_replace_exp epairs e0; - if (phys_equal e0 e0') { - e - } else { - Exp.Lfield e0' fname ty - } - | Exp.Lindex base index => - let base' = exp_replace_exp epairs base; - let index' = exp_replace_exp epairs index; - if (phys_equal base base' && phys_equal index index') { - e - } else { - Exp.Lindex base' index' - } - | _ => e - } - }; - -let atom_replace_exp epairs atom => atom_expmap (fun e => exp_replace_exp epairs e) atom; - -let rec strexp_replace_exp epairs => - fun - | Eexp e inst => Eexp (exp_replace_exp epairs e) inst - | Estruct fsel inst => { - let f (fld, se) => (fld, strexp_replace_exp epairs se); - Estruct (List.map ::f fsel) inst - } - | Earray len isel inst => { - let len' = exp_replace_exp epairs len; - let f (idx, se) => { - let idx' = exp_replace_exp epairs idx; - (idx', strexp_replace_exp epairs se) - }; - Earray len' (List.map ::f isel) inst - }; - -let hpred_replace_exp epairs => - fun - | Hpointsto root se te => { - let root_repl = exp_replace_exp epairs root; - let strexp_repl = strexp_replace_exp epairs se; - let te_repl = exp_replace_exp epairs te; - Hpointsto root_repl strexp_repl te_repl - } - | Hlseg k para root next shared => { - let root_repl = exp_replace_exp epairs root; - let next_repl = exp_replace_exp epairs next; - let shared_repl = List.map f::(exp_replace_exp epairs) shared; - Hlseg k para root_repl next_repl shared_repl - } - | Hdllseg k para e1 e2 e3 e4 shared => { - let e1' = exp_replace_exp epairs e1; - let e2' = exp_replace_exp epairs e2; - let e3' = exp_replace_exp epairs e3; - let e4' = exp_replace_exp epairs e4; - let shared_repl = List.map f::(exp_replace_exp epairs) shared; - Hdllseg k para e1' e2' e3' e4' shared_repl - }; - - -/** {2 Compaction} */ -module HpredInstHash = - Hashtbl.Make { - type t = hpred; - let equal = equal_hpred inst::true; - let hash = Hashtbl.hash; - }; - -type sharing_env = {exph: Exp.Hash.t Exp.t, hpredh: HpredInstHash.t hpred}; - - -/** Create a sharing env to store canonical representations */ -let create_sharing_env () => {exph: Exp.Hash.create 3, hpredh: HpredInstHash.create 3}; - - -/** Return a canonical representation of the exp */ -let exp_compact sh e => - try (Exp.Hash.find sh.exph e) { - | Not_found => - Exp.Hash.add sh.exph e e; - e - }; - -let rec sexp_compact sh se => - switch se { - | Eexp e inst => Eexp (exp_compact sh e) inst - | Estruct fsel inst => Estruct (List.map f::(fun (f, se) => (f, sexp_compact sh se)) fsel) inst - | Earray _ => se - }; - - -/** Return a compact representation of the hpred */ -let _hpred_compact sh hpred => - switch hpred { - | Hpointsto e1 se e2 => - let e1' = exp_compact sh e1; - let e2' = exp_compact sh e2; - let se' = sexp_compact sh se; - Hpointsto e1' se' e2' - | Hlseg _ => hpred - | Hdllseg _ => hpred - }; - -let hpred_compact sh hpred => - try (HpredInstHash.find sh.hpredh hpred) { - | Not_found => - let hpred' = _hpred_compact sh hpred; - HpredInstHash.add sh.hpredh hpred' hpred'; - hpred' - }; - - -/** {2 Functions for constructing or destructing entities in this module} */ - -/** Compute the offset list of an expression */ -let exp_get_offsets exp => { - let rec f offlist_past e => - switch (e: Exp.t) { - | Var _ - | Const _ - | UnOp _ - | BinOp _ - | Exn _ - | Closure _ - | Lvar _ - | Sizeof {dynamic_length: None} => offlist_past - | Sizeof {dynamic_length: Some l} => f offlist_past l - | Cast _ sub_exp => f offlist_past sub_exp - | Lfield sub_exp fldname typ => f [Off_fld fldname typ, ...offlist_past] sub_exp - | Lindex sub_exp e => f [Off_index e, ...offlist_past] sub_exp - }; - f [] exp -}; - -let exp_add_offsets exp offsets => { - let rec f acc => - fun - | [] => acc - | [Off_fld fld typ, ...offs'] => f (Exp.Lfield acc fld typ) offs' - | [Off_index e, ...offs'] => f (Exp.Lindex acc e) offs'; - f exp offsets -}; - - -/** Convert all the lseg's in sigma to nonempty lsegs. */ -let sigma_to_sigma_ne sigma :list (list atom, list hpred) => - if Config.nelseg { - let f eqs_sigma_list hpred => - switch hpred { - | Hpointsto _ - | Hlseg Lseg_NE _ _ _ _ - | Hdllseg Lseg_NE _ _ _ _ _ _ => - let g (eqs, sigma) => (eqs, [hpred, ...sigma]); - List.map f::g eqs_sigma_list - | Hlseg Lseg_PE para e1 e2 el => - let g (eqs, sigma) => [ - ([Aeq e1 e2, ...eqs], sigma), - (eqs, [Hlseg Lseg_NE para e1 e2 el, ...sigma]) - ]; - List.concat_map f::g eqs_sigma_list - | Hdllseg Lseg_PE para_dll e1 e2 e3 e4 el => - let g (eqs, sigma) => [ - ([Aeq e1 e3, Aeq e2 e4, ...eqs], sigma), - (eqs, [Hdllseg Lseg_NE para_dll e1 e2 e3 e4 el, ...sigma]) - ]; - List.concat_map f::g eqs_sigma_list - }; - List.fold ::f init::[([], [])] sigma - } else { - [([], sigma)] - }; - - -/** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], - [e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], - then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] - for some fresh [_zs'].*/ -let hpara_instantiate para e1 e2 elist => { - let subst_for_svars = { - let g id e => (id, e); - try (List.map2_exn f::g para.svars elist) { - | Invalid_argument _ => assert false - } - }; - let ids_evars = { - let g _ => Ident.create_fresh Ident.kprimed; - List.map f::g para.evars - }; - let subst_for_evars = { - let g id id' => (id, Exp.Var id'); - try (List.map2_exn f::g para.evars ids_evars) { - | Invalid_argument _ => assert false - } - }; - let subst = - `Exp ( - exp_subst_of_list ([(para.root, e1), (para.next, e2), ...subst_for_svars] @ subst_for_evars) - ); - (ids_evars, List.map f::(hpred_sub subst) para.body) -}; - - -/** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], - [blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], - then the result of the instantiation is - [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] - for some fresh [_zs'].*/ -let hpara_dll_instantiate (para: hpara_dll) cell blink flink elist => { - let subst_for_svars = { - let g id e => (id, e); - try (List.map2_exn f::g para.svars_dll elist) { - | Invalid_argument _ => assert false - } - }; - let ids_evars = { - let g _ => Ident.create_fresh Ident.kprimed; - List.map f::g para.evars_dll - }; - let subst_for_evars = { - let g id id' => (id, Exp.Var id'); - try (List.map2_exn f::g para.evars_dll ids_evars) { - | Invalid_argument _ => assert false - } - }; - let subst = - `Exp ( - exp_subst_of_list ( - [(para.cell, cell), (para.blink, blink), (para.flink, flink), ...subst_for_svars] @ subst_for_evars - ) - ); - (ids_evars, List.map f::(hpred_sub subst) para.body_dll) -}; - -let custom_error = Pvar.mk_global (Mangled.from_string "INFER_CUSTOM_ERROR") Pvar.TUExtern; diff --git a/infer/src/IR/Sil.rei b/infer/src/IR/Sil.rei deleted file mode 100644 index e089bfe73..000000000 --- a/infer/src/IR/Sil.rei +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language */ -module F = Format; - - -/** {2 Programs and Types} */ - -/** Convert expression lists to expression sets. */ -let elist_to_eset: list Exp.t => Exp.Set.t; - - -/** Kind of prune instruction */ -type if_kind = - | Ik_bexp /* boolean expressions, and exp ? exp : exp */ - | Ik_dowhile - | Ik_for - | Ik_if - | Ik_land_lor /* obtained from translation of && or || */ - | Ik_while - | Ik_switch -[@@deriving compare]; - - -/** An instruction. */ -type instr = - /** Load a value from the heap into an identifier. - [x = *lexp:typ] where - [lexp] is an expression denoting a heap address - [typ] is the root type of [lexp]. */ - /* Note for frontend writers: - [x] must be used in a subsequent instruction, otherwise the entire - `Load` instruction may be eliminated by copy-propagation. */ - | Load Ident.t Exp.t Typ.t Location.t - /** Store the value of an expression into the heap. - [*lexp1:typ = exp2] where - [lexp1] is an expression denoting a heap address - [typ] is the root type of [lexp1] - [exp2] is the expression whose value is store. */ - | Store Exp.t Typ.t Exp.t Location.t - /** prune the state based on [exp=1], the boolean indicates whether true branch */ - | Prune Exp.t Location.t bool if_kind - /** [Call (ret_id, e_fun, arg_ts, loc, call_flags)] represents an instruction - [ret_id = e_fun(arg_ts);]. The return value is ignored when [ret_id = None]. */ - | Call (option (Ident.t, Typ.t)) Exp.t (list (Exp.t, Typ.t)) Location.t CallFlags.t - /** nullify stack variable */ - | Nullify Pvar.t Location.t - | Abstract Location.t /** apply abstraction */ - | Remove_temps (list Ident.t) Location.t /** remove temporaries */ - | Declare_locals (list (Pvar.t, Typ.t)) Location.t /** declare local variables */ -[@@deriving compare]; - -let equal_instr: instr => instr => bool; - - -/** compare instructions from different procedures without considering loc's, ident's, and pvar's. - the [exp_map] param gives a mapping of names used in the procedure of [instr1] to identifiers - used in the procedure of [instr2] */ -let compare_structural_instr: instr => instr => Exp.Map.t Exp.t => (int, Exp.Map.t Exp.t); - -let skip_instr: instr; - - -/** Check if an instruction is auxiliary, or if it comes from source instructions. */ -let instr_is_auxiliary: instr => bool; - - -/** Offset for an lvalue. */ -type offset = - | Off_fld Typ.Fieldname.t Typ.t - | Off_index Exp.t; - - -/** {2 Components of Propositions} */ - -/** an atom is a pure atomic formula */ -type atom = - | Aeq Exp.t Exp.t /** equality */ - | Aneq Exp.t Exp.t /** disequality */ - | Apred PredSymb.t (list Exp.t) /** predicate symbol applied to exps */ - | Anpred PredSymb.t (list Exp.t) /** negated predicate symbol applied to exps */ -[@@deriving compare]; - -let equal_atom: atom => atom => bool; - -let atom_has_local_addr: atom => bool; - - -/** kind of lseg or dllseg predicates */ -type lseg_kind = - | Lseg_NE /** nonempty (possibly circular) listseg */ - | Lseg_PE /** possibly empty (possibly circular) listseg */ -[@@deriving compare]; - -let equal_lseg_kind: lseg_kind => lseg_kind => bool; - - -/** The boolean is true when the pointer was dereferenced without testing for zero. */ -type zero_flag = option bool; - - -/** True when the value was obtained by doing case analysis on null in a procedure call. */ -type null_case_flag = bool; - - -/** instrumentation of heap values */ -type inst = - | Iabstraction - | Iactual_precondition - | Ialloc - | Iformal zero_flag null_case_flag - | Iinitial - | Ilookup - | Inone - | Inullify - | Irearrange zero_flag null_case_flag int PredSymb.path_pos - | Itaint - | Iupdate zero_flag null_case_flag int PredSymb.path_pos - | Ireturn_from_call int -[@@deriving compare]; - -let equal_inst: inst => inst => bool; - -let inst_abstraction: inst; - -let inst_actual_precondition: inst; - -let inst_alloc: inst; - -let inst_formal: inst; /** for formal parameters and heap values at the beginning of the function */ - -let inst_initial: inst; /** for initial values */ - -let inst_lookup: inst; - -let inst_none: inst; - -let inst_nullify: inst; - - -/** the boolean indicates whether the pointer is known nonzero */ -let inst_rearrange: bool => Location.t => PredSymb.path_pos => inst; - -let inst_taint: inst; - -let inst_update: Location.t => PredSymb.path_pos => inst; - - -/** Get the null case flag of the inst. */ -let inst_get_null_case_flag: inst => option bool; - - -/** Set the null case flag of the inst. */ -let inst_set_null_case_flag: inst => inst; - - -/** update the location of the instrumentation */ -let inst_new_loc: Location.t => inst => inst; - - -/** Update [inst_old] to [inst_new] preserving the zero flag */ -let update_inst: inst => inst => inst; - -exception JoinFail; - - -/** join of instrumentations, can raise JoinFail */ -let inst_partial_join: inst => inst => inst; - - -/** meet of instrumentations */ -let inst_partial_meet: inst => inst => inst; - - -/** structured expressions represent a value of structured type, such as an array or a struct. */ -type strexp0 'inst = - | Eexp Exp.t 'inst /** Base case: expression with instrumentation */ - | Estruct (list (Typ.Fieldname.t, strexp0 'inst)) 'inst /** C structure */ - /** Array of given length - There are two conditions imposed / used in the array case. - First, if some index and value pair appears inside an array - in a strexp, then the index is less than the length of the array. - For instance, x |->[10 | e1: v1] implies that e1 <= 9. - Second, if two indices appear in an array, they should be different. - For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. */ - | Earray Exp.t (list (Exp.t, strexp0 'inst)) 'inst -[@@deriving compare]; - -type strexp = strexp0 inst; - - -/** Comparison function for strexp. - The inst:: parameter specifies whether instumentations should also - be considered (false by default). */ -let compare_strexp: inst::bool? => strexp => strexp => int; - - -/** Equality function for strexp. - The inst:: parameter specifies whether instumentations should also - be considered (false by default). */ -let equal_strexp: inst::bool? => strexp => strexp => bool; - - -/** an atomic heap predicate */ -type hpred0 'inst = - | Hpointsto Exp.t (strexp0 'inst) Exp.t - /** represents [exp|->strexp:typexp] where [typexp] - is an expression representing a type, e.h. [sizeof(t)]. */ - | Hlseg lseg_kind (hpara0 'inst) Exp.t Exp.t (list Exp.t) - /** higher - order predicate for singly - linked lists. - Should ensure that exp1!= exp2 implies that exp1 is allocated. - This assumption is used in the rearrangement. The last [exp list] parameter - is used to denote the shared links by all the nodes in the list. */ - | Hdllseg lseg_kind (hpara_dll0 'inst) Exp.t Exp.t Exp.t Exp.t (list Exp.t) - /** higher-order predicate for doubly-linked lists. - Parameter for the higher-order singly-linked list predicate. - Means "lambda (root,next,svars). Exists evars. body". - Assume that root, next, svars, evars are disjoint sets of - primed identifiers, and include all the free primed identifiers in body. - body should not contain any non - primed identifiers or program - variables (i.e. pvars). */ -[@@deriving compare] -and hpara0 'inst = { - root: Ident.t, - next: Ident.t, - svars: list Ident.t, - evars: list Ident.t, - body: list (hpred0 'inst) -} -[@@deriving compare] -/** parameter for the higher-order doubly-linked list predicates. - Assume that all the free identifiers in body_dll should belong to - cell, blink, flink, svars_dll, evars_dll. */ -and hpara_dll0 'inst = { - cell: Ident.t, /** address cell */ - blink: Ident.t, /** backward link */ - flink: Ident.t, /** forward link */ - svars_dll: list Ident.t, - evars_dll: list Ident.t, - body_dll: list (hpred0 'inst) -} -[@@deriving compare]; - -type hpred = hpred0 inst; - -type hpara = hpara0 inst; - -type hpara_dll = hpara_dll0 inst; - - -/** Comparison function for hpred. - The inst:: parameter specifies whether instumentations should also - be considered (false by default). */ -let compare_hpred: inst::bool? => hpred => hpred => int; - - -/** Equality function for hpred. - The inst:: parameter specifies whether instumentations should also - be considered (false by default). */ -let equal_hpred: inst::bool? => hpred => hpred => bool; - - -/** Sets of heap predicates */ -module HpredSet: Caml.Set.S with type elt = hpred; - - -/** {2 Compaction} */ -type sharing_env; - - -/** Create a sharing env to store canonical representations */ -let create_sharing_env: unit => sharing_env; - - -/** Return a canonical representation of the exp */ -let exp_compact: sharing_env => Exp.t => Exp.t; - - -/** Return a compact representation of the exp */ -let hpred_compact: sharing_env => hpred => hpred; - - -/** {2 Comparision And Inspection Functions} */ -let has_objc_ref_counter: Tenv.t => hpred => bool; - - -/** Returns the zero value of a type, for int, float and ptr types, None othwewise */ -let zero_value_of_numerical_type_option: Typ.t => option Exp.t; - - -/** Returns the zero value of a type, for int, float and ptr types, fail otherwise */ -let zero_value_of_numerical_type: Typ.t => Exp.t; - - -/** Make a static local name in objc */ -let mk_static_local_name: string => string => string; - - -/** Check if a pvar is a local static in objc */ -let is_static_local_name: string => Pvar.t => bool; - -/* A block pvar used to explain retain cycles */ -let block_pvar: Pvar.t; - - -/** Check if a pvar is a local pointing to a block in objc */ -let is_block_pvar: Pvar.t => bool; - - -/** Return the lhs expression of a hpred */ -let hpred_get_lhs: hpred => Exp.t; - - -/** {2 Pretty Printing} */ - -/** Begin change color if using diff printing, return updated printenv and change status */ -let color_pre_wrapper: Pp.env => F.formatter => 'a => (Pp.env, bool); - - -/** Close color annotation if changed */ -let color_post_wrapper: bool => Pp.env => F.formatter => unit; - - -/** Pretty print an expression. */ -let pp_exp_printenv: Pp.env => F.formatter => Exp.t => unit; - - -/** Pretty print an expression with type. */ -let pp_exp_typ: Pp.env => F.formatter => (Exp.t, Typ.t) => unit; - - -/** dump an expression. */ -let d_exp: Exp.t => unit; - - -/** Pretty print a type. */ -let pp_texp: Pp.env => F.formatter => Exp.t => unit; - - -/** Pretty print a type with all the details. */ -let pp_texp_full: Pp.env => F.formatter => Exp.t => unit; - - -/** Dump a type expression with all the details. */ -let d_texp_full: Exp.t => unit; - - -/** Pretty print a list of expressions. */ -let pp_exp_list: Pp.env => F.formatter => list Exp.t => unit; - - -/** Dump a list of expressions. */ -let d_exp_list: list Exp.t => unit; - - -/** Pretty print an offset */ -let pp_offset: Pp.env => F.formatter => offset => unit; - - -/** Convert an offset to a string */ -let offset_to_string: offset => string; - - -/** Dump an offset */ -let d_offset: offset => unit; - - -/** Pretty print a list of offsets */ -let pp_offset_list: Pp.env => F.formatter => list offset => unit; - - -/** Dump a list of offsets */ -let d_offset_list: list offset => unit; - - -/** Get the location of the instruction */ -let instr_get_loc: instr => Location.t; - - -/** get the expressions occurring in the instruction */ -let instr_get_exps: instr => list Exp.t; - - -/** Pretty print an instruction. */ -let pp_instr: Pp.env => F.formatter => instr => unit; - - -/** Dump an instruction. */ -let d_instr: instr => unit; - - -/** Pretty print a list of instructions. */ -let pp_instr_list: Pp.env => F.formatter => list instr => unit; - - -/** Dump a list of instructions. */ -let d_instr_list: list instr => unit; - - -/** Pretty print an atom. */ -let pp_atom: Pp.env => F.formatter => atom => unit; - - -/** Dump an atom. */ -let d_atom: atom => unit; - - -/** return a string representing the inst */ -let inst_to_string: inst => string; - - -/** Pretty print a strexp. */ -let pp_sexp: Pp.env => F.formatter => strexp => unit; - - -/** Dump a strexp. */ -let d_sexp: strexp => unit; - - -/** Pretty print a strexp list. */ -let pp_sexp_list: Pp.env => F.formatter => list strexp => unit; - - -/** Dump a strexp. */ -let d_sexp_list: list strexp => unit; - - -/** Pretty print a hpred. */ -let pp_hpred: Pp.env => F.formatter => hpred => unit; - - -/** Dump a hpred. */ -let d_hpred: hpred => unit; - - -/** Pretty print a hpara. */ -let pp_hpara: Pp.env => F.formatter => hpara => unit; - - -/** Pretty print a list of hparas. */ -let pp_hpara_list: Pp.env => F.formatter => list hpara => unit; - - -/** Pretty print a hpara_dll. */ -let pp_hpara_dll: Pp.env => F.formatter => hpara_dll => unit; - - -/** Pretty print a list of hpara_dlls. */ -let pp_hpara_dll_list: Pp.env => F.formatter => list hpara_dll => unit; - - -/** Module Predicates records the occurrences of predicates as parameters - of (doubly -)linked lists and Epara. - Provides unique numbering for predicates and an iterator. */ -module Predicates: { - - /** predicate environment */ - type env; - - /** create an empty predicate environment */ - let empty_env: unit => env; - - /** return true if the environment is empty */ - let is_empty: env => bool; - - /** return the id of the hpara */ - let get_hpara_id: env => hpara => int; - - /** return the id of the hpara_dll */ - let get_hpara_dll_id: env => hpara_dll => int; - - /** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, - passing the unique id to the functions. The iterator can only be used once. */ - let iter: env => (int => hpara => unit) => (int => hpara_dll => unit) => unit; - - /** Process one hpred, updating the predicate environment */ - let process_hpred: env => hpred => unit; -}; - - -/** Pretty print a hpred with optional predicate env */ -let pp_hpred_env: Pp.env => option Predicates.env => F.formatter => hpred => unit; - - -/** {2 Functions for traversing SIL data types} */ - -/** This function should be used before adding a new - index to Earray. The [exp] is the newly created - index. This function "cleans" [exp] according to whether it is the - footprint or current part of the prop. - The function faults in the re - execution mode, as an internal check of the tool. */ -let array_clean_new_index: bool => Exp.t => Exp.t; - - -/** Change exps in strexp using [f]. */ - -/** WARNING: the result might not be normalized. */ -let strexp_expmap: ((Exp.t, option inst) => (Exp.t, option inst)) => strexp => strexp; - - -/** Change exps in hpred by [f]. */ - -/** WARNING: the result might not be normalized. */ -let hpred_expmap: ((Exp.t, option inst) => (Exp.t, option inst)) => hpred => hpred; - - -/** Change instrumentations in hpred using [f]. */ -let hpred_instmap: (inst => inst) => hpred => hpred; - - -/** Change exps in hpred list by [f]. */ - -/** WARNING: the result might not be normalized. */ -let hpred_list_expmap: ((Exp.t, option inst) => (Exp.t, option inst)) => list hpred => list hpred; - - -/** Change exps in atom by [f]. */ - -/** WARNING: the result might not be normalized. */ -let atom_expmap: (Exp.t => Exp.t) => atom => atom; - - -/** Change exps in atom list by [f]. */ - -/** WARNING: the result might not be normalized. */ -let atom_list_expmap: (Exp.t => Exp.t) => list atom => list atom; - - -/** {2 Function for computing lexps in sigma} */ -let hpred_list_get_lexps: (Exp.t => bool) => list hpred => list Exp.t; - - -/** {2 Functions for computing program variables} */ -let exp_fpv: Exp.t => list Pvar.t; - -let strexp_fpv: strexp => list Pvar.t; - -let atom_fpv: atom => list Pvar.t; - -let hpred_fpv: hpred => list Pvar.t; - -let hpara_fpv: hpara => list Pvar.t; - - -/** {2 Functions for computing free non-program variables} */ - -/** Type of free variables. These include primed, normal and footprint variables. - We remember the order in which variables are added. */ -type fav; - - -/** flag to indicate whether fav's are stored in duplicate form. - Only to be used with fav_to_list */ -let fav_duplicates: ref bool; - - -/** Pretty print a fav. */ -let pp_fav: Pp.env => F.formatter => fav => unit; - - -/** Create a new [fav]. */ -let fav_new: unit => fav; - - -/** Emptyness check. */ -let fav_is_empty: fav => bool; - - -/** Check whether a predicate holds for all elements. */ -let fav_for_all: fav => (Ident.t => bool) => bool; - - -/** Check whether a predicate holds for some elements. */ -let fav_exists: fav => (Ident.t => bool) => bool; - - -/** Membership test fot [fav] */ -let fav_mem: fav => Ident.t => bool; - - -/** Convert a list to a fav. */ -let fav_from_list: list Ident.t => fav; - - -/** Convert a [fav] to a list of identifiers while preserving the order - that identifiers were added to [fav]. */ -let fav_to_list: fav => list Ident.t; - - -/** Copy a [fav]. */ -let fav_copy: fav => fav; - - -/** Turn a xxx_fav_add function into a xxx_fav function */ -let fav_imperative_to_functional: (fav => 'a => unit) => 'a => fav; - - -/** [fav_filter_ident fav f] only keeps [id] if [f id] is true. */ -let fav_filter_ident: fav => (Ident.t => bool) => unit; - - -/** Like [fav_filter_ident] but return a copy. */ -let fav_copy_filter_ident: fav => (Ident.t => bool) => fav; - - -/** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] - is in [fav2].*/ -let fav_subset_ident: fav => fav => bool; - - -/** add identifier list to fav */ -let ident_list_fav_add: list Ident.t => fav => unit; - - -/** [exp_fav_add fav exp] extends [fav] with the free variables of [exp] */ -let exp_fav_add: fav => Exp.t => unit; - -let exp_fav: Exp.t => fav; - -let exp_fav_list: Exp.t => list Ident.t; - -let ident_in_exp: Ident.t => Exp.t => bool; - -let strexp_fav_add: fav => strexp => unit; - -let atom_fav_add: fav => atom => unit; - -let atom_fav: atom => fav; - -let hpred_fav_add: fav => hpred => unit; - -let hpred_fav: hpred => fav; - - -/** Variables in hpara, excluding bound vars in the body */ -let hpara_shallow_av: hpara => fav; - - -/** Variables in hpara_dll, excluding bound vars in the body */ -let hpara_dll_shallow_av: hpara_dll => fav; - - -/** {2 Functions for computing all free or bound non-program variables} */ - -/** Non-program variables include all of primed, normal and footprint - variables. Thus, the functions essentially compute all the - identifiers occuring in a parameter. Some variables can appear more - than once in the result. */ -let exp_av_add: fav => Exp.t => unit; - -let strexp_av_add: fav => strexp => unit; - -let atom_av_add: fav => atom => unit; - -let hpred_av_add: fav => hpred => unit; - -let hpara_av_add: fav => hpara => unit; - - -/** {2 Substitution} */ -type exp_subst [@@deriving compare]; - -type subst = [ | `Exp exp_subst | `Typ Typ.type_subst_t] [@@deriving compare]; - -type subst_fun = [ | `Exp (Ident.t => Exp.t) | `Typ (Typ.t => Typ.t, Typ.Name.t => Typ.Name.t)]; - - -/** Equality for substitutions. */ -let equal_exp_subst: exp_subst => exp_subst => bool; - - -/** Create a substitution from a list of pairs. - For all (id1, e1), (id2, e2) in the input list, - if id1 = id2, then e1 = e2. */ -let exp_subst_of_list: list (Ident.t, Exp.t) => exp_subst; - -let subst_of_list: list (Ident.t, Exp.t) => subst; - - -/** like exp_subst_of_list, but allow duplicate ids and only keep the first occurrence */ -let exp_subst_of_list_duplicates: list (Ident.t, Exp.t) => exp_subst; - - -/** Convert a subst to a list of pairs. */ -let sub_to_list: exp_subst => list (Ident.t, Exp.t); - - -/** The empty substitution. */ -let sub_empty: subst; - -let exp_sub_empty: exp_subst; - -let is_sub_empty: subst => bool; - -/* let to_exp_subst : [< `Exp exp_subst] => exp_subst; */ - -/** Compute the common id-exp part of two inputs [subst1] and [subst2]. - The first component of the output is this common part. - The second and third components are the remainder of [subst1] - and [subst2], respectively. */ -let sub_join: exp_subst => exp_subst => exp_subst; - - -/** Compute the common id-exp part of two inputs [subst1] and [subst2]. - The first component of the output is this common part. - The second and third components are the remainder of [subst1] - and [subst2], respectively. */ -let sub_symmetric_difference: exp_subst => exp_subst => (exp_subst, exp_subst, exp_subst); - - -/** [sub_find filter sub] returns the expression associated to the first identifier - that satisfies [filter]. - Raise [Not_found] if there isn't one. */ -let sub_find: (Ident.t => bool) => exp_subst => Exp.t; - - -/** [sub_filter filter sub] restricts the domain of [sub] to the - identifiers satisfying [filter]. */ -let sub_filter: (Ident.t => bool) => exp_subst => exp_subst; - - -/** [sub_filter_exp filter sub] restricts the domain of [sub] to the - identifiers satisfying [filter(id, sub(id))]. */ -let sub_filter_pair: exp_subst => f::((Ident.t, Exp.t) => bool) => exp_subst; - - -/** [sub_range_partition filter sub] partitions [sub] according to - whether range expressions satisfy [filter]. */ -let sub_range_partition: (Exp.t => bool) => exp_subst => (exp_subst, exp_subst); - - -/** [sub_domain_partition filter sub] partitions [sub] according to - whether domain identifiers satisfy [filter]. */ -let sub_domain_partition: (Ident.t => bool) => exp_subst => (exp_subst, exp_subst); - - -/** Return the list of identifiers in the domain of the substitution. */ -let sub_domain: exp_subst => list Ident.t; - - -/** Return the list of expressions in the range of the substitution. */ -let sub_range: exp_subst => list Exp.t; - - -/** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. */ -let sub_range_map: (Exp.t => Exp.t) => exp_subst => exp_subst; - - -/** [sub_map f g sub] applies the renaming [f] to identifiers in the domain - of [sub] and the substitution [g] to the expressions in the range of [sub]. */ -let sub_map: (Ident.t => Ident.t) => (Exp.t => Exp.t) => exp_subst => exp_subst; - - -/** Checks whether [id] belongs to the domain of [subst]. */ -let mem_sub: Ident.t => exp_subst => bool; - - -/** Extend substitution and return [None] if not possible. */ -let extend_sub: exp_subst => Ident.t => Exp.t => option exp_subst; - - -/** Free auxilary variables in the domain and range of the - substitution. */ -let sub_fav_add: fav => exp_subst => unit; - - -/** Free or bound auxilary variables in the domain and range of the - substitution. */ -let sub_av_add: fav => exp_subst => unit; - - -/** substitution functions */ - -/** WARNING: these functions do not ensure that the results are normalized. */ -let exp_sub: subst => Exp.t => Exp.t; - -let atom_sub: subst => atom => atom; - - -/** apply [subst] to all id's in [instr], including LHS id's */ -let instr_sub: subst => instr => instr; - -let hpred_sub: subst => hpred => hpred; - - -/** apply [f] to id's in [instr]. if [sub_id_binders] is false, [f] is only applied to bound id's */ -let instr_sub_ids: sub_id_binders::bool => subst_fun => instr => instr; - - -/** {2 Functions for replacing occurrences of expressions.} */ - -/** The first parameter should define a partial function. - No parts of hpara are replaced by these functions. */ -let exp_replace_exp: list (Exp.t, Exp.t) => Exp.t => Exp.t; - -let strexp_replace_exp: list (Exp.t, Exp.t) => strexp => strexp; - -let atom_replace_exp: list (Exp.t, Exp.t) => atom => atom; - -let hpred_replace_exp: list (Exp.t, Exp.t) => hpred => hpred; - - -/** {2 Functions for constructing or destructing entities in this module} */ - -/** Compute the offset list of an expression */ -let exp_get_offsets: Exp.t => list offset; - - -/** Add the offset list to an expression */ -let exp_add_offsets: Exp.t => list offset => Exp.t; - -let sigma_to_sigma_ne: list hpred => list (list atom, list hpred); - - -/** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], - [e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], - then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] - for some fresh [_zs'].*/ -let hpara_instantiate: hpara => Exp.t => Exp.t => list Exp.t => (list Ident.t, list hpred); - - -/** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], - [blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], - then the result of the instantiation is - [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] - for some fresh [_zs'].*/ -let hpara_dll_instantiate: - hpara_dll => Exp.t => Exp.t => Exp.t => list Exp.t => (list Ident.t, list hpred); - -let custom_error: Pvar.t; diff --git a/infer/src/IR/Subtype.ml b/infer/src/IR/Subtype.ml new file mode 100644 index 000000000..4bd791b99 --- /dev/null +++ b/infer/src/IR/Subtype.ml @@ -0,0 +1,281 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Subtypes *) +open! IStd +module L = Logging +module F = Format + +let list_to_string list = + if Int.equal (List.length list) 0 then "( sub )" + else "- {" ^ String.concat ~sep:", " (List.map ~f:Typ.Name.name list) ^ "}" + +type t' = + | Exact (** denotes the current type only *) + | Subtypes of Typ.Name.t list + [@@deriving compare] + +let equal_modulo_flag (st1, _) (st2, _) = [%compare.equal : t'] st1 st2 + +(** denotes the current type and a list of types that are not their subtypes *) +type kind = CAST | INSTOF | NORMAL [@@deriving compare] + +let equal_kind = [%compare.equal : kind] + +type t = t' * kind [@@deriving compare] + +type result = No | Unknown | Yes [@@deriving compare] + +let equal_result = [%compare.equal : result] + +let sub_type tname_subst st_pair = + let st, kind = st_pair in + match st with + | Subtypes tnames + -> let tnames' = IList.map_changed tname_subst tnames in + if phys_equal tnames tnames' then st_pair else (Subtypes tnames', kind) + | Exact + -> st_pair + +let max_result res1 res2 = if compare_result res1 res2 <= 0 then res2 else res1 + +let is_interface tenv (class_name: Typ.Name.t) = + match (class_name, Tenv.lookup tenv class_name) with + | JavaClass _, Some {fields= []; methods= []} + -> true + | _ + -> false + +let is_root_class class_name = + match class_name with + | Typ.JavaClass _ + -> Typ.Name.equal class_name Typ.Name.Java.java_lang_object + | _ + -> false + +(** check if c1 is a subclass of c2 *) +let check_subclass_tenv tenv c1 c2 : result = + let rec loop best_result classnames : result = + (* Check if the name c2 is found in the list of super types and + keep the best results according to Yes > Unknown > No *) + if equal_result best_result Yes then Yes + else + match classnames with + | [] + -> best_result + | cn :: cns + -> loop (max_result best_result (check cn)) cns + and check cn : result = + if Typ.Name.equal cn c2 then Yes + else + match Tenv.lookup tenv cn with + | None when is_root_class cn + -> No + | None + -> Unknown + | Some {supers} + -> loop No supers + in + if is_root_class c2 then Yes else check c1 + +module SubtypesMap = Caml.Map.Make (struct + (* pair of subtypes *) + type t = Typ.Name.t * Typ.Name.t [@@deriving compare] +end) + +let check_subtype = + let subtMap = ref SubtypesMap.empty in + fun tenv c1 c2 -> + ( try SubtypesMap.find (c1, c2) !subtMap + with Not_found -> + let is_subt = check_subclass_tenv tenv c1 c2 in + subtMap := SubtypesMap.add (c1, c2) is_subt !subtMap ; + is_subt + : result ) + +let is_known_subtype tenv c1 c2 : bool = equal_result (check_subtype tenv c1 c2) Yes + +let is_known_not_subtype tenv c1 c2 : bool = equal_result (check_subtype tenv c1 c2) No + +let flag_to_string flag = match flag with CAST -> "(cast)" | INSTOF -> "(instof)" | NORMAL -> "" + +let pp f (t, flag) = + if Config.print_types then + match t with + | Exact + -> F.fprintf f "%s" (flag_to_string flag) + | Subtypes list + -> F.fprintf f "%s" (list_to_string list ^ flag_to_string flag) + +let exact = (Exact, NORMAL) + +let all_subtypes = Subtypes [] + +let subtypes = (all_subtypes, NORMAL) + +let subtypes_cast = (all_subtypes, CAST) + +let subtypes_instof = (all_subtypes, INSTOF) + +let is_cast t = equal_kind (snd t) CAST + +let is_instof t = equal_kind (snd t) INSTOF + +let list_intersect equal l1 l2 = + let in_l2 a = List.mem ~equal l2 a in + List.filter ~f:in_l2 l1 + +let join_flag flag1 flag2 = + match (flag1, flag2) with CAST, _ -> CAST | _, CAST -> CAST | _, _ -> NORMAL + +let join (s1, flag1) (s2, flag2) = + let s = + match (s1, s2) with + | Exact, _ + -> s2 + | _, Exact + -> s1 + | Subtypes l1, Subtypes l2 + -> Subtypes (list_intersect Typ.Name.equal l1 l2) + in + let flag = join_flag flag1 flag2 in + (s, flag) + +let update_flag c1 c2 flag flag' = + match flag with INSTOF -> if Typ.Name.equal c1 c2 then flag else flag' | _ -> flag' + +let change_flag st_opt c1 c2 flag' = + match st_opt with + | Some st -> ( + match st with + | Exact, flag + -> let new_flag = update_flag c1 c2 flag flag' in + Some (Exact, new_flag) + | Subtypes t, flag + -> let new_flag = update_flag c1 c2 flag flag' in + Some (Subtypes t, new_flag) ) + | None + -> None + +let normalize_subtypes t_opt c1 c2 flag1 flag2 = + let new_flag = update_flag c1 c2 flag1 flag2 in + match t_opt with + | Some t -> ( + match t with + | Exact + -> Some (t, new_flag) + | Subtypes l + -> Some (Subtypes (List.sort ~cmp:Typ.Name.compare l), new_flag) ) + | None + -> None + +let subtypes_to_string t = + match fst t with + | Exact + -> "ex" ^ flag_to_string (snd t) + | Subtypes l + -> list_to_string l ^ flag_to_string (snd t) + +(* c is a subtype when it does not appear in the list l of no-subtypes *) +let no_subtype_in_list tenv c l = not (List.exists ~f:(is_known_subtype tenv c) l) + +let is_strict_subtype tenv c1 c2 = is_known_subtype tenv c1 c2 && not (Typ.Name.equal c1 c2) + +(* checks for redundancies when adding c to l + Xi in A - { X1,..., Xn } is redundant in two cases: + 1) not (Xi <: A) because removing the subtypes of Xi has no effect unless Xi is a subtype of A + 2) Xi <: Xj because the subtypes of Xi are a subset of the subtypes of Xj *) +let check_redundancies tenv c l = + let aux (l, add) ci = + let l, should_add = + if is_known_subtype tenv ci c then (l, true) + else if is_known_subtype tenv c ci then (ci :: l, false) + else (ci :: l, true) + in + (l, add && should_add) + in + List.fold ~f:aux ~init:([], true) l + +let rec updates_head f c l = + match l with + | [] + -> [] + | ci :: rest + -> if is_strict_subtype f ci c then ci :: updates_head f c rest else updates_head f c rest + +(* adds the classes of l2 to l1 and checks that no redundancies or inconsistencies will occur + A - { X1,..., Xn } is inconsistent if A <: Xi for some i *) +let rec add_not_subtype tenv c1 l1 l2 = + match l2 with + | [] + -> l1 + | c :: rest + -> if is_known_subtype tenv c1 c then add_not_subtype tenv c1 l1 rest + else + (* checks for inconsistencies *) + let l1', should_add = check_redundancies tenv c l1 in + (* checks for redundancies *) + let rest' = add_not_subtype tenv c1 l1' rest in + if should_add then c :: rest' else rest' + +let get_subtypes tenv (c1, ((st1, flag1): t)) (c2, ((st2, flag2): t)) = + let is_sub = is_known_subtype tenv c1 c2 in + let pos_st, neg_st = + match (st1, st2) with + | Exact, Exact + -> if is_sub then (Some st1, None) else (None, Some st1) + | Exact, Subtypes l2 + -> if is_sub && no_subtype_in_list tenv c1 l2 then (Some st1, None) else (None, Some st1) + | Subtypes l1, Exact + -> if is_sub then (Some st1, None) + else + let l1' = updates_head tenv c2 l1 in + if no_subtype_in_list tenv c2 l1 then + (Some (Subtypes l1'), Some (Subtypes (add_not_subtype tenv c1 l1 [c2]))) + else (None, Some st1) + | Subtypes l1, Subtypes l2 + -> if is_interface tenv c2 || is_sub then + if no_subtype_in_list tenv c1 l2 then + let l2' = updates_head tenv c1 l2 in + (Some (Subtypes (add_not_subtype tenv c1 l1 l2')), None) + else (None, Some st1) + else if (is_interface tenv c1 || is_known_subtype tenv c2 c1) + && no_subtype_in_list tenv c2 l1 + then + let l1' = updates_head tenv c2 l1 in + ( Some (Subtypes (add_not_subtype tenv c2 l1' l2)) + , Some (Subtypes (add_not_subtype tenv c1 l1 [c2])) ) + else (None, Some st1) + in + (normalize_subtypes pos_st c1 c2 flag1 flag2, normalize_subtypes neg_st c1 c2 flag1 flag2) + +let case_analysis_basic tenv (c1, st) (c2, (_, flag2)) = + let pos_st, neg_st = + if is_known_subtype tenv c1 c2 then (Some st, None) + else if is_known_subtype tenv c2 c1 then + match st with + | Exact, _ + -> if Typ.Name.equal c1 c2 then (Some st, None) else (None, Some st) + | Subtypes _, _ + -> if Typ.Name.equal c1 c2 then (Some st, None) else (Some st, Some st) + else (None, Some st) + in + (change_flag pos_st c1 c2 flag2, change_flag neg_st c1 c2 flag2) + +(** [case_analysis (c1, st1) (c2, st2) f] performs case analysis on [c1 <: c2] + according to [st1] and [st2] + where f c1 c2 is true if c1 is a subtype of c2. + get_subtypes returning a pair: + - whether [st1] and [st2] admit [c1 <: c2], and in case return the updated subtype [st1] + - whether [st1] and [st2] admit [not(c1 <: c2)], + and in case return the updated subtype [st1] *) +let case_analysis tenv (c1, st1) (c2, st2) = + if Config.subtype_multirange then get_subtypes tenv (c1, st1) (c2, st2) + else case_analysis_basic tenv (c1, st1) (c2, st2) diff --git a/infer/src/IR/Subtype.mli b/infer/src/IR/Subtype.mli new file mode 100644 index 000000000..546b59eba --- /dev/null +++ b/infer/src/IR/Subtype.mli @@ -0,0 +1,66 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Subtypes *) +open! IStd +module L = Logging +module F = Format + +type t [@@deriving compare] + +val pp : F.formatter -> t -> unit + +val sub_type : (Typ.Name.t -> Typ.Name.t) -> t -> t + +val exact : t + +(** denotes the current type only *) + +val subtypes : t + +(** denotes the current type and any subtypes *) + +val subtypes_cast : t + +val subtypes_instof : t + +val join : t -> t -> t + +(** [case_analysis tenv (c1, st1) (c2, st2)] performs case analysis on [c1 <: c2] according + to [st1] and [st2]. + [case_analysis] returns a pair: + - whether [st1] and [st2] admit [c1 <: c2], and in case returns the updated subtype [st1] + - whether [st1] and [st2] admit [not(c1 <: c2)], and in case returns the updated subtype [st1] *) + +val case_analysis : Tenv.t -> Typ.Name.t * t -> Typ.Name.t * t -> t option * t option + +(** [is_known_subtype tenv c1 c2] returns true if there is enough information in [tenv] to prove + that [c1] is a subtype of [c2]. + Note that [not (is_known_subtype tenv c1 c2) == true] does not imply + that [is_known_not_subtype tenv c1 c2 == true] *) + +val is_known_subtype : Tenv.t -> Typ.Name.t -> Typ.Name.t -> bool + +(** [is_known_not_subtype tenv c1 c2] returns true if there is enough information in [tenv] to prove + that [c1] is not a subtype of [c2]. + Note that [not (is_known_not_subtype tenv c1 c2) == true] does not imply + that [is_known_subtype tenv c1 c2 == true] *) + +val is_known_not_subtype : Tenv.t -> Typ.Name.t -> Typ.Name.t -> bool + +val subtypes_to_string : t -> string + +val is_cast : t -> bool + +val is_instof : t -> bool + +(** equality ignoring flags in the subtype *) + +val equal_modulo_flag : t -> t -> bool diff --git a/infer/src/IR/Subtype.re b/infer/src/IR/Subtype.re deleted file mode 100644 index 629f4e521..000000000 --- a/infer/src/IR/Subtype.re +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Subtypes */ -module L = Logging; - -module F = Format; - -let list_to_string list => - if (Int.equal (List.length list) 0) { - "( sub )" - } else { - "- {" ^ String.concat sep::", " (List.map f::Typ.Name.name list) ^ "}" - }; - -type t' = - | Exact /** denotes the current type only */ - | Subtypes (list Typ.Name.t) -[@@deriving compare]; - -let equal_modulo_flag (st1, _) (st2, _) => [%compare.equal : t'] st1 st2; - - -/** denotes the current type and a list of types that are not their subtypes */ -type kind = - | CAST - | INSTOF - | NORMAL -[@@deriving compare]; - -let equal_kind = [%compare.equal : kind]; - -type t = (t', kind) [@@deriving compare]; - -type result = - | No - | Unknown - | Yes -[@@deriving compare]; - -let equal_result = [%compare.equal : result]; - -let sub_type tname_subst st_pair => { - let (st, kind) = st_pair; - switch st { - | Subtypes tnames => - let tnames' = IList.map_changed tname_subst tnames; - if (phys_equal tnames tnames') { - st_pair - } else { - (Subtypes tnames', kind) - } - | Exact => st_pair - } -}; - -let max_result res1 res2 => - if (compare_result res1 res2 <= 0) { - res2 - } else { - res1 - }; - -let is_interface tenv (class_name: Typ.Name.t) => - switch (class_name, Tenv.lookup tenv class_name) { - | (JavaClass _, Some {fields: [], methods: []}) => true - | _ => false - }; - -let is_root_class class_name => - switch class_name { - | Typ.JavaClass _ => Typ.Name.equal class_name Typ.Name.Java.java_lang_object - | _ => false - }; - - -/** check if c1 is a subclass of c2 */ -let check_subclass_tenv tenv c1 c2 :result => { - let rec loop best_result classnames :result => - /* Check if the name c2 is found in the list of super types and - keep the best results according to Yes > Unknown > No */ - if (equal_result best_result Yes) { - Yes - } else { - switch classnames { - | [] => best_result - | [cn, ...cns] => loop (max_result best_result (check cn)) cns - } - } - and check cn :result => - if (Typ.Name.equal cn c2) { - Yes - } else { - switch (Tenv.lookup tenv cn) { - | None when is_root_class cn => No - | None => Unknown - | Some {supers} => loop No supers - } - }; - if (is_root_class c2) { - Yes - } else { - check c1 - } -}; - -module SubtypesMap = - Caml.Map.Make { - /* pair of subtypes */ - type t = (Typ.Name.t, Typ.Name.t) [@@deriving compare]; - }; - -let check_subtype = { - let subtMap = ref SubtypesMap.empty; - fun tenv c1 c2 => ( - try (SubtypesMap.find (c1, c2) !subtMap) { - | Not_found => - let is_subt = check_subclass_tenv tenv c1 c2; - subtMap := SubtypesMap.add (c1, c2) is_subt !subtMap; - is_subt - }: result - ) -}; - -let is_known_subtype tenv c1 c2 :bool => equal_result (check_subtype tenv c1 c2) Yes; - -let is_known_not_subtype tenv c1 c2 :bool => equal_result (check_subtype tenv c1 c2) No; - -let flag_to_string flag => - switch flag { - | CAST => "(cast)" - | INSTOF => "(instof)" - | NORMAL => "" - }; - -let pp f (t, flag) => - if Config.print_types { - switch t { - | Exact => F.fprintf f "%s" (flag_to_string flag) - | Subtypes list => F.fprintf f "%s" (list_to_string list ^ flag_to_string flag) - } - }; - -let exact = (Exact, NORMAL); - -let all_subtypes = Subtypes []; - -let subtypes = (all_subtypes, NORMAL); - -let subtypes_cast = (all_subtypes, CAST); - -let subtypes_instof = (all_subtypes, INSTOF); - -let is_cast t => equal_kind (snd t) CAST; - -let is_instof t => equal_kind (snd t) INSTOF; - -let list_intersect equal l1 l2 => { - let in_l2 a => List.mem ::equal l2 a; - List.filter f::in_l2 l1 -}; - -let join_flag flag1 flag2 => - switch (flag1, flag2) { - | (CAST, _) => CAST - | (_, CAST) => CAST - | (_, _) => NORMAL - }; - -let join (s1, flag1) (s2, flag2) => { - let s = - switch (s1, s2) { - | (Exact, _) => s2 - | (_, Exact) => s1 - | (Subtypes l1, Subtypes l2) => Subtypes (list_intersect Typ.Name.equal l1 l2) - }; - let flag = join_flag flag1 flag2; - (s, flag) -}; - -let update_flag c1 c2 flag flag' => - switch flag { - | INSTOF => - if (Typ.Name.equal c1 c2) { - flag - } else { - flag' - } - | _ => flag' - }; - -let change_flag st_opt c1 c2 flag' => - switch st_opt { - | Some st => - switch st { - | (Exact, flag) => - let new_flag = update_flag c1 c2 flag flag'; - Some (Exact, new_flag) - | (Subtypes t, flag) => - let new_flag = update_flag c1 c2 flag flag'; - Some (Subtypes t, new_flag) - } - | None => None - }; - -let normalize_subtypes t_opt c1 c2 flag1 flag2 => { - let new_flag = update_flag c1 c2 flag1 flag2; - switch t_opt { - | Some t => - switch t { - | Exact => Some (t, new_flag) - | Subtypes l => Some (Subtypes (List.sort cmp::Typ.Name.compare l), new_flag) - } - | None => None - } -}; - -let subtypes_to_string t => - switch (fst t) { - | Exact => "ex" ^ flag_to_string (snd t) - | Subtypes l => list_to_string l ^ flag_to_string (snd t) - }; - -/* c is a subtype when it does not appear in the list l of no-subtypes */ -let no_subtype_in_list tenv c l => not (List.exists f::(is_known_subtype tenv c) l); - -let is_strict_subtype tenv c1 c2 => is_known_subtype tenv c1 c2 && not (Typ.Name.equal c1 c2); - -/* checks for redundancies when adding c to l - Xi in A - { X1,..., Xn } is redundant in two cases: - 1) not (Xi <: A) because removing the subtypes of Xi has no effect unless Xi is a subtype of A - 2) Xi <: Xj because the subtypes of Xi are a subset of the subtypes of Xj */ -let check_redundancies tenv c l => { - let aux (l, add) ci => { - let (l, should_add) = - if (is_known_subtype tenv ci c) { - (l, true) - } else if (is_known_subtype tenv c ci) { - ([ci, ...l], false) - } else { - ([ci, ...l], true) - }; - (l, add && should_add) - }; - List.fold f::aux init::([], true) l -}; - -let rec updates_head f c l => - switch l { - | [] => [] - | [ci, ...rest] => - if (is_strict_subtype f ci c) { - [ci, ...updates_head f c rest] - } else { - updates_head f c rest - } - }; - -/* adds the classes of l2 to l1 and checks that no redundancies or inconsistencies will occur - A - { X1,..., Xn } is inconsistent if A <: Xi for some i */ -let rec add_not_subtype tenv c1 l1 l2 => - switch l2 { - | [] => l1 - | [c, ...rest] => - if (is_known_subtype tenv c1 c) { - add_not_subtype tenv c1 l1 rest - } else { - /* checks for inconsistencies */ - let (l1', should_add) = check_redundancies tenv c l1; /* checks for redundancies */ - let rest' = add_not_subtype tenv c1 l1' rest; - if should_add { - [c, ...rest'] - } else { - rest' - } - } - }; - -let get_subtypes tenv (c1, (st1, flag1): t) (c2, (st2, flag2): t) => { - let is_sub = is_known_subtype tenv c1 c2; - let (pos_st, neg_st) = - switch (st1, st2) { - | (Exact, Exact) => - if is_sub { - (Some st1, None) - } else { - (None, Some st1) - } - | (Exact, Subtypes l2) => - if (is_sub && no_subtype_in_list tenv c1 l2) { - (Some st1, None) - } else { - (None, Some st1) - } - | (Subtypes l1, Exact) => - if is_sub { - (Some st1, None) - } else { - let l1' = updates_head tenv c2 l1; - if (no_subtype_in_list tenv c2 l1) { - (Some (Subtypes l1'), Some (Subtypes (add_not_subtype tenv c1 l1 [c2]))) - } else { - (None, Some st1) - } - } - | (Subtypes l1, Subtypes l2) => - if (is_interface tenv c2 || is_sub) { - if (no_subtype_in_list tenv c1 l2) { - let l2' = updates_head tenv c1 l2; - (Some (Subtypes (add_not_subtype tenv c1 l1 l2')), None) - } else { - (None, Some st1) - } - } else if ( - (is_interface tenv c1 || is_known_subtype tenv c2 c1) && no_subtype_in_list tenv c2 l1 - ) { - let l1' = updates_head tenv c2 l1; - ( - Some (Subtypes (add_not_subtype tenv c2 l1' l2)), - Some (Subtypes (add_not_subtype tenv c1 l1 [c2])) - ) - } else { - (None, Some st1) - } - }; - (normalize_subtypes pos_st c1 c2 flag1 flag2, normalize_subtypes neg_st c1 c2 flag1 flag2) -}; - -let case_analysis_basic tenv (c1, st) (c2, (_, flag2)) => { - let (pos_st, neg_st) = - if (is_known_subtype tenv c1 c2) { - (Some st, None) - } else if (is_known_subtype tenv c2 c1) { - switch st { - | (Exact, _) => - if (Typ.Name.equal c1 c2) { - (Some st, None) - } else { - (None, Some st) - } - | (Subtypes _, _) => - if (Typ.Name.equal c1 c2) { - (Some st, None) - } else { - (Some st, Some st) - } - } - } else { - (None, Some st) - }; - (change_flag pos_st c1 c2 flag2, change_flag neg_st c1 c2 flag2) -}; - - -/** [case_analysis (c1, st1) (c2, st2) f] performs case analysis on [c1 <: c2] - according to [st1] and [st2] - where f c1 c2 is true if c1 is a subtype of c2. - get_subtypes returning a pair: - - whether [st1] and [st2] admit [c1 <: c2], and in case return the updated subtype [st1] - - whether [st1] and [st2] admit [not(c1 <: c2)], - and in case return the updated subtype [st1] */ -let case_analysis tenv (c1, st1) (c2, st2) => - if Config.subtype_multirange { - get_subtypes tenv (c1, st1) (c2, st2) - } else { - case_analysis_basic tenv (c1, st1) (c2, st2) - }; diff --git a/infer/src/IR/Subtype.rei b/infer/src/IR/Subtype.rei deleted file mode 100644 index d06a3412d..000000000 --- a/infer/src/IR/Subtype.rei +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Subtypes */ -module L = Logging; - -module F = Format; - -type t [@@deriving compare]; - -let pp: F.formatter => t => unit; - -let sub_type: (Typ.Name.t => Typ.Name.t) => t => t; - -let exact: t; /** denotes the current type only */ - -let subtypes: t; /** denotes the current type and any subtypes */ - -let subtypes_cast: t; - -let subtypes_instof: t; - -let join: t => t => t; - - -/** [case_analysis tenv (c1, st1) (c2, st2)] performs case analysis on [c1 <: c2] according - to [st1] and [st2]. - [case_analysis] returns a pair: - - whether [st1] and [st2] admit [c1 <: c2], and in case returns the updated subtype [st1] - - whether [st1] and [st2] admit [not(c1 <: c2)], and in case returns the updated subtype [st1] */ -let case_analysis: Tenv.t => (Typ.Name.t, t) => (Typ.Name.t, t) => (option t, option t); - - -/** [is_known_subtype tenv c1 c2] returns true if there is enough information in [tenv] to prove - that [c1] is a subtype of [c2]. - Note that [not (is_known_subtype tenv c1 c2) == true] does not imply - that [is_known_not_subtype tenv c1 c2 == true] */ -let is_known_subtype: Tenv.t => Typ.Name.t => Typ.Name.t => bool; - - -/** [is_known_not_subtype tenv c1 c2] returns true if there is enough information in [tenv] to prove - that [c1] is not a subtype of [c2]. - Note that [not (is_known_not_subtype tenv c1 c2) == true] does not imply - that [is_known_subtype tenv c1 c2 == true] */ -let is_known_not_subtype: Tenv.t => Typ.Name.t => Typ.Name.t => bool; - -let subtypes_to_string: t => string; - -let is_cast: t => bool; - -let is_instof: t => bool; - - -/** equality ignoring flags in the subtype */ -let equal_modulo_flag: t => t => bool; diff --git a/infer/src/IR/Tenv.ml b/infer/src/IR/Tenv.ml new file mode 100644 index 000000000..992df74e2 --- /dev/null +++ b/infer/src/IR/Tenv.ml @@ -0,0 +1,119 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module Hashtbl = Caml.Hashtbl + +(** 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 ?supers ?annots name = + let struct_typ = + Typ.Struct.internal_mk_struct ?default ?fields ?statics ?methods ?supers ?annots () + in + TypenameHash.replace tenv name struct_typ ; struct_typ + +(** Check if typename is found in tenv *) +let mem tenv name = TypenameHash.mem tenv name + +(** Look up a name in the global type environment. *) +let lookup tenv name : Typ.Struct.t option = + try Some (TypenameHash.find tenv name) + with 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 Not_found -> None ) + | CppClass (m, NoTemplate) -> ( + try Some (TypenameHash.find tenv (CStruct m)) + with Not_found -> None ) + | _ + -> None + +(** Add a (name,type) pair to the global type environment. *) +let add tenv name struct_typ = TypenameHash.replace tenv name struct_typ + +(** Get method that is being overriden by java_pname (if any) **) +let get_overriden_method tenv pname_java = + let struct_typ_get_method_by_name (struct_typ: Typ.Struct.t) method_name = + List.find_exn + ~f:(fun meth -> String.equal method_name (Typ.Procname.get_method meth)) + struct_typ.methods + in + let rec get_overriden_method_in_supers pname_java supers = + match supers with + | superclass :: supers_tail -> ( + match lookup tenv superclass with + | Some struct_typ -> ( + try + Some (struct_typ_get_method_by_name struct_typ (Typ.Procname.java_get_method pname_java)) + with Not_found -> + get_overriden_method_in_supers pname_java (supers_tail @ struct_typ.supers) ) + | None + -> get_overriden_method_in_supers pname_java supers_tail ) + | [] + -> None + in + match lookup tenv (Typ.Procname.java_get_class_type_name pname_java) with + | Some {supers} + -> get_overriden_method_in_supers pname_java supers + | _ + -> None + +(** Serializer for type environments *) +let tenv_serializer : t Serialization.serializer = + Serialization.create_serializer Serialization.Key.tenv + +let global_tenv : t option ref = ref None + +(** Load a type environment from a file *) +let load_from_file (filename: DB.filename) : t option = + if DB.equal_filename filename DB.global_tenv_fname then ( + if is_none !global_tenv then global_tenv + := Serialization.read_from_file tenv_serializer DB.global_tenv_fname ; + !global_tenv ) + else Serialization.read_from_file tenv_serializer filename + +(** Save a type environment into a file *) +let store_to_file (filename: DB.filename) (tenv: t) = + (* 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 *) + if DB.equal_filename filename DB.global_tenv_fname then global_tenv := Some tenv ; + Serialization.write_to_file tenv_serializer filename ~data:tenv ; + if Config.debug_mode then + let debug_filename = DB.filename_to_string (DB.filename_add_suffix filename ".debug") in + let out_channel = Out_channel.create debug_filename in + let fmt = Format.formatter_of_out_channel out_channel in + Format.fprintf fmt "%a" pp tenv ; Out_channel.close out_channel + +let iter f tenv = TypenameHash.iter f tenv + +let fold f tenv = TypenameHash.fold f tenv diff --git a/infer/src/IR/Tenv.mli b/infer/src/IR/Tenv.mli new file mode 100644 index 000000000..a680a18de --- /dev/null +++ b/infer/src/IR/Tenv.mli @@ -0,0 +1,63 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** Module for Type Environments. *) + +type t + +(** Type for type environment. *) + +(** Add a (name,typename) pair to the global type environment. *) + +val add : t -> Typ.Name.t -> Typ.Struct.t -> unit + +(** Create a new type environment. *) + +val create : unit -> t + +(** Fold a function over the elements of the type environment. *) + +val fold : (Typ.Name.t -> Typ.Struct.t -> 'a -> 'a) -> t -> 'a -> 'a + +(** iterate over a type environment *) + +val iter : (Typ.Name.t -> Typ.Struct.t -> unit) -> t -> unit + +(** Load a type environment from a file *) + +val load_from_file : DB.filename -> t option + +(** Look up a name in the global type environment. *) + +val lookup : t -> Typ.Name.t -> Typ.Struct.t option + +(** Construct a struct_typ, normalizing field types *) + +val mk_struct : + t -> ?default:Typ.Struct.t -> ?fields:Typ.Struct.fields -> ?statics:Typ.Struct.fields + -> ?methods:Typ.Procname.t list -> ?supers:Typ.Name.t list -> ?annots:Annot.Item.t -> Typ.Name.t + -> Typ.Struct.t + +(** Check if typename is found in t *) + +val mem : t -> Typ.Name.t -> bool + +(** print a type environment *) + +val pp : Format.formatter -> t -> unit + +(** Save a type environment into a file *) + +val store_to_file : DB.filename -> t -> unit + +(** Get method that is being overriden by java_pname (if any) **) + +val get_overriden_method : t -> Typ.Procname.java -> Typ.Procname.t option diff --git a/infer/src/IR/Tenv.re b/infer/src/IR/Tenv.re deleted file mode 100644 index 42182f12c..000000000 --- a/infer/src/IR/Tenv.re +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** Module for Type Environments. */ - -/** Hash tables on strings. */ -module TypenameHash = - Hashtbl.Make { - type t = Typ.Name.t; - let equal tn1 tn2 => Typ.Name.equal tn1 tn2; - let hash = Hashtbl.hash; - }; - - -/** Type for type environment. */ -type t = TypenameHash.t Typ.Struct.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=? ::supers=? ::annots=? name => { - let struct_typ = - Typ.Struct.internal_mk_struct - ::?default ::?fields ::?statics ::?methods ::?supers ::?annots (); - TypenameHash.replace tenv name struct_typ; - struct_typ -}; - - -/** Check if typename is found in tenv */ -let mem tenv name => TypenameHash.mem tenv name; - - -/** Look up a name in the global type environment. */ -let lookup tenv name :option Typ.Struct.t => - try (Some (TypenameHash.find tenv name)) { - | Not_found => - /* ToDo: remove the following additional lookups once C/C++ interop is resolved */ - switch (name: Typ.Name.t) { - | CStruct m => - try (Some (TypenameHash.find tenv (CppClass m NoTemplate))) { - | Not_found => None - } - | CppClass m NoTemplate => - try (Some (TypenameHash.find tenv (CStruct m))) { - | Not_found => None - } - | _ => None - } - }; - - -/** Add a (name,type) pair to the global type environment. */ -let add tenv name struct_typ => TypenameHash.replace tenv name struct_typ; - - -/** Get method that is being overriden by java_pname (if any) **/ -let get_overriden_method tenv pname_java => { - let struct_typ_get_method_by_name (struct_typ: Typ.Struct.t) method_name => - List.find_exn - f::(fun meth => String.equal method_name (Typ.Procname.get_method meth)) struct_typ.methods; - let rec get_overriden_method_in_supers pname_java supers => - switch supers { - | [superclass, ...supers_tail] => - switch (lookup tenv superclass) { - | Some struct_typ => - try ( - Some (struct_typ_get_method_by_name struct_typ (Typ.Procname.java_get_method pname_java)) - ) { - | Not_found => get_overriden_method_in_supers pname_java (supers_tail @ struct_typ.supers) - } - | None => get_overriden_method_in_supers pname_java supers_tail - } - | [] => None - }; - switch (lookup tenv (Typ.Procname.java_get_class_type_name pname_java)) { - | Some {supers} => get_overriden_method_in_supers pname_java supers - | _ => None - } -}; - - -/** Serializer for type environments */ -let tenv_serializer: Serialization.serializer t = - Serialization.create_serializer Serialization.Key.tenv; - -let global_tenv: ref (option t) = ref None; - - -/** Load a type environment from a file */ -let load_from_file (filename: DB.filename) :option t => - if (DB.equal_filename filename DB.global_tenv_fname) { - if (is_none !global_tenv) { - global_tenv := Serialization.read_from_file tenv_serializer DB.global_tenv_fname - }; - !global_tenv - } else { - Serialization.read_from_file tenv_serializer filename - }; - - -/** Save a type environment into a file */ -let store_to_file (filename: DB.filename) (tenv: t) => { - /* 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 */ - if (DB.equal_filename filename DB.global_tenv_fname) { - global_tenv := Some tenv - }; - Serialization.write_to_file tenv_serializer filename data::tenv; - if Config.debug_mode { - let debug_filename = DB.filename_to_string (DB.filename_add_suffix filename ".debug"); - let out_channel = Out_channel.create debug_filename; - let fmt = Format.formatter_of_out_channel out_channel; - Format.fprintf fmt "%a" pp tenv; - Out_channel.close out_channel - } -}; - -let iter f tenv => TypenameHash.iter f tenv; - -let fold f tenv => TypenameHash.fold f tenv; diff --git a/infer/src/IR/Tenv.rei b/infer/src/IR/Tenv.rei deleted file mode 100644 index 3e8f0e73e..000000000 --- a/infer/src/IR/Tenv.rei +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Module for Type Environments. */ -type t; /** Type for type environment. */ - - -/** Add a (name,typename) pair to the global type environment. */ -let add: t => Typ.Name.t => Typ.Struct.t => unit; - - -/** Create a new type environment. */ -let create: unit => t; - - -/** Fold a function over the elements of the type environment. */ -let fold: (Typ.Name.t => Typ.Struct.t => 'a => 'a) => t => 'a => 'a; - - -/** iterate over a type environment */ -let iter: (Typ.Name.t => Typ.Struct.t => unit) => t => unit; - - -/** Load a type environment from a file */ -let load_from_file: DB.filename => option t; - - -/** Look up a name in the global type environment. */ -let lookup: t => Typ.Name.t => option Typ.Struct.t; - - -/** Construct a struct_typ, normalizing field types */ -let mk_struct: - t => - default::Typ.Struct.t? => - fields::Typ.Struct.fields? => - statics::Typ.Struct.fields? => - methods::list Typ.Procname.t? => - supers::list Typ.Name.t? => - annots::Annot.Item.t? => - Typ.Name.t => - Typ.Struct.t; - - -/** Check if typename is found in t */ -let mem: t => Typ.Name.t => bool; - - -/** print a type environment */ -let pp: Format.formatter => t => unit; - - -/** Save a type environment into a file */ -let store_to_file: DB.filename => t => unit; - - -/** Get method that is being overriden by java_pname (if any) **/ -let get_overriden_method: t => Typ.Procname.java => option Typ.Procname.t; diff --git a/infer/src/IR/Typ.ml b/infer/src/IR/Typ.ml new file mode 100644 index 000000000..46ac1ed3d --- /dev/null +++ b/infer/src/IR/Typ.ml @@ -0,0 +1,1278 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Types *) +open! IStd +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +(** Kinds of integers *) +type ikind = + | IChar (** [char] *) + | ISChar (** [signed char] *) + | IUChar (** [unsigned char] *) + | IBool (** [bool] *) + | IInt (** [int] *) + | IUInt (** [unsigned int] *) + | IShort (** [short] *) + | IUShort (** [unsigned short] *) + | ILong (** [long] *) + | IULong (** [unsigned long] *) + | ILongLong (** [long long] (or [_int64] on Microsoft Visual C) *) + | IULongLong (** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) *) + | I128 (** [__int128_t] *) + | IU128 (** [__uint128_t] *) + [@@deriving compare] + +let ikind_to_string = function + | IChar + -> "char" + | ISChar + -> "signed char" + | IUChar + -> "unsigned char" + | IBool + -> "_Bool" + | IInt + -> "int" + | IUInt + -> "unsigned int" + | IShort + -> "short" + | IUShort + -> "unsigned short" + | ILong + -> "long" + | IULong + -> "unsigned long" + | ILongLong + -> "long long" + | IULongLong + -> "unsigned long long" + | I128 + -> "__int128_t" + | IU128 + -> "__uint128_t" + +let ikind_is_char = function IChar | ISChar | IUChar -> true | _ -> false + +let ikind_is_unsigned = function + | IUChar | IUInt | IUShort | IULong | IULongLong + -> true + | _ + -> false + +let int_of_int64_kind i ik = IntLit.of_int64_unsigned i (ikind_is_unsigned ik) + +(** Kinds of floating-point numbers *) +type fkind = + | FFloat (** [float] *) + | FDouble (** [double] *) + | FLongDouble (** [long double] *) + [@@deriving compare] + +let fkind_to_string = function + | FFloat + -> "float" + | FDouble + -> "double" + | FLongDouble + -> "long double" + +(** kind of pointer *) +type ptr_kind = + | Pk_pointer (** C/C++, Java, Objc standard/__strong pointer *) + | Pk_reference (** C++ reference *) + | Pk_objc_weak (** Obj-C __weak pointer *) + | Pk_objc_unsafe_unretained (** Obj-C __unsafe_unretained pointer *) + | Pk_objc_autoreleasing (** Obj-C __autoreleasing pointer *) + [@@deriving compare] + +let equal_ptr_kind = [%compare.equal : ptr_kind] + +let ptr_kind_string = function + | Pk_reference + -> "&" + | Pk_pointer + -> "*" + | Pk_objc_weak + -> "__weak *" + | Pk_objc_unsafe_unretained + -> "__unsafe_unretained *" + | Pk_objc_autoreleasing + -> "__autoreleasing *" + +module T = struct + type type_quals = {is_const: bool; is_restrict: bool; is_volatile: bool} [@@deriving compare] + + (** types for sil (structured) expressions *) + type t = {desc: desc; quals: type_quals} [@@deriving compare] + + and desc = + | Tint of ikind (** integer type *) + | Tfloat of fkind (** float type *) + | Tvoid (** void type *) + | Tfun of bool (** function type with noreturn attribute *) + | Tptr of t * ptr_kind (** pointer type *) + | Tstruct of name (** structured value type name *) + | TVar of string (** type variable (ie. C++ template variables) *) + | Tarray of + t + * IntLit.t option + * (** array type with statically fixed length and stride *) IntLit.t option + [@@deriving compare] + + and name = + | CStruct of QualifiedCppName.t + | CUnion of QualifiedCppName.t + | CppClass of QualifiedCppName.t * template_spec_info + | JavaClass of Mangled.t + | ObjcClass of QualifiedCppName.t + | ObjcProtocol of QualifiedCppName.t + [@@deriving compare] + + and template_spec_info = NoTemplate | Template of t option list [@@deriving compare] + + let equal_desc = [%compare.equal : desc] + + let equal_quals = [%compare.equal : type_quals] + + let equal = [%compare.equal : t] + + let hash = Hashtbl.hash +end + +include T + +let mk_type_quals ?default ?is_const ?is_restrict ?is_volatile () = + let default_ = {is_const= false; is_restrict= false; is_volatile= false} in + let mk_aux ?(default= default_) ?(is_const= default.is_const) ?(is_restrict= default.is_restrict) + ?(is_volatile= default.is_volatile) () = + {is_const; is_restrict; is_volatile} + in + mk_aux ?default ?is_const ?is_restrict ?is_volatile () + +let is_const {is_const} = is_const + +let is_restrict {is_restrict} = is_restrict + +let is_volatile {is_volatile} = is_volatile + +let mk ?default ?quals desc : t = + let default_ = {desc; quals= mk_type_quals ()} in + let mk_aux ?(default= default_) ?(quals= default.quals) desc = {desc; quals} in + mk_aux ?default ?quals desc + +let merge_quals quals1 quals2 = + { is_const= quals1.is_const || quals2.is_const + ; is_restrict= quals1.is_restrict || quals2.is_restrict + ; is_volatile= quals1.is_volatile || quals2.is_volatile } + +let escape pe = if Pp.equal_print_kind pe.Pp.kind Pp.HTML then Escape.escape_xml else ident + +(** Pretty print a type with all the details, using the C syntax. *) +let rec pp_full pe f typ = + let pp_quals f {quals} = + if is_const quals then F.fprintf f " const " ; + if is_restrict quals then F.fprintf f " __restrict " ; + if is_volatile quals then F.fprintf f " volatile " + in + let pp_desc f {desc} = + match desc with + | Tstruct tname + -> F.fprintf f "%a" (pp_name_c_syntax pe) tname + | TVar name + -> F.fprintf f "%s" name + | Tint ik + -> F.fprintf f "%s" (ikind_to_string ik) + | Tfloat fk + -> F.fprintf f "%s" (fkind_to_string fk) + | Tvoid + -> F.fprintf f "void" + | Tfun false + -> F.fprintf f "_fn_" + | Tfun true + -> F.fprintf f "_fn_noreturn_" + | Tptr (({desc= Tarray _ | Tfun _} as typ), pk) + -> F.fprintf f "%a(%s)" (pp_full pe) typ (ptr_kind_string pk |> escape pe) + | Tptr (typ, pk) + -> F.fprintf f "%a%s" (pp_full pe) typ (ptr_kind_string pk |> escape pe) + | Tarray (typ, static_len, static_stride) + -> let pp_int_opt fmt = function Some x -> IntLit.pp fmt x | None -> F.fprintf fmt "_" in + F.fprintf f "%a[%a*%a]" (pp_full pe) typ pp_int_opt static_len pp_int_opt static_stride + in + F.fprintf f "%a%a" pp_desc typ pp_quals typ + +and pp_name_c_syntax pe f = function + | CStruct name | CUnion name | ObjcClass name | ObjcProtocol name + -> F.fprintf f "%a" QualifiedCppName.pp name + | CppClass (name, template_spec) + -> F.fprintf f "%a%a" QualifiedCppName.pp name (pp_template_spec_info pe) template_spec + | JavaClass name + -> F.fprintf f "%a" Mangled.pp name + +and pp_template_spec_info pe f = function + | NoTemplate + -> () + | Template args + -> let pp_arg_opt f = function + | Some typ + -> F.fprintf f "%a" (pp_full pe) typ + | None + -> F.fprintf f "_" + in + F.fprintf f "%s%a%s" (escape pe "<") (Pp.comma_seq pp_arg_opt) args (escape pe ">") + +(** Pretty print a type. Do nothing by default. *) +let pp pe f te = if Config.print_types then pp_full pe f te else () + +let to_string typ = + let pp fmt = pp_full Pp.text fmt typ in + F.asprintf "%t" pp + +type type_subst_t = (string * t) list [@@deriving compare] + +let is_type_subst_empty = List.is_empty + +(** Given the template type mapping and the type, substitute tvars within the type. *) +let rec sub_type subst generic_typ : t = + match generic_typ.desc with + | TVar tname -> ( + match List.Assoc.find subst ~equal:String.equal tname with + | Some t + -> (* Type qualifiers may come from original type or be part of substitution. Merge them *) + mk ~quals:(merge_quals t.quals generic_typ.quals) t.desc + | None + -> generic_typ ) + | Tarray (typ, arg1, arg2) + -> let typ' = sub_type subst typ in + if phys_equal typ typ' then generic_typ + else mk ~default:generic_typ (Tarray (typ', arg1, arg2)) + | Tptr (typ, arg) + -> let typ' = sub_type subst typ in + if phys_equal typ typ' then generic_typ else mk ~default:generic_typ (Tptr (typ', arg)) + | Tstruct tname + -> let tname' = sub_tname subst tname in + if phys_equal tname tname' then generic_typ else mk ~default:generic_typ (Tstruct tname') + | _ + -> generic_typ + +and sub_tname subst tname = + match tname with + | CppClass (name, Template spec_info) + -> let sub_typ_opt typ_opt = + match typ_opt with + | Some typ + -> let typ' = sub_type subst typ in + if phys_equal typ typ' then typ_opt else Some typ' + | None + -> typ_opt + in + let spec_info' = IList.map_changed sub_typ_opt spec_info in + if phys_equal spec_info spec_info' then tname else CppClass (name, Template spec_info') + | _ + -> tname + +module Name = struct + type t = name [@@deriving compare] + + let equal = [%compare.equal : t] + + let qual_name = function + | CStruct name | CUnion name | ObjcClass name | ObjcProtocol name + -> name + | CppClass (name, templ_args) + -> let template_suffix = F.asprintf "%a" (pp_template_spec_info Pp.text) templ_args in + QualifiedCppName.append_template_args_to_last name ~args:template_suffix + | JavaClass _ + -> QualifiedCppName.empty + + let unqualified_name = function + | CStruct name | CUnion name | ObjcClass name | ObjcProtocol name + -> name + | CppClass (name, _) + -> name + | JavaClass _ + -> QualifiedCppName.empty + + let name n = + match n with + | CStruct _ | CUnion _ | CppClass (_, _) | ObjcClass _ | ObjcProtocol _ + -> qual_name n |> QualifiedCppName.to_qual_string + | JavaClass name + -> Mangled.to_string name + + let pp fmt tname = + let prefix = function + | CStruct _ + -> "struct" + | CUnion _ + -> "union" + | CppClass (_, _) | JavaClass _ | ObjcClass _ + -> "class" + | ObjcProtocol _ + -> "protocol" + in + F.fprintf fmt "%s %a" (prefix tname) (pp_name_c_syntax Pp.text) tname + + let to_string = F.asprintf "%a" pp + + let is_class = function CppClass (_, _) | JavaClass _ | ObjcClass _ -> true | _ -> false + + let is_same_type t1 t2 = + match (t1, t2) with + | CStruct _, CStruct _ + | CUnion _, CUnion _ + | CppClass (_, _), CppClass (_, _) + | JavaClass _, JavaClass _ + | ObjcClass _, ObjcClass _ + | ObjcProtocol _, ObjcProtocol _ + -> true + | _ + -> false + + module C = struct + let from_qual_name qual_name = CStruct qual_name + + let from_string name_str = QualifiedCppName.of_qual_string name_str |> from_qual_name + + let union_from_qual_name qual_name = CUnion qual_name + end + + module Java = struct + let from_string name_str = JavaClass (Mangled.from_string name_str) + + let from_package_class package_name class_name = + if String.equal package_name "" then from_string class_name + else from_string (package_name ^ "." ^ class_name) + + let is_class = function JavaClass _ -> true | _ -> false + + let java_lang_object = from_string "java.lang.Object" + + let java_io_serializable = from_string "java.io.Serializable" + + let java_lang_cloneable = from_string "java.lang.Cloneable" + end + + module Cpp = struct + let from_qual_name template_spec_info qual_name = CppClass (qual_name, template_spec_info) + + let is_class = function CppClass _ -> true | _ -> false + end + + module Objc = struct + let from_qual_name qual_name = ObjcClass qual_name + + let from_string name_str = QualifiedCppName.of_qual_string name_str |> from_qual_name + + let protocol_from_qual_name qual_name = ObjcProtocol qual_name + + let is_class = function ObjcClass _ -> true | _ -> false + end + + module Set = Caml.Set.Make (struct + type nonrec t = t + + let compare = compare + end) +end + +(** {2 Sets and maps of types} *) +module Set = Caml.Set.Make (T) +module Map = Caml.Map.Make (T) +module Tbl = Hashtbl.Make (T) + +(** dump a type with all the details. *) +let d_full (t: t) = L.add_print_action (L.PTtyp_full, Obj.repr t) + +(** dump a list of types. *) +let d_list (tl: t list) = L.add_print_action (L.PTtyp_list, Obj.repr tl) + +let name typ = match typ.desc with Tstruct name -> Some name | _ -> None + +let unsome s = function + | Some default_typ + -> default_typ + | None + -> L.internal_error "No default typ in %s@." s ; + assert false + +(** turn a *T into a T. fails if [typ] is not a pointer type *) +let strip_ptr typ = match typ.desc with Tptr (t, _) -> t | _ -> assert false + +(** If an array type, return the type of the element. + If not, return the default type if given, otherwise raise an exception *) +let array_elem default_opt typ = + match typ.desc with Tarray (t_el, _, _) -> t_el | _ -> unsome "array_elem" default_opt + +let is_class_of_kind check_fun typ = + match typ.desc with Tstruct tname -> check_fun tname | _ -> false + +let is_objc_class = is_class_of_kind Name.Objc.is_class + +let is_cpp_class = is_class_of_kind Name.Cpp.is_class + +let is_java_class = is_class_of_kind Name.Java.is_class + +let rec is_array_of_cpp_class typ = + match typ.desc with Tarray (typ, _, _) -> is_array_of_cpp_class typ | _ -> is_cpp_class typ + +let is_pointer_to_cpp_class typ = match typ.desc with Tptr (t, _) -> is_cpp_class t | _ -> false + +let has_block_prefix s = + match Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) s with + | _ :: _ :: _ + -> true + | _ + -> false + +(** Check if type is a type for a block in objc *) +let is_block_type typ = has_block_prefix (to_string typ) + +(** Java types by name *) +let rec java_from_string : string -> t = function + | "" | "void" + -> mk Tvoid + | "int" + -> mk (Tint IInt) + | "byte" + -> mk (Tint IShort) + | "short" + -> mk (Tint IShort) + | "boolean" + -> mk (Tint IBool) + | "char" + -> mk (Tint IChar) + | "long" + -> mk (Tint ILong) + | "float" + -> mk (Tfloat FFloat) + | "double" + -> mk (Tfloat FDouble) + | typ_str when String.contains typ_str '[' + -> let stripped_typ = String.sub typ_str ~pos:0 ~len:(String.length typ_str - 2) in + mk (Tptr (mk (Tarray (java_from_string stripped_typ, None, None)), Pk_pointer)) + | typ_str + -> mk (Tstruct (Name.Java.from_string typ_str)) + +type typ = t + +module Procname = struct + (* e.g. ("", "int") for primitive types or ("java.io", "PrintWriter") for objects *) + type java_type = string option * string + + (* compare in inverse order *) + let compare_java_type (p1, c1) (p2, c2) = [%compare : string * string option] (c1, p1) (c2, p2) + + type method_kind = + | Non_Static + (* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface *) + | Static + (* in Java, procedures called with invokestatic *) + [@@deriving compare] + + let equal_method_kind = [%compare.equal : method_kind] + + (** Type of java procedure names. *) + type java = + { method_name: string + ; parameters: java_type list + ; class_name: Name.t + ; return_type: java_type option + ; (* option because constructors have no return type *) + kind: method_kind } + [@@deriving compare] + + (** Type of c procedure names. *) + type c = + { name: QualifiedCppName.t + ; mangled: string option + ; template_args: template_spec_info + ; is_generic_model: bool } + [@@deriving compare] + + type objc_cpp_method_kind = + | CPPMethod of (** with mangling *) string option + | CPPConstructor of (string option * bool) (** with mangling + is it constexpr? *) + | ObjCClassMethod + | ObjCInstanceMethod + | ObjCInternalMethod + [@@deriving compare] + + (** Type of Objective C and C++ procedure names: method signatures. *) + type objc_cpp = + { method_name: string + ; class_name: Name.t + ; kind: objc_cpp_method_kind + ; template_args: template_spec_info + ; is_generic_model: bool } + [@@deriving compare] + + (** Type of Objective C block names. *) + type block = string [@@deriving compare] + + (** Type of procedure names. *) + type t = + | Java of java + | C of c + | Linters_dummy_method + | Block of block + | ObjC_Cpp of objc_cpp + [@@deriving compare] + + let equal = [%compare.equal : t] + + (** Level of verbosity of some to_string functions. *) + type detail_level = Verbose | Non_verbose | Simple [@@deriving compare] + + let equal_detail_level = [%compare.equal : detail_level] + + let objc_method_kind_of_bool is_instance = + if is_instance then ObjCInstanceMethod else ObjCClassMethod + + let empty_block = Block "" + + let is_verbose v = match v with Verbose -> true | _ -> false + + (** A type is a pair (package, type_name) that is translated in a string package.type_name *) + let java_type_to_string_verbosity p verbosity = + match p with + | None, typ + -> typ + | Some p, cls + -> if is_verbose verbosity then p ^ "." ^ cls else cls + + let java_type_to_string p = java_type_to_string_verbosity p Verbose + + (** Given a list of types, it creates a unique string of types separated by commas *) + let rec java_param_list_to_string inputList verbosity = + match inputList with + | [] + -> "" + | [head] + -> java_type_to_string_verbosity head verbosity + | head :: rest + -> java_type_to_string_verbosity head verbosity ^ "," + ^ java_param_list_to_string rest verbosity + + (** It is the same as java_type_to_string, but Java return types are optional because of constructors without type *) + let java_return_type_to_string j verbosity = + match j.return_type with None -> "" | Some typ -> java_type_to_string_verbosity typ verbosity + + (** Given a package.class_name string, it looks for the latest dot and split the string + in two (package, class_name) *) + let split_classname package_classname = + match String.rsplit2 package_classname ~on:'.' with + | Some (x, y) + -> (Some x, y) + | None + -> (None, package_classname) + + let split_typename typename = split_classname (Name.name typename) + + let c name mangled template_args ~is_generic_model = + {name; mangled= Some mangled; template_args; is_generic_model} + + let from_string_c_fun (name: string) = + C + { name= QualifiedCppName.of_qual_string name + ; mangled= None + ; template_args= NoTemplate + ; is_generic_model= false } + + let java class_name return_type method_name parameters kind = + {class_name; return_type; method_name; parameters; kind} + + (** Create an objc procedure name from a class_name and method_name. *) + let objc_cpp class_name method_name kind template_args ~is_generic_model = + {class_name; method_name; kind; template_args; is_generic_model} + + let get_default_objc_class_method objc_class = + let objc_cpp = + objc_cpp objc_class "__find_class_" ObjCInternalMethod NoTemplate ~is_generic_model:false + in + ObjC_Cpp objc_cpp + + (** Create an objc procedure name from a class_name and method_name. *) + let mangled_objc_block name = Block name + + let is_java = function Java _ -> true | _ -> false + + let is_c_method = function ObjC_Cpp _ -> true | _ -> false + + let is_constexpr = function ObjC_Cpp {kind= CPPConstructor (_, true)} -> true | _ -> false + + (** Replace the class name component of a procedure name. + In case of Java, replace package and class name. *) + let replace_class t (new_class: Name.t) = + match t with + | Java j + -> Java {j with class_name= new_class} + | ObjC_Cpp osig + -> ObjC_Cpp {osig with class_name= new_class} + | C _ | Block _ | Linters_dummy_method + -> t + + (** Get the class name of a Objective-C/C++ procedure name. *) + let objc_cpp_get_class_name objc_cpp = Name.name objc_cpp.class_name + + let objc_cpp_get_class_type_name objc_cpp = objc_cpp.class_name + + (** Return the package.classname of a java procname. *) + let java_get_class_name (j: java) = Name.name j.class_name + + (** Return the package.classname as a typename of a java procname. *) + let java_get_class_type_name (j: java) = j.class_name + + (** Return the class name of a java procedure name. *) + let java_get_simple_class_name (j: java) = snd (split_classname (java_get_class_name j)) + + (** Return the package of a java procname. *) + let java_get_package (j: java) = fst (split_classname (java_get_class_name j)) + + (** Return the method of a java procname. *) + let java_get_method (j: java) = j.method_name + + (** Replace the method of a java procname. *) + let java_replace_method (j: java) mname = {j with method_name= mname} + + (** Replace the return type of a java procname. *) + let java_replace_return_type j ret_type = {j with return_type= Some ret_type} + + (** Replace the parameters of a java procname. *) + let java_replace_parameters j parameters = {j with parameters} + + (** Return the method/function of a procname. *) + let get_method = function + | ObjC_Cpp name + -> name.method_name + | C {name} + -> QualifiedCppName.to_qual_string name + | Block name + -> name + | Java j + -> j.method_name + | Linters_dummy_method + -> "Linters_dummy_method" + + (** Return the language of the procedure. *) + let get_language = function + | ObjC_Cpp _ + -> Config.Clang + | C _ + -> Config.Clang + | Block _ + -> Config.Clang + | Linters_dummy_method + -> Config.Clang + | Java _ + -> Config.Java + + (** Return the return type of a java procname. *) + let java_get_return_type (j: java) = java_return_type_to_string j Verbose + + (** Return the parameters of a java procname. *) + let java_get_parameters j = j.parameters + + (** Return the parameters of a java procname as strings. *) + let java_get_parameters_as_strings j = + List.map ~f:(fun param -> java_type_to_string param) j.parameters + + (** Return true if the java procedure is static *) + let java_is_static = function Java j -> equal_method_kind j.kind Static | _ -> false + + let java_is_lambda = function + | Java j + -> String.is_prefix ~prefix:"lambda$" j.method_name + | _ + -> false + + let java_is_generated = function + | Java j + -> String.is_prefix ~prefix:"$" j.method_name + | _ + -> false + + (** Prints a string of a java procname with the given level of verbosity *) + let java_to_string ?(withclass= false) (j: java) verbosity = + match verbosity with + | Verbose | Non_verbose + -> (* if verbose, then package.class.method(params): rtype, + else rtype package.class.method(params) + verbose is used for example to create unique filenames, non_verbose to create reports *) + let return_type = java_return_type_to_string j verbosity in + let params = java_param_list_to_string j.parameters verbosity in + let class_name = java_type_to_string_verbosity (split_typename j.class_name) verbosity in + let separator = + match (j.return_type, verbosity) with None, _ -> "" | Some _, Verbose -> ":" | _ -> " " + in + let output = class_name ^ "." ^ j.method_name ^ "(" ^ params ^ ")" in + if equal_detail_level verbosity Verbose then output ^ separator ^ return_type + else return_type ^ separator ^ output + | Simple + -> (* methodname(...) or without ... if there are no parameters *) + let cls_prefix = + if withclass then java_type_to_string_verbosity (split_typename j.class_name) verbosity + ^ "." + else "" + in + let params = match j.parameters with [] -> "" | _ -> "..." in + let method_name = + if String.equal j.method_name "" then java_get_simple_class_name j + else cls_prefix ^ j.method_name + in + method_name ^ "(" ^ params ^ ")" + + (** Check if the class name is for an anonymous inner class. *) + let is_anonymous_inner_class_name class_name = + let class_name_no_package = snd (split_typename class_name) in + match String.rsplit2 class_name_no_package ~on:'$' with + | Some (_, s) + -> let is_int = + try + ignore (int_of_string (String.strip s)) ; + true + with Failure _ -> false + in + is_int + | None + -> false + + (** Check if the procedure belongs to an anonymous inner class. *) + let java_is_anonymous_inner_class = function + | Java j + -> is_anonymous_inner_class_name j.class_name + | _ + -> false + + (** Check if the last parameter is a hidden inner class, and remove it if present. + This is used in private constructors, where a proxy constructor is generated + with an extra parameter and calls the normal constructor. *) + let java_remove_hidden_inner_class_parameter = function + | Java js -> ( + match List.rev js.parameters with + | (_, s) :: par' + -> if is_anonymous_inner_class_name (Name.Java.from_string s) then + Some (Java {js with parameters= List.rev par'}) + else None + | [] + -> None ) + | _ + -> None + + (** Check if the procedure name is an anonymous inner class constructor. *) + let java_is_anonymous_inner_class_constructor = function + | Java js + -> is_anonymous_inner_class_name js.class_name + | _ + -> false + + (** Check if the procedure name is an acess method (e.g. access$100 used to + access private members from a nested class. *) + let java_is_access_method = function + | Java js -> ( + match String.rsplit2 js.method_name ~on:'$' with + | Some ("access", s) + -> let is_int = + try + ignore (int_of_string s) ; + true + with Failure _ -> false + in + is_int + | _ + -> false ) + | _ + -> false + + (** Check if the procedure name is of an auto-generated method containing '$'. *) + let java_is_autogen_method = function + | Java js + -> String.contains js.method_name '$' + | _ + -> false + + (** Check if the proc name has the type of a java vararg. + Note: currently only checks that the last argument has type Object[]. *) + let java_is_vararg = function + | Java js -> ( + match List.rev js.parameters with (_, "java.lang.Object[]") :: _ -> true | _ -> false ) + | _ + -> false + + let is_objc_constructor method_name = + String.equal method_name "new" || String.is_prefix ~prefix:"init" method_name + + let is_objc_kind = function + | ObjCClassMethod | ObjCInstanceMethod | ObjCInternalMethod + -> true + | _ + -> false + + (** [is_constructor pname] returns true if [pname] is a constructor *) + let is_constructor = function + | Java js + -> String.equal js.method_name "" + | ObjC_Cpp {kind= CPPConstructor _} + -> true + | ObjC_Cpp {kind; method_name} when is_objc_kind kind + -> is_objc_constructor method_name + | _ + -> false + + let is_objc_dealloc method_name = String.equal method_name "dealloc" + + (** [is_dealloc pname] returns true if [pname] is the dealloc method in Objective-C + TODO: add case for C++ *) + let is_destructor = function ObjC_Cpp name -> is_objc_dealloc name.method_name | _ -> false + + let java_is_close = function Java js -> String.equal js.method_name "close" | _ -> false + + (** [is_class_initializer pname] returns true if [pname] is a class initializer *) + let is_class_initializer = function + | Java js + -> String.equal js.method_name "" + | _ + -> false + + (** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc *) + let is_infer_undefined pn = + match pn with + | Java j + -> let regexp = Str.regexp "com.facebook.infer.builtins.InferUndefined" in + Str.string_match regexp (java_get_class_name j) 0 + | _ + -> (* TODO: add cases for obj-c, c, c++ *) + false + + let get_global_name_of_initializer = function + | C {name} + when String.is_prefix ~prefix:Config.clang_initializer_prefix + (QualifiedCppName.to_qual_string name) + -> let name_str = QualifiedCppName.to_qual_string name in + let prefix_len = String.length Config.clang_initializer_prefix in + Some (String.sub name_str ~pos:prefix_len ~len:(String.length name_str - prefix_len)) + | _ + -> None + + (** to_string for C_function type *) + let to_readable_string (c1, c2) verbose = + let plain = QualifiedCppName.to_qual_string c1 in + if verbose then match c2 with None -> plain | Some s -> plain ^ "{" ^ s ^ "}" else plain + + let c_method_kind_verbose_str kind = + match kind with + | CPPMethod m + -> "(" ^ (match m with None -> "" | Some s -> s) ^ ")" + | CPPConstructor (m, is_constexpr) + -> "{" ^ (match m with None -> "" | Some s -> s) ^ (if is_constexpr then "|constexpr" else "") + ^ "}" + | ObjCClassMethod + -> "class" + | ObjCInstanceMethod + -> "instance" + | ObjCInternalMethod + -> "internal" + + let c_method_to_string osig detail_level = + match detail_level with + | Simple + -> osig.method_name + | Non_verbose + -> Name.name osig.class_name ^ "_" ^ osig.method_name + | Verbose + -> let m_str = c_method_kind_verbose_str osig.kind in + Name.name osig.class_name ^ "_" ^ osig.method_name ^ m_str + + (** Very verbose representation of an existing Procname.t *) + let to_unique_id pn = + match pn with + | Java j + -> java_to_string j Verbose + | C {name; mangled} + -> to_readable_string (name, mangled) true + | ObjC_Cpp osig + -> c_method_to_string osig Verbose + | Block name + -> name + | Linters_dummy_method + -> "Linters_dummy_method" + + (** Convert a proc name to a string for the user to see *) + let to_string p = + match p with + | Java j + -> java_to_string j Non_verbose + | C {name; mangled} + -> to_readable_string (name, mangled) false + | ObjC_Cpp osig + -> c_method_to_string osig Non_verbose + | Block name + -> name + | Linters_dummy_method + -> to_unique_id p + + (** Convenient representation of a procname for external tools (e.g. eclipse plugin) *) + let to_simplified_string ?(withclass= false) p = + match p with + | Java j + -> java_to_string ~withclass j Simple + | C {name; mangled} + -> to_readable_string (name, mangled) false ^ "()" + | ObjC_Cpp osig + -> c_method_to_string osig Simple + | Block _ + -> "block" + | Linters_dummy_method + -> to_unique_id p + + (** Pretty print a proc name *) + let pp f pn = F.fprintf f "%s" (to_string pn) + + (** hash function for procname *) + let hash_pname = Hashtbl.hash + + module Hash = Hashtbl.Make (struct + type nonrec t = t + + let equal = equal + + let hash = hash_pname + end) + + module Map = PrettyPrintable.MakePPMap (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) + + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) + + (** Pretty print a set of proc names *) + let pp_set fmt set = Set.iter (fun pname -> F.fprintf fmt "%a " pp pname) set + + let objc_cpp_get_class_qualifiers objc_cpp = Name.qual_name objc_cpp.class_name + + let get_qualifiers pname = + match pname with + | C {name} + -> name + | ObjC_Cpp objc_cpp + -> objc_cpp_get_class_qualifiers objc_cpp + |> QualifiedCppName.append_qualifier ~qual:objc_cpp.method_name + | _ + -> QualifiedCppName.empty + + (** Convert a proc name to a filename *) + let to_concrete_filename pname = + (* filenames for clang procs are REVERSED qualifiers with '#' as separator *) + let get_qual_name_str pname = + get_qualifiers pname |> QualifiedCppName.to_rev_list |> String.concat ~sep:"#" + in + let proc_id = + match pname with + | C {mangled} + -> get_qual_name_str pname :: Option.to_list mangled |> String.concat ~sep:"#" + | ObjC_Cpp objc_cpp + -> get_qual_name_str pname ^ "#" ^ c_method_kind_verbose_str objc_cpp.kind + | _ + -> to_unique_id pname + in + Escape.escape_filename @@ DB.append_crc_cutoff proc_id + + let to_generic_filename pname = + let proc_id = + get_qualifiers pname |> QualifiedCppName.strip_template_args |> QualifiedCppName.to_rev_list + |> String.concat ~sep:"#" + in + Escape.escape_filename @@ DB.append_crc_cutoff proc_id + + let to_filename pname = + match pname with + | (C {is_generic_model} | ObjC_Cpp {is_generic_model}) when Bool.equal is_generic_model true + -> to_generic_filename pname + | _ + -> to_concrete_filename pname + + let get_template_args_mapping generic_procname concrete_procname = + (** given two template arguments, try to generate mapping from generic ones to concrete ones. *) + let mapping_for_template_args (generic_name, generic_args) (concrete_name, concrete_args) = + match (generic_args, concrete_args) with + | Template generic_typs, Template concrete_typs + when QualifiedCppName.equal generic_name concrete_name -> ( + try + `Valid + (List.fold2_exn generic_typs concrete_typs ~init:[] ~f: + (fun (* result will be reversed list. Ordering in template mapping doesn't matter so it's ok *) + result + gtyp + ctyp + -> + match (gtyp, ctyp) with + | Some {desc= TVar name}, Some concrete + -> (name, concrete) :: result + | _ + -> result )) + with Invalid_argument _ -> + `Invalid (* fold2_exn throws on length mismatch, we need to handle it *) ) + | NoTemplate, NoTemplate + -> `NoTemplate + | _ + -> `Invalid + in + let combine_mappings mapping1 mapping2 = + match (mapping1, mapping2) with + | `Valid m1, `Valid m2 + -> `Valid (List.append m1 m2) + | `NoTemplate, a | a, `NoTemplate + -> a + (* no template is no-op state, simply return the other state *) | _ + -> `Invalid + (* otherwise there is no valid mapping *) + in + let extract_mapping = function `Invalid | `NoTemplate -> None | `Valid m -> Some m in + let empty_qual = + QualifiedCppName.of_qual_string "FIXME" + (* TODO we should look at procedure names *) + in + match (generic_procname, concrete_procname) with + | C {template_args= args1}, C {template_args= args2} (* template function *) + -> mapping_for_template_args (empty_qual, args1) (empty_qual, args2) |> extract_mapping + | ( ObjC_Cpp {template_args= args1; class_name= CppClass (name1, class_args1)} + , ObjC_Cpp {template_args= args2; class_name= CppClass (name2, class_args2)} + (* template methods/template classes/both *) ) + -> combine_mappings + (mapping_for_template_args (name1, class_args1) (name2, class_args2)) + (mapping_for_template_args (empty_qual, args1) (empty_qual, args2)) + |> extract_mapping + | _ + -> None +end + +(** Return the return type of [pname_java]. *) +let java_proc_return_typ pname_java : t = + let typ = java_from_string (Procname.java_get_return_type pname_java) in + match typ.desc with Tstruct _ -> mk (Tptr (typ, Pk_pointer)) | _ -> typ + +module Fieldname = struct + type clang_field_info = {class_name: Name.t; field_name: string} [@@deriving compare] + + type t = + | Hidden + (* Backend relies that Hidden is the smallest (first) field in Abs.should_raise_objc_leak *) + | Clang of clang_field_info + | Java of string + [@@deriving compare] + + let hidden_str = ".hidden" + + let equal = [%compare.equal : t] + + module T = struct + type nonrec t = t + + let compare = compare + end + + module Set = Caml.Set.Make (T) + module Map = Caml.Map.Make (T) + + module Clang = struct + let from_class_name class_name field_name = Clang {class_name; field_name} + end + + module Java = struct + let from_string n = Java n + end + + (** Convert a fieldname to a string. *) + let to_string = function + | Hidden + -> hidden_str + | Java fname + -> fname + | Clang {field_name} + -> field_name + + (** Convert a fieldname to a simplified string with at most one-level path. *) + let to_simplified_string fn = + let s = to_string fn in + match String.rsplit2 s ~on:'.' with + | Some (s1, s2) -> ( + match String.rsplit2 s1 ~on:'.' with Some (_, s4) -> s4 ^ "." ^ s2 | _ -> s ) + | _ + -> s + + let to_full_string fname = + match fname with + | Clang {class_name; field_name} + -> Name.to_string class_name ^ "::" ^ field_name + | _ + -> to_string fname + + (** Convert a fieldname to a flat string without path. *) + let to_flat_string fn = + let s = to_string fn in + match String.rsplit2 s ~on:'.' with Some (_, s2) -> s2 | _ -> s + + let pp f = function + | Hidden + -> Format.fprintf f "%s" hidden_str + | Java field_name | Clang {field_name} + -> Format.fprintf f "%s" field_name + + let pp_latex style f fn = Latex.pp_string style f (to_string fn) + + let class_name_replace fname ~f = + match fname with + | Clang {class_name; field_name} + -> let class_name' = f class_name in + if phys_equal class_name class_name' then fname + else Clang {class_name= class_name'; field_name} + | _ + -> fname + + (** Returns the class part of the fieldname *) + let java_get_class fn = + let fn = to_string fn in + let ri = String.rindex_exn fn '.' in + String.slice fn 0 ri + + (** Returns the last component of the fieldname *) + let java_get_field fn = + let fn = to_string fn in + let ri = 1 + String.rindex_exn fn '.' in + String.slice fn ri 0 + + (** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. *) + let java_is_outer_instance fn = + let fn = to_string fn in + let fn_len = String.length fn in + fn_len <> 0 + && + let this = ".this$" in + let last_char = fn.[fn_len - 1] in + (last_char >= '0' && last_char <= '9') + && String.is_suffix fn ~suffix:(this ^ String.of_char last_char) + + let clang_get_qual_class = function + | Clang {class_name} + -> Some (Name.qual_name class_name) + | _ + -> None + + (** hidded fieldname constant *) + let hidden = Hidden + + (** hidded fieldname constant *) + let is_hidden fn = equal fn hidden +end + +module Struct = struct + type field = Fieldname.t * T.t * Annot.Item.t [@@deriving compare] + + type fields = field list + + (** Type for a structured value. *) + type t = + { fields: fields (** non-static fields *) + ; statics: fields (** static fields *) + ; supers: Name.t list (** superclasses *) + ; methods: Procname.t list (** methods defined *) + ; annots: Annot.Item.t (** annotations *) } + + type lookup = Name.t -> t option + + let pp pe name f {fields; supers; methods; annots} = + if Config.debug_mode then + (* change false to true to print the details of struct *) + F.fprintf f + "%a @\n\tfields: {%a@\n\t}@\n\tsupers: {%a@\n\t}@\n\tmethods: {%a@\n\t}@\n\tannots: {%a@\n\t}" + Name.pp name + (Pp.seq (fun f (fld, t, a) -> + F.fprintf f "@\n\t\t%a %a %a" (pp_full pe) t Fieldname.pp fld Annot.Item.pp a )) + fields + (Pp.seq (fun f n -> F.fprintf f "@\n\t\t%a" Name.pp n)) + supers + (Pp.seq (fun f m -> F.fprintf f "@\n\t\t%a" Procname.pp m)) + methods Annot.Item.pp annots + else F.fprintf f "%a" Name.pp name + + let internal_mk_struct ?default ?fields ?statics ?methods ?supers ?annots () = + let default_ = {fields= []; statics= []; methods= []; supers= []; annots= Annot.Item.empty} in + let mk_struct_ ?(default= default_) ?(fields= default.fields) ?(statics= default.statics) + ?(methods= default.methods) ?(supers= default.supers) ?(annots= default.annots) () = + {fields; statics; methods; supers; annots} + in + mk_struct_ ?default ?fields ?statics ?methods ?supers ?annots () + + (** the element typ of the final extensible array in the given typ, if any *) + let rec get_extensible_array_element_typ ~lookup (typ: T.t) = + match typ.desc with + | Tarray (typ, _, _) + -> Some typ + | 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: T.t) = + 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 + + let get_field_type_and_annotation ~lookup fn (typ: T.t) = + match typ.desc with + | Tstruct name | Tptr ({desc= Tstruct name}, _) -> ( + match lookup name with + | Some {fields; statics} + -> List.find_map + ~f:(fun (f, t, a) -> + match Fieldname.equal f fn with true -> Some (t, a) | false -> None) + (fields @ statics) + | None + -> None ) + | _ + -> None + + let objc_ref_counter_annot = [({Annot.class_name= "ref_counter"; parameters= []}, false)] + + (** Field used for objective-c reference counting *) + let objc_ref_counter_field = (Fieldname.hidden, mk (T.Tint IInt), objc_ref_counter_annot) + + let is_objc_ref_counter_field (fld, _, a) = + Fieldname.is_hidden fld && Annot.Item.equal a objc_ref_counter_annot +end diff --git a/infer/src/IR/Typ.mli b/infer/src/IR/Typ.mli new file mode 100644 index 000000000..2f49c5922 --- /dev/null +++ b/infer/src/IR/Typ.mli @@ -0,0 +1,708 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Types *) +open! IStd +module F = Format + +(** Kinds of integers *) + +type ikind = + | IChar (** [char] *) + | ISChar (** [signed char] *) + | IUChar (** [unsigned char] *) + | IBool (** [bool] *) + | IInt (** [int] *) + | IUInt (** [unsigned int] *) + | IShort (** [short] *) + | IUShort (** [unsigned short] *) + | ILong (** [long] *) + | IULong (** [unsigned long] *) + | ILongLong (** [long long] (or [_int64] on Microsoft Visual C) *) + | IULongLong (** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) *) + | I128 (** [__int128_t] *) + | IU128 (** [__uint128_t] *) + [@@deriving compare] + +(** Check wheter the integer kind is a char *) + +val ikind_is_char : ikind -> bool + +(** Check wheter the integer kind is unsigned *) + +val ikind_is_unsigned : ikind -> bool + +(** Convert an int64 into an IntLit.t given the kind: + the int64 is interpreted as unsigned according to the kind *) + +val int_of_int64_kind : int64 -> ikind -> IntLit.t + +(** Kinds of floating-point numbers *) + +type fkind = + | FFloat (** [float] *) + | FDouble (** [double] *) + | FLongDouble (** [long double] *) + [@@deriving compare] + +(** kind of pointer *) + +type ptr_kind = + | Pk_pointer (** C/C++, Java, Objc standard/__strong pointer *) + | Pk_reference (** C++ reference *) + | Pk_objc_weak (** Obj-C __weak pointer *) + | Pk_objc_unsafe_unretained (** Obj-C __unsafe_unretained pointer *) + | Pk_objc_autoreleasing (** Obj-C __autoreleasing pointer *) + [@@deriving compare] + +val equal_ptr_kind : ptr_kind -> ptr_kind -> bool + +type type_quals [@@deriving compare] + +val mk_type_quals : + ?default:type_quals -> ?is_const:bool -> ?is_restrict:bool -> ?is_volatile:bool -> unit + -> type_quals + +val is_const : type_quals -> bool + +val is_restrict : type_quals -> bool + +val is_volatile : type_quals -> bool + +(** types for sil (structured) expressions *) + +type t = {desc: desc; quals: type_quals} [@@deriving compare] + +and desc = + | Tint of ikind (** integer type *) + | Tfloat of fkind (** float type *) + | Tvoid (** void type *) + | Tfun of bool (** function type with noreturn attribute *) + | Tptr of t * ptr_kind (** pointer type *) + | Tstruct of name (** structured value type name *) + | TVar of string (** type variable (ie. C++ template variables) *) + | Tarray of + t + * IntLit.t option + * (** array type with statically fixed stride and length *) IntLit.t option + [@@deriving compare] + +and name = + | CStruct of QualifiedCppName.t + | CUnion of QualifiedCppName.t + (* qualified name does NOT contain template arguments of the class. It will contain template + args of its parent classes, for example: MyClass::InnerClass will store + "MyClass", "InnerClass" *) + | CppClass of QualifiedCppName.t * template_spec_info + | JavaClass of Mangled.t + | ObjcClass of QualifiedCppName.t + | ObjcProtocol of QualifiedCppName.t + [@@deriving compare] + +and template_spec_info = NoTemplate | Template of t option list [@@deriving compare] + +(** Create Typ.t from given desc. if [default] is passed then use its value to set other fields such as quals *) + +val mk : ?default:t -> ?quals:type_quals -> desc -> t + +(** Stores information about type substitution *) + +type type_subst_t [@@deriving compare] + +module Name : sig + (** Named types. *) + + type t = name [@@deriving compare] + + (** Equality for typenames *) + + val equal : t -> t -> bool + + (** convert the typename to a string *) + + val to_string : t -> string + + val pp : Format.formatter -> t -> unit + + (** [is_class name] holds if [name] names CPP/Objc/Java class *) + + val is_class : t -> bool + + (** [is_class name1 name2] holds if [name1] and [name2] name same kind of type *) + + val is_same_type : t -> t -> bool + + (** name of the typename without qualifier *) + + val name : t -> string + + (** qualified name of the type, may return nonsense for Java classes *) + + val qual_name : t -> QualifiedCppName.t + + val unqualified_name : t -> QualifiedCppName.t + + module C : sig + val from_string : string -> t + + val from_qual_name : QualifiedCppName.t -> t + + val union_from_qual_name : QualifiedCppName.t -> t + end + + module Java : sig + (** Create a typename from a Java classname in the form "package.class" *) + + val from_string : string -> t + + (** Create a typename from a package name and a class name *) + + val from_package_class : string -> string -> t + + (** [is_class name] holds if [name] names a Java class *) + + val is_class : t -> bool + + val java_lang_object : t + + val java_io_serializable : t + + val java_lang_cloneable : t + end + + module Cpp : sig + (** Create a typename from a C++ classname *) + + val from_qual_name : template_spec_info -> QualifiedCppName.t -> t + + (** [is_class name] holds if [name] names a C++ class *) + + val is_class : t -> bool + end + + module Objc : sig + (** Create a typename from a Objc classname *) + + val from_string : string -> t + + val from_qual_name : QualifiedCppName.t -> t + + val protocol_from_qual_name : QualifiedCppName.t -> t + + (** [is_class name] holds if [name] names a Objc class *) + + val is_class : t -> bool + end + + module Set : Caml.Set.S with type elt = t +end + +(** Equality for types. *) + +val equal : t -> t -> bool + +val equal_desc : desc -> desc -> bool + +val equal_quals : type_quals -> type_quals -> bool + +val sub_type : type_subst_t -> t -> t + +val sub_tname : type_subst_t -> Name.t -> Name.t + +val is_type_subst_empty : type_subst_t -> bool + +(** Sets of types. *) + +module Set : Caml.Set.S with type elt = t + +(** Maps with type keys. *) + +module Map : Caml.Map.S with type key = t + +module Tbl : Caml.Hashtbl.S with type key = t + +(** Pretty print a type with all the details. *) + +val pp_full : Pp.env -> F.formatter -> t -> unit + +(** Pretty print a type. *) + +val pp : Pp.env -> F.formatter -> t -> unit + +val to_string : t -> string + +(** Dump a type with all the details. *) + +val d_full : t -> unit + +(** Dump a list of types. *) + +val d_list : t list -> unit + +(** The name of a type *) + +val name : t -> Name.t option + +(** turn a *T into a T. fails if [t] is not a pointer type *) + +val strip_ptr : t -> t + +(** If an array type, return the type of the element. + If not, return the default type if given, otherwise raise an exception *) + +val array_elem : t option -> t -> t + +val is_objc_class : t -> bool + +val is_cpp_class : t -> bool + +val is_java_class : t -> bool + +val is_array_of_cpp_class : t -> bool + +val is_pointer_to_cpp_class : t -> bool + +val has_block_prefix : string -> bool + +(** Check if type is a type for a block in objc *) + +val is_block_type : t -> bool + +val unsome : string -> t option -> t + +type typ = t + +module Procname : sig + (** Module for Procedure Names. *) + + (** Type of java procedure names. *) + + type java + + (** Type of c procedure names. *) + + type c + + (** Type of Objective C and C++ procedure names. *) + + type objc_cpp + + (** Type of Objective C block names. *) + + type block + + (** Type of procedure names. *) + + type t = + | Java of java + | C of c + | Linters_dummy_method + | Block of block + | ObjC_Cpp of objc_cpp + [@@deriving compare] + + (** Equality for proc names. *) + + val equal : t -> t -> bool + + type java_type = string option * string + + type method_kind = + | Non_Static + (* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface *) + | Static + + (* in Java, procedures called with invokestatic *) + + type objc_cpp_method_kind = + | CPPMethod of (** with mangling *) string option + | CPPConstructor of (string option * bool) (** with mangling + is it constexpr? *) + | ObjCClassMethod + | ObjCInstanceMethod + | ObjCInternalMethod + + (** Hash tables with proc names as keys. *) + + module Hash : Caml.Hashtbl.S with type key = t + + (** Maps from proc names. *) + + module Map : PrettyPrintable.PPMap with type key = t + + (** Sets of proc names. *) + + module Set : PrettyPrintable.PPSet with type elt = t + + (** Create a C procedure name from plain and mangled name. *) + + val c : QualifiedCppName.t -> string -> template_spec_info -> is_generic_model:bool -> c + + (** Empty block name. *) + + val empty_block : t + + (** Convert a string to a proc name. *) + + val from_string_c_fun : string -> t + + (** Return the language of the procedure. *) + + val get_language : t -> Config.language + + (** Return the method/function of a procname. *) + + val get_method : t -> string + + (** Hash function for procname. *) + + val hash_pname : t -> int + + (** Check if a class string is an anoynmous inner class name. *) + + val is_anonymous_inner_class_name : Name.t -> bool + + (** Check if this is an Objective-C/C++ method name. *) + + val is_c_method : t -> bool + + (** Check if this is a constructor method in Objective-C. *) + + val is_objc_constructor : string -> bool + + (** Check if this is a constructor. *) + + val is_constructor : t -> bool + + (** Check if this is a constexpr function. *) + + val is_constexpr : t -> bool + + (** Check if this is a Java procedure name. *) + + val is_java : t -> bool + + (** Check if this is a dealloc method in Objective-C. *) + + val is_objc_dealloc : string -> bool + + (** Check if this is a dealloc method. *) + + val is_destructor : t -> bool + + (** Create a Java procedure name from its + class_name method_name args_type_name return_type_name method_kind. *) + + val java : Name.t -> java_type option -> string -> java_type list -> method_kind -> java + + (** Replace the parameters of a java procname. *) + + val java_replace_parameters : java -> java_type list -> java + + (** Replace the method of a java procname. *) + + val java_replace_return_type : java -> java_type -> java + + (** Create an objc block name. *) + + val mangled_objc_block : string -> t + + (** Create an objc procedure name from a class_name and method_name. *) + + val objc_cpp : + Name.t -> string -> objc_cpp_method_kind -> template_spec_info -> is_generic_model:bool + -> objc_cpp + + val get_default_objc_class_method : Name.t -> t + + (** Get the class name of a Objective-C/C++ procedure name. *) + + val objc_cpp_get_class_name : objc_cpp -> string + + val objc_cpp_get_class_type_name : objc_cpp -> Name.t + + (** Create ObjC method type from a bool is_instance. *) + + val objc_method_kind_of_bool : bool -> objc_cpp_method_kind + + (** Return the class name of a java procedure name. *) + + val java_get_class_name : java -> string + + (** Return the class name as a typename of a java procedure name. *) + + val java_get_class_type_name : java -> Name.t + + (** Return the simple class name of a java procedure name. *) + + val java_get_simple_class_name : java -> string + + (** Return the package name of a java procedure name. *) + + val java_get_package : java -> string option + + (** Return the method name of a java procedure name. *) + + val java_get_method : java -> string + + (** Return the return type of a java procedure name. *) + + val java_get_return_type : java -> string + + (** Return the parameters of a java procedure name. *) + + val java_get_parameters : java -> java_type list + + (** Return the parameters of a java procname as strings. *) + + val java_get_parameters_as_strings : java -> string list + + (** Check if the procedure name is an acess method (e.g. access$100 used to + access private members from a nested class. *) + + val java_is_access_method : t -> bool + + (** Check if the procedure name is of an auto-generated method containing '$'. *) + + val java_is_autogen_method : t -> bool + + (** Check if the procedure belongs to an anonymous inner class. *) + + val java_is_anonymous_inner_class : t -> bool + + (** Check if the procedure name is an anonymous inner class constructor. *) + + val java_is_anonymous_inner_class_constructor : t -> bool + + (** Check if the method name is "close". *) + + val java_is_close : t -> bool + + (** Check if the java procedure is static. *) + + val java_is_static : t -> bool + + (** Check if the proc name has the type of a java vararg. + Note: currently only checks that the last argument has type Object[]. *) + + val java_is_vararg : t -> bool + + (** Check if the proc name comes from a lambda expression *) + + val java_is_lambda : t -> bool + + (** Check if the proc name comes from generated code *) + + val java_is_generated : t -> bool + + (** Check if the last parameter is a hidden inner class, and remove it if present. + This is used in private constructors, where a proxy constructor is generated + with an extra parameter and calls the normal constructor. *) + + val java_remove_hidden_inner_class_parameter : t -> t option + + (** Replace the method name of an existing java procname. *) + + val java_replace_method : java -> string -> java + + (** Convert a java type to a string. *) + + val java_type_to_string : java_type -> string + + (** Check if this is a class initializer. *) + + val is_class_initializer : t -> bool + + (** Check if this is a special Infer undefined procedure. *) + + val is_infer_undefined : t -> bool + + (** Return the name of the global for which this procedure is the initializer if this is an + initializer, None otherwise. *) + + val get_global_name_of_initializer : t -> string option + + (** Pretty print a proc name. *) + + val pp : Format.formatter -> t -> unit + + (** Pretty print a set of proc names. *) + + val pp_set : Format.formatter -> Set.t -> unit + + (** Replace the class name component of a procedure name. + In case of Java, replace package and class name. *) + + val replace_class : t -> Name.t -> t + + (** Given a package.class_name string, look for the latest dot and split the string + in two (package, class_name). *) + + val split_classname : string -> string option * string + + (** Convert a proc name to a string for the user to see. *) + + val to_string : t -> string + + (** Convert a proc name into a easy string for the user to see in an IDE. *) + + val to_simplified_string : ?withclass:bool -> t -> string + + (** Convert a proc name into a unique identifier. *) + + val to_unique_id : t -> string + + (** Convert a proc name to a filename. *) + + val to_filename : t -> string + + (** get qualifiers of C/objc/C++ method/function *) + + val get_qualifiers : t -> QualifiedCppName.t + + (** get qualifiers of a class owning objc/C++ method *) + + val objc_cpp_get_class_qualifiers : objc_cpp -> QualifiedCppName.t + + (** Return type substitution that would produce concrete procname from generic procname. Returns None if + such substitution doesn't exist + NOTE: this function doesn't check if such substitution is correct in terms of return + type/function parameters. + NOTE: this function doesn't deal with nested template classes, it only extracts mapping for function + and/or direct parent (class that defines the method) if it exists. *) + + val get_template_args_mapping : t -> t -> type_subst_t option +end + +(** Return the return type of [pname_java]. *) + +val java_proc_return_typ : Procname.java -> t + +module Fieldname : sig + (** Names for fields of class/struct/union *) + + type t [@@deriving compare] + + (** Equality for field names. *) + + val equal : t -> t -> bool + + (** Set for fieldnames *) + + module Set : Caml.Set.S with type elt = t + + (** Map for fieldnames *) + + module Map : Caml.Map.S with type key = t + + module Clang : sig + val from_class_name : Name.t -> string -> t + end + + module Java : sig + (** Create a java field name from string *) + + val from_string : string -> t + end + + (** Convert a field name to a string. *) + + val to_string : t -> string + + val to_full_string : t -> string + + val class_name_replace : t -> f:(Name.t -> Name.t) -> t + + (** Convert a fieldname to a simplified string with at most one-level path. *) + + val to_simplified_string : t -> string + + (** Convert a fieldname to a flat string without path. *) + + val to_flat_string : t -> string + + (** Pretty print a field name. *) + + val pp : Format.formatter -> t -> unit + + (** Pretty print a field name in latex. *) + + val pp_latex : Latex.style -> Format.formatter -> t -> unit + + (** The class part of the fieldname *) + + val java_get_class : t -> string + + (** The last component of the fieldname *) + + val java_get_field : t -> string + + (** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. *) + + val java_is_outer_instance : t -> bool + + (** get qualified classname of a field if it's coming from clang frontend. returns None otherwise *) + + val clang_get_qual_class : t -> QualifiedCppName.t option + + (** hidded fieldname constant *) + + val hidden : t + + (** hidded fieldname constant *) + + val is_hidden : t -> bool +end + +module Struct : sig + type field = Fieldname.t * typ * Annot.Item.t [@@deriving compare] + + type fields = field list + + (** Type for a structured value. *) + + type t = private + { fields: fields (** non-static fields *) + ; statics: fields (** static fields *) + ; supers: Name.t list (** supers *) + ; methods: Procname.t list (** methods defined *) + ; annots: Annot.Item.t (** annotations *) } + + type lookup = Name.t -> t option + + (** Pretty print a struct type. *) + + val pp : Pp.env -> Name.t -> F.formatter -> t -> unit + + (** Construct a struct_typ, normalizing field types *) + + val internal_mk_struct : + ?default:t -> ?fields:fields -> ?statics:fields -> ?methods:Procname.t list + -> ?supers:Name.t list -> ?annots:Annot.Item.t -> unit -> t + + (** the element typ of the final extensible array in the given typ, if any *) + + val get_extensible_array_element_typ : lookup:lookup -> typ -> typ option + + (** If a struct type with field f, return the type of f. + If not, return the default type if given, otherwise raise an exception *) + + val fld_typ : lookup:lookup -> default:typ -> Fieldname.t -> typ -> typ + + (** Return the type of the field [fn] and its annotation, None if [typ] has no field named [fn] *) + + val get_field_type_and_annotation : + lookup:lookup -> Fieldname.t -> typ -> (typ * Annot.Item.t) option + + (** Field used for objective-c reference counting *) + + val objc_ref_counter_field : Fieldname.t * typ * Annot.Item.t + + val is_objc_ref_counter_field : Fieldname.t * typ * Annot.Item.t -> bool +end diff --git a/infer/src/IR/Typ.re b/infer/src/IR/Typ.re deleted file mode 100644 index 66a56a3b7..000000000 --- a/infer/src/IR/Typ.re +++ /dev/null @@ -1,1366 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module Hashtbl = Caml.Hashtbl; - - -/** The Smallfoot Intermediate Language: Types */ -module L = Logging; - -module F = Format; - - -/** Kinds of integers */ -type ikind = - | IChar /** [char] */ - | ISChar /** [signed char] */ - | IUChar /** [unsigned char] */ - | IBool /** [bool] */ - | IInt /** [int] */ - | IUInt /** [unsigned int] */ - | IShort /** [short] */ - | IUShort /** [unsigned short] */ - | ILong /** [long] */ - | IULong /** [unsigned long] */ - | ILongLong /** [long long] (or [_int64] on Microsoft Visual C) */ - | IULongLong /** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) */ - | I128 /** [__int128_t] */ - | IU128 /** [__uint128_t] */ -[@@deriving compare]; - -let ikind_to_string = - fun - | IChar => "char" - | ISChar => "signed char" - | IUChar => "unsigned char" - | IBool => "_Bool" - | IInt => "int" - | IUInt => "unsigned int" - | IShort => "short" - | IUShort => "unsigned short" - | ILong => "long" - | IULong => "unsigned long" - | ILongLong => "long long" - | IULongLong => "unsigned long long" - | I128 => "__int128_t" - | IU128 => "__uint128_t"; - -let ikind_is_char = - fun - | IChar - | ISChar - | IUChar => true - | _ => false; - -let ikind_is_unsigned = - fun - | IUChar - | IUInt - | IUShort - | IULong - | IULongLong => true - | _ => false; - -let int_of_int64_kind i ik => IntLit.of_int64_unsigned i (ikind_is_unsigned ik); - - -/** Kinds of floating-point numbers */ -type fkind = - | FFloat /** [float] */ - | FDouble /** [double] */ - | FLongDouble /** [long double] */ -[@@deriving compare]; - -let fkind_to_string = - fun - | FFloat => "float" - | FDouble => "double" - | FLongDouble => "long double"; - - -/** kind of pointer */ -type ptr_kind = - | Pk_pointer /** C/C++, Java, Objc standard/__strong pointer */ - | Pk_reference /** C++ reference */ - | Pk_objc_weak /** Obj-C __weak pointer */ - | Pk_objc_unsafe_unretained /** Obj-C __unsafe_unretained pointer */ - | Pk_objc_autoreleasing /** Obj-C __autoreleasing pointer */ -[@@deriving compare]; - -let equal_ptr_kind = [%compare.equal : ptr_kind]; - -let ptr_kind_string = - fun - | Pk_reference => "&" - | Pk_pointer => "*" - | Pk_objc_weak => "__weak *" - | Pk_objc_unsafe_unretained => "__unsafe_unretained *" - | Pk_objc_autoreleasing => "__autoreleasing *"; - -module T = { - type type_quals = {is_const: bool, is_restrict: bool, is_volatile: bool} [@@deriving compare]; - - /** types for sil (structured) expressions */ - type t = {desc, quals: type_quals} [@@deriving compare] - and desc = - | Tint ikind /** integer type */ - | Tfloat fkind /** float type */ - | Tvoid /** void type */ - | Tfun bool /** function type with noreturn attribute */ - | Tptr t ptr_kind /** pointer type */ - | Tstruct name /** structured value type name */ - | TVar string /** type variable (ie. C++ template variables) */ - | Tarray t (option IntLit.t) (option IntLit.t) /** array type with statically fixed length and stride */ - [@@deriving compare] - and name = - | CStruct QualifiedCppName.t - | CUnion QualifiedCppName.t - | CppClass QualifiedCppName.t template_spec_info - | JavaClass Mangled.t - | ObjcClass QualifiedCppName.t - | ObjcProtocol QualifiedCppName.t - [@@deriving compare] - and template_spec_info = - | NoTemplate - | Template (list (option t)) - [@@deriving compare]; - let equal_desc = [%compare.equal : desc]; - let equal_quals = [%compare.equal : type_quals]; - let equal = [%compare.equal : t]; - let hash = Hashtbl.hash; -}; - -include T; - -let mk_type_quals ::default=? ::is_const=? ::is_restrict=? ::is_volatile=? () => { - let default_ = {is_const: false, is_restrict: false, is_volatile: false}; - let mk_aux - ::default=default_ - ::is_const=default.is_const - ::is_restrict=default.is_restrict - ::is_volatile=default.is_volatile - () => { - is_const, - is_restrict, - is_volatile - }; - mk_aux ::?default ::?is_const ::?is_restrict ::?is_volatile () -}; - -let is_const {is_const} => is_const; - -let is_restrict {is_restrict} => is_restrict; - -let is_volatile {is_volatile} => is_volatile; - -let mk ::default=? ::quals=? desc :t => { - let default_ = {desc, quals: mk_type_quals ()}; - let mk_aux ::default=default_ ::quals=default.quals desc => {desc, quals}; - mk_aux ::?default ::?quals desc -}; - -let merge_quals quals1 quals2 => { - is_const: quals1.is_const || quals2.is_const, - is_restrict: quals1.is_restrict || quals2.is_restrict, - is_volatile: quals1.is_volatile || quals2.is_volatile -}; - -let escape pe => - if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) { - Escape.escape_xml - } else { - ident - }; - - -/** Pretty print a type with all the details, using the C syntax. */ -let rec pp_full pe f typ => { - let pp_quals f {quals} => { - if (is_const quals) { - F.fprintf f " const " - }; - if (is_restrict quals) { - F.fprintf f " __restrict " - }; - if (is_volatile quals) { - F.fprintf f " volatile " - } - }; - let pp_desc f {desc} => - switch desc { - | Tstruct tname => F.fprintf f "%a" (pp_name_c_syntax pe) tname - | TVar name => F.fprintf f "%s" name - | Tint ik => F.fprintf f "%s" (ikind_to_string ik) - | Tfloat fk => F.fprintf f "%s" (fkind_to_string fk) - | Tvoid => F.fprintf f "void" - | Tfun false => F.fprintf f "_fn_" - | Tfun true => F.fprintf f "_fn_noreturn_" - | Tptr ({desc: Tarray _ | Tfun _} as typ) pk => - F.fprintf f "%a(%s)" (pp_full pe) typ (ptr_kind_string pk |> escape pe) - | Tptr typ pk => F.fprintf f "%a%s" (pp_full pe) typ (ptr_kind_string pk |> escape pe) - | Tarray typ static_len static_stride => - let pp_int_opt fmt => ( - fun - | Some x => IntLit.pp fmt x - | None => F.fprintf fmt "_" - ); - F.fprintf f "%a[%a*%a]" (pp_full pe) typ pp_int_opt static_len pp_int_opt static_stride - }; - F.fprintf f "%a%a" pp_desc typ pp_quals typ -} -and pp_name_c_syntax pe f => - fun - | CStruct name - | CUnion name - | ObjcClass name - | ObjcProtocol name => F.fprintf f "%a" QualifiedCppName.pp name - | CppClass name template_spec => - F.fprintf f "%a%a" QualifiedCppName.pp name (pp_template_spec_info pe) template_spec - | JavaClass name => F.fprintf f "%a" Mangled.pp name -and pp_template_spec_info pe f => - fun - | NoTemplate => () - | Template args => { - let pp_arg_opt f => ( - fun - | Some typ => F.fprintf f "%a" (pp_full pe) typ - | None => F.fprintf f "_" - ); - F.fprintf f "%s%a%s" (escape pe "<") (Pp.comma_seq pp_arg_opt) args (escape pe ">") - }; - - -/** Pretty print a type. Do nothing by default. */ -let pp pe f te => - if Config.print_types { - pp_full pe f te - } else { - () - }; - -let to_string typ => { - let pp fmt => pp_full Pp.text fmt typ; - F.asprintf "%t" pp -}; - -type type_subst_t = list (string, t) [@@deriving compare]; - -let is_type_subst_empty = List.is_empty; - - -/** Given the template type mapping and the type, substitute tvars within the type. */ -let rec sub_type subst generic_typ :t => - switch generic_typ.desc { - | TVar tname => - switch (List.Assoc.find subst equal::String.equal tname) { - | Some t => - /* Type qualifiers may come from original type or be part of substitution. Merge them */ - mk quals::(merge_quals t.quals generic_typ.quals) t.desc - | None => generic_typ - } - | Tarray typ arg1 arg2 => - let typ' = sub_type subst typ; - if (phys_equal typ typ') { - generic_typ - } else { - mk default::generic_typ (Tarray typ' arg1 arg2) - } - | Tptr typ arg => - let typ' = sub_type subst typ; - if (phys_equal typ typ') { - generic_typ - } else { - mk default::generic_typ (Tptr typ' arg) - } - | Tstruct tname => - let tname' = sub_tname subst tname; - if (phys_equal tname tname') { - generic_typ - } else { - mk default::generic_typ (Tstruct tname') - } - | _ => generic_typ - } -and sub_tname subst tname => - switch tname { - | CppClass name (Template spec_info) => - let sub_typ_opt typ_opt => - switch typ_opt { - | Some typ => - let typ' = sub_type subst typ; - if (phys_equal typ typ') { - typ_opt - } else { - Some typ' - } - | None => typ_opt - }; - let spec_info' = IList.map_changed sub_typ_opt spec_info; - if (phys_equal spec_info spec_info') { - tname - } else { - CppClass name (Template spec_info') - } - | _ => tname - }; - -module Name = { - type t = name [@@deriving compare]; - let equal = [%compare.equal : t]; - let qual_name = - fun - | CStruct name - | CUnion name - | ObjcClass name - | ObjcProtocol name => name - | CppClass name templ_args => { - let template_suffix = F.asprintf "%a" (pp_template_spec_info Pp.text) templ_args; - QualifiedCppName.append_template_args_to_last name args::template_suffix - } - | JavaClass _ => QualifiedCppName.empty; - let unqualified_name = - fun - | CStruct name - | CUnion name - | ObjcClass name - | ObjcProtocol name => name - | CppClass name _ => name - | JavaClass _ => QualifiedCppName.empty; - let name n => - switch n { - | CStruct _ - | CUnion _ - | CppClass _ _ - | ObjcClass _ - | ObjcProtocol _ => qual_name n |> QualifiedCppName.to_qual_string - | JavaClass name => Mangled.to_string name - }; - let pp fmt tname => { - let prefix = - fun - | CStruct _ => "struct" - | CUnion _ => "union" - | CppClass _ _ - | JavaClass _ - | ObjcClass _ => "class" - | ObjcProtocol _ => "protocol"; - F.fprintf fmt "%s %a" (prefix tname) (pp_name_c_syntax Pp.text) tname - }; - let to_string = F.asprintf "%a" pp; - let is_class = - fun - | CppClass _ _ - | JavaClass _ - | ObjcClass _ => true - | _ => false; - let is_same_type t1 t2 => - switch (t1, t2) { - | (CStruct _, CStruct _) - | (CUnion _, CUnion _) - | (CppClass _ _, CppClass _ _) - | (JavaClass _, JavaClass _) - | (ObjcClass _, ObjcClass _) - | (ObjcProtocol _, ObjcProtocol _) => true - | _ => false - }; - module C = { - let from_qual_name qual_name => CStruct qual_name; - let from_string name_str => QualifiedCppName.of_qual_string name_str |> from_qual_name; - let union_from_qual_name qual_name => CUnion qual_name; - }; - module Java = { - let from_string name_str => JavaClass (Mangled.from_string name_str); - let from_package_class package_name class_name => - if (String.equal package_name "") { - from_string class_name - } else { - from_string (package_name ^ "." ^ class_name) - }; - let is_class = - fun - | JavaClass _ => true - | _ => false; - let java_lang_object = from_string "java.lang.Object"; - let java_io_serializable = from_string "java.io.Serializable"; - let java_lang_cloneable = from_string "java.lang.Cloneable"; - }; - module Cpp = { - let from_qual_name template_spec_info qual_name => CppClass qual_name template_spec_info; - let is_class = - fun - | CppClass _ => true - | _ => false; - }; - module Objc = { - let from_qual_name qual_name => ObjcClass qual_name; - let from_string name_str => QualifiedCppName.of_qual_string name_str |> from_qual_name; - let protocol_from_qual_name qual_name => ObjcProtocol qual_name; - let is_class = - fun - | ObjcClass _ => true - | _ => false; - }; - module Set = - Caml.Set.Make { - type nonrec t = t; - let compare = compare; - }; -}; - - -/** {2 Sets and maps of types} */ -module Set = Caml.Set.Make T; - -module Map = Caml.Map.Make T; - -module Tbl = Hashtbl.Make T; - - -/** dump a type with all the details. */ -let d_full (t: t) => L.add_print_action (L.PTtyp_full, Obj.repr t); - - -/** dump a list of types. */ -let d_list (tl: list t) => L.add_print_action (L.PTtyp_list, Obj.repr tl); - -let name typ => - switch typ.desc { - | Tstruct name => Some name - | _ => None - }; - -let unsome s => - fun - | Some default_typ => default_typ - | None => { - L.internal_error "No default typ in %s@." s; - assert false - }; - - -/** turn a *T into a T. fails if [typ] is not a pointer type */ -let strip_ptr typ => - switch typ.desc { - | Tptr t _ => t - | _ => assert false - }; - - -/** If an array type, return the type of the element. - If not, return the default type if given, otherwise raise an exception */ -let array_elem default_opt typ => - switch typ.desc { - | Tarray t_el _ _ => t_el - | _ => unsome "array_elem" default_opt - }; - -let is_class_of_kind check_fun typ => - switch typ.desc { - | Tstruct tname => check_fun tname - | _ => false - }; - -let is_objc_class = is_class_of_kind Name.Objc.is_class; - -let is_cpp_class = is_class_of_kind Name.Cpp.is_class; - -let is_java_class = is_class_of_kind Name.Java.is_class; - -let rec is_array_of_cpp_class typ => - switch typ.desc { - | Tarray typ _ _ => is_array_of_cpp_class typ - | _ => is_cpp_class typ - }; - -let is_pointer_to_cpp_class typ => - switch typ.desc { - | Tptr t _ => is_cpp_class t - | _ => false - }; - -let has_block_prefix s => - switch (Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) s) { - | [_, _, ..._] => true - | _ => false - }; - - -/** Check if type is a type for a block in objc */ -let is_block_type typ => has_block_prefix (to_string typ); - - -/** Java types by name */ -let rec java_from_string: string => t = - fun - | "" - | "void" => mk Tvoid - | "int" => mk (Tint IInt) - | "byte" => mk (Tint IShort) - | "short" => mk (Tint IShort) - | "boolean" => mk (Tint IBool) - | "char" => mk (Tint IChar) - | "long" => mk (Tint ILong) - | "float" => mk (Tfloat FFloat) - | "double" => mk (Tfloat FDouble) - | typ_str when String.contains typ_str '[' => { - let stripped_typ = String.sub typ_str pos::0 len::(String.length typ_str - 2); - mk (Tptr (mk (Tarray (java_from_string stripped_typ) None None)) Pk_pointer) - } - | typ_str => mk (Tstruct (Name.Java.from_string typ_str)); - -type typ = t; - -module Procname = { - /* e.g. ("", "int") for primitive types or ("java.io", "PrintWriter") for objects */ - type java_type = (option string, string); - /* compare in inverse order */ - let compare_java_type (p1, c1) (p2, c2) => - [%compare : (string, option string)] (c1, p1) (c2, p2); - type method_kind = - | Non_Static /* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface */ - | Static /* in Java, procedures called with invokestatic */ - [@@deriving compare]; - let equal_method_kind = [%compare.equal : method_kind]; - - /** Type of java procedure names. */ - type java = { - method_name: string, - parameters: list java_type, - class_name: Name.t, - return_type: option java_type, /* option because constructors have no return type */ - kind: method_kind - } - [@@deriving compare]; - - /** Type of c procedure names. */ - type c = { - name: QualifiedCppName.t, - mangled: option string, - template_args: template_spec_info, - is_generic_model: bool - } - [@@deriving compare]; - type objc_cpp_method_kind = - | CPPMethod (option string) /** with mangling */ - | CPPConstructor (option string, bool) /** with mangling + is it constexpr? */ - | ObjCClassMethod - | ObjCInstanceMethod - | ObjCInternalMethod - [@@deriving compare]; - - /** Type of Objective C and C++ procedure names: method signatures. */ - type objc_cpp = { - method_name: string, - class_name: Name.t, - kind: objc_cpp_method_kind, - template_args: template_spec_info, - is_generic_model: bool - } - [@@deriving compare]; - - /** Type of Objective C block names. */ - type block = string [@@deriving compare]; - - /** Type of procedure names. */ - type t = - | Java java - | C c - | Linters_dummy_method - | Block block - | ObjC_Cpp objc_cpp - [@@deriving compare]; - let equal = [%compare.equal : t]; - - /** Level of verbosity of some to_string functions. */ - type detail_level = - | Verbose - | Non_verbose - | Simple - [@@deriving compare]; - let equal_detail_level = [%compare.equal : detail_level]; - let objc_method_kind_of_bool is_instance => - if is_instance {ObjCInstanceMethod} else {ObjCClassMethod}; - let empty_block = Block ""; - let is_verbose v => - switch v { - | Verbose => true - | _ => false - }; - - /** A type is a pair (package, type_name) that is translated in a string package.type_name */ - let java_type_to_string_verbosity p verbosity => - switch p { - | (None, typ) => typ - | (Some p, cls) => - if (is_verbose verbosity) { - p ^ "." ^ cls - } else { - cls - } - }; - let java_type_to_string p => java_type_to_string_verbosity p Verbose; - - /** Given a list of types, it creates a unique string of types separated by commas */ - let rec java_param_list_to_string inputList verbosity => - switch inputList { - | [] => "" - | [head] => java_type_to_string_verbosity head verbosity - | [head, ...rest] => - java_type_to_string_verbosity head verbosity ^ "," ^ java_param_list_to_string rest verbosity - }; - - /** It is the same as java_type_to_string, but Java return types are optional because of constructors without type */ - let java_return_type_to_string j verbosity => - switch j.return_type { - | None => "" - | Some typ => java_type_to_string_verbosity typ verbosity - }; - - /** Given a package.class_name string, it looks for the latest dot and split the string - in two (package, class_name) */ - let split_classname package_classname => - switch (String.rsplit2 package_classname on::'.') { - | Some (x, y) => (Some x, y) - | None => (None, package_classname) - }; - let split_typename typename => split_classname (Name.name typename); - let c name mangled template_args ::is_generic_model => { - name, - mangled: Some mangled, - template_args, - is_generic_model - }; - let from_string_c_fun (name: string) => - C { - name: QualifiedCppName.of_qual_string name, - mangled: None, - template_args: NoTemplate, - is_generic_model: false - }; - let java class_name return_type method_name parameters kind => { - class_name, - return_type, - method_name, - parameters, - kind - }; - - /** Create an objc procedure name from a class_name and method_name. */ - let objc_cpp class_name method_name kind template_args ::is_generic_model => { - class_name, - method_name, - kind, - template_args, - is_generic_model - }; - let get_default_objc_class_method objc_class => { - let objc_cpp = - objc_cpp objc_class "__find_class_" ObjCInternalMethod NoTemplate is_generic_model::false; - ObjC_Cpp objc_cpp - }; - - /** Create an objc procedure name from a class_name and method_name. */ - let mangled_objc_block name => Block name; - let is_java = - fun - | Java _ => true - | _ => false; - let is_c_method = - fun - | ObjC_Cpp _ => true - | _ => false; - let is_constexpr = - fun - | ObjC_Cpp {kind: CPPConstructor (_, true)} => true - | _ => false; - - /** Replace the class name component of a procedure name. - In case of Java, replace package and class name. */ - let replace_class t (new_class: Name.t) => - switch t { - | Java j => Java {...j, class_name: new_class} - | ObjC_Cpp osig => ObjC_Cpp {...osig, class_name: new_class} - | C _ - | Block _ - | Linters_dummy_method => t - }; - - /** Get the class name of a Objective-C/C++ procedure name. */ - let objc_cpp_get_class_name objc_cpp => Name.name objc_cpp.class_name; - let objc_cpp_get_class_type_name objc_cpp => objc_cpp.class_name; - - /** Return the package.classname of a java procname. */ - let java_get_class_name (j: java) => Name.name j.class_name; - - /** Return the package.classname as a typename of a java procname. */ - let java_get_class_type_name (j: java) => j.class_name; - - /** Return the class name of a java procedure name. */ - let java_get_simple_class_name (j: java) => snd (split_classname (java_get_class_name j)); - - /** Return the package of a java procname. */ - let java_get_package (j: java) => fst (split_classname (java_get_class_name j)); - - /** Return the method of a java procname. */ - let java_get_method (j: java) => j.method_name; - - /** Replace the method of a java procname. */ - let java_replace_method (j: java) mname => {...j, method_name: mname}; - - /** Replace the return type of a java procname. */ - let java_replace_return_type j ret_type => {...j, return_type: Some ret_type}; - - /** Replace the parameters of a java procname. */ - let java_replace_parameters j parameters => {...j, parameters}; - - /** Return the method/function of a procname. */ - let get_method = - fun - | ObjC_Cpp name => name.method_name - | C {name} => QualifiedCppName.to_qual_string name - | Block name => name - | Java j => j.method_name - | Linters_dummy_method => "Linters_dummy_method"; - - /** Return the language of the procedure. */ - let get_language = - fun - | ObjC_Cpp _ => Config.Clang - | C _ => Config.Clang - | Block _ => Config.Clang - | Linters_dummy_method => Config.Clang - | Java _ => Config.Java; - - /** Return the return type of a java procname. */ - let java_get_return_type (j: java) => java_return_type_to_string j Verbose; - - /** Return the parameters of a java procname. */ - let java_get_parameters j => j.parameters; - - /** Return the parameters of a java procname as strings. */ - let java_get_parameters_as_strings j => - List.map f::(fun param => java_type_to_string param) j.parameters; - - /** Return true if the java procedure is static */ - let java_is_static = - fun - | Java j => equal_method_kind j.kind Static - | _ => false; - let java_is_lambda = - fun - | Java j => String.is_prefix prefix::"lambda$" j.method_name - | _ => false; - let java_is_generated = - fun - | Java j => String.is_prefix prefix::"$" j.method_name - | _ => false; - - /** Prints a string of a java procname with the given level of verbosity */ - let java_to_string ::withclass=false (j: java) verbosity => - switch verbosity { - | Verbose - | Non_verbose => - /* if verbose, then package.class.method(params): rtype, - else rtype package.class.method(params) - verbose is used for example to create unique filenames, non_verbose to create reports */ - let return_type = java_return_type_to_string j verbosity; - let params = java_param_list_to_string j.parameters verbosity; - let class_name = java_type_to_string_verbosity (split_typename j.class_name) verbosity; - let separator = - switch (j.return_type, verbosity) { - | (None, _) => "" - | (Some _, Verbose) => ":" - | _ => " " - }; - let output = class_name ^ "." ^ j.method_name ^ "(" ^ params ^ ")"; - if (equal_detail_level verbosity Verbose) { - output ^ separator ^ return_type - } else { - return_type ^ separator ^ output - } - | Simple => - /* methodname(...) or without ... if there are no parameters */ - let cls_prefix = - if withclass { - java_type_to_string_verbosity (split_typename j.class_name) verbosity ^ "." - } else { - "" - }; - let params = - switch j.parameters { - | [] => "" - | _ => "..." - }; - let method_name = - if (String.equal j.method_name "") { - java_get_simple_class_name j - } else { - cls_prefix ^ j.method_name - }; - method_name ^ "(" ^ params ^ ")" - }; - - /** Check if the class name is for an anonymous inner class. */ - let is_anonymous_inner_class_name class_name => { - let class_name_no_package = snd (split_typename class_name); - switch (String.rsplit2 class_name_no_package on::'$') { - | Some (_, s) => - let is_int = - try { - ignore (int_of_string (String.strip s)); - true - } { - | Failure _ => false - }; - is_int - | None => false - } - }; - - /** Check if the procedure belongs to an anonymous inner class. */ - let java_is_anonymous_inner_class = - fun - | Java j => is_anonymous_inner_class_name j.class_name - | _ => false; - - /** Check if the last parameter is a hidden inner class, and remove it if present. - This is used in private constructors, where a proxy constructor is generated - with an extra parameter and calls the normal constructor. */ - let java_remove_hidden_inner_class_parameter = - fun - | Java js => - switch (List.rev js.parameters) { - | [(_, s), ...par'] => - if (is_anonymous_inner_class_name (Name.Java.from_string s)) { - Some (Java {...js, parameters: List.rev par'}) - } else { - None - } - | [] => None - } - | _ => None; - - /** Check if the procedure name is an anonymous inner class constructor. */ - let java_is_anonymous_inner_class_constructor = - fun - | Java js => is_anonymous_inner_class_name js.class_name - | _ => false; - - /** Check if the procedure name is an acess method (e.g. access$100 used to - access private members from a nested class. */ - let java_is_access_method = - fun - | Java js => - switch (String.rsplit2 js.method_name on::'$') { - | Some ("access", s) => - let is_int = - try { - ignore (int_of_string s); - true - } { - | Failure _ => false - }; - is_int - | _ => false - } - | _ => false; - - /** Check if the procedure name is of an auto-generated method containing '$'. */ - let java_is_autogen_method = - fun - | Java js => String.contains js.method_name '$' - | _ => false; - - /** Check if the proc name has the type of a java vararg. - Note: currently only checks that the last argument has type Object[]. */ - let java_is_vararg = - fun - | Java js => - switch (List.rev js.parameters) { - | [(_, "java.lang.Object[]"), ..._] => true - | _ => false - } - | _ => false; - let is_objc_constructor method_name => - String.equal method_name "new" || String.is_prefix prefix::"init" method_name; - let is_objc_kind = - fun - | ObjCClassMethod - | ObjCInstanceMethod - | ObjCInternalMethod => true - | _ => false; - - /** [is_constructor pname] returns true if [pname] is a constructor */ - let is_constructor = - fun - | Java js => String.equal js.method_name "" - | ObjC_Cpp {kind: CPPConstructor _} => true - | ObjC_Cpp {kind, method_name} when is_objc_kind kind => is_objc_constructor method_name - | _ => false; - let is_objc_dealloc method_name => String.equal method_name "dealloc"; - - /** [is_dealloc pname] returns true if [pname] is the dealloc method in Objective-C - TODO: add case for C++ */ - let is_destructor = - fun - | ObjC_Cpp name => is_objc_dealloc name.method_name - | _ => false; - let java_is_close = - fun - | Java js => String.equal js.method_name "close" - | _ => false; - - /** [is_class_initializer pname] returns true if [pname] is a class initializer */ - let is_class_initializer = - fun - | Java js => String.equal js.method_name "" - | _ => false; - - /** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc */ - let is_infer_undefined pn => - switch pn { - | Java j => - let regexp = Str.regexp "com.facebook.infer.builtins.InferUndefined"; - Str.string_match regexp (java_get_class_name j) 0 - | _ => - /* TODO: add cases for obj-c, c, c++ */ - false - }; - let get_global_name_of_initializer = - fun - | C {name} - when - String.is_prefix - prefix::Config.clang_initializer_prefix (QualifiedCppName.to_qual_string name) => { - let name_str = QualifiedCppName.to_qual_string name; - let prefix_len = String.length Config.clang_initializer_prefix; - Some (String.sub name_str pos::prefix_len len::(String.length name_str - prefix_len)) - } - | _ => None; - - /** to_string for C_function type */ - let to_readable_string (c1, c2) verbose => { - let plain = QualifiedCppName.to_qual_string c1; - if verbose { - switch c2 { - | None => plain - | Some s => plain ^ "{" ^ s ^ "}" - } - } else { - plain - } - }; - let c_method_kind_verbose_str kind => - switch kind { - | CPPMethod m => - "(" ^ - ( - switch m { - | None => "" - | Some s => s - } - ) ^ ")" - | CPPConstructor (m, is_constexpr) => - "{" ^ - ( - switch m { - | None => "" - | Some s => s - } - ) ^ - (if is_constexpr {"|constexpr"} else {""}) ^ "}" - | ObjCClassMethod => "class" - | ObjCInstanceMethod => "instance" - | ObjCInternalMethod => "internal" - }; - let c_method_to_string osig detail_level => - switch detail_level { - | Simple => osig.method_name - | Non_verbose => Name.name osig.class_name ^ "_" ^ osig.method_name - | Verbose => - let m_str = c_method_kind_verbose_str osig.kind; - Name.name osig.class_name ^ "_" ^ osig.method_name ^ m_str - }; - - /** Very verbose representation of an existing Procname.t */ - let to_unique_id pn => - switch pn { - | Java j => java_to_string j Verbose - | C {name, mangled} => to_readable_string (name, mangled) true - | ObjC_Cpp osig => c_method_to_string osig Verbose - | Block name => name - | Linters_dummy_method => "Linters_dummy_method" - }; - - /** Convert a proc name to a string for the user to see */ - let to_string p => - switch p { - | Java j => java_to_string j Non_verbose - | C {name, mangled} => to_readable_string (name, mangled) false - | ObjC_Cpp osig => c_method_to_string osig Non_verbose - | Block name => name - | Linters_dummy_method => to_unique_id p - }; - - /** Convenient representation of a procname for external tools (e.g. eclipse plugin) */ - let to_simplified_string ::withclass=false p => - switch p { - | Java j => java_to_string ::withclass j Simple - | C {name, mangled} => to_readable_string (name, mangled) false ^ "()" - | ObjC_Cpp osig => c_method_to_string osig Simple - | Block _ => "block" - | Linters_dummy_method => to_unique_id p - }; - - /** Pretty print a proc name */ - let pp f pn => F.fprintf f "%s" (to_string pn); - - /** hash function for procname */ - let hash_pname = Hashtbl.hash; - module Hash = - Hashtbl.Make { - type nonrec t = t; - let equal = equal; - let hash = hash_pname; - }; - module Map = - PrettyPrintable.MakePPMap { - type nonrec t = t; - let compare = compare; - let pp = pp; - }; - module Set = - PrettyPrintable.MakePPSet { - type nonrec t = t; - let compare = compare; - let pp = pp; - }; - - /** Pretty print a set of proc names */ - let pp_set fmt set => Set.iter (fun pname => F.fprintf fmt "%a " pp pname) set; - let objc_cpp_get_class_qualifiers objc_cpp => Name.qual_name objc_cpp.class_name; - let get_qualifiers pname => - switch pname { - | C {name} => name - | ObjC_Cpp objc_cpp => - objc_cpp_get_class_qualifiers objc_cpp |> - QualifiedCppName.append_qualifier qual::objc_cpp.method_name - | _ => QualifiedCppName.empty - }; - - /** Convert a proc name to a filename */ - let to_concrete_filename pname => { - /* filenames for clang procs are REVERSED qualifiers with '#' as separator */ - let get_qual_name_str pname => - get_qualifiers pname |> QualifiedCppName.to_rev_list |> String.concat sep::"#"; - let proc_id = - switch pname { - | C {mangled} => - [get_qual_name_str pname, ...Option.to_list mangled] |> String.concat sep::"#" - | ObjC_Cpp objc_cpp => - get_qual_name_str pname ^ "#" ^ c_method_kind_verbose_str objc_cpp.kind - | _ => to_unique_id pname - }; - Escape.escape_filename @@ DB.append_crc_cutoff proc_id - }; - let to_generic_filename pname => { - let proc_id = - get_qualifiers pname |> QualifiedCppName.strip_template_args |> QualifiedCppName.to_rev_list |> - String.concat sep::"#"; - Escape.escape_filename @@ DB.append_crc_cutoff proc_id - }; - let to_filename pname => - switch pname { - | C {is_generic_model} - | ObjC_Cpp {is_generic_model} when Bool.equal is_generic_model true => - to_generic_filename pname - | _ => to_concrete_filename pname - }; - let get_template_args_mapping generic_procname concrete_procname => { - - /** given two template arguments, try to generate mapping from generic ones to concrete ones. */ - let mapping_for_template_args (generic_name, generic_args) (concrete_name, concrete_args) => - switch (generic_args, concrete_args) { - | (Template generic_typs, Template concrete_typs) - when QualifiedCppName.equal generic_name concrete_name => - try ( - `Valid ( - List.fold2_exn - generic_typs - concrete_typs - init::[] - f::( - /* result will be reversed list. Ordering in template mapping doesn't matter so it's ok */ - fun result gtyp ctyp => - switch (gtyp, ctyp) { - | (Some {desc: TVar name}, Some concrete) => [(name, concrete), ...result] - | _ => result - } - ) - ) - ) { - | Invalid_argument _ => - /* fold2_exn throws on length mismatch, we need to handle it */ `Invalid - } - | (NoTemplate, NoTemplate) => `NoTemplate - | _ => `Invalid - }; - let combine_mappings mapping1 mapping2 => - switch (mapping1, mapping2) { - | (`Valid m1, `Valid m2) => `Valid (List.append m1 m2) - | (`NoTemplate, a) - | (a, `NoTemplate) => /* no template is no-op state, simply return the other state */ a - | _ => /* otherwise there is no valid mapping */ `Invalid - }; - let extract_mapping = - fun - | `Invalid - | `NoTemplate => None - | `Valid m => Some m; - let empty_qual = QualifiedCppName.of_qual_string "FIXME" /* TODO we should look at procedure names */; - switch (generic_procname, concrete_procname) { - | (C {template_args: args1}, C {template_args: args2}) /* template function */ => - mapping_for_template_args (empty_qual, args1) (empty_qual, args2) |> extract_mapping - | ( - ObjC_Cpp {template_args: args1, class_name: CppClass name1 class_args1}, - ObjC_Cpp {template_args: args2, class_name: CppClass name2 class_args2} - ) /* template methods/template classes/both */ => - combine_mappings - (mapping_for_template_args (name1, class_args1) (name2, class_args2)) - (mapping_for_template_args (empty_qual, args1) (empty_qual, args2)) |> extract_mapping - | _ => None - } - }; -}; - - -/** Return the return type of [pname_java]. */ -let java_proc_return_typ pname_java :t => { - let typ = java_from_string (Procname.java_get_return_type pname_java); - switch typ.desc { - | Tstruct _ => mk (Tptr typ Pk_pointer) - | _ => typ - } -}; - -module Fieldname = { - type clang_field_info = {class_name: Name.t, field_name: string} [@@deriving compare]; - type t = - | Hidden /* Backend relies that Hidden is the smallest (first) field in Abs.should_raise_objc_leak */ - | Clang clang_field_info - | Java string - [@@deriving compare]; - let hidden_str = ".hidden"; - let equal = [%compare.equal : t]; - module T = { - type nonrec t = t; - let compare = compare; - }; - module Set = Caml.Set.Make T; - module Map = Caml.Map.Make T; - module Clang = { - let from_class_name class_name field_name => Clang {class_name, field_name}; - }; - module Java = { - let from_string n => Java n; - }; - - /** Convert a fieldname to a string. */ - let to_string = - fun - | Hidden => hidden_str - | Java fname => fname - | Clang {field_name} => field_name; - - /** Convert a fieldname to a simplified string with at most one-level path. */ - let to_simplified_string fn => { - let s = to_string fn; - switch (String.rsplit2 s on::'.') { - | Some (s1, s2) => - switch (String.rsplit2 s1 on::'.') { - | Some (_, s4) => s4 ^ "." ^ s2 - | _ => s - } - | _ => s - } - }; - let to_full_string fname => - switch fname { - | Clang {class_name, field_name} => Name.to_string class_name ^ "::" ^ field_name - | _ => to_string fname - }; - - /** Convert a fieldname to a flat string without path. */ - let to_flat_string fn => { - let s = to_string fn; - switch (String.rsplit2 s on::'.') { - | Some (_, s2) => s2 - | _ => s - } - }; - let pp f => - fun - | Hidden => Format.fprintf f "%s" hidden_str - | Java field_name - | Clang {field_name} => Format.fprintf f "%s" field_name; - let pp_latex style f fn => Latex.pp_string style f (to_string fn); - let class_name_replace fname ::f => - switch fname { - | Clang {class_name, field_name} => - let class_name' = f class_name; - if (phys_equal class_name class_name') { - fname - } else { - Clang {class_name: class_name', field_name} - } - | _ => fname - }; - - /** Returns the class part of the fieldname */ - let java_get_class fn => { - let fn = to_string fn; - let ri = String.rindex_exn fn '.'; - String.slice fn 0 ri - }; - - /** Returns the last component of the fieldname */ - let java_get_field fn => { - let fn = to_string fn; - let ri = 1 + String.rindex_exn fn '.'; - String.slice fn ri 0 - }; - - /** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. */ - let java_is_outer_instance fn => { - let fn = to_string fn; - let fn_len = String.length fn; - fn_len != 0 && { - let this = ".this$"; - let last_char = fn.[fn_len - 1]; - (last_char >= '0' && last_char <= '9') && - String.is_suffix fn suffix::(this ^ String.of_char last_char) - } - }; - let clang_get_qual_class = - fun - | Clang {class_name} => Some (Name.qual_name class_name) - | _ => None; - - /** hidded fieldname constant */ - let hidden = Hidden; - - /** hidded fieldname constant */ - let is_hidden fn => equal fn hidden; -}; - -module Struct = { - type field = (Fieldname.t, T.t, Annot.Item.t) [@@deriving compare]; - type fields = list field; - - /** Type for a structured value. */ - type t = { - fields, /** non-static fields */ - statics: fields, /** static fields */ - supers: list Name.t, /** superclasses */ - methods: list Procname.t, /** methods defined */ - annots: Annot.Item.t /** annotations */ - }; - type lookup = Name.t => option t; - let pp pe name f {fields, supers, methods, annots} => - if Config.debug_mode { - /* change false to true to print the details of struct */ - F.fprintf - f - "%a @\n\tfields: {%a@\n\t}@\n\tsupers: {%a@\n\t}@\n\tmethods: {%a@\n\t}@\n\tannots: {%a@\n\t}" - Name.pp - name - ( - Pp.seq ( - fun f (fld, t, a) => - F.fprintf f "@\n\t\t%a %a %a" (pp_full pe) t Fieldname.pp fld Annot.Item.pp a - ) - ) - fields - (Pp.seq (fun f n => F.fprintf f "@\n\t\t%a" Name.pp n)) - supers - (Pp.seq (fun f m => F.fprintf f "@\n\t\t%a" Procname.pp m)) - methods - Annot.Item.pp - annots - } else { - F.fprintf f "%a" Name.pp name - }; - let internal_mk_struct ::default=? ::fields=? ::statics=? ::methods=? ::supers=? ::annots=? () => { - let default_ = {fields: [], statics: [], methods: [], supers: [], annots: Annot.Item.empty}; - let mk_struct_ - ::default=default_ - ::fields=default.fields - ::statics=default.statics - ::methods=default.methods - ::supers=default.supers - ::annots=default.annots - () => { - fields, - statics, - methods, - supers, - annots - }; - mk_struct_ ::?default ::?fields ::?statics ::?methods ::?supers ::?annots () - }; - - /** the element typ of the final extensible array in the given typ, if any */ - let rec get_extensible_array_element_typ ::lookup (typ: T.t) => - switch typ.desc { - | Tarray typ _ _ => Some typ - | Tstruct name => - switch (lookup name) { - | Some {fields} => - switch (List.last fields) { - | 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: T.t) => - switch typ.desc { - | Tstruct name => - switch (lookup name) { - | Some {fields} => - List.find f::(fun (f, _, _) => Fieldname.equal f fn) fields |> - Option.value_map f::snd3 ::default - | None => default - } - | _ => default - }; - let get_field_type_and_annotation ::lookup fn (typ: T.t) => - switch typ.desc { - | Tstruct name - | Tptr {desc: Tstruct name} _ => - switch (lookup name) { - | Some {fields, statics} => - List.find_map - f::(fun (f, t, a) => Fieldname.equal f fn ? Some (t, a) : None) (fields @ statics) - | None => None - } - | _ => None - }; - let objc_ref_counter_annot = [({Annot.class_name: "ref_counter", parameters: []}, false)]; - - /** Field used for objective-c reference counting */ - let objc_ref_counter_field = (Fieldname.hidden, mk (T.Tint IInt), objc_ref_counter_annot); - let is_objc_ref_counter_field (fld, _, a) => - Fieldname.is_hidden fld && Annot.Item.equal a objc_ref_counter_annot; -}; diff --git a/infer/src/IR/Typ.rei b/infer/src/IR/Typ.rei deleted file mode 100644 index 426b91de5..000000000 --- a/infer/src/IR/Typ.rei +++ /dev/null @@ -1,597 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Types */ -module F = Format; - - -/** Kinds of integers */ -type ikind = - | IChar /** [char] */ - | ISChar /** [signed char] */ - | IUChar /** [unsigned char] */ - | IBool /** [bool] */ - | IInt /** [int] */ - | IUInt /** [unsigned int] */ - | IShort /** [short] */ - | IUShort /** [unsigned short] */ - | ILong /** [long] */ - | IULong /** [unsigned long] */ - | ILongLong /** [long long] (or [_int64] on Microsoft Visual C) */ - | IULongLong /** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) */ - | I128 /** [__int128_t] */ - | IU128 /** [__uint128_t] */ -[@@deriving compare]; - - -/** Check wheter the integer kind is a char */ -let ikind_is_char: ikind => bool; - - -/** Check wheter the integer kind is unsigned */ -let ikind_is_unsigned: ikind => bool; - - -/** Convert an int64 into an IntLit.t given the kind: - the int64 is interpreted as unsigned according to the kind */ -let int_of_int64_kind: int64 => ikind => IntLit.t; - - -/** Kinds of floating-point numbers */ -type fkind = - | FFloat /** [float] */ - | FDouble /** [double] */ - | FLongDouble /** [long double] */ -[@@deriving compare]; - - -/** kind of pointer */ -type ptr_kind = - | Pk_pointer /** C/C++, Java, Objc standard/__strong pointer */ - | Pk_reference /** C++ reference */ - | Pk_objc_weak /** Obj-C __weak pointer */ - | Pk_objc_unsafe_unretained /** Obj-C __unsafe_unretained pointer */ - | Pk_objc_autoreleasing /** Obj-C __autoreleasing pointer */ -[@@deriving compare]; - -let equal_ptr_kind: ptr_kind => ptr_kind => bool; - -type type_quals [@@deriving compare]; - -let mk_type_quals: - default::type_quals? => - is_const::bool? => - is_restrict::bool? => - is_volatile::bool? => - unit => - type_quals; - -let is_const: type_quals => bool; - -let is_restrict: type_quals => bool; - -let is_volatile: type_quals => bool; - - -/** types for sil (structured) expressions */ -type t = {desc, quals: type_quals} [@@deriving compare] -and desc = - | Tint ikind /** integer type */ - | Tfloat fkind /** float type */ - | Tvoid /** void type */ - | Tfun bool /** function type with noreturn attribute */ - | Tptr t ptr_kind /** pointer type */ - | Tstruct name /** structured value type name */ - | TVar string /** type variable (ie. C++ template variables) */ - | Tarray t (option IntLit.t) (option IntLit.t) /** array type with statically fixed stride and length */ -[@@deriving compare] -and name = - | CStruct QualifiedCppName.t - | CUnion QualifiedCppName.t - /* qualified name does NOT contain template arguments of the class. It will contain template - args of its parent classes, for example: MyClass::InnerClass will store - "MyClass", "InnerClass" */ - | CppClass QualifiedCppName.t template_spec_info - | JavaClass Mangled.t - | ObjcClass QualifiedCppName.t - | ObjcProtocol QualifiedCppName.t -[@@deriving compare] -and template_spec_info = - | NoTemplate - | Template (list (option t)) -[@@deriving compare]; - - -/** Create Typ.t from given desc. if [default] is passed then use its value to set other fields such as quals */ -let mk: default::t? => quals::type_quals? => desc => t; - - -/** Stores information about type substitution */ -type type_subst_t [@@deriving compare]; - -module Name: { - - /** Named types. */ - type t = name [@@deriving compare]; - - /** Equality for typenames */ - let equal: t => t => bool; - - /** convert the typename to a string */ - let to_string: t => string; - let pp: Format.formatter => t => unit; - - /** [is_class name] holds if [name] names CPP/Objc/Java class */ - let is_class: t => bool; - - /** [is_class name1 name2] holds if [name1] and [name2] name same kind of type */ - let is_same_type: t => t => bool; - - /** name of the typename without qualifier */ - let name: t => string; - - /** qualified name of the type, may return nonsense for Java classes */ - let qual_name: t => QualifiedCppName.t; - let unqualified_name: t => QualifiedCppName.t; - module C: { - let from_string: string => t; - let from_qual_name: QualifiedCppName.t => t; - let union_from_qual_name: QualifiedCppName.t => t; - }; - module Java: { - - /** Create a typename from a Java classname in the form "package.class" */ - let from_string: string => t; - - /** Create a typename from a package name and a class name */ - let from_package_class: string => string => t; - - /** [is_class name] holds if [name] names a Java class */ - let is_class: t => bool; - let java_lang_object: t; - let java_io_serializable: t; - let java_lang_cloneable: t; - }; - module Cpp: { - - /** Create a typename from a C++ classname */ - let from_qual_name: template_spec_info => QualifiedCppName.t => t; - - /** [is_class name] holds if [name] names a C++ class */ - let is_class: t => bool; - }; - module Objc: { - - /** Create a typename from a Objc classname */ - let from_string: string => t; - let from_qual_name: QualifiedCppName.t => t; - let protocol_from_qual_name: QualifiedCppName.t => t; - - /** [is_class name] holds if [name] names a Objc class */ - let is_class: t => bool; - }; - module Set: Caml.Set.S with type elt = t; -}; - - -/** Equality for types. */ -let equal: t => t => bool; - -let equal_desc: desc => desc => bool; - -let equal_quals: type_quals => type_quals => bool; - -let sub_type: type_subst_t => t => t; - -let sub_tname: type_subst_t => Name.t => Name.t; - -let is_type_subst_empty: type_subst_t => bool; - - -/** Sets of types. */ -module Set: Caml.Set.S with type elt = t; - - -/** Maps with type keys. */ -module Map: Caml.Map.S with type key = t; - -module Tbl: Caml.Hashtbl.S with type key = t; - - -/** Pretty print a type with all the details. */ -let pp_full: Pp.env => F.formatter => t => unit; - - -/** Pretty print a type. */ -let pp: Pp.env => F.formatter => t => unit; - -let to_string: t => string; - - -/** Dump a type with all the details. */ -let d_full: t => unit; - - -/** Dump a list of types. */ -let d_list: list t => unit; - - -/** The name of a type */ -let name: t => option Name.t; - - -/** turn a *T into a T. fails if [t] is not a pointer type */ -let strip_ptr: t => t; - - -/** If an array type, return the type of the element. - If not, return the default type if given, otherwise raise an exception */ -let array_elem: option t => t => t; - -let is_objc_class: t => bool; - -let is_cpp_class: t => bool; - -let is_java_class: t => bool; - -let is_array_of_cpp_class: t => bool; - -let is_pointer_to_cpp_class: t => bool; - -let has_block_prefix: string => bool; - - -/** Check if type is a type for a block in objc */ -let is_block_type: t => bool; - -let unsome: string => option t => t; - -type typ = t; - -module Procname: { - - /** Module for Procedure Names. */ - - /** Type of java procedure names. */ - type java; - - /** Type of c procedure names. */ - type c; - - /** Type of Objective C and C++ procedure names. */ - type objc_cpp; - - /** Type of Objective C block names. */ - type block; - - /** Type of procedure names. */ - type t = - | Java java - | C c - | Linters_dummy_method - | Block block - | ObjC_Cpp objc_cpp - [@@deriving compare]; - - /** Equality for proc names. */ - let equal: t => t => bool; - type java_type = (option string, string); - type method_kind = - | Non_Static /* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface */ - | Static /* in Java, procedures called with invokestatic */; - type objc_cpp_method_kind = - | CPPMethod (option string) /** with mangling */ - | CPPConstructor (option string, bool) /** with mangling + is it constexpr? */ - | ObjCClassMethod - | ObjCInstanceMethod - | ObjCInternalMethod; - - /** Hash tables with proc names as keys. */ - module Hash: Caml.Hashtbl.S with type key = t; - - /** Maps from proc names. */ - module Map: PrettyPrintable.PPMap with type key = t; - - /** Sets of proc names. */ - module Set: PrettyPrintable.PPSet with type elt = t; - - /** Create a C procedure name from plain and mangled name. */ - let c: QualifiedCppName.t => string => template_spec_info => is_generic_model::bool => c; - - /** Empty block name. */ - let empty_block: t; - - /** Convert a string to a proc name. */ - let from_string_c_fun: string => t; - - /** Return the language of the procedure. */ - let get_language: t => Config.language; - - /** Return the method/function of a procname. */ - let get_method: t => string; - - /** Hash function for procname. */ - let hash_pname: t => int; - - /** Check if a class string is an anoynmous inner class name. */ - let is_anonymous_inner_class_name: Name.t => bool; - - /** Check if this is an Objective-C/C++ method name. */ - let is_c_method: t => bool; - - /** Check if this is a constructor method in Objective-C. */ - let is_objc_constructor: string => bool; - - /** Check if this is a constructor. */ - let is_constructor: t => bool; - - /** Check if this is a constexpr function. */ - let is_constexpr: t => bool; - - /** Check if this is a Java procedure name. */ - let is_java: t => bool; - - /** Check if this is a dealloc method in Objective-C. */ - let is_objc_dealloc: string => bool; - - /** Check if this is a dealloc method. */ - let is_destructor: t => bool; - - /** Create a Java procedure name from its - class_name method_name args_type_name return_type_name method_kind. */ - let java: Name.t => option java_type => string => list java_type => method_kind => java; - - /** Replace the parameters of a java procname. */ - let java_replace_parameters: java => list java_type => java; - - /** Replace the method of a java procname. */ - let java_replace_return_type: java => java_type => java; - - /** Create an objc block name. */ - let mangled_objc_block: string => t; - - /** Create an objc procedure name from a class_name and method_name. */ - let objc_cpp: - Name.t => - string => - objc_cpp_method_kind => - template_spec_info => - is_generic_model::bool => - objc_cpp; - let get_default_objc_class_method: Name.t => t; - - /** Get the class name of a Objective-C/C++ procedure name. */ - let objc_cpp_get_class_name: objc_cpp => string; - let objc_cpp_get_class_type_name: objc_cpp => Name.t; - - /** Create ObjC method type from a bool is_instance. */ - let objc_method_kind_of_bool: bool => objc_cpp_method_kind; - - /** Return the class name of a java procedure name. */ - let java_get_class_name: java => string; - - /** Return the class name as a typename of a java procedure name. */ - let java_get_class_type_name: java => Name.t; - - /** Return the simple class name of a java procedure name. */ - let java_get_simple_class_name: java => string; - - /** Return the package name of a java procedure name. */ - let java_get_package: java => option string; - - /** Return the method name of a java procedure name. */ - let java_get_method: java => string; - - /** Return the return type of a java procedure name. */ - let java_get_return_type: java => string; - - /** Return the parameters of a java procedure name. */ - let java_get_parameters: java => list java_type; - - /** Return the parameters of a java procname as strings. */ - let java_get_parameters_as_strings: java => list string; - - /** Check if the procedure name is an acess method (e.g. access$100 used to - access private members from a nested class. */ - let java_is_access_method: t => bool; - - /** Check if the procedure name is of an auto-generated method containing '$'. */ - let java_is_autogen_method: t => bool; - - /** Check if the procedure belongs to an anonymous inner class. */ - let java_is_anonymous_inner_class: t => bool; - - /** Check if the procedure name is an anonymous inner class constructor. */ - let java_is_anonymous_inner_class_constructor: t => bool; - - /** Check if the method name is "close". */ - let java_is_close: t => bool; - - /** Check if the java procedure is static. */ - let java_is_static: t => bool; - - /** Check if the proc name has the type of a java vararg. - Note: currently only checks that the last argument has type Object[]. */ - let java_is_vararg: t => bool; - - /** Check if the proc name comes from a lambda expression */ - let java_is_lambda: t => bool; - - /** Check if the proc name comes from generated code */ - let java_is_generated: t => bool; - - /** Check if the last parameter is a hidden inner class, and remove it if present. - This is used in private constructors, where a proxy constructor is generated - with an extra parameter and calls the normal constructor. */ - let java_remove_hidden_inner_class_parameter: t => option t; - - /** Replace the method name of an existing java procname. */ - let java_replace_method: java => string => java; - - /** Convert a java type to a string. */ - let java_type_to_string: java_type => string; - - /** Check if this is a class initializer. */ - let is_class_initializer: t => bool; - - /** Check if this is a special Infer undefined procedure. */ - let is_infer_undefined: t => bool; - - /** Return the name of the global for which this procedure is the initializer if this is an - initializer, None otherwise. */ - let get_global_name_of_initializer: t => option string; - - /** Pretty print a proc name. */ - let pp: Format.formatter => t => unit; - - /** Pretty print a set of proc names. */ - let pp_set: Format.formatter => Set.t => unit; - - /** Replace the class name component of a procedure name. - In case of Java, replace package and class name. */ - let replace_class: t => Name.t => t; - - /** Given a package.class_name string, look for the latest dot and split the string - in two (package, class_name). */ - let split_classname: string => (option string, string); - - /** Convert a proc name to a string for the user to see. */ - let to_string: t => string; - - /** Convert a proc name into a easy string for the user to see in an IDE. */ - let to_simplified_string: withclass::bool? => t => string; - - /** Convert a proc name into a unique identifier. */ - let to_unique_id: t => string; - - /** Convert a proc name to a filename. */ - let to_filename: t => string; - - /** get qualifiers of C/objc/C++ method/function */ - let get_qualifiers: t => QualifiedCppName.t; - - /** get qualifiers of a class owning objc/C++ method */ - let objc_cpp_get_class_qualifiers: objc_cpp => QualifiedCppName.t; - - /** Return type substitution that would produce concrete procname from generic procname. Returns None if - such substitution doesn't exist - NOTE: this function doesn't check if such substitution is correct in terms of return - type/function parameters. - NOTE: this function doesn't deal with nested template classes, it only extracts mapping for function - and/or direct parent (class that defines the method) if it exists. */ - let get_template_args_mapping: t => t => option type_subst_t; -}; - - -/** Return the return type of [pname_java]. */ -let java_proc_return_typ: Procname.java => t; - -module Fieldname: { - - /** Names for fields of class/struct/union */ - type t [@@deriving compare]; - - /** Equality for field names. */ - let equal: t => t => bool; - - /** Set for fieldnames */ - module Set: Caml.Set.S with type elt = t; - - /** Map for fieldnames */ - module Map: Caml.Map.S with type key = t; - module Clang: {let from_class_name: Name.t => string => t;}; - module Java: { - - /** Create a java field name from string */ - let from_string: string => t; - }; - - /** Convert a field name to a string. */ - let to_string: t => string; - let to_full_string: t => string; - let class_name_replace: t => f::(Name.t => Name.t) => t; - - /** Convert a fieldname to a simplified string with at most one-level path. */ - let to_simplified_string: t => string; - - /** Convert a fieldname to a flat string without path. */ - let to_flat_string: t => string; - - /** Pretty print a field name. */ - let pp: Format.formatter => t => unit; - - /** Pretty print a field name in latex. */ - let pp_latex: Latex.style => Format.formatter => t => unit; - - /** The class part of the fieldname */ - let java_get_class: t => string; - - /** The last component of the fieldname */ - let java_get_field: t => string; - - /** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. */ - let java_is_outer_instance: t => bool; - - /** get qualified classname of a field if it's coming from clang frontend. returns None otherwise */ - let clang_get_qual_class: t => option QualifiedCppName.t; - - /** hidded fieldname constant */ - let hidden: t; - - /** hidded fieldname constant */ - let is_hidden: t => bool; -}; - -module Struct: { - type field = (Fieldname.t, typ, Annot.Item.t) [@@deriving compare]; - type fields = list field; - - /** Type for a structured value. */ - type t = - pri { - fields, /** non-static fields */ - statics: fields, /** static fields */ - supers: list Name.t, /** supers */ - methods: list Procname.t, /** methods defined */ - annots: Annot.Item.t /** annotations */ - }; - type lookup = Name.t => option t; - - /** Pretty print a struct type. */ - let pp: Pp.env => Name.t => F.formatter => t => unit; - - /** Construct a struct_typ, normalizing field types */ - let internal_mk_struct: - default::t? => - fields::fields? => - statics::fields? => - methods::list Procname.t? => - supers::list Name.t? => - annots::Annot.Item.t? => - unit => - t; - - /** the element typ of the final extensible array in the given typ, if any */ - let get_extensible_array_element_typ: lookup::lookup => typ => option typ; - - /** If a struct type with field f, return the type of f. - If not, return the default type if given, otherwise raise an exception */ - let fld_typ: lookup::lookup => default::typ => Fieldname.t => typ => typ; - - /** Return the type of the field [fn] and its annotation, None if [typ] has no field named [fn] */ - let get_field_type_and_annotation: - lookup::lookup => Fieldname.t => typ => option (typ, Annot.Item.t); - - /** Field used for objective-c reference counting */ - let objc_ref_counter_field: (Fieldname.t, typ, Annot.Item.t); - let is_objc_ref_counter_field: (Fieldname.t, typ, Annot.Item.t) => bool; -}; diff --git a/infer/src/IR/Unop.ml b/infer/src/IR/Unop.ml new file mode 100644 index 000000000..c5b5641d4 --- /dev/null +++ b/infer/src/IR/Unop.ml @@ -0,0 +1,26 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Unary Operators *) +open! IStd +module L = Logging +module F = Format + +(** Unary operations *) +type t = + | Neg (** Unary minus *) + | BNot (** Bitwise complement (~) *) + | LNot (** Logical Not (!) *) + [@@deriving compare] + +let equal = [%compare.equal : t] + +(** String representation of unary operator. *) +let str = function Neg -> "-" | BNot -> "~" | LNot -> "!" diff --git a/infer/src/IR/Unop.mli b/infer/src/IR/Unop.mli new file mode 100644 index 000000000..30d228fbb --- /dev/null +++ b/infer/src/IR/Unop.mli @@ -0,0 +1,28 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** The Smallfoot Intermediate Language: Unary Operators *) +open! IStd +module L = Logging +module F = Format + +(** Unary operations *) + +type t = + | Neg (** Unary minus *) + | BNot (** Bitwise complement (~) *) + | LNot (** Logical Not (!) *) + [@@deriving compare] + +val equal : t -> t -> bool + +(** String representation of a unary operator. *) + +val str : t -> string diff --git a/infer/src/IR/Unop.re b/infer/src/IR/Unop.re deleted file mode 100644 index 7ec03e9a9..000000000 --- a/infer/src/IR/Unop.re +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Unary Operators */ -module L = Logging; - -module F = Format; - - -/** Unary operations */ -type t = - | Neg /** Unary minus */ - | BNot /** Bitwise complement (~) */ - | LNot /** Logical Not (!) */ -[@@deriving compare]; - -let equal = [%compare.equal : t]; - - -/** String representation of unary operator. */ -let str = - fun - | Neg => "-" - | BNot => "~" - | LNot => "!"; diff --git a/infer/src/IR/Unop.rei b/infer/src/IR/Unop.rei deleted file mode 100644 index c5a517201..000000000 --- a/infer/src/IR/Unop.rei +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** The Smallfoot Intermediate Language: Unary Operators */ -module L = Logging; - -module F = Format; - - -/** Unary operations */ -type t = - | Neg /** Unary minus */ - | BNot /** Bitwise complement (~) */ - | LNot /** Logical Not (!) */ -[@@deriving compare]; - -let equal: t => t => bool; - - -/** String representation of a unary operator. */ -let str: t => string; diff --git a/infer/src/Makefile b/infer/src/Makefile index 609f49868..5a694edc0 100644 --- a/infer/src/Makefile +++ b/infer/src/Makefile @@ -24,7 +24,7 @@ ATDGEN_SUFFIXES = _t.ml _t.mli _j.ml _j.mli GENERATED_OCAML_SOURCES_GLOB = <*{clang_plugin/*,backend/jsonbug_*,checkers/stacktree_*}> -OCAML_FATAL_WARNINGS = +3+5+6+8+10+11+12+18+19+20+21+23+26+29+27+33+34+35+37+38+39+50+52+57 +OCAML_FATAL_WARNINGS = +3+5+6+8+10+11+12+18+19+20+21+23+26+29+27+33+34+35+37+38+39+50+52+57-50 # options for ocamlbuild # Notice that use-ocamlfind is used here to avoid a linker bug in ocamlbuild when using -tag thread @@ -143,7 +143,7 @@ DEPENDENCIES = \ # ocamlbuild command with options common to all build targets OCAMLBUILD_BASE = \ - $(REBUILD) $(OCAMLBUILD_OPTIONS) -j $(NCPU) $(addprefix -I , $(DEPENDENCIES)) \ + $(OCAMLBUILD) $(OCAMLBUILD_OPTIONS) -j $(NCPU) $(addprefix -I , $(DEPENDENCIES)) \ # ocamlbuild with options necessary to build all targets at once, regardless of configure flags OCAMLBUILD_ALL = $(OCAMLBUILD_BASE) $(JAVA_OCAMLBUILD_OPTIONS) @@ -262,16 +262,6 @@ module: base/Version.ml $(OCAML_ALL_SOURCES) mli: $(OCAMLFIND) ocamlc -package atdgen,oUnit,str,unix,yojson,zip $(addprefix -I $(INFER_BUILD_DIR),$(DEPENDENCIES)) -i $(M).ml > $(M).mli -rei: - $(OCAMLFIND) ocamlc -package atdgen,oUnit,str,unix,yojson,zip $(addprefix -I $(INFER_BUILD_DIR),$(DEPENDENCIES)) -i -pp refmt -impl $(M).re > $(M).rei - -# convert to reason -%.re : %.ml - $(SCRIPT_DIR)/refmt.sh -parse ml -print re $< > $*.re - -%.rei : %.mli - $(SCRIPT_DIR)/refmt.sh -parse ml -print re $< > $*.rei - roots:=Infer ifeq ($(IS_FACEBOOK_TREE),yes) roots += $(INFER_CREATE_TRACEVIEW_LINKS_MODULE) @@ -279,15 +269,13 @@ endif clusters:=base clang java IR ml_src_files:=$(shell find $(DEPENDENCIES) -regex '.*\.ml\(i\)*') -re_src_files:=$(shell find $(DEPENDENCIES) -regex '.*\.re\(i\)*') inc_flags:=$(foreach dir,$(DEPENDENCIES),-I $(dir)) root_flags:=$(foreach root,$(roots),-r $(root)) cluster_flags:=$(foreach cluster,$(clusters),-c $(cluster)) -mod_dep.dot: $(ml_src_files) $(re_src_files) +mod_dep.dot: $(ml_src_files) $(MAKE) -C $(DEPENDENCIES_DIR)/ocamldot - { ocamldep.opt $(inc_flags) -ml-synonym .re -mli-synonym .rei $(ml_src_files); \ - ocamldep.opt $(inc_flags) -ml-synonym .re -mli-synonym .rei -pp refmt $(re_src_files); } \ + ocamldep.opt $(inc_flags) $(ml_src_files) \ | $(DEPENDENCIES_DIR)/ocamldot/ocamldot $(cluster_flags) $(root_flags) \ | grep -v -e "\"IList\"\|\"Utils\"" \ > mod_dep.dot @@ -297,7 +285,7 @@ mod_dep.pdf: mod_dep.dot .PHONY: dsort dsort: - $(QUIET)ocamldep.opt -sort $(inc_flags) -ml-synonym .re -mli-synonym .rei $(ml_src_files) -pp refmt $(re_src_files) + $(QUIET)ocamldep.opt -sort $(inc_flags) $(ml_src_files) define to_ocaml_module $(shell \ @@ -335,7 +323,7 @@ checkCopyright: $(CHECKCOPYRIGHT_BIN) $(CHECKCOPYRIGHT_BIN): $(CHECKCOPYRIGHT_MAIN).ml $(MAKEFILE_LIST) $(MKDIR_P) $(BASE_BUILD_DIR) - $(REBUILD) -quiet -r -j $(NCPU) -build-dir $(BASE_BUILD_DIR)/checkCopyright \ + $(OCAMLBUILD) -quiet -r -j $(NCPU) -build-dir $(BASE_BUILD_DIR)/checkCopyright \ -cflags -g,-safe-string -lflags -g \ -pkgs core,str -tag thread -use-ocamlfind $(CHECKCOPYRIGHT_MAIN).native $(INSTALL_PROGRAM) \ @@ -424,6 +412,10 @@ clean: $(REMOVE) mod_dep.dot $(REMOVE) mod_dep.pdf +.PHONY: fmt +fmt: + @$(MAKE) -C $(ROOT_DIR) fmt + # print any variable for Makefile debugging print-%: $(QUIET)echo '$*=$($*)' diff --git a/infer/src/absint/AbstractDomain.ml b/infer/src/absint/AbstractDomain.ml index 60298719a..e62a3df38 100644 --- a/infer/src/absint/AbstractDomain.ml +++ b/infer/src/absint/AbstractDomain.ml @@ -8,15 +8,19 @@ *) open! IStd - module F = Format module type S = sig type astate - val (<=) : lhs:astate -> rhs:astate -> bool (* fst \sqsubseteq snd? *) + val ( <= ) : lhs:astate -> rhs:astate -> bool + + (* fst \sqsubseteq snd? *) + val join : astate -> astate -> astate + val widen : prev:astate -> next:astate -> num_iters:int -> astate + val pp : F.formatter -> astate -> unit end @@ -33,224 +37,211 @@ module type WithTop = sig end module BottomLifted (Domain : S) = struct - type astate = - | Bottom - | NonBottom of Domain.astate + type astate = Bottom | NonBottom of Domain.astate let empty = Bottom - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else - match lhs, rhs with - | Bottom, _ -> true - | _ , Bottom -> false - | NonBottom lhs, NonBottom rhs -> Domain.(<=) ~lhs ~rhs + match (lhs, rhs) with + | Bottom, _ + -> true + | _, Bottom + -> false + | NonBottom lhs, NonBottom rhs + -> Domain.( <= ) ~lhs ~rhs let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else - match astate1, astate2 with - | Bottom, _ -> astate2 - | _, Bottom -> astate1 - | NonBottom a1, NonBottom a2 -> NonBottom (Domain.join a1 a2) + match (astate1, astate2) with + | Bottom, _ + -> astate2 + | _, Bottom + -> astate1 + | NonBottom a1, NonBottom a2 + -> NonBottom (Domain.join a1 a2) let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else - match prev, next with - | Bottom, _ -> next - | _, Bottom -> prev - | NonBottom prev, NonBottom next -> NonBottom (Domain.widen ~prev ~next ~num_iters) - - let pp fmt = function - | Bottom -> F.fprintf fmt "_|_" - | NonBottom astate -> Domain.pp fmt astate + match (prev, next) with + | Bottom, _ + -> next + | _, Bottom + -> prev + | NonBottom prev, NonBottom next + -> NonBottom (Domain.widen ~prev ~next ~num_iters) + + let pp fmt = function Bottom -> F.fprintf fmt "_|_" | NonBottom astate -> Domain.pp fmt astate end -module TopLifted (Domain: S) = struct - type astate = - | Top - | NonTop of Domain.astate +module TopLifted (Domain : S) = struct + type astate = Top | NonTop of Domain.astate let top = Top - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else - match lhs, rhs with - | _, Top -> true - | Top, _ -> false - | NonTop lhs, NonTop rhs -> Domain.(<=) ~lhs ~rhs + match (lhs, rhs) with + | _, Top + -> true + | Top, _ + -> false + | NonTop lhs, NonTop rhs + -> Domain.( <= ) ~lhs ~rhs let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else - match astate1, astate2 with - | Top, _ - | _, Top -> Top - | NonTop a1, NonTop a2 -> NonTop (Domain.join a1 a2) + match (astate1, astate2) with + | Top, _ | _, Top + -> Top + | NonTop a1, NonTop a2 + -> NonTop (Domain.join a1 a2) let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else - match prev, next with - | Top, _ - | _, Top -> Top - | NonTop prev, NonTop next -> NonTop (Domain.widen ~prev ~next ~num_iters) - - let pp fmt = function - | Top -> F.fprintf fmt "T" - | NonTop astate -> Domain.pp fmt astate + match (prev, next) with + | Top, _ | _, Top + -> Top + | NonTop prev, NonTop next + -> NonTop (Domain.widen ~prev ~next ~num_iters) + let pp fmt = function Top -> F.fprintf fmt "T" | NonTop astate -> Domain.pp fmt astate end module Pair (Domain1 : S) (Domain2 : S) = struct type astate = Domain1.astate * Domain2.astate - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true - else - Domain1.(<=) ~lhs:(fst lhs) ~rhs:(fst rhs) && Domain2.(<=) ~lhs:(snd lhs) ~rhs:(snd rhs) + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true + else Domain1.( <= ) ~lhs:(fst lhs) ~rhs:(fst rhs) + && Domain2.( <= ) ~lhs:(snd lhs) ~rhs:(snd rhs) let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 - else Domain1.join (fst astate1) (fst astate2), Domain2.join (snd astate1) (snd astate2) + if phys_equal astate1 astate2 then astate1 + else (Domain1.join (fst astate1) (fst astate2), Domain2.join (snd astate1) (snd astate2)) let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else - Domain1.widen ~prev:(fst prev) ~next:(fst next) ~num_iters, - Domain2.widen ~prev:(snd prev) ~next:(snd next) ~num_iters + ( Domain1.widen ~prev:(fst prev) ~next:(fst next) ~num_iters + , Domain2.widen ~prev:(snd prev) ~next:(snd next) ~num_iters ) - let pp fmt (astate1, astate2) = - F.fprintf fmt "(%a, %a)" Domain1.pp astate1 Domain2.pp astate2 + let pp fmt (astate1, astate2) = F.fprintf fmt "(%a, %a)" Domain1.pp astate1 Domain2.pp astate2 end module FiniteSet (Element : PrettyPrintable.PrintableOrderedType) = struct - include PrettyPrintable.MakePPSet(Element) + include PrettyPrintable.MakePPSet (Element) + type astate = t - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true - else subset lhs rhs + let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true else subset lhs rhs - let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 - else union astate1 astate2 + let join astate1 astate2 = if phys_equal astate1 astate2 then astate1 else union astate1 astate2 - let widen ~prev ~next ~num_iters:_ = - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next end module InvertedSet (S : PrettyPrintable.PPSet) = struct include S + type astate = t - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true - else subset rhs lhs + let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true else subset rhs lhs - let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 - else inter astate1 astate2 + let join astate1 astate2 = if phys_equal astate1 astate2 then astate1 else inter astate1 astate2 - let widen ~prev ~next ~num_iters:_ = - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next end module Map (Key : PrettyPrintable.PrintableOrderedType) (ValueDomain : S) = struct - module M = PrettyPrintable.MakePPMap(Key) + module M = PrettyPrintable.MakePPMap (Key) include M + type astate = ValueDomain.astate M.t (** true if all keys in [lhs] are in [rhs], and each lhs value <= corresponding rhs value *) - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else M.for_all (fun k lhs_v -> - try ValueDomain.(<=) ~lhs:lhs_v ~rhs:(M.find k rhs) - with Not_found -> false) + try ValueDomain.( <= ) ~lhs:lhs_v ~rhs:(M.find k rhs) + with Not_found -> false) lhs let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else M.merge - (fun _ v1_opt v2_opt -> match v1_opt, v2_opt with - | Some v1, Some v2 -> Some (ValueDomain.join v1 v2) - | Some v, _ | _, Some v -> Some v - | None, None -> None) - astate1 - astate2 + (fun _ v1_opt v2_opt -> + match (v1_opt, v2_opt) with + | Some v1, Some v2 + -> Some (ValueDomain.join v1 v2) + | Some v, _ | _, Some v + -> Some v + | None, None + -> None) + astate1 astate2 let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else M.merge - (fun _ v1_opt v2_opt -> match v1_opt, v2_opt with - | Some v1, Some v2 -> Some (ValueDomain.widen ~prev:v1 ~next:v2 ~num_iters) - | Some v, _ | _, Some v -> Some v - | None, None -> None) - prev - next - - let pp fmt astate = - M.pp ~pp_value:ValueDomain.pp fmt astate + (fun _ v1_opt v2_opt -> + match (v1_opt, v2_opt) with + | Some v1, Some v2 + -> Some (ValueDomain.widen ~prev:v1 ~next:v2 ~num_iters) + | Some v, _ | _, Some v + -> Some v + | None, None + -> None) + prev next + + let pp fmt astate = M.pp ~pp_value:ValueDomain.pp fmt astate end module InvertedMap (M : PrettyPrintable.PPMap) (ValueDomain : S) = struct include M + type astate = ValueDomain.astate M.t - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else - try M.for_all (fun k rhs_v -> ValueDomain.(<=) ~lhs:(M.find k lhs) ~rhs:rhs_v) rhs + try M.for_all (fun k rhs_v -> ValueDomain.( <= ) ~lhs:(M.find k lhs) ~rhs:rhs_v) rhs with Not_found -> false let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else M.merge - (fun _ v1_opt v2_opt -> match v1_opt, v2_opt with - | Some v1, Some v2 -> Some (ValueDomain.join v1 v2) - | _ -> None) - astate1 - astate2 + (fun _ v1_opt v2_opt -> + match (v1_opt, v2_opt) with + | Some v1, Some v2 + -> Some (ValueDomain.join v1 v2) + | _ + -> None) + astate1 astate2 let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else M.merge - (fun _ v1_opt v2_opt -> match v1_opt, v2_opt with - | Some v1, Some v2 -> Some (ValueDomain.widen ~prev:v1 ~next:v2 ~num_iters) - | _ -> None) - prev - next + (fun _ v1_opt v2_opt -> + match (v1_opt, v2_opt) with + | Some v1, Some v2 + -> Some (ValueDomain.widen ~prev:v1 ~next:v2 ~num_iters) + | _ + -> None) + prev next - let pp fmt astate = - M.pp ~pp_value:ValueDomain.pp fmt astate + let pp fmt astate = M.pp ~pp_value:ValueDomain.pp fmt astate (* hide empty so we don't accidentally satisfy the WithBottom signature *) let empty = `This_domain_is_not_pointed @@ -259,27 +250,23 @@ end module BooleanAnd = struct type astate = bool - let (<=) ~lhs ~rhs = lhs || not rhs + let ( <= ) ~lhs ~rhs = lhs || not rhs - let join = (&&) + let join = ( && ) - let widen ~prev ~next ~num_iters:_ = - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next - let pp fmt astate = - F.fprintf fmt "%b" astate + let pp fmt astate = F.fprintf fmt "%b" astate end module BooleanOr = struct type astate = bool - let (<=) ~lhs ~rhs = not lhs || rhs + let ( <= ) ~lhs ~rhs = not lhs || rhs - let join = (||) + let join = ( || ) - let widen ~prev ~next ~num_iters:_ = - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next - let pp fmt astate = - F.fprintf fmt "%b" astate + let pp fmt astate = F.fprintf fmt "%b" astate end diff --git a/infer/src/absint/AbstractDomain.mli b/infer/src/absint/AbstractDomain.mli index 4d796f7a8..168602349 100644 --- a/infer/src/absint/AbstractDomain.mli +++ b/infer/src/absint/AbstractDomain.mli @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Abstract domains and domain combinators *) @@ -16,8 +15,8 @@ module F = Format module type S = sig type astate + val ( <= ) : lhs:astate -> rhs:astate -> bool (** the partial order induced by join *) - val (<=) : lhs:astate -> rhs:astate -> bool val join : astate -> astate -> astate @@ -30,10 +29,10 @@ end module type WithBottom = sig include S + val empty : astate (** The bottom value of the domain. Naming it empty instead of bottom helps to bind the empty value for sets and maps to the natural definition for bottom *) - val empty : astate end (** A domain with an explicit top value *) @@ -45,43 +44,41 @@ end (** Lift a pre-domain to a domain *) module BottomLifted (Domain : S) : sig - type astate = - | Bottom - | NonBottom of Domain.astate + type astate = Bottom | NonBottom of Domain.astate include WithBottom with type astate := astate end (** Create a domain with Top element from a pre-domain *) module TopLifted (Domain : S) : sig - type astate = - | Top - | NonTop of Domain.astate + type astate = Top | NonTop of Domain.astate include WithTop with type astate := astate end - (** Cartesian product of two domains. *) module Pair (Domain1 : S) (Domain2 : S) : S with type astate = Domain1.astate * Domain2.astate (** Lift a set to a powerset domain ordered by subset. The elements of the set should be drawn from a *finite* collection of possible values, since the widening operator here is just union. *) module FiniteSet (Element : PrettyPrintable.PrintableOrderedType) : sig - include (module type of PrettyPrintable.MakePPSet(Element)) + include module type of PrettyPrintable.MakePPSet (Element) + include WithBottom with type astate = t end (** Lift a set to a powerset domain ordered by superset, so the join operator is intersection *) module InvertedSet (Set : PrettyPrintable.PPSet) : sig include PrettyPrintable.PPSet with type t = Set.t and type elt = Set.elt + include S with type astate = t end (** Map domain ordered by union over the set of bindings, so the bottom element is the empty map. Every element implicitly maps to bottom unless it is explicitly bound to something else *) module Map (Key : PrettyPrintable.PrintableOrderedType) (ValueDomain : S) : sig - include (module type of PrettyPrintable.MakePPMap(Key)) + include module type of PrettyPrintable.MakePPMap (Key) + include WithBottom with type astate = ValueDomain.astate t end @@ -89,9 +86,11 @@ end map. Every element implictly maps to top unless it is explicitly bound to something else *) module InvertedMap (Map : PrettyPrintable.PPMap) (ValueDomain : S) : sig include PrettyPrintable.PPMap with type 'a t = 'a Map.t and type key = Map.key + include S with type astate = ValueDomain.astate Map.t - val empty : [`This_domain_is_not_pointed] (** this domain doesn't have a natural bottom element *) + val empty : [`This_domain_is_not_pointed] + (** this domain doesn't have a natural bottom element *) end (** Boolean domain ordered by p || ~q. Useful when you want a boolean that's true only when it's diff --git a/infer/src/absint/AbstractInterpreter.ml b/infer/src/absint/AbstractInterpreter.ml index f699375b2..941ed7103 100644 --- a/infer/src/absint/AbstractInterpreter.ml +++ b/infer/src/absint/AbstractInterpreter.ml @@ -8,10 +8,9 @@ *) open! IStd - module L = Logging -type 'a state = { pre: 'a; post: 'a; visit_count: int; } +type 'a state = {pre: 'a; post: 'a; visit_count: int} module type S = sig module TransferFunctions : TransferFunctions.SIL @@ -21,17 +20,12 @@ module type S = sig type invariant_map = TransferFunctions.Domain.astate state InvariantMap.t val compute_post : - ?debug:bool -> - TransferFunctions.extras ProcData.t -> - initial:TransferFunctions.Domain.astate -> - TransferFunctions.Domain.astate option + ?debug:bool -> TransferFunctions.extras ProcData.t -> initial:TransferFunctions.Domain.astate + -> TransferFunctions.Domain.astate option val exec_cfg : - TransferFunctions.CFG.t -> - TransferFunctions.extras ProcData.t -> - initial:TransferFunctions.Domain.astate -> - debug:bool -> - invariant_map + TransferFunctions.CFG.t -> TransferFunctions.extras ProcData.t + -> initial:TransferFunctions.Domain.astate -> debug:bool -> invariant_map val exec_pdesc : TransferFunctions.extras ProcData.t -> initial:TransferFunctions.Domain.astate -> invariant_map @@ -45,8 +39,8 @@ end module MakeNoCFG (Scheduler : Scheduler.S) - (TransferFunctions : TransferFunctions.SIL with module CFG = Scheduler.CFG) = struct - + (TransferFunctions : TransferFunctions.SIL with module CFG = Scheduler.CFG) = +struct module CFG = Scheduler.CFG module InvariantMap = ProcCfg.NodeIdMap (CFG) module TransferFunctions = TransferFunctions @@ -61,53 +55,47 @@ module MakeNoCFG (** extract the postcondition of node [n] from [inv_map] *) let extract_post node_id inv_map = - match extract_state node_id inv_map with - | Some state -> Some state.post - | None -> None + match extract_state node_id inv_map with Some state -> Some state.post | None -> None (** extract the precondition of node [n] from [inv_map] *) let extract_pre node_id inv_map = - match extract_state node_id inv_map with - | Some state -> Some state.pre - | None -> None + match extract_state node_id inv_map with Some state -> Some state.pre | None -> None - let exec_node node astate_pre work_queue inv_map ({ ProcData.pdesc; } as proc_data) ~debug = + let exec_node node astate_pre work_queue inv_map ({ProcData.pdesc} as proc_data) ~debug = let node_id = CFG.id node in let update_inv_map pre visit_count = let compute_post (pre, inv_map) (instr, id_opt) = let post = TransferFunctions.exec_instr pre proc_data node instr in match id_opt with - | Some id -> post, InvariantMap.add id { pre; post; visit_count; } inv_map - | None -> post, inv_map in + | Some id + -> (post, InvariantMap.add id {pre; post; visit_count} inv_map) + | None + -> (post, inv_map) + in (* hack to ensure that we call `exec_instr` on a node even if it has no instructions *) - let instr_ids = match CFG.instr_ids node with - | [] -> [Sil.skip_instr, None] - | l -> l in - if debug then NodePrinter.start_session (CFG.underlying_node node); - let astate_post, inv_map_post = - List.fold ~f:compute_post ~init:(pre, inv_map) instr_ids in - if debug - then - begin + let instr_ids = match CFG.instr_ids node with [] -> [(Sil.skip_instr, None)] | l -> l in + if debug then NodePrinter.start_session (CFG.underlying_node node) ; + let astate_post, inv_map_post = List.fold ~f:compute_post ~init:(pre, inv_map) instr_ids in + ( if debug then let instrs = List.map ~f:fst instr_ids in L.d_strln - (Format.asprintf "PRE: %a@.INSTRS: %aPOST: %a@." - Domain.pp pre (Sil.pp_instr_list Pp.(html Green)) instrs Domain.pp astate_post); - NodePrinter.finish_session (CFG.underlying_node node); - end; + (Format.asprintf "PRE: %a@.INSTRS: %aPOST: %a@." Domain.pp pre + (Sil.pp_instr_list Pp.(html Green)) + instrs Domain.pp astate_post) ; + NodePrinter.finish_session (CFG.underlying_node node) ) ; let inv_map'' = - InvariantMap.add node_id { pre; post=astate_post; visit_count; } inv_map_post in - inv_map'', Scheduler.schedule_succs work_queue node in - if InvariantMap.mem node_id inv_map - then + InvariantMap.add node_id {pre; post= astate_post; visit_count} inv_map_post + in + (inv_map'', Scheduler.schedule_succs work_queue node) + in + if InvariantMap.mem node_id inv_map then let old_state = InvariantMap.find node_id inv_map in let widened_pre = - if CFG.is_loop_head pdesc node - then Domain.widen ~prev:old_state.pre ~next:astate_pre ~num_iters:old_state.visit_count + if CFG.is_loop_head pdesc node then + Domain.widen ~prev:old_state.pre ~next:astate_pre ~num_iters:old_state.visit_count else astate_pre in - if Domain.(<=) ~lhs:widened_pre ~rhs:old_state.pre - then inv_map, work_queue + if Domain.( <= ) ~lhs:widened_pre ~rhs:old_state.pre then (inv_map, work_queue) else update_inv_map widened_pre (old_state.visit_count + 1) else (* first time visiting this node *) @@ -122,34 +110,43 @@ module MakeNoCFG (* if the [pred] -> [node] transition was exceptional, use pre([pred]) *) let extract_pre_f acc pred = extract_pre (CFG.id pred) inv_map :: acc in let all_posts = - List.fold ~f:extract_pre_f ~init:normal_posts (CFG.exceptional_preds cfg node) in + List.fold ~f:extract_pre_f ~init:normal_posts (CFG.exceptional_preds cfg node) + in match List.filter_opt all_posts with - | post :: posts -> Some (List.fold ~f:Domain.join ~init:post posts) - | [] -> None in + | post :: posts + -> Some (List.fold ~f:Domain.join ~init:post posts) + | [] + -> None + in match Scheduler.pop work_queue with - | Some (_, [], work_queue') -> - exec_worklist cfg work_queue' inv_map proc_data ~debug - | Some (node, _, work_queue') -> - let inv_map_post, work_queue_post = match compute_pre node inv_map with - | Some astate_pre -> exec_node node astate_pre work_queue' inv_map proc_data ~debug - | None -> inv_map, work_queue' in + | Some (_, [], work_queue') + -> exec_worklist cfg work_queue' inv_map proc_data ~debug + | Some (node, _, work_queue') + -> let inv_map_post, work_queue_post = + match compute_pre node inv_map with + | Some astate_pre + -> exec_node node astate_pre work_queue' inv_map proc_data ~debug + | None + -> (inv_map, work_queue') + in exec_worklist cfg work_queue_post inv_map_post proc_data ~debug - | None -> - inv_map + | None + -> inv_map (* compute and return an invariant map for [cfg] *) let exec_cfg cfg proc_data ~initial ~debug = let start_node = CFG.start_node cfg in let inv_map', work_queue' = - exec_node start_node initial (Scheduler.empty cfg) InvariantMap.empty proc_data ~debug in + exec_node start_node initial (Scheduler.empty cfg) InvariantMap.empty proc_data ~debug + in exec_worklist cfg work_queue' inv_map' proc_data ~debug (* compute and return an invariant map for [pdesc] *) - let exec_pdesc ({ ProcData.pdesc; } as proc_data) = + let exec_pdesc ({ProcData.pdesc} as proc_data) = exec_cfg (CFG.from_pdesc pdesc) proc_data ~debug:Config.write_html (* compute and return the postcondition of [pdesc] *) - let compute_post ?(debug=Config.write_html) ({ ProcData.pdesc; } as proc_data) ~initial = + let compute_post ?(debug= Config.write_html) ({ProcData.pdesc} as proc_data) ~initial = let cfg = CFG.from_pdesc pdesc in let inv_map = exec_cfg cfg proc_data ~initial ~debug in extract_post (CFG.id (CFG.exit_node cfg)) inv_map @@ -157,6 +154,5 @@ end module MakeWithScheduler (C : ProcCfg.S) (S : Scheduler.Make) (T : TransferFunctions.MakeSIL) = MakeNoCFG (S (C)) (T (C)) - module Make (C : ProcCfg.S) (T : TransferFunctions.MakeSIL) = MakeWithScheduler (C) (Scheduler.ReversePostorder) (T) diff --git a/infer/src/absint/AbstractInterpreter.mli b/infer/src/absint/AbstractInterpreter.mli index 416813068..8c0455612 100644 --- a/infer/src/absint/AbstractInterpreter.mli +++ b/infer/src/absint/AbstractInterpreter.mli @@ -9,7 +9,7 @@ open! IStd -type 'a state = { pre: 'a; post: 'a; visit_count: int; } +type 'a state = {pre: 'a; post: 'a; visit_count: int} (** type of an intraprocedural abstract interpreter *) module type S = sig @@ -20,35 +20,30 @@ module type S = sig (** invariant map from node id -> state representing postcondition for node id *) type invariant_map = TransferFunctions.Domain.astate state InvariantMap.t + val compute_post : + ?debug:bool -> TransferFunctions.extras ProcData.t -> initial:TransferFunctions.Domain.astate + -> TransferFunctions.Domain.astate option (** compute and return the postcondition for the given procedure starting from [initial]. If [debug] is true, print html debugging output. *) - val compute_post : - ?debug:bool -> - TransferFunctions.extras ProcData.t -> - initial:TransferFunctions.Domain.astate -> - TransferFunctions.Domain.astate option + val exec_cfg : + TransferFunctions.CFG.t -> TransferFunctions.extras ProcData.t + -> initial:TransferFunctions.Domain.astate -> debug:bool -> invariant_map (** compute and return invariant map for the given CFG/procedure starting from [initial]. if [debug] is true, print html debugging output. *) - val exec_cfg : - TransferFunctions.CFG.t -> - TransferFunctions.extras ProcData.t -> - initial:TransferFunctions.Domain.astate -> - debug:bool -> - invariant_map - (** compute and return invariant map for the given procedure starting from [initial] *) val exec_pdesc : TransferFunctions.extras ProcData.t -> initial:TransferFunctions.Domain.astate -> invariant_map + (** compute and return invariant map for the given procedure starting from [initial] *) - (** extract the postcondition for a node id from the given invariant map *) val extract_post : InvariantMap.key -> 'a state InvariantMap.t -> 'a option + (** extract the postcondition for a node id from the given invariant map *) - (** extract the precondition for a node id from the given invariant map *) val extract_pre : InvariantMap.key -> 'a state InvariantMap.t -> 'a option + (** extract the precondition for a node id from the given invariant map *) - (** extract the state for a node id from the given invariant map *) val extract_state : InvariantMap.key -> 'a InvariantMap.t -> 'a option + (** extract the state for a node id from the given invariant map *) end (** create an intraprocedural abstract interpreter from a scheduler and transfer functions *) @@ -59,7 +54,5 @@ module MakeNoCFG (** create an intraprocedural abstract interpreter from a CFG and functors for creating a scheduler/ transfer functions from a CFG *) -module Make - (CFG : ProcCfg.S) - (MakeTransferFunctions : TransferFunctions.MakeSIL) : +module Make (CFG : ProcCfg.S) (MakeTransferFunctions : TransferFunctions.MakeSIL) : S with module TransferFunctions = MakeTransferFunctions(CFG) diff --git a/infer/src/absint/Checkers.ml b/infer/src/absint/Checkers.ml index 70cbd7e9a..56046244d 100644 --- a/infer/src/absint/Checkers.ml +++ b/infer/src/absint/Checkers.ml @@ -20,36 +20,30 @@ module PP = struct and [nafter] lines after [loc] *) let pp_loc_range linereader nbefore nafter fmt loc = let printline n = - match Printer.LineReader.from_loc linereader { loc with Location.line = n } with - | Some s -> F.fprintf fmt "%s%s@\n" (if Int.equal n loc.Location.line then "-->" else " ") s - | _ -> () in - F.fprintf fmt "%a:%d@\n" SourceFile.pp loc.Location.file loc.Location.line; + match Printer.LineReader.from_loc linereader {loc with Location.line= n} with + | Some s + -> F.fprintf fmt "%s%s@\n" (if Int.equal n loc.Location.line then "-->" else " ") s + | _ + -> () + in + F.fprintf fmt "%a:%d@\n" SourceFile.pp loc.Location.file loc.Location.line ; for n = loc.Location.line - nbefore to loc.Location.line + nafter do printline n done -end (* PP *) +end +(* PP *) (** State that persists in the .specs files. *) module ST = struct - - let report_error tenv - proc_name - proc_desc - kind - loc - ?(advice = None) - ?(field_name = None) - ?(origin_loc = None) - ?(exception_kind = fun k d -> Exceptions.Checkers (k, d)) - ?(always_report = false) - description = + let report_error tenv proc_name proc_desc kind loc ?(advice= None) ?(field_name= None) + ?(origin_loc= None) ?(exception_kind= fun k d -> Exceptions.Checkers (k, d)) + ?(always_report= false) description = let lookup = Tenv.lookup tenv in - let localized_description = Localise.custom_desc_with_advice - description - (Option.value ~default:"" advice) - [("always_report", string_of_bool always_report)] in + let localized_description = + Localise.custom_desc_with_advice description (Option.value ~default:"" advice) + [("always_report", string_of_bool always_report)] + in let exn = exception_kind (Localise.to_issue_id kind) localized_description in let proc_attributes = Specs.pdesc_resolve_attributes proc_desc in - (* Errors can be suppressed with annotations. An error of kind CHECKER_ERROR_NAME can be suppressed with the following annotations: - @android.annotation.SuppressLint("checker-error-name") @@ -57,63 +51,61 @@ module ST = struct where the kind matching is case - insensitive and ignores '-' and '_' characters. *) let suppressed = let annotation_matches (a: Annot.t) = - let normalize str = - Str.global_replace (Str.regexp "[_-]") "" (String.lowercase str) in - let drop_prefix str = - Str.replace_first (Str.regexp "^[A-Za-z]+_") "" str in - let normalized_equal s1 s2 = - String.equal (normalize s1) (normalize s2) in - + let normalize str = Str.global_replace (Str.regexp "[_-]") "" (String.lowercase str) in + let drop_prefix str = Str.replace_first (Str.regexp "^[A-Za-z]+_") "" str in + let normalized_equal s1 s2 = String.equal (normalize s1) (normalize s2) in let is_parameter_suppressed = - String.is_suffix a.class_name ~suffix:Annotations.suppress_lint && - List.mem ~equal:normalized_equal a.parameters (Localise.to_issue_id kind) in + String.is_suffix a.class_name ~suffix:Annotations.suppress_lint + && List.mem ~equal:normalized_equal a.parameters (Localise.to_issue_id kind) + in let is_annotation_suppressed = String.is_suffix ~suffix:(normalize (drop_prefix (Localise.to_issue_id kind))) - (normalize a.class_name) in - - is_parameter_suppressed || is_annotation_suppressed in - + (normalize a.class_name) + in + is_parameter_suppressed || is_annotation_suppressed + in let is_method_suppressed = - Annotations.ma_has_annotation_with - proc_attributes.ProcAttributes.method_annotation - annotation_matches in - + Annotations.ma_has_annotation_with proc_attributes.ProcAttributes.method_annotation + annotation_matches + in let is_field_suppressed = - match field_name, PatternMatch.get_this_type proc_attributes with - | Some field_name, Some t -> begin - match Typ.Struct.get_field_type_and_annotation ~lookup field_name t with - | Some (_, ia) -> Annotations.ia_has_annotation_with ia annotation_matches - | None -> false - end - | _ -> false in - + match (field_name, PatternMatch.get_this_type proc_attributes) with + | Some field_name, Some t -> ( + match Typ.Struct.get_field_type_and_annotation ~lookup field_name t with + | Some (_, ia) + -> Annotations.ia_has_annotation_with ia annotation_matches + | None + -> false ) + | _ + -> false + in let is_class_suppressed = match PatternMatch.get_this_type proc_attributes with - | Some t -> begin - match (PatternMatch.type_get_annotation tenv t) with - | Some ia -> Annotations.ia_has_annotation_with ia annotation_matches - | None -> false - end - | None -> false in - - is_method_suppressed || is_field_suppressed || is_class_suppressed in - + | Some t -> ( + match PatternMatch.type_get_annotation tenv t with + | Some ia + -> Annotations.ia_has_annotation_with ia annotation_matches + | None + -> false ) + | None + -> false + in + is_method_suppressed || is_field_suppressed || is_class_suppressed + in let trace = let origin_elements = match origin_loc with - | Some oloc -> [Errlog.make_trace_element 0 oloc "origin" []] - | None -> [] in + | Some oloc + -> [Errlog.make_trace_element 0 oloc "origin" []] + | None + -> [] + in origin_elements @ [Errlog.make_trace_element 0 loc description []] in - - if not suppressed then - begin - L.progress "%s: %a: %s@\n" - (Localise.to_issue_id kind) - SourceFile.pp loc.Location.file - (Typ.Procname.to_string proc_name); - L.progress "%s@." description; - Reporting.log_error_deprecated proc_name ~loc ~ltr:trace exn - end + if not suppressed then ( + L.progress "%s: %a: %s@\n" (Localise.to_issue_id kind) SourceFile.pp loc.Location.file + (Typ.Procname.to_string proc_name) ; + L.progress "%s@." description ; + Reporting.log_error_deprecated proc_name ~loc ~ltr:trace exn ) end diff --git a/infer/src/absint/Checkers.mli b/infer/src/absint/Checkers.mli index 18f19608e..5bd852185 100644 --- a/infer/src/absint/Checkers.mli +++ b/infer/src/absint/Checkers.mli @@ -11,29 +11,21 @@ open! IStd (** Module for user-defined checkers. *) - (** State that persists in the .specs files. *) module ST : sig - + val report_error : + Tenv.t -> Typ.Procname.t -> Procdesc.t -> Localise.t -> Location.t -> ?advice:string option + -> ?field_name:Typ.Fieldname.t option -> ?origin_loc:Location.t option + -> ?exception_kind:(string -> Localise.error_desc -> exn) -> ?always_report:bool -> string + -> unit (** Report an error. *) - val report_error: - Tenv.t -> - Typ.Procname.t -> - Procdesc.t -> - Localise.t -> - Location.t -> - ?advice: string option -> - ?field_name: Typ.Fieldname.t option -> - ?origin_loc: Location.t option -> - ?exception_kind: (string -> Localise.error_desc -> exn) -> - ?always_report: bool -> - string -> - unit +end -end (* ST *) +(* ST *) module PP : sig + val pp_loc_range : Printer.LineReader.t -> int -> int -> Format.formatter -> Location.t -> unit (** Print a range of lines of the source file in [loc], including [nbefore] lines before loc and [nafter] lines after [loc] *) - val pp_loc_range : Printer.LineReader.t -> int -> int -> Format.formatter -> Location.t -> unit -end (* PP *) +end +(* PP *) diff --git a/infer/src/absint/FormalMap.ml b/infer/src/absint/FormalMap.ml index d7ebbd9c2..6ccb1d903 100644 --- a/infer/src/absint/FormalMap.ml +++ b/infer/src/absint/FormalMap.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -20,13 +19,13 @@ let make pdesc = let formals_with_nums = List.mapi ~f:(fun index (name, typ) -> - let pvar = Pvar.mk name pname in - AccessPath.base_of_pvar pvar typ, index) - attrs.ProcAttributes.formals in + let pvar = Pvar.mk name pname in + (AccessPath.base_of_pvar pvar typ, index)) + attrs.ProcAttributes.formals + in List.fold ~f:(fun formal_map (base, index) -> AccessPath.BaseMap.add base index formal_map) - ~init:AccessPath.BaseMap.empty - formals_with_nums + ~init:AccessPath.BaseMap.empty formals_with_nums let empty = AccessPath.BaseMap.empty diff --git a/infer/src/absint/FormalMap.mli b/infer/src/absint/FormalMap.mli index 7dd59ba1d..afa53a3b6 100644 --- a/infer/src/absint/FormalMap.mli +++ b/infer/src/absint/FormalMap.mli @@ -8,25 +8,24 @@ *) open! IStd - module F = Format module L = Logging (** a map from a formal to its positional index *) type t -(** create a formal map for the given procdesc *) val make : Procdesc.t -> t +(** create a formal map for the given procdesc *) -(** the empty formal map *) val empty : t +(** the empty formal map *) -(** return true if the given base var is a formal according to the given formal map *) val is_formal : AccessPath.base -> t -> bool +(** return true if the given base var is a formal according to the given formal map *) -(** return the index for the given base var if it is a formal, or None if it is not *) val get_formal_index : AccessPath.base -> t -> int option +(** return the index for the given base var if it is a formal, or None if it is not *) +val get_formal_base : int -> t -> AccessPath.base option (** return the base var for the given index if it exists, or None if it does not. Note: this is linear in the size of the formal map *) -val get_formal_base : int -> t -> AccessPath.base option diff --git a/infer/src/absint/LowerHil.ml b/infer/src/absint/LowerHil.ml index 5361b20b4..a3e5b68be 100644 --- a/infer/src/absint/LowerHil.ml +++ b/infer/src/absint/LowerHil.ml @@ -8,52 +8,39 @@ *) open! IStd - module L = Logging module Make (MakeTransferFunctions : TransferFunctions.MakeHIL) (CFG : ProcCfg.S) = struct module TransferFunctions = MakeTransferFunctions (CFG) module CFG = TransferFunctions.CFG module Domain = AbstractDomain.Pair (TransferFunctions.Domain) (IdAccessPathMapDomain) - type extras = TransferFunctions.extras - let exec_instr ((actual_state, id_map) as astate) extras node instr = + type extras = TransferFunctions.extras + let exec_instr (actual_state, id_map as astate) extras node instr = let f_resolve_id id = try Some (IdAccessPathMapDomain.find id id_map) - with Not_found -> None in + with Not_found -> None + in match HilInstr.of_sil ~f_resolve_id instr with - | Bind (id, access_path) -> - let id_map' = IdAccessPathMapDomain.add id access_path id_map in - if phys_equal id_map id_map' - then astate - else actual_state, id_map' - | Unbind ids -> - let id_map' = - List.fold - ~f:(fun acc id -> IdAccessPathMapDomain.remove id acc) ~init:id_map ids in - if phys_equal id_map id_map' - then astate - else actual_state, id_map' - | Instr hil_instr -> - let actual_state' = TransferFunctions.exec_instr actual_state extras node hil_instr in - if Config.write_html - then - begin + | Bind (id, access_path) + -> let id_map' = IdAccessPathMapDomain.add id access_path id_map in + if phys_equal id_map id_map' then astate else (actual_state, id_map') + | Unbind ids + -> let id_map' = + List.fold ~f:(fun acc id -> IdAccessPathMapDomain.remove id acc) ~init:id_map ids + in + if phys_equal id_map id_map' then astate else (actual_state, id_map') + | Instr hil_instr + -> let actual_state' = TransferFunctions.exec_instr actual_state extras node hil_instr in + ( if Config.write_html then let underyling_node = CFG.underlying_node node in - NodePrinter.start_session underyling_node; + NodePrinter.start_session underyling_node ; L.d_strln - (Format.asprintf - "PRE: %a@.INSTR: %a@.POST: %a@." - TransferFunctions.Domain.pp (fst astate) - HilInstr.pp hil_instr - TransferFunctions.Domain.pp actual_state'); - NodePrinter.finish_session underyling_node; - end; - - if phys_equal actual_state actual_state' - then astate - else actual_state', id_map - | Ignore -> - astate + (Format.asprintf "PRE: %a@.INSTR: %a@.POST: %a@." TransferFunctions.Domain.pp + (fst astate) HilInstr.pp hil_instr TransferFunctions.Domain.pp actual_state') ; + NodePrinter.finish_session underyling_node ) ; + if phys_equal actual_state actual_state' then astate else (actual_state', id_map) + | Ignore + -> astate end diff --git a/infer/src/absint/LowerHil.mli b/infer/src/absint/LowerHil.mli index 7e2ae4254..9182e5625 100644 --- a/infer/src/absint/LowerHil.mli +++ b/infer/src/absint/LowerHil.mli @@ -11,12 +11,12 @@ open! IStd (** Functor for turning HIL transfer functions into SIL transfer functions *) module Make (MakeTransferFunctions : TransferFunctions.MakeHIL) (CFG : ProcCfg.S) : sig - module TransferFunctions : module type of (MakeTransferFunctions (CFG)) + module TransferFunctions : module type of MakeTransferFunctions (CFG) module CFG : module type of TransferFunctions.CFG module Domain : - module type of AbstractDomain.Pair (TransferFunctions.Domain) (IdAccessPathMapDomain) + module type of AbstractDomain.Pair (TransferFunctions.Domain) (IdAccessPathMapDomain) type extras = TransferFunctions.extras diff --git a/infer/src/absint/NodePrinter.ml b/infer/src/absint/NodePrinter.ml index c5a8551cd..1050301cc 100644 --- a/infer/src/absint/NodePrinter.ml +++ b/infer/src/absint/NodePrinter.ml @@ -8,8 +8,6 @@ *) open! IStd - - module L = Logging module F = Format @@ -20,22 +18,16 @@ let new_session node = let pname = Procdesc.Node.get_proc_name node in let node_id = (Procdesc.Node.get_id node :> int) in match Specs.get_summary pname with - | None -> - 0 - | Some summary -> - summary.stats.nodes_visited_fp <- IntSet.add node_id summary.stats.nodes_visited_fp; - incr summary.Specs.sessions; + | None + -> 0 + | Some summary + -> (summary.stats).nodes_visited_fp <- IntSet.add node_id summary.stats.nodes_visited_fp ; + incr summary.Specs.sessions ; !(summary.Specs.sessions) let start_session node = if Config.write_html then - begin - let session = new_session node in - Printer.node_start_session node session - end + let session = new_session node in + Printer.node_start_session node session -let finish_session node = - if Config.write_html then - begin - Printer.node_finish_session node - end +let finish_session node = if Config.write_html then Printer.node_finish_session node diff --git a/infer/src/absint/NodePrinter.mli b/infer/src/absint/NodePrinter.mli index 4a9087c68..764e16467 100644 --- a/infer/src/absint/NodePrinter.mli +++ b/infer/src/absint/NodePrinter.mli @@ -11,8 +11,8 @@ open! IStd (** Simplified html node printer for checkers *) -(** To be called before analyzing a node *) val start_session : Procdesc.Node.t -> unit +(** To be called before analyzing a node *) -(** To be called after analyzing a node *) val finish_session : Procdesc.Node.t -> unit +(** To be called after analyzing a node *) diff --git a/infer/src/absint/PatternMatch.ml b/infer/src/absint/PatternMatch.ml index ecd4b85c7..cd8587041 100644 --- a/infer/src/absint/PatternMatch.ml +++ b/infer/src/absint/PatternMatch.ml @@ -14,175 +14,180 @@ open! IStd module L = Logging module F = Format -type taint_spec = { - classname : string; - method_name : string; - ret_type : string; - params : string list; - is_static : bool; - taint_kind : PredSymb.taint_kind; - language : Config.language -} +type taint_spec = + { classname: string + ; method_name: string + ; ret_type: string + ; params: string list + ; is_static: bool + ; taint_kind: PredSymb.taint_kind + ; language: Config.language } let type_is_object typ = match typ.Typ.desc with - | Tptr ({desc=Tstruct name}, _) -> Typ.Name.equal name Typ.Name.Java.java_lang_object - | _ -> false + | Tptr ({desc= Tstruct name}, _) + -> Typ.Name.equal name Typ.Name.Java.java_lang_object + | _ + -> false let java_proc_name_with_class_method pn_java class_with_path method_name = - (try - String.equal (Typ.Procname.java_get_class_name pn_java) class_with_path && - String.equal (Typ.Procname.java_get_method pn_java) method_name - with _ -> false) + try + String.equal (Typ.Procname.java_get_class_name pn_java) class_with_path + && String.equal (Typ.Procname.java_get_method pn_java) method_name + with _ -> false (** Holds iff the predicate holds on a supertype of the named type, including the type itself *) let rec supertype_exists tenv pred name = match Tenv.lookup tenv name with - | Some ({supers} as struct_typ) -> - pred name struct_typ || List.exists ~f:(fun name -> supertype_exists tenv pred name) supers - | None -> - false + | Some ({supers} as struct_typ) + -> pred name struct_typ || List.exists ~f:(fun name -> supertype_exists tenv pred name) supers + | None + -> false let rec supertype_find_map_opt tenv f name = match Tenv.lookup tenv name with - | Some ({supers} as struct_typ) -> - begin - match f name struct_typ with - | None -> List.find_map ~f:(supertype_find_map_opt tenv f) supers - | result -> result - end - | None -> - None + | Some ({supers} as struct_typ) -> ( + match f name struct_typ with + | None + -> List.find_map ~f:(supertype_find_map_opt tenv f) supers + | result + -> result ) + | None + -> None let is_immediate_subtype tenv this_type_name super_type_name = match Tenv.lookup tenv this_type_name with - | Some {supers} -> List.exists ~f:(Typ.Name.equal super_type_name) supers - | None -> false + | Some {supers} + -> List.exists ~f:(Typ.Name.equal super_type_name) supers + | None + -> false (** return true if [typ0] <: [typ1] *) let is_subtype tenv name0 name1 = - Typ.Name.equal name0 name1 || - supertype_exists tenv (fun name _ -> Typ.Name.equal name name1) name0 + Typ.Name.equal name0 name1 + || supertype_exists tenv (fun name _ -> Typ.Name.equal name name1) name0 let is_subtype_of_str tenv cn1 classname_str = let typename = Typ.Name.Java.from_string classname_str in is_subtype tenv cn1 typename (** The type the method is invoked on *) -let get_this_type proc_attributes = match proc_attributes.ProcAttributes.formals with - | (_, t) :: _ -> Some t - | _ -> None +let get_this_type proc_attributes = + match proc_attributes.ProcAttributes.formals with (_, t) :: _ -> Some t | _ -> None let type_get_direct_supertypes tenv (typ: Typ.t) = match typ.desc with - | Tptr ({desc=Tstruct name}, _) - | Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { supers } -> supers - | None -> [] - ) - | _ -> - [] - -let type_get_class_name {Typ.desc} = match desc with - | Typ.Tptr (typ, _) -> Typ.name typ - | _ -> None - -let type_get_annotation tenv (typ: Typ.t): Annot.Item.t option = + | Tptr ({desc= Tstruct name}, _) | Tstruct name -> ( + match Tenv.lookup tenv name with Some {supers} -> supers | None -> [] ) + | _ + -> [] + +let type_get_class_name {Typ.desc} = + match desc with Typ.Tptr (typ, _) -> Typ.name typ | _ -> None + +let type_get_annotation tenv (typ: Typ.t) : Annot.Item.t option = match typ.desc with - | Tptr ({desc=Tstruct name}, _) - | Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { annots } -> Some annots - | None -> None - ) - | _ -> None + | Tptr ({desc= Tstruct name}, _) | Tstruct name -> ( + match Tenv.lookup tenv name with Some {annots} -> Some annots | None -> None ) + | _ + -> None let rec get_type_name {Typ.desc} = match desc with - | Typ.Tstruct name -> - Typ.Name.name name - | Typ.Tptr (t, _) -> get_type_name t - | _ -> "_" - -let get_field_type_name tenv - (typ: Typ.t) - (fieldname: Typ.Fieldname.t): string option = + | Typ.Tstruct name + -> Typ.Name.name name + | Typ.Tptr (t, _) + -> get_type_name t + | _ + -> "_" + +let get_field_type_name tenv (typ: Typ.t) (fieldname: Typ.Fieldname.t) : string option = match typ.desc with - | Tstruct name | Tptr ({desc=Tstruct name}, _) -> ( - match Tenv.lookup tenv name with - | Some { fields } -> ( - match List.find - ~f:(function | (fn, _, _) -> Typ.Fieldname.equal fn fieldname) - fields with - | Some (_, ft, _) -> Some (get_type_name ft) - | None -> None - ) - | None -> None - ) - | _ -> None - -let java_get_const_type_name - (const: Const.t): string = + | Tstruct name | Tptr ({desc= Tstruct name}, _) -> ( + match Tenv.lookup tenv name with + | Some {fields} -> ( + match List.find ~f:(function fn, _, _ -> Typ.Fieldname.equal fn fieldname) fields with + | Some (_, ft, _) + -> Some (get_type_name ft) + | None + -> None ) + | None + -> None ) + | _ + -> None + +let java_get_const_type_name (const: Const.t) : string = match const with - | Const.Cstr _ -> "java.lang.String" - | Const.Cint _ -> "java.lang.Integer" - | Const.Cfloat _ -> "java.lang.Double" - | _ -> "_" - -let get_vararg_type_names tenv - (call_node: Procdesc.Node.t) - (ivar: Pvar.t): string list = + | Const.Cstr _ + -> "java.lang.String" + | Const.Cint _ + -> "java.lang.Integer" + | Const.Cfloat _ + -> "java.lang.Double" + | _ + -> "_" + +let get_vararg_type_names tenv (call_node: Procdesc.Node.t) (ivar: Pvar.t) : string list = (* Is this the node creating ivar? *) let rec initializes_array instrs = match instrs with - | Sil.Call (Some (t1, _), Exp.Const (Const.Cfun pn), _, _, _):: - Sil.Store (Exp.Lvar iv, _, Exp.Var t2, _):: is -> - (Pvar.equal ivar iv && Ident.equal t1 t2 && - Typ.Procname.equal pn (Typ.Procname.from_string_c_fun "__new_array")) + | (Sil.Call (Some (t1, _), Exp.Const Const.Cfun pn, _, _, _)) + :: (Sil.Store (Exp.Lvar iv, _, Exp.Var t2, _)) :: is + -> Pvar.equal ivar iv && Ident.equal t1 t2 + && Typ.Procname.equal pn (Typ.Procname.from_string_c_fun "__new_array") || initializes_array is - | _:: is -> initializes_array is - | _ -> false in - + | _ :: is + -> initializes_array is + | _ + -> false + in (* Get the type name added to ivar or None *) let added_type_name node = let rec nvar_type_name nvar instrs = match instrs with - | Sil.Load (nv, Exp.Lfield (_, id, t), _, _):: _ - when Ident.equal nv nvar -> get_field_type_name tenv t id - | Sil.Load (nv, _, t, _):: _ - when Ident.equal nv nvar -> - Some (get_type_name t) - | _:: is -> nvar_type_name nvar is - | _ -> None in + | (Sil.Load (nv, Exp.Lfield (_, id, t), _, _)) :: _ when Ident.equal nv nvar + -> get_field_type_name tenv t id + | (Sil.Load (nv, _, t, _)) :: _ when Ident.equal nv nvar + -> Some (get_type_name t) + | _ :: is + -> nvar_type_name nvar is + | _ + -> None + in let rec added_nvar array_nvar instrs = match instrs with - | Sil.Store (Exp.Lindex (Exp.Var iv, _), _, Exp.Var nvar, _):: _ - when Ident.equal iv array_nvar -> nvar_type_name nvar (Procdesc.Node.get_instrs node) - | Sil.Store (Exp.Lindex (Exp.Var iv, _), _, Exp.Const c, _):: _ - when Ident.equal iv array_nvar -> Some (java_get_const_type_name c) - | _:: is -> added_nvar array_nvar is - | _ -> None in + | (Sil.Store (Exp.Lindex (Exp.Var iv, _), _, Exp.Var nvar, _)) :: _ + when Ident.equal iv array_nvar + -> nvar_type_name nvar (Procdesc.Node.get_instrs node) + | (Sil.Store (Exp.Lindex (Exp.Var iv, _), _, Exp.Const c, _)) :: _ + when Ident.equal iv array_nvar + -> Some (java_get_const_type_name c) + | _ :: is + -> added_nvar array_nvar is + | _ + -> None + in let rec array_nvar instrs = match instrs with - | Sil.Load (nv, Exp.Lvar iv, _, _):: _ - when Pvar.equal iv ivar -> - added_nvar nv instrs - | _:: is -> array_nvar is - | _ -> None in - array_nvar (Procdesc.Node.get_instrs node) in - + | (Sil.Load (nv, Exp.Lvar iv, _, _)) :: _ when Pvar.equal iv ivar + -> added_nvar nv instrs + | _ :: is + -> array_nvar is + | _ + -> None + in + array_nvar (Procdesc.Node.get_instrs node) + in (* Walk nodes backward until definition of ivar, adding type names *) let rec type_names node = - if initializes_array (Procdesc.Node.get_instrs node) then - [] + if initializes_array (Procdesc.Node.get_instrs node) then [] else - match (Procdesc.Node.get_preds node) with - | [n] -> (match (added_type_name node) with - | Some name -> name:: type_names n - | None -> type_names n) - | _ -> raise Not_found in - + match Procdesc.Node.get_preds node with + | [n] -> ( + match added_type_name node with Some name -> name :: type_names n | None -> type_names n ) + | _ + -> raise Not_found + in List.rev (type_names call_node) let has_formal_proc_argument_type_names proc_desc argument_type_names = @@ -192,8 +197,8 @@ let has_formal_proc_argument_type_names proc_desc argument_type_names = && List.for_all2_exn ~f:equal_formal_arg formals argument_type_names let has_formal_method_argument_type_names cfg pname_java argument_type_names = - has_formal_proc_argument_type_names - cfg ((Typ.Procname.java_get_class_name pname_java):: argument_type_names) + has_formal_proc_argument_type_names cfg + (Typ.Procname.java_get_class_name pname_java :: argument_type_names) let is_getter pname_java = Str.string_match (Str.regexp "get*") (Typ.Procname.java_get_method pname_java) 0 @@ -203,153 +208,162 @@ let is_setter pname_java = (** Returns the signature of a field access (class name, field name, field type name) *) let get_java_field_access_signature = function - | Sil.Load (_, Exp.Lfield (_, fn, ft), bt, _) -> - Some (get_type_name bt, Typ.Fieldname.java_get_field fn, get_type_name ft) - | _ -> None + | Sil.Load (_, Exp.Lfield (_, fn, ft), bt, _) + -> Some (get_type_name bt, Typ.Fieldname.java_get_field fn, get_type_name ft) + | _ + -> None (** Returns the formal signature (class name, method name, argument type names and return type name) *) let get_java_method_call_formal_signature = function - | Sil.Call (_, Exp.Const (Const.Cfun pn), (_, tt):: args, _, _) -> - (match pn with - | Typ.Procname.Java pn_java -> - let arg_names = List.map ~f:(function | _, t -> get_type_name t) args in - let rt_name = Typ.Procname.java_get_return_type pn_java in - let m_name = Typ.Procname.java_get_method pn_java in - Some (get_type_name tt, m_name, arg_names, rt_name) - | _ -> - None) - | _ -> None - + | Sil.Call (_, Exp.Const Const.Cfun pn, (_, tt) :: args, _, _) -> ( + match pn with + | Typ.Procname.Java pn_java + -> let arg_names = List.map ~f:(function _, t -> get_type_name t) args in + let rt_name = Typ.Procname.java_get_return_type pn_java in + let m_name = Typ.Procname.java_get_method pn_java in + Some (get_type_name tt, m_name, arg_names, rt_name) + | _ + -> None ) + | _ + -> None let type_is_class typ = match typ.Typ.desc with - | Tptr ({desc=Tstruct _}, _) -> true - | Tptr ({desc=Tarray _}, _) -> true - | Tstruct _ -> true - | _ -> false + | Tptr ({desc= Tstruct _}, _) + -> true + | Tptr ({desc= Tarray _}, _) + -> true + | Tstruct _ + -> true + | _ + -> false let initializer_classes = List.map ~f:Typ.Name.Java.from_string - [ - "android.app.Activity"; - "android.app.Application"; - "android.app.Fragment"; - "android.app.Service"; - "android.support.v4.app.Fragment"; - "junit.framework.TestCase"; - ] - -let initializer_methods = [ - "onActivityCreated"; - "onAttach"; - "onCreate"; - "onCreateView"; - "setUp"; -] + [ "android.app.Activity" + ; "android.app.Application" + ; "android.app.Fragment" + ; "android.app.Service" + ; "android.support.v4.app.Fragment" + ; "junit.framework.TestCase" ] + +let initializer_methods = ["onActivityCreated"; "onAttach"; "onCreate"; "onCreateView"; "setUp"] (** Check if the type has in its supertypes from the initializer_classes list. *) -let type_has_initializer (tenv: Tenv.t) (t: Typ.t): bool = +let type_has_initializer (tenv: Tenv.t) (t: Typ.t) : bool = let is_initializer_class typename _ = - List.mem ~equal:Typ.Name.equal initializer_classes typename in + List.mem ~equal:Typ.Name.equal initializer_classes typename + in match t.desc with - | Typ.Tstruct name | Tptr ({ desc=Tstruct name}, _) -> - supertype_exists tenv is_initializer_class name - | _ -> - false + | Typ.Tstruct name | Tptr ({desc= Tstruct name}, _) + -> supertype_exists tenv is_initializer_class name + | _ + -> false (** Check if the method is one of the known initializer methods. *) -let method_is_initializer - (tenv: Tenv.t) - (proc_attributes: ProcAttributes.t) : bool = +let method_is_initializer (tenv: Tenv.t) (proc_attributes: ProcAttributes.t) : bool = match get_this_type proc_attributes with - | Some this_type -> - if type_has_initializer tenv this_type then + | Some this_type + -> if type_has_initializer tenv this_type then match proc_attributes.ProcAttributes.proc_name with - | Typ.Procname.Java pname_java -> - let mname = Typ.Procname.java_get_method pname_java in + | Typ.Procname.Java pname_java + -> let mname = Typ.Procname.java_get_method pname_java in List.exists ~f:(String.equal mname) initializer_methods - | _ -> - false - else - false - | None -> false + | _ + -> false + else false + | None + -> false (** Get the vararg values by looking for array assignments to the pvar. *) let java_get_vararg_values node pvar idenv = let values = ref [] in let do_instr = function | Sil.Store (Exp.Lindex (array_exp, _), _, content_exp, _) - when Exp.equal (Exp.Lvar pvar) (Idenv.expand_expr idenv array_exp) -> - (* Each vararg argument is an assigment to a pvar denoting an array of objects. *) + when Exp.equal (Exp.Lvar pvar) (Idenv.expand_expr idenv array_exp) + -> (* Each vararg argument is an assigment to a pvar denoting an array of objects. *) values := content_exp :: !values - | _ -> () in - let do_node n = - List.iter ~f:do_instr (Procdesc.Node.get_instrs n) in - let () = match Errdesc.find_program_variable_assignment node pvar with - | Some (node', _) -> - Procdesc.iter_slope_range do_node node' node - | None -> () in + | _ + -> () + in + let do_node n = List.iter ~f:do_instr (Procdesc.Node.get_instrs n) in + let () = + match Errdesc.find_program_variable_assignment node pvar with + | Some (node', _) + -> Procdesc.iter_slope_range do_node node' node + | None + -> () + in !values let proc_calls resolve_attributes pdesc filter : (Typ.Procname.t * ProcAttributes.t) list = let res = ref [] in - let do_instruction _ instr = match instr with - | Sil.Call (_, Exp.Const (Const.Cfun callee_pn), _, _, _) -> - begin - match resolve_attributes callee_pn with - | Some callee_attributes -> - if filter callee_pn callee_attributes then - res := (callee_pn, callee_attributes) :: !res - | None -> () - end - | _ -> () in + let do_instruction _ instr = + match instr with + | Sil.Call (_, Exp.Const Const.Cfun callee_pn, _, _, _) -> ( + match resolve_attributes callee_pn with + | Some callee_attributes + -> if filter callee_pn callee_attributes then res := (callee_pn, callee_attributes) :: !res + | None + -> () ) + | _ + -> () + in let do_node node = let instrs = Procdesc.Node.get_instrs node in - List.iter ~f:(do_instruction node) instrs in + List.iter ~f:(do_instruction node) instrs + in let nodes = Procdesc.get_nodes pdesc in - List.iter ~f:do_node nodes; + List.iter ~f:do_node nodes ; List.rev !res let override_exists f tenv proc_name = let rec super_type_exists tenv super_class_name = - let super_proc_name = - Typ.Procname.replace_class proc_name super_class_name in + let super_proc_name = Typ.Procname.replace_class proc_name super_class_name in match Tenv.lookup tenv super_class_name with - | Some ({ methods; supers; }) -> - let is_override pname = - Typ.Procname.equal pname super_proc_name && not (Typ.Procname.is_constructor pname) in - List.exists ~f:(fun pname -> is_override pname && f pname) methods || - List.exists ~f:(super_type_exists tenv) supers - | _ -> - false in - - f proc_name || + | Some {methods; supers} + -> let is_override pname = + Typ.Procname.equal pname super_proc_name && not (Typ.Procname.is_constructor pname) + in + List.exists ~f:(fun pname -> is_override pname && f pname) methods + || List.exists ~f:(super_type_exists tenv) supers + | _ + -> false + in + f proc_name + || match proc_name with - | Typ.Procname.Java proc_name_java -> - let type_name = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name proc_name_java) in - List.exists - ~f:(super_type_exists tenv) + | Typ.Procname.Java proc_name_java + -> let type_name = + Typ.Name.Java.from_string (Typ.Procname.java_get_class_name proc_name_java) + in + List.exists ~f:(super_type_exists tenv) (type_get_direct_supertypes tenv (Typ.mk (Tstruct type_name))) - | _ -> - false (* Only java supported at the moment *) + | _ + -> false + +(* Only java supported at the moment *) let override_iter f tenv proc_name = - ignore(override_exists (fun pname -> f pname; false) tenv proc_name) + ignore (override_exists (fun pname -> f pname ; false) tenv proc_name) (** return the set of instance fields that are assigned to a null literal in [procdesc] *) let get_fields_nullified procdesc = (* walk through the instructions and look for instance fields that are assigned to null *) let collect_nullified_flds (nullified_flds, this_ids) _ = function | Sil.Store (Exp.Lfield (Exp.Var lhs, fld, _), _, rhs, _) - when Exp.is_null_literal rhs && Ident.IdentSet.mem lhs this_ids -> - (Typ.Fieldname.Set.add fld nullified_flds, this_ids) - | Sil.Load (id, rhs, _, _) when Exp.is_this rhs -> - (nullified_flds, Ident.IdentSet.add id this_ids) - | _ -> (nullified_flds, this_ids) in - let (nullified_flds, _) = - Procdesc.fold_instrs - collect_nullified_flds (Typ.Fieldname.Set.empty, Ident.IdentSet.empty) procdesc in + when Exp.is_null_literal rhs && Ident.IdentSet.mem lhs this_ids + -> (Typ.Fieldname.Set.add fld nullified_flds, this_ids) + | Sil.Load (id, rhs, _, _) when Exp.is_this rhs + -> (nullified_flds, Ident.IdentSet.add id this_ids) + | _ + -> (nullified_flds, this_ids) + in + let nullified_flds, _ = + Procdesc.fold_instrs collect_nullified_flds (Typ.Fieldname.Set.empty, Ident.IdentSet.empty) + procdesc + in nullified_flds (** Checks if the exception is an unchecked exception *) @@ -357,43 +371,39 @@ let is_runtime_exception tenv typename = is_subtype_of_str tenv typename "java.lang.RuntimeException" (** Checks if the class name is a Java exception *) -let is_exception tenv typename = - is_subtype_of_str tenv typename "java.lang.Exception" +let is_exception tenv typename = is_subtype_of_str tenv typename "java.lang.Exception" (** Checks if the class name is a Java exception *) -let is_throwable tenv typename = - is_subtype_of_str tenv typename "java.lang.Throwable" +let is_throwable tenv typename = is_subtype_of_str tenv typename "java.lang.Throwable" (** tests whether any class attributes (e.g., @ThreadSafe) pass check of first argument, including for supertypes*) let check_class_attributes check tenv = function - | Typ.Procname.Java java_pname -> - let check_class_annots _ { Typ.Struct.annots; } = check annots in - supertype_exists tenv - check_class_annots - (Typ.Procname.java_get_class_type_name java_pname) - | _ -> false + | Typ.Procname.Java java_pname + -> let check_class_annots _ {Typ.Struct.annots} = check annots in + supertype_exists tenv check_class_annots (Typ.Procname.java_get_class_type_name java_pname) + | _ + -> false (** tests whether any class attributes (e.g., @ThreadSafe) pass check of first argument, for the current class only*) let check_current_class_attributes check tenv = function - | Typ.Procname.Java java_pname -> - (match Tenv.lookup tenv (Typ.Procname.java_get_class_type_name java_pname) with - | Some (struct_typ) -> check struct_typ.annots - | _ -> false - ) - | _ -> false - + | Typ.Procname.Java java_pname -> ( + match Tenv.lookup tenv (Typ.Procname.java_get_class_type_name java_pname) with + | Some struct_typ + -> check struct_typ.annots + | _ + -> false ) + | _ + -> false (** find superclasss with attributes (e.g., @ThreadSafe), including current class*) let rec find_superclasses_with_attributes check tenv tname = match Tenv.lookup tenv tname with - | Some (struct_typ) -> - let result_from_supers = List.concat - (List.map ~f:(find_superclasses_with_attributes check tenv) struct_typ.supers) + | Some struct_typ + -> let result_from_supers = + List.concat (List.map ~f:(find_superclasses_with_attributes check tenv) struct_typ.supers) in - if check struct_typ.annots then - tname ::result_from_supers - else - result_from_supers - | _ -> [] + if check struct_typ.annots then tname :: result_from_supers else result_from_supers + | _ + -> [] diff --git a/infer/src/absint/PatternMatch.mli b/infer/src/absint/PatternMatch.mli index 00cf16871..39747500f 100644 --- a/infer/src/absint/PatternMatch.mli +++ b/infer/src/absint/PatternMatch.mli @@ -11,117 +11,113 @@ open! IStd (** Module for Pattern matching. *) -type taint_spec = { - classname : string; - method_name : string; - ret_type : string; - params : string list; - is_static : bool; - taint_kind : PredSymb.taint_kind; - language : Config.language -} +type taint_spec = + { classname: string + ; method_name: string + ; ret_type: string + ; params: string list + ; is_static: bool + ; taint_kind: PredSymb.taint_kind + ; language: Config.language } -(** Returns the signature of a field access (class name, field name, field type name) *) val get_java_field_access_signature : Sil.instr -> (string * string * string) option +(** Returns the signature of a field access (class name, field name, field type name) *) -(** Returns the formal signature (class name, method name, - argument type names and return type name) *) val get_java_method_call_formal_signature : Sil.instr -> (string * string * string list * string) option +(** Returns the formal signature (class name, method name, + argument type names and return type name) *) -(** Get the this type of a procedure *) val get_this_type : ProcAttributes.t -> Typ.t option +(** Get the this type of a procedure *) -(** Get the name of a type *) val get_type_name : Typ.t -> string +(** Get the name of a type *) -(** Get the type names of a variable argument *) val get_vararg_type_names : Tenv.t -> Procdesc.Node.t -> Pvar.t -> string list +(** Get the type names of a variable argument *) -val has_formal_method_argument_type_names : - Procdesc.t -> Typ.Procname.java -> string list -> bool +val has_formal_method_argument_type_names : Procdesc.t -> Typ.Procname.java -> string list -> bool -(** Check if the method is one of the known initializer methods. *) val method_is_initializer : Tenv.t -> ProcAttributes.t -> bool +(** Check if the method is one of the known initializer methods. *) -(** Is this a getter proc name? *) val is_getter : Typ.Procname.java -> bool +(** Is this a getter proc name? *) -(** Is this a setter proc name? *) val is_setter : Typ.Procname.java -> bool +(** Is this a setter proc name? *) -(** Is the type a direct subtype of the typename? *) val is_immediate_subtype : Tenv.t -> Typ.Name.t -> Typ.Name.t -> bool +(** Is the type a direct subtype of the typename? *) -(** Is the type a transitive subtype of the typename? *) val is_subtype : Tenv.t -> Typ.Name.t -> Typ.Name.t -> bool +(** Is the type a transitive subtype of the typename? *) -(** Resolve [typ_str] in [tenv], then check [typ] <: [typ_str] *) val is_subtype_of_str : Tenv.t -> Typ.Name.t -> string -> bool +(** Resolve [typ_str] in [tenv], then check [typ] <: [typ_str] *) -(** Holds iff the predicate holds on a supertype of the named type, including the type itself *) val supertype_exists : Tenv.t -> (Typ.Name.t -> Typ.Struct.t -> bool) -> Typ.Name.t -> bool +(** Holds iff the predicate holds on a supertype of the named type, including the type itself *) -(** Return the first non-None result found when applying the given function to supertypes of the - named type, including the type itself *) val supertype_find_map_opt : Tenv.t -> (Typ.Name.t -> Typ.Struct.t -> 'a option) -> Typ.Name.t -> 'a option +(** Return the first non-None result found when applying the given function to supertypes of the + named type, including the type itself *) -(** Get the name of the type of a constant *) val java_get_const_type_name : Const.t -> string +(** Get the name of the type of a constant *) -(** Get the values of a vararg parameter given the pvar used to assign the elements. *) val java_get_vararg_values : Procdesc.Node.t -> Pvar.t -> Idenv.t -> Exp.t list +(** Get the values of a vararg parameter given the pvar used to assign the elements. *) val java_proc_name_with_class_method : Typ.Procname.java -> string -> string -> bool -(** Return the callees that satisfy [filter]. *) val proc_calls : - (Typ.Procname.t -> ProcAttributes.t option) -> - Procdesc.t -> - (Typ.Procname.t -> ProcAttributes.t -> bool) -> - (Typ.Procname.t * ProcAttributes.t) list + (Typ.Procname.t -> ProcAttributes.t option) -> Procdesc.t + -> (Typ.Procname.t -> ProcAttributes.t -> bool) -> (Typ.Procname.t * ProcAttributes.t) list +(** Return the callees that satisfy [filter]. *) +val override_exists : (Typ.Procname.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool (** Return true if applying the given predicate to an override of [procname] or [procname] itself returns true. For the moment, this only works for Java *) -val override_exists : (Typ.Procname.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool +val override_iter : (Typ.Procname.t -> unit) -> Tenv.t -> Typ.Procname.t -> unit (** Apply the given predicate to procname and each override of [procname]. For the moment, this only works for Java *) -val override_iter : (Typ.Procname.t -> unit) -> Tenv.t -> Typ.Procname.t -> unit val type_get_annotation : Tenv.t -> Typ.t -> Annot.Item.t option -(** Get the class name of the type *) val type_get_class_name : Typ.t -> Typ.Name.t option +(** Get the class name of the type *) -(** Is the type a class type *) val type_is_class : Typ.t -> bool +(** Is the type a class type *) -(** Is the type java.lang.Object *) val type_is_object : Typ.t -> bool +(** Is the type java.lang.Object *) -(** return the set of instance fields that are assigned to a null literal in [procdesc] *) val get_fields_nullified : Procdesc.t -> Typ.Fieldname.Set.t +(** return the set of instance fields that are assigned to a null literal in [procdesc] *) -(** [is_exception tenv class_name] checks if class_name is of type java.lang.Exception *) val is_exception : Tenv.t -> Typ.Name.t -> bool +(** [is_exception tenv class_name] checks if class_name is of type java.lang.Exception *) -(** [is_throwable tenv class_name] checks if class_name is of type java.lang.Throwable *) val is_throwable : Tenv.t -> Typ.Name.t -> bool +(** [is_throwable tenv class_name] checks if class_name is of type java.lang.Throwable *) +val is_runtime_exception : Tenv.t -> Typ.Name.t -> bool (** [is_runtime_exception tenv class_name] checks if classname is of type java.lang.RuntimeException *) -val is_runtime_exception : Tenv.t -> Typ.Name.t -> bool +val check_class_attributes : (Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool (** tests whether any class attributes (e.g., @ThreadSafe) pass check of first argument, including supertypes*) -val check_class_attributes : (Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool +val check_current_class_attributes : (Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool (** tests whether any class attributes (e.g., @ThreadSafe) pass check of first argument, for current class only*) -val check_current_class_attributes : (Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> bool +val find_superclasses_with_attributes : + (Annot.Item.t -> bool) -> Tenv.t -> Typ.Name.t -> Typ.Name.t list (** find superclasss with attributes (e.g., @ThreadSafe), including current class*) -val find_superclasses_with_attributes : (Annot.Item.t -> bool) -> Tenv.t - -> Typ.Name.t -> Typ.Name.t list diff --git a/infer/src/absint/ProcCfg.ml b/infer/src/absint/ProcCfg.ml index 975cebee7..ff58608f7 100644 --- a/infer/src/absint/ProcCfg.ml +++ b/infer/src/absint/ProcCfg.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Control-flow graph for a single procedure (as opposed to cfg.ml, which represents a cfg for a @@ -19,39 +18,54 @@ type index = Node_index | Instr_index of int [@@deriving compare] module type Node = sig type t + type id val kind : t -> Procdesc.Node.nodekind + val id : t -> id + val hash : t -> int + val loc : t -> Location.t + val underlying_node : t -> Procdesc.Node.t + val compare_id : id -> id -> int + val pp_id : F.formatter -> id -> unit end module DefaultNode = struct type t = Procdesc.Node.t + type id = Procdesc.Node.id let kind = Procdesc.Node.get_kind + let id = Procdesc.Node.get_id + let hash = Procdesc.Node.hash + let loc = Procdesc.Node.get_loc + let underlying_node t = t + let compare_id = Procdesc.Node.compare_id + let pp_id = Procdesc.Node.pp_id end module InstrNode = struct type t = Procdesc.Node.t + type id = Procdesc.Node.id * index let kind = Procdesc.Node.get_kind let underlying_node t = t - let id t = Procdesc.Node.get_id (underlying_node t), Node_index + let id t = (Procdesc.Node.get_id (underlying_node t), Node_index) let hash node = Hashtbl.hash (id node) @@ -61,44 +75,48 @@ module InstrNode = struct let compare_id (id1, index1) (id2, index2) = let n = Procdesc.Node.compare_id id1 id2 in - if n <> 0 then n - else compare_index index1 index2 - - let pp_id fmt (id, index) = match index with - | Node_index -> Procdesc.Node.pp_id fmt id - | Instr_index i -> F.fprintf fmt "(%a: %d)" Procdesc.Node.pp_id id i + if n <> 0 then n else compare_index index1 index2 + + let pp_id fmt (id, index) = + match index with + | Node_index + -> Procdesc.Node.pp_id fmt id + | Instr_index i + -> F.fprintf fmt "(%a: %d)" Procdesc.Node.pp_id id i end module type S = sig type t + type node - include (Node with type t := node) - (** get the instructions from a node *) + include Node with type t := node + val instrs : node -> Sil.instr list + (** get the instructions from a node *) + val instr_ids : node -> (Sil.instr * id option) list (** explode a block into its instructions and an optional id for the instruction. the purpose of this is to specify a policy for fine-grained storage of invariants by the abstract interpreter. the interpreter will forget invariants at program points where the id is None, and remember them otherwise *) - val instr_ids : node -> (Sil.instr * id option) list val succs : t -> node -> node list - (** all predecessors (normal and exceptional) *) val preds : t -> node -> node list + (** all predecessors (normal and exceptional) *) - (** non-exceptional successors *) val normal_succs : t -> node -> node list + (** non-exceptional successors *) - (** non-exceptional predecessors *) val normal_preds : t -> node -> node list + (** non-exceptional predecessors *) - (** exceptional successors *) val exceptional_succs : t -> node -> node list + (** exceptional successors *) - (** exceptional predescessors *) val exceptional_preds : t -> node -> node list + (** exceptional predescessors *) val start_node : t -> node @@ -116,40 +134,59 @@ end (** Forward CFG with no exceptional control-flow *) module Normal = struct type t = Procdesc.t + type node = DefaultNode.t + include (DefaultNode : module type of DefaultNode with type t := node) let instrs = Procdesc.Node.get_instrs - let instr_ids n = List.map ~f:(fun i -> i, None) (instrs n) + + let instr_ids n = List.map ~f:(fun i -> (i, None)) (instrs n) + let normal_succs _ n = Procdesc.Node.get_succs n + let normal_preds _ n = Procdesc.Node.get_preds n + (* prune away exceptional control flow *) let exceptional_succs _ _ = [] + let exceptional_preds _ _ = [] + let succs = normal_succs + let preds = normal_preds + let start_node = Procdesc.get_start_node + let exit_node = Procdesc.get_exit_node + let proc_desc t = t + let nodes = Procdesc.get_nodes + let from_pdesc pdesc = pdesc + let is_loop_head = Procdesc.is_loop_head end (** Forward CFG with exceptional control-flow *) module Exceptional = struct type node = DefaultNode.t + type id_node_map = node list Procdesc.IdMap.t + type t = Procdesc.t * id_node_map + include (DefaultNode : module type of DefaultNode with type t := node) - let exceptional_succs _ n = match Procdesc.Node.get_kind n with - | Procdesc.Node.Stmt_node ("call_noexcept") -> - (* Hack: signal from the frontend that this node should be modelled as non-throwing. + let exceptional_succs _ n = + match Procdesc.Node.get_kind n with + | Procdesc.Node.Stmt_node "call_noexcept" + -> (* Hack: signal from the frontend that this node should be modelled as non-throwing. Eventually, we'll just avoid translating the exceptional edge in the frontend instead. *) [] - | _ -> - Procdesc.Node.get_exn n + | _ + -> Procdesc.Node.get_exn n let from_pdesc pdesc = (* map from a node to its exceptional predecessors *) @@ -158,20 +195,23 @@ module Exceptional = struct let exn_succ_node_id = Procdesc.Node.get_id exn_succ_node in let existing_exn_preds = try Procdesc.IdMap.find exn_succ_node_id exn_preds_acc - with Not_found -> [] in - if not (List.mem ~equal:Procdesc.Node.equal existing_exn_preds n) - then (* don't add duplicates *) + with Not_found -> [] + in + if not (List.mem ~equal:Procdesc.Node.equal existing_exn_preds n) then + (* don't add duplicates *) Procdesc.IdMap.add exn_succ_node_id (n :: existing_exn_preds) exn_preds_acc - else - exn_preds_acc in - List.fold ~f:add_exn_pred ~init:exn_preds_acc (exceptional_succs pdesc n) in + else exn_preds_acc + in + List.fold ~f:add_exn_pred ~init:exn_preds_acc (exceptional_succs pdesc n) + in let exceptional_preds = - List.fold ~f:add_exn_preds ~init:Procdesc.IdMap.empty (Procdesc.get_nodes pdesc) in - pdesc, exceptional_preds + List.fold ~f:add_exn_preds ~init:Procdesc.IdMap.empty (Procdesc.get_nodes pdesc) + in + (pdesc, exceptional_preds) let instrs = Procdesc.Node.get_instrs - let instr_ids n = List.map ~f:(fun i -> i, None) (instrs n) + let instr_ids n = List.map ~f:(fun i -> (i, None)) (instrs n) let nodes (t, _) = Procdesc.get_nodes t @@ -187,67 +227,81 @@ module Exceptional = struct let succs t n = let normal_succs = normal_succs t n in match exceptional_succs t n with - | [] -> - normal_succs - | exceptional_succs -> - normal_succs @ exceptional_succs - |> List.sort ~cmp:Procdesc.Node.compare + | [] + -> normal_succs + | exceptional_succs + -> normal_succs @ exceptional_succs |> List.sort ~cmp:Procdesc.Node.compare |> List.remove_consecutive_duplicates ~equal:Procdesc.Node.equal (** get all normal and exceptional predecessors of [n]. *) let preds t n = let normal_preds = normal_preds t n in match exceptional_preds t n with - | [] -> - normal_preds - | exceptional_preds -> - normal_preds @ exceptional_preds - |> List.sort ~cmp:Procdesc.Node.compare + | [] + -> normal_preds + | exceptional_preds + -> normal_preds @ exceptional_preds |> List.sort ~cmp:Procdesc.Node.compare |> List.remove_consecutive_duplicates ~equal:Procdesc.Node.equal let proc_desc (pdesc, _) = pdesc + let start_node (pdesc, _) = Procdesc.get_start_node pdesc + let exit_node (pdesc, _) = Procdesc.get_exit_node pdesc + let is_loop_head = Procdesc.is_loop_head end (** Wrapper that reverses the direction of the CFG *) module Backward (Base : S) = struct include Base + let instrs n = List.rev (Base.instrs n) + let instr_ids n = List.rev (Base.instr_ids n) let succs = Base.preds + let preds = Base.succs + let start_node = Base.exit_node + let exit_node = Base.start_node + let normal_succs = Base.normal_preds + let normal_preds = Base.normal_succs + let exceptional_succs = Base.exceptional_preds + let exceptional_preds = Base.exceptional_succs end -module OneInstrPerNode (Base : S with type node = Procdesc.Node.t - and type id = Procdesc.Node.id) = struct +module OneInstrPerNode (Base : S with type node = Procdesc.Node.t and type id = Procdesc.Node.id) = +struct include (Base : module type of Base with type id := Procdesc.Node.id and type t = Base.t) + type id = Base.id * index + include (InstrNode : module type of InstrNode with type t := node and type id := id) (* keep the invariants before/after each instruction *) let instr_ids t = List.mapi ~f:(fun i instr -> - let id = Procdesc.Node.get_id t, Instr_index i in - instr, Some id) + let id = (Procdesc.Node.get_id t, Instr_index i) in + (instr, Some id)) (instrs t) end -module NodeIdMap (CFG : S) = Caml.Map.Make(struct - type t = CFG.id - let compare = CFG.compare_id - end) +module NodeIdMap (CFG : S) = Caml.Map.Make (struct + type t = CFG.id + + let compare = CFG.compare_id +end) + +module NodeIdSet (CFG : S) = Caml.Set.Make (struct + type t = CFG.id -module NodeIdSet (CFG : S) = Caml.Set.Make(struct - type t = CFG.id - let compare = CFG.compare_id - end) + let compare = CFG.compare_id +end) diff --git a/infer/src/absint/ProcCfg.mli b/infer/src/absint/ProcCfg.mli index 40960bf66..f5c98886a 100644 --- a/infer/src/absint/ProcCfg.mli +++ b/infer/src/absint/ProcCfg.mli @@ -17,47 +17,56 @@ type index = Node_index | Instr_index of int module type Node = sig type t + type id val kind : t -> Procdesc.Node.nodekind + val id : t -> id + val hash : t -> int + val loc : t -> Location.t + val underlying_node : t -> Procdesc.Node.t + val compare_id : id -> id -> int + val pp_id : Format.formatter -> id -> unit end module type S = sig type t + type node - include (Node with type t := node) - (** get the instructions from a node *) + include Node with type t := node + val instrs : node -> Sil.instr list + (** get the instructions from a node *) + val instr_ids : node -> (Sil.instr * id option) list (** explode a block into its instructions and an optional id for the instruction. the purpose of this is to specify a policy for fine-grained storage of invariants by the abstract interpreter. the interpreter will forget invariants at program points where the id is None, and remember them otherwise *) - val instr_ids : node -> (Sil.instr * id option) list val succs : t -> node -> node list - (** all predecessors (normal and exceptional) *) val preds : t -> node -> node list + (** all predecessors (normal and exceptional) *) - (** non-exceptional successors *) val normal_succs : t -> node -> node list + (** non-exceptional successors *) - (** non-exceptional predecessors *) val normal_preds : t -> node -> node list + (** non-exceptional predecessors *) - (** exceptional successors *) val exceptional_succs : t -> node -> node list + (** exceptional successors *) - (** exceptional predescessors *) val exceptional_preds : t -> node -> node list + (** exceptional predescessors *) val start_node : t -> node @@ -77,24 +86,21 @@ module DefaultNode : Node with type t = Procdesc.Node.t and type id = Procdesc.N module InstrNode : Node with type t = Procdesc.Node.t and type id = Procdesc.Node.id * index (** Forward CFG with no exceptional control-flow *) -module Normal : S with type t = Procdesc.t - and type node = DefaultNode.t - and type id = DefaultNode.id +module Normal : + S with type t = Procdesc.t and type node = DefaultNode.t and type id = DefaultNode.id (** Forward CFG with exceptional control-flow *) -module Exceptional : S with type t = Procdesc.t * DefaultNode.t list Procdesc.IdMap.t - and type node = DefaultNode.t - and type id = DefaultNode.id +module Exceptional : + S + with type t = Procdesc.t * DefaultNode.t list Procdesc.IdMap.t + and type node = DefaultNode.t + and type id = DefaultNode.id (** Wrapper that reverses the direction of the CFG *) -module Backward (Base : S) : S with type t = Base.t - and type node = Base.node - and type id = Base.id +module Backward (Base : S) : S with type t = Base.t and type node = Base.node and type id = Base.id module OneInstrPerNode (Base : S with type node = DefaultNode.t and type id = DefaultNode.id) : - S with type t = Base.t - and type node = Base.node - and type id = Base.id * index + S with type t = Base.t and type node = Base.node and type id = Base.id * index module NodeIdMap (CFG : S) : Caml.Map.S with type key = CFG.id diff --git a/infer/src/absint/ProcData.ml b/infer/src/absint/ProcData.ml index 17a051c13..1bda53b52 100644 --- a/infer/src/absint/ProcData.ml +++ b/infer/src/absint/ProcData.ml @@ -9,7 +9,7 @@ open! IStd -type 'a t = { pdesc : Procdesc.t; tenv : Tenv.t; extras : 'a; } +type 'a t = {pdesc: Procdesc.t; tenv: Tenv.t; extras: 'a} type no_extras = unit @@ -17,8 +17,6 @@ let empty_extras = () let make_empty_extras _ = () -let make pdesc tenv extras = - { pdesc; tenv; extras; } +let make pdesc tenv extras = {pdesc; tenv; extras} -let make_default pdesc tenv = - make pdesc tenv empty_extras +let make_default pdesc tenv = make pdesc tenv empty_extras diff --git a/infer/src/absint/ProcData.mli b/infer/src/absint/ProcData.mli index e3623085b..7f0f70574 100644 --- a/infer/src/absint/ProcData.mli +++ b/infer/src/absint/ProcData.mli @@ -9,7 +9,7 @@ open! IStd -type 'a t = { pdesc : Procdesc.t; tenv : Tenv.t; extras : 'a; } +type 'a t = {pdesc: Procdesc.t; tenv: Tenv.t; extras: 'a} type no_extras diff --git a/infer/src/absint/Scheduler.ml b/infer/src/absint/Scheduler.ml index 6030c43a3..baa3aa4bf 100644 --- a/infer/src/absint/Scheduler.ml +++ b/infer/src/absint/Scheduler.ml @@ -8,24 +8,28 @@ *) open! IStd - module F = Format module L = Logging module type S = sig module CFG : ProcCfg.S + type t (* schedule the successors of [node] *) + val schedule_succs : t -> CFG.node -> t + (* remove and return the node with the highest priority, the ids of its visited predecessors, and the new schedule *) + val pop : t -> (CFG.node * CFG.id list * t) option + val empty : CFG.t -> t end module type Make = functor (CFG : ProcCfg.S) -> sig - include (S with module CFG = CFG) + include S with module CFG = CFG end (* simple scheduler that visits CFG nodes in reverse postorder. fast/precise for straightline code @@ -37,11 +41,13 @@ module ReversePostorder (CFG : ProcCfg.S) = struct module WorkUnit = struct module IdSet = ProcCfg.NodeIdSet (CFG) - type t = { - node : CFG.node; (* node whose instructions will be analyzed *) - visited_preds : IdSet.t ; (* predecessors of [node] we have already visited in current iter *) - priority : int; (* |preds| - |visited preds|. *) - } + type t = + { node: CFG.node + ; (* node whose instructions will be analyzed *) + visited_preds: IdSet.t + ; (* predecessors of [node] we have already visited in current iter *) + priority: int + (* |preds| - |visited preds|. *) } let node t = t.node @@ -55,16 +61,16 @@ module ReversePostorder (CFG : ProcCfg.S) = struct let make cfg node = let visited_preds = IdSet.empty in let priority = compute_priority cfg node visited_preds in - { node; visited_preds; priority; } + {node; visited_preds; priority} (* add [node_id] to the visited preds for [t] *) let add_visited_pred cfg t node_id = let visited_preds' = IdSet.add node_id t.visited_preds in let priority' = compute_priority cfg t.node visited_preds' in - { t with visited_preds = visited_preds'; priority = priority'; } + {t with visited_preds= visited_preds'; priority= priority'} end - type t = { worklist : WorkUnit.t M.t; cfg : CFG.t; } + type t = {worklist: WorkUnit.t M.t; cfg: CFG.t} (* schedule the succs of [node] for analysis *) let schedule_succs t node = @@ -74,11 +80,13 @@ module ReversePostorder (CFG : ProcCfg.S) = struct let id_to_schedule = CFG.id node_to_schedule in let old_work = try M.find id_to_schedule worklist_acc - with Not_found -> WorkUnit.make t.cfg node_to_schedule in + with Not_found -> WorkUnit.make t.cfg node_to_schedule + in let new_work = WorkUnit.add_visited_pred t.cfg old_work node_id in - M.add id_to_schedule new_work worklist_acc in + M.add id_to_schedule new_work worklist_acc + in let new_worklist = List.fold ~f:schedule_succ ~init:t.worklist (CFG.succs t.cfg node) in - { t with worklist = new_worklist; } + {t with worklist= new_worklist} (* remove and return the node with the highest priority (note that smaller integers have higher priority), the ids of its visited predecessors, and new schedule *) @@ -91,17 +99,15 @@ module ReversePostorder (CFG : ProcCfg.S) = struct let max_priority_id, _ = M.fold (fun id work (lowest_id, lowest_priority) -> - let priority = WorkUnit.priority work in - if priority < lowest_priority - then id, priority - else lowest_id, lowest_priority) - t.worklist - (init_id, init_priority) in + let priority = WorkUnit.priority work in + if priority < lowest_priority then (id, priority) else (lowest_id, lowest_priority)) + t.worklist (init_id, init_priority) + in let max_priority_work = M.find max_priority_id t.worklist in let node = WorkUnit.node max_priority_work in - let t' = { t with worklist = M.remove (CFG.id node) t.worklist } in + let t' = {t with worklist= M.remove (CFG.id node) t.worklist} in Some (node, WorkUnit.visited_preds max_priority_work, t') with Not_found -> None - let empty cfg = { worklist = M.empty; cfg; } + let empty cfg = {worklist= M.empty; cfg} end diff --git a/infer/src/absint/Summary.ml b/infer/src/absint/Summary.ml index dc0887e9e..b420c1705 100644 --- a/infer/src/absint/Summary.ml +++ b/infer/src/absint/Summary.ml @@ -15,7 +15,6 @@ module type Payload = sig val update_payload : payload -> Specs.summary -> Specs.summary val read_payload : Specs.summary -> payload option - end module type S = sig @@ -24,19 +23,17 @@ module type S = sig val update_summary : payload -> Specs.summary -> Specs.summary val read_summary : Procdesc.t -> Typ.Procname.t -> payload option - end module Make (P : Payload) : S with type payload = P.payload = struct - type payload = P.payload - let update_summary payload summary = - P.update_payload payload summary + let update_summary payload summary = P.update_payload payload summary let read_summary caller_pdesc callee_pname = match Ondemand.analyze_proc_name ~propagate_exceptions:false caller_pdesc callee_pname with - | None -> None - | Some summary -> P.read_payload summary - + | None + -> None + | Some summary + -> P.read_payload summary end diff --git a/infer/src/absint/Summary.mli b/infer/src/absint/Summary.mli index 9fc5e4294..c44ca44ba 100644 --- a/infer/src/absint/Summary.mli +++ b/infer/src/absint/Summary.mli @@ -12,24 +12,22 @@ open! IStd module type Payload = sig type payload - (** Uptade the corresponding part of the payload in the procedure summary *) val update_payload : payload -> Specs.summary -> Specs.summary + (** Uptade the corresponding part of the payload in the procedure summary *) - (** Read the corresponding part of the payload from the procedure summary *) val read_payload : Specs.summary -> payload option - + (** Read the corresponding part of the payload from the procedure summary *) end module type S = sig type payload - (** Uptade the corresponding part of the payload in the procedure summary *) val update_summary : payload -> Specs.summary -> Specs.summary + (** Uptade the corresponding part of the payload in the procedure summary *) + val read_summary : Procdesc.t -> Typ.Procname.t -> payload option (** Return the payload for the given procedure. Runs the analysis on-demand if necessary *) - val read_summary : Procdesc.t -> Typ.Procname.t -> payload option - end module Make (P : Payload) : S with type payload = P.payload diff --git a/infer/src/absint/TransferFunctions.ml b/infer/src/absint/TransferFunctions.ml index f0b8393bf..8edeedb47 100644 --- a/infer/src/absint/TransferFunctions.ml +++ b/infer/src/absint/TransferFunctions.ml @@ -11,26 +11,28 @@ open! IStd module type S = sig module CFG : ProcCfg.S + module Domain : AbstractDomain.S type extras + type instr val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> instr -> Domain.astate end module type SIL = sig - include (S with type instr := Sil.instr) + include S with type instr := Sil.instr end module type HIL = sig - include (S with type instr := HilInstr.t) + include S with type instr := HilInstr.t end module type MakeSIL = functor (C : ProcCfg.S) -> sig - include (SIL with module CFG = C) + include SIL with module CFG = C end module type MakeHIL = functor (C : ProcCfg.S) -> sig - include (HIL with module CFG = C) + include HIL with module CFG = C end diff --git a/infer/src/absint/TransferFunctions.mli b/infer/src/absint/TransferFunctions.mli index f1ca11d30..0af8487e4 100644 --- a/infer/src/absint/TransferFunctions.mli +++ b/infer/src/absint/TransferFunctions.mli @@ -24,22 +24,22 @@ module type S = sig (** type of the instructions the transfer functions operate on *) type instr - (** {A} instr {A'}. [node] is the node of the current instruction *) val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> instr -> Domain.astate + (** {A} instr {A'}. [node] is the node of the current instruction *) end module type SIL = sig - include (S with type instr := Sil.instr) + include S with type instr := Sil.instr end module type HIL = sig - include (S with type instr := HilInstr.t) + include S with type instr := HilInstr.t end module type MakeSIL = functor (C : ProcCfg.S) -> sig - include (SIL with module CFG = C) + include SIL with module CFG = C end module type MakeHIL = functor (C : ProcCfg.S) -> sig - include (HIL with module CFG = C) + include HIL with module CFG = C end diff --git a/infer/src/absint/Var.ml b/infer/src/absint/Var.ml index 3591cb0ae..7ffc680b1 100644 --- a/infer/src/absint/Var.ml +++ b/infer/src/absint/Var.ml @@ -11,44 +11,34 @@ open! IStd (** Single abstraction for all the kinds of variables in SIL *) -type t = - | LogicalVar of Ident.t - | ProgramVar of Pvar.t -[@@deriving compare] +type t = LogicalVar of Ident.t | ProgramVar of Pvar.t [@@deriving compare] let equal = [%compare.equal : t] -let of_id id = - LogicalVar id +let of_id id = LogicalVar id -let of_pvar pvar = - ProgramVar pvar +let of_pvar pvar = ProgramVar pvar -let of_formal_index formal_index = - of_id (Ident.create_footprint Ident.name_spec formal_index) +let of_formal_index formal_index = of_id (Ident.create_footprint Ident.name_spec formal_index) -let to_exp = function - | ProgramVar pvar -> Exp.Lvar pvar - | LogicalVar id -> Exp.Var id +let to_exp = function ProgramVar pvar -> Exp.Lvar pvar | LogicalVar id -> Exp.Var id -let is_global = function - | ProgramVar pvar -> Pvar.is_global pvar - | LogicalVar _ -> false +let is_global = function ProgramVar pvar -> Pvar.is_global pvar | LogicalVar _ -> false -let is_return = function - | ProgramVar pvar -> Pvar.is_return pvar - | LogicalVar _ -> false +let is_return = function ProgramVar pvar -> Pvar.is_return pvar | LogicalVar _ -> false -let is_footprint = function - | ProgramVar _ -> false - | LogicalVar id -> Ident.is_footprint id +let is_footprint = function ProgramVar _ -> false | LogicalVar id -> Ident.is_footprint id let pp fmt = function - | ProgramVar pv -> (Pvar.pp Pp.text) fmt pv - | LogicalVar id -> (Ident.pp Pp.text) fmt id - -module Map = PrettyPrintable.MakePPMap(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + | ProgramVar pv + -> Pvar.pp Pp.text fmt pv + | LogicalVar id + -> Ident.pp Pp.text fmt id + +module Map = PrettyPrintable.MakePPMap (struct + type nonrec t = t + + let compare = compare + + let pp = pp +end) diff --git a/infer/src/absint/Var.mli b/infer/src/absint/Var.mli index 0c34380eb..cf5ccfc43 100644 --- a/infer/src/absint/Var.mli +++ b/infer/src/absint/Var.mli @@ -11,10 +11,7 @@ open! IStd (** Single abstraction for all the kinds of variables in SIL *) -type t = private - | LogicalVar of Ident.t - | ProgramVar of Pvar.t -[@@deriving compare] +type t = private LogicalVar of Ident.t | ProgramVar of Pvar.t [@@deriving compare] val equal : t -> t -> bool @@ -22,8 +19,8 @@ val of_id : Ident.t -> t val of_pvar : Pvar.t -> t -(** Create a variable representing the ith formal of the current procedure *) val of_formal_index : int -> t +(** Create a variable representing the ith formal of the current procedure *) val to_exp : t -> Exp.t diff --git a/infer/src/backend/Attribute.ml b/infer/src/backend/Attribute.ml index 8aa3d986b..eba05aee9 100644 --- a/infer/src/backend/Attribute.ml +++ b/infer/src/backend/Attribute.ml @@ -15,15 +15,11 @@ open! IStd module L = Logging module F = Format - (** Check whether an atom is used to mark an attribute *) -let is_pred atom = - match atom with - | Sil.Apred _ | Anpred _ -> true - | _ -> false +let is_pred atom = match atom with Sil.Apred _ | Anpred _ -> true | _ -> false (** Add an attribute associated to the argument expressions *) -let add tenv ?(footprint = false) ?(polarity = true) prop attr args = +let add tenv ?(footprint= false) ?(polarity= true) prop attr args = Prop.prop_atom_and tenv ~footprint prop (if polarity then Sil.Apred (attr, args) else Sil.Anpred (attr, args)) @@ -35,182 +31,185 @@ let attributes_in_same_category attr1 attr2 = (** Replace an attribute associated to the expression *) let add_or_replace_check_changed tenv check_attribute_change prop atom0 = match atom0 with - | Sil.Apred (att0, ((_ :: _) as exps0)) | Anpred (att0, ((_ :: _) as exps0)) -> - let pairs = - List.map ~f:(fun e -> (e, Prop.exp_normalize_prop tenv prop e)) exps0 in - let _, nexp = List.hd_exn pairs in (* len exps0 > 0 by match *) + | Sil.Apred (att0, (_ :: _ as exps0)) | Anpred (att0, (_ :: _ as exps0)) + -> let pairs = List.map ~f:(fun e -> (e, Prop.exp_normalize_prop tenv prop e)) exps0 in + let _, nexp = List.hd_exn pairs in + (* len exps0 > 0 by match *) let natom = Sil.atom_replace_exp pairs atom0 in let atom_map = function - | Sil.Apred (att, exp :: _) | Anpred (att, exp :: _) - when Exp.equal nexp exp && attributes_in_same_category att att0 -> - check_attribute_change att att0; - natom - | atom -> - atom in + | Sil.Apred (att, exp :: _) + | Anpred (att, exp :: _) + when Exp.equal nexp exp && attributes_in_same_category att att0 + -> check_attribute_change att att0 ; natom + | atom + -> atom + in let pi = prop.Prop.pi in let pi' = IList.map_changed atom_map pi in - if phys_equal pi pi' - then Prop.prop_atom_and tenv prop natom + if phys_equal pi pi' then Prop.prop_atom_and tenv prop natom else Prop.normalize tenv (Prop.set prop ~pi:pi') - | _ -> - prop + | _ + -> prop let add_or_replace tenv prop atom = (* wrapper for the most common case: do nothing *) - let check_attr_changed = (fun _ _ -> ()) in + let check_attr_changed _ _ = () in add_or_replace_check_changed tenv check_attr_changed prop atom (** Get all the attributes of the prop *) let get_all (prop: 'a Prop.t) = let res = ref [] in let do_atom a = if is_pred a then res := a :: !res in - List.iter ~f:do_atom prop.pi; + List.iter ~f:do_atom prop.pi ; List.rev !res (** Get all the attributes of the prop *) let get_for_symb prop att = - List.filter ~f:(function - | Sil.Apred (att', _) | Anpred (att', _) -> PredSymb.equal att' att - | _ -> false - ) prop.Prop.pi + List.filter + ~f:(function Sil.Apred (att', _) | Anpred (att', _) -> PredSymb.equal att' att | _ -> false) + prop.Prop.pi (** Get the attribute associated to the expression, if any *) let get_for_exp tenv (prop: 'a Prop.t) exp = let nexp = Prop.exp_normalize_prop tenv prop exp in let atom_get_attr attributes atom = match atom with - | Sil.Apred (_, es) | Anpred (_, es) - when List.mem ~equal:Exp.equal es nexp -> atom :: attributes - | _ -> attributes in + | (Sil.Apred (_, es) | Anpred (_, es)) when List.mem ~equal:Exp.equal es nexp + -> atom :: attributes + | _ + -> attributes + in List.fold ~f:atom_get_attr ~init:[] prop.pi let get tenv prop exp category = let atts = get_for_exp tenv prop exp in List.find ~f:(function - | Sil.Apred (att, _) | Anpred (att, _) -> - PredSymb.equal_category (PredSymb.to_category att) category - | _ -> false) + | Sil.Apred (att, _) | Anpred (att, _) + -> PredSymb.equal_category (PredSymb.to_category att) category + | _ + -> false) atts -let get_undef tenv prop exp = - get tenv prop exp ACundef +let get_undef tenv prop exp = get tenv prop exp ACundef -let get_resource tenv prop exp = - get tenv prop exp ACresource +let get_resource tenv prop exp = get tenv prop exp ACresource -let get_taint tenv prop exp = - get tenv prop exp ACtaint +let get_taint tenv prop exp = get tenv prop exp ACtaint -let get_autorelease tenv prop exp = - get tenv prop exp ACautorelease +let get_autorelease tenv prop exp = get tenv prop exp ACautorelease -let get_objc_null tenv prop exp = - get tenv prop exp ACobjc_null +let get_objc_null tenv prop exp = get tenv prop exp ACobjc_null -let get_div0 tenv prop exp = - get tenv prop exp ACdiv0 +let get_div0 tenv prop exp = get tenv prop exp ACdiv0 -let get_observer tenv prop exp = - get tenv prop exp ACobserver +let get_observer tenv prop exp = get tenv prop exp ACobserver -let get_retval tenv prop exp = - get tenv prop exp ACretval +let get_retval tenv prop exp = get tenv prop exp ACretval let has_dangling_uninit tenv prop exp = let la = get_for_exp tenv prop exp in - List.exists ~f:(function - | Sil.Apred (a, _) -> PredSymb.equal a (Adangling DAuninit) - | _ -> false - ) la + List.exists + ~f:(function Sil.Apred (a, _) -> PredSymb.equal a (Adangling DAuninit) | _ -> false) + la let filter_atoms tenv ~f prop = let pi0 = prop.Prop.pi in let pi1 = IList.filter_changed f pi0 in - if phys_equal pi1 pi0 then - prop - else - Prop.normalize tenv (Prop.set prop ~pi:pi1) + if phys_equal pi1 pi0 then prop else Prop.normalize tenv (Prop.set prop ~pi:pi1) let remove tenv prop atom = if is_pred atom then let natom = Prop.atom_normalize_prop tenv prop atom in let f a = not (Sil.equal_atom natom a) in filter_atoms tenv ~f prop - else - prop + else prop (** Remove an attribute from all the atoms in the heap *) let remove_for_attr tenv prop att0 = let f = function - | Sil.Apred (att, _) | Anpred (att, _) -> not (PredSymb.equal att0 att) - | _ -> true in + | Sil.Apred (att, _) | Anpred (att, _) + -> not (PredSymb.equal att0 att) + | _ + -> true + in filter_atoms tenv ~f prop let remove_resource tenv ra_kind ra_res = let f = function - | Sil.Apred (Aresource res_action, _) -> - PredSymb.compare_res_act_kind res_action.ra_kind ra_kind <> 0 + | Sil.Apred (Aresource res_action, _) + -> PredSymb.compare_res_act_kind res_action.ra_kind ra_kind <> 0 || PredSymb.compare_resource res_action.ra_res ra_res <> 0 - | _ -> true in + | _ + -> true + in filter_atoms tenv ~f (** Apply f to every resource attribute in the prop *) let map_resource tenv prop f = let attribute_map e = function - | PredSymb.Aresource ra -> PredSymb.Aresource (f e ra) - | att -> att in + | PredSymb.Aresource ra + -> PredSymb.Aresource (f e ra) + | att + -> att + in let atom_map = function - | Sil.Apred (att, ([e] as es)) -> Sil.Apred (attribute_map e att, es) - | Sil.Anpred (att, ([e] as es)) -> Sil.Anpred (attribute_map e att, es) - | atom -> atom in + | Sil.Apred (att, ([e] as es)) + -> Sil.Apred (attribute_map e att, es) + | Sil.Anpred (att, ([e] as es)) + -> Sil.Anpred (attribute_map e att, es) + | atom + -> atom + in let pi0 = prop.Prop.pi in let pi1 = IList.map_changed atom_map pi0 in - if phys_equal pi1 pi0 then - prop - else - Prop.normalize tenv (Prop.set prop ~pi:pi1) + if phys_equal pi1 pi0 then prop else Prop.normalize tenv (Prop.set prop ~pi:pi1) (* Replace an attribute OBJC_NULL($n1) with OBJC_NULL(var) when var = $n1, and also sets $n1 = 0 *) let replace_objc_null tenv prop lhs_exp rhs_exp = - match get_objc_null tenv prop rhs_exp, rhs_exp with - | Some atom, Exp.Var _ -> - let prop = remove tenv prop atom in + match (get_objc_null tenv prop rhs_exp, rhs_exp) with + | Some atom, Exp.Var _ + -> let prop = remove tenv prop atom in let prop = Prop.conjoin_eq tenv rhs_exp Exp.zero prop in let natom = Sil.atom_replace_exp [(rhs_exp, lhs_exp)] atom in add_or_replace tenv prop natom - | _ -> prop + | _ + -> prop let rec nullify_exp_with_objc_null tenv prop exp = match exp with - | Exp.BinOp (_, exp1, exp2) -> - let prop' = nullify_exp_with_objc_null tenv prop exp1 in + | Exp.BinOp (_, exp1, exp2) + -> let prop' = nullify_exp_with_objc_null tenv prop exp1 in nullify_exp_with_objc_null tenv prop' exp2 - | Exp.UnOp (_, exp, _) -> - nullify_exp_with_objc_null tenv prop exp - | Exp.Var _ -> - (match get_objc_null tenv prop exp with - | Some atom -> - let prop' = remove tenv prop atom in - Prop.conjoin_eq tenv exp Exp.zero prop' - | _ -> prop) - | _ -> prop + | Exp.UnOp (_, exp, _) + -> nullify_exp_with_objc_null tenv prop exp + | Exp.Var _ -> ( + match get_objc_null tenv prop exp with + | Some atom + -> let prop' = remove tenv prop atom in + Prop.conjoin_eq tenv exp Exp.zero prop' + | _ + -> prop ) + | _ + -> prop (** mark Exp.Var's or Exp.Lvar's as undefined *) let mark_vars_as_undefined tenv prop vars_to_mark callee_pname ret_annots loc path_pos = let att_undef = PredSymb.Aundef (callee_pname, ret_annots, loc, path_pos) in let mark_var_as_undefined exp prop = match exp with - | Exp.Var _ | Lvar _ -> add_or_replace tenv prop (Apred (att_undef, [exp])) - | _ -> prop in + | Exp.Var _ | Lvar _ + -> add_or_replace tenv prop (Apred (att_undef, [exp])) + | _ + -> prop + in List.fold ~f:(fun prop id -> mark_var_as_undefined id prop) ~init:prop vars_to_mark (** type for arithmetic problems *) type arith_problem = (* division by zero *) | Div0 of Exp.t - (* unary minus of unsigned type applied to the given expression *) | UminusUnsigned of Exp.t * Typ.t @@ -221,64 +220,92 @@ let find_arithmetic_problem tenv proc_node_session prop exp = let res = ref prop in let check_zero e = match Prop.exp_normalize_prop tenv prop e with - | Exp.Const c when Const.iszero_int_float c -> true - | _ -> - res := add_or_replace tenv !res (Apred (Adiv0 proc_node_session, [e])); - false in + | Exp.Const c when Const.iszero_int_float c + -> true + | _ + -> res := add_or_replace tenv !res (Apred (Adiv0 proc_node_session, [e])) ; + false + in let rec walk = function - | Exp.Var _ -> () - | Exp.UnOp (Unop.Neg, e, Some ( - ({Typ.desc=Tint - (Typ.IUChar | Typ.IUInt | Typ.IUShort | Typ.IULong | Typ.IULongLong)} as typ))) -> - uminus_unsigned := (e, typ) :: !uminus_unsigned - | Exp.UnOp(_, e, _) -> walk e - | Exp.BinOp(op, e1, e2) -> - if Binop.equal op Binop.Div || Binop.equal op Binop.Mod - then exps_divided := e2 :: !exps_divided; - walk e1; walk e2 - | Exp.Exn _ -> () - | Exp.Closure _ -> () - | Exp.Const _ -> () - | Exp.Cast (_, e) -> walk e - | Exp.Lvar _ -> () - | Exp.Lfield (e, _, _) -> walk e - | Exp.Lindex (e1, e2) -> walk e1; walk e2 - | Exp.Sizeof {dynamic_length=None} -> () - | Exp.Sizeof {dynamic_length=Some len} -> walk len in - walk exp; + | Exp.Var _ + -> () + | Exp.UnOp + ( Unop.Neg + , e + , Some + ( {Typ.desc= Tint (Typ.IUChar | Typ.IUInt | Typ.IUShort | Typ.IULong | Typ.IULongLong)} + as typ ) ) + -> uminus_unsigned := (e, typ) :: !uminus_unsigned + | Exp.UnOp (_, e, _) + -> walk e + | Exp.BinOp (op, e1, e2) + -> if Binop.equal op Binop.Div || Binop.equal op Binop.Mod then exps_divided + := e2 :: !exps_divided ; + walk e1 ; + walk e2 + | Exp.Exn _ + -> () + | Exp.Closure _ + -> () + | Exp.Const _ + -> () + | Exp.Cast (_, e) + -> walk e + | Exp.Lvar _ + -> () + | Exp.Lfield (e, _, _) + -> walk e + | Exp.Lindex (e1, e2) + -> walk e1 ; walk e2 + | Exp.Sizeof {dynamic_length= None} + -> () + | Exp.Sizeof {dynamic_length= Some len} + -> walk len + in + walk exp ; let problem_opt = match (List.find ~f:check_zero !exps_divided, !uminus_unsigned) with - | Some e, _ -> - Some (Div0 e) - | None, (e, t):: _ -> - Some (UminusUnsigned (e, t)) - | None, [] -> - None in - problem_opt, !res + | Some e, _ + -> Some (Div0 e) + | None, (e, t) :: _ + -> Some (UminusUnsigned (e, t)) + | None, [] + -> None + in + (problem_opt, !res) (** Deallocate the stack variables in [pvars], and replace them by normal variables. Return the list of stack variables whose address was still present after deallocation. *) let deallocate_stack_vars tenv (p: 'a Prop.t) pvars = let filter = function - | Sil.Hpointsto (Exp.Lvar v, _, _) -> - List.exists ~f:(Pvar.equal v) pvars - | _ -> false in + | Sil.Hpointsto (Exp.Lvar v, _, _) + -> List.exists ~f:(Pvar.equal v) pvars + | _ + -> false + in let sigma_stack, sigma_other = List.partition_tf ~f:filter p.sigma in - let fresh_address_vars = ref [] in (* fresh vars substituted for the address of stack vars *) - let stack_vars_address_in_post = ref [] in (* stack vars whose address is still present *) - let exp_replace = List.map ~f:(function - | Sil.Hpointsto (Exp.Lvar v, _, _) -> - let freshv = Ident.create_fresh Ident.kprimed in - fresh_address_vars := (v, freshv) :: !fresh_address_vars; - (Exp.Lvar v, Exp.Var freshv) - | _ -> assert false) sigma_stack in + let fresh_address_vars = ref [] in + (* fresh vars substituted for the address of stack vars *) + let stack_vars_address_in_post = ref [] in + (* stack vars whose address is still present *) + let exp_replace = + List.map + ~f:(function + | Sil.Hpointsto (Exp.Lvar v, _, _) + -> let freshv = Ident.create_fresh Ident.kprimed in + fresh_address_vars := (v, freshv) :: !fresh_address_vars ; + (Exp.Lvar v, Exp.Var freshv) + | _ + -> assert false) + sigma_stack + in let pi1 = List.map ~f:(fun (id, e) -> Sil.Aeq (Exp.Var id, e)) (Sil.sub_to_list p.sub) in let pi = List.map ~f:(Sil.atom_replace_exp exp_replace) (p.pi @ pi1) in let p' = Prop.normalize tenv - (Prop.set p - ~sub:Sil.exp_sub_empty - ~sigma: (Prop.sigma_replace_exp tenv exp_replace sigma_other)) in + (Prop.set p ~sub:Sil.exp_sub_empty + ~sigma:(Prop.sigma_replace_exp tenv exp_replace sigma_other)) + in let p'' = let res = ref p' in let p'_fav = Prop.prop_fav p' in @@ -286,60 +313,68 @@ let deallocate_stack_vars tenv (p: 'a Prop.t) pvars = (* static locals are not stack-allocated *) if not (Pvar.is_static_local v) then (* the address of a de-allocated stack var in in the post *) - if Sil.fav_mem p'_fav freshv then - begin - stack_vars_address_in_post := v :: !stack_vars_address_in_post; - let pred = Sil.Apred (Adangling DAaddr_stack_var, [Exp.Var freshv]) in - res := add_or_replace tenv !res pred - end in - List.iter ~f:do_var !fresh_address_vars; - !res in + if Sil.fav_mem p'_fav freshv then ( + stack_vars_address_in_post := v :: !stack_vars_address_in_post ; + let pred = Sil.Apred (Adangling DAaddr_stack_var, [Exp.Var freshv]) in + res := add_or_replace tenv !res pred ) + in + List.iter ~f:do_var !fresh_address_vars ; + !res + in (* Filter out local addresses in p'' *) - let filtered_pi, changed = List.fold_right p''.pi ~init:([], false) - ~f:(fun a (filtered, changed) -> - if Sil.atom_has_local_addr a then - filtered, true - else - a :: filtered, changed - ) in + let filtered_pi, changed = + List.fold_right p''.pi ~init:([], false) ~f:(fun a (filtered, changed) -> + if Sil.atom_has_local_addr a then (filtered, true) else (a :: filtered, changed) ) + in (* Avoid normalization when p'' does not change *) - let p''' = if changed then Prop.normalize tenv (Prop.set p'' ~pi:filtered_pi) - else p'' in - !stack_vars_address_in_post, List.fold ~f:(Prop.prop_atom_and tenv) ~init:p''' pi + let p''' = if changed then Prop.normalize tenv (Prop.set p'' ~pi:filtered_pi) else p'' in + (!stack_vars_address_in_post, List.fold ~f:(Prop.prop_atom_and tenv) ~init:p''' pi) (** Input of this method is an exp in a prop. Output is a formal variable or path from a formal variable that is equal to the expression, or the OBJC_NULL attribute of the expression. *) let find_equal_formal_path tenv e prop = let rec find_in_sigma e seen_hpreds = - List.fold_right ~f:( - fun hpred res -> + List.fold_right + ~f:(fun hpred res -> if List.mem ~equal:Sil.equal_hpred seen_hpreds hpred then None else let seen_hpreds = hpred :: seen_hpreds in match res with - | Some _ -> res + | Some _ + -> res | None -> - match hpred with - | Sil.Hpointsto (Exp.Lvar pvar1, Sil.Eexp (exp2, Sil.Iformal(_, _) ), _) - when Exp.equal exp2 e && - (Pvar.is_local pvar1 || Pvar.is_seed pvar1) -> - Some (Exp.Lvar pvar1) - | Sil.Hpointsto (exp1, Sil.Estruct (fields, _), _) -> - List.fold_right ~f:(fun (field, strexp) res -> - match res with - | Some _ -> res - | None -> - match strexp with - | Sil.Eexp (exp2, _) when Exp.equal exp2 e -> - (match find_in_sigma exp1 seen_hpreds with - | Some vfs -> Some (Exp.Lfield (vfs, field, Typ.mk Tvoid)) - | None -> None) - | _ -> None) fields ~init:None - | _ -> None) prop.Prop.sigma ~init:None in + match hpred with + | Sil.Hpointsto (Exp.Lvar pvar1, Sil.Eexp (exp2, Sil.Iformal (_, _)), _) + when Exp.equal exp2 e && (Pvar.is_local pvar1 || Pvar.is_seed pvar1) + -> Some (Exp.Lvar pvar1) + | Sil.Hpointsto (exp1, Sil.Estruct (fields, _), _) + -> List.fold_right + ~f:(fun (field, strexp) res -> + match res with + | Some _ + -> res + | None -> + match strexp with + | Sil.Eexp (exp2, _) when Exp.equal exp2 e -> ( + match find_in_sigma exp1 seen_hpreds with + | Some vfs + -> Some (Exp.Lfield (vfs, field, Typ.mk Tvoid)) + | None + -> None ) + | _ + -> None) + fields ~init:None + | _ + -> None) + prop.Prop.sigma ~init:None + in match find_in_sigma e [] with - | Some vfs -> Some vfs + | Some vfs + -> Some vfs | None -> - match get_objc_null tenv prop e with - | Some (Apred (Aobjc_null, [_; vfs])) -> Some vfs - | _ -> None + match get_objc_null tenv prop e with + | Some Apred (Aobjc_null, [_; vfs]) + -> Some vfs + | _ + -> None diff --git a/infer/src/backend/Attribute.mli b/infer/src/backend/Attribute.mli index ce8b67ec5..12384b95b 100644 --- a/infer/src/backend/Attribute.mli +++ b/infer/src/backend/Attribute.mli @@ -15,99 +15,102 @@ open! IStd module L = Logging module F = Format - -(** Check whether an atom is used to mark an attribute *) val is_pred : Sil.atom -> bool +(** Check whether an atom is used to mark an attribute *) +val add : + Tenv.t -> ?footprint:bool -> ?polarity:bool -> Prop.normal Prop.t -> PredSymb.t -> Exp.t list + -> Prop.normal Prop.t (** Add an attribute associated to the argument expressions *) -val add : Tenv.t -> ?footprint: bool -> ?polarity: bool -> - Prop.normal Prop.t -> PredSymb.t -> Exp.t list -> Prop.normal Prop.t -(** Replace an attribute associated to the expression *) val add_or_replace : Tenv.t -> Prop.normal Prop.t -> Sil.atom -> Prop.normal Prop.t +(** Replace an attribute associated to the expression *) +val add_or_replace_check_changed : + Tenv.t -> (PredSymb.t -> PredSymb.t -> unit) -> Prop.normal Prop.t -> Sil.atom + -> Prop.normal Prop.t (** Replace an attribute associated to the expression, and call the given function with new and old attributes if they changed. *) -val add_or_replace_check_changed : - Tenv.t -> (PredSymb.t -> PredSymb.t -> unit) -> Prop.normal Prop.t -> Sil.atom -> Prop.normal Prop.t -(** Get all the attributes of the prop *) val get_all : 'a Prop.t -> Sil.atom list +(** Get all the attributes of the prop *) -(** Get the attributes associated to the expression, if any *) val get_for_exp : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom list +(** Get the attributes associated to the expression, if any *) -(** Retrieve all the atoms that contain a specific attribute *) val get_for_symb : 'a Prop.t -> PredSymb.t -> Sil.atom list +(** Retrieve all the atoms that contain a specific attribute *) -(** Get the autorelease attribute associated to the expression, if any *) val get_autorelease : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the autorelease attribute associated to the expression, if any *) -(** Get the div0 attribute associated to the expression, if any *) val get_div0 : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the div0 attribute associated to the expression, if any *) -(** Get the objc null attribute associated to the expression, if any *) val get_objc_null : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the objc null attribute associated to the expression, if any *) -(** Get the observer attribute associated to the expression, if any *) val get_observer : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the observer attribute associated to the expression, if any *) -(** Get the resource attribute associated to the expression, if any *) val get_resource : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the resource attribute associated to the expression, if any *) -(** Get the retval null attribute associated to the expression, if any *) val get_retval : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the retval null attribute associated to the expression, if any *) -(** Get the taint attribute associated to the expression, if any *) val get_taint : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the taint attribute associated to the expression, if any *) -(** Get the undef attribute associated to the expression, if any *) val get_undef : Tenv.t -> 'a Prop.t -> Exp.t -> Sil.atom option +(** Get the undef attribute associated to the expression, if any *) -(** Test for existence of an Adangling DAuninit attribute associated to the exp *) val has_dangling_uninit : Tenv.t -> 'a Prop.t -> Exp.t -> bool +(** Test for existence of an Adangling DAuninit attribute associated to the exp *) -(** Remove an attribute *) val remove : Tenv.t -> Prop.normal Prop.t -> Sil.atom -> Prop.normal Prop.t +(** Remove an attribute *) -(** Remove all attributes for the given attr *) val remove_for_attr : Tenv.t -> Prop.normal Prop.t -> PredSymb.t -> Prop.normal Prop.t +(** Remove all attributes for the given attr *) -(** Remove all attributes for the given resource and kind *) val remove_resource : Tenv.t -> PredSymb.res_act_kind -> PredSymb.resource -> Prop.normal Prop.t -> Prop.normal Prop.t +(** Remove all attributes for the given resource and kind *) -(** Apply f to every resource attribute in the prop *) val map_resource : - Tenv.t -> Prop.normal Prop.t -> (Exp.t -> PredSymb.res_action -> PredSymb.res_action) -> Prop.normal Prop.t + Tenv.t -> Prop.normal Prop.t -> (Exp.t -> PredSymb.res_action -> PredSymb.res_action) + -> Prop.normal Prop.t +(** Apply f to every resource attribute in the prop *) +val replace_objc_null : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> Prop.normal Prop.t (** [replace_objc_null lhs rhs]. If rhs has the objc_null attribute, replace the attribute and set the lhs = 0 *) -val replace_objc_null : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> Prop.normal Prop.t +val nullify_exp_with_objc_null : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Prop.normal Prop.t (** For each Var subexp of the argument with an Aobjc_null attribute, remove the attribute and conjoin an equality to zero. *) -val nullify_exp_with_objc_null : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Prop.normal Prop.t -(** mark Exp.Var's or Exp.Lvar's as undefined *) val mark_vars_as_undefined : - Tenv.t -> Prop.normal Prop.t -> Exp.t list -> Typ.Procname.t -> Annot.Item.t -> - Location.t -> PredSymb.path_pos -> Prop.normal Prop.t + Tenv.t -> Prop.normal Prop.t -> Exp.t list -> Typ.Procname.t -> Annot.Item.t -> Location.t + -> PredSymb.path_pos -> Prop.normal Prop.t +(** mark Exp.Var's or Exp.Lvar's as undefined *) (** type for arithmetic problems *) type arith_problem = (* division by zero *) | Div0 of Exp.t - (* unary minus of unsigned type applied to the given expression *) | UminusUnsigned of Exp.t * Typ.t -(** Look for an arithmetic problem in [exp] *) val find_arithmetic_problem : - Tenv.t -> PredSymb.path_pos -> Prop.normal Prop.t -> Exp.t -> arith_problem option * Prop.normal Prop.t + Tenv.t -> PredSymb.path_pos -> Prop.normal Prop.t -> Exp.t + -> arith_problem option * Prop.normal Prop.t +(** Look for an arithmetic problem in [exp] *) +val deallocate_stack_vars : + Tenv.t -> Prop.normal Prop.t -> Pvar.t list -> Pvar.t list * Prop.normal Prop.t (** Deallocate the stack variables in [pvars], and replace them by normal variables. Return the list of stack variables whose address was still present after deallocation. *) -val deallocate_stack_vars : Tenv.t -> Prop.normal Prop.t -> Pvar.t list -> Pvar.t list * Prop.normal Prop.t val find_equal_formal_path : Tenv.t -> Exp.t -> 'a Prop.t -> Exp.t option diff --git a/infer/src/backend/BuiltinDefn.ml b/infer/src/backend/BuiltinDefn.ml index a71431030..ce1378867 100644 --- a/infer/src/backend/BuiltinDefn.ml +++ b/infer/src/backend/BuiltinDefn.ml @@ -12,26 +12,24 @@ open! IStd (** Models for the builtin functions supported *) open SymExec - module L = Logging module F = Format type t = Builtin.registered -let execute___no_op prop path: Builtin.ret_typ = - [(prop, path)] +let execute___no_op prop path : Builtin.ret_typ = [(prop, path)] (** model va_arg as always returning 0 *) -let execute___builtin_va_arg { Builtin.pdesc; tenv; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = - match args, ret_id with - | [_; _; (lexp3, typ3)], _ -> - let instr' = Sil.Store (lexp3, typ3, Exp.zero, loc) in +let execute___builtin_va_arg {Builtin.pdesc; tenv; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = + match (args, ret_id) with + | [_; _; (lexp3, typ3)], _ + -> let instr' = Sil.Store (lexp3, typ3, Exp.zero, loc) in SymExec.instrs ~mask_errors:true tenv pdesc [instr'] [(prop_, path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) -let mk_empty_array len = - Sil.Earray (len, [], Sil.inst_none) +let mk_empty_array len = Sil.Earray (len, [], Sil.inst_none) (* Make a rearranged array. As it is rearranged when it appears in a precondition it requires that the function is called with the array allocated. If not infer @@ -40,21 +38,20 @@ let mk_empty_array_rearranged len = Sil.Earray (len, [], Sil.inst_rearrange true (State.get_loc ()) (State.get_path_pos ())) let extract_array_type typ = - if (Config.curr_language_is Config.Java) then - match typ.Typ.desc with - | Typ.Tptr ({Typ.desc=Tarray _} as arr, _) -> Some arr - | _ -> None + if Config.curr_language_is Config.Java then + match typ.Typ.desc with Typ.Tptr (({Typ.desc= Tarray _} as arr), _) -> Some arr | _ -> None else match typ.Typ.desc with - | Typ.Tarray _ -> Some typ - | Typ.Tptr (elt, _) -> Some (Typ.mk ~default:typ (Tarray (elt, None, None))) - | _ -> None + | Typ.Tarray _ + -> Some typ + | Typ.Tptr (elt, _) + -> Some (Typ.mk ~default:typ (Tarray (elt, None, None))) + | _ + -> None (** Return a result from a procedure call. *) let return_result tenv e prop ret_id = - match ret_id with - | Some (ret_id, _) -> Prop.conjoin_eq tenv e (Exp.Var ret_id) prop - | _ -> prop + match ret_id with Some (ret_id, _) -> Prop.conjoin_eq tenv e (Exp.Var ret_id) prop | _ -> prop (* Add an array of typ pointed to by lexp to prop_ if it doesn't already exist *) (* Return the new prop and the array length *) @@ -64,84 +61,92 @@ let add_array_to_prop tenv pdesc prop_ lexp typ = let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in let hpred_opt = List.find - ~f:(function - | Sil.Hpointsto(e, _, _) -> Exp.equal e n_lexp - | _ -> false) - prop.Prop.sigma in + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e n_lexp | _ -> false) + prop.Prop.sigma + in match hpred_opt with - | Some (Sil.Hpointsto (_, Sil.Earray (len, _, _), _)) -> - Some (len, prop) - | Some _ -> - None (* e points to something but not an array *) - | None -> - extract_array_type typ |> - Option.map ~f:(fun arr_typ -> - let len = Exp.Var (Ident.create_fresh Ident.kfootprint) in - let s = mk_empty_array_rearranged len in - let hpred = - Prop.mk_ptsto tenv n_lexp s (Exp.Sizeof {typ=arr_typ; nbytes=None; - dynamic_length=None; subtype=Subtype.exact}) in - let sigma = prop.Prop.sigma in - let sigma_fp = prop.Prop.sigma_fp in - let prop'= Prop.set prop ~sigma:(hpred:: sigma) in - let prop''= Prop.set prop' ~sigma_fp:(hpred:: sigma_fp) in - let prop''= Prop.normalize tenv prop'' in - (len, prop'')) + | Some Sil.Hpointsto (_, Sil.Earray (len, _, _), _) + -> Some (len, prop) + | Some _ + -> None (* e points to something but not an array *) + | None + -> extract_array_type typ + |> Option.map ~f:(fun arr_typ -> + let len = Exp.Var (Ident.create_fresh Ident.kfootprint) in + let s = mk_empty_array_rearranged len in + let hpred = + Prop.mk_ptsto tenv n_lexp s + (Exp.Sizeof + {typ= arr_typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact}) + in + let sigma = prop.Prop.sigma in + let sigma_fp = prop.Prop.sigma_fp in + let prop' = Prop.set prop ~sigma:(hpred :: sigma) in + let prop'' = Prop.set prop' ~sigma_fp:(hpred :: sigma_fp) in + let prop'' = Prop.normalize tenv prop'' in + (len, prop'') ) (* Add an array in prop if it is not allocated.*) -let execute___require_allocated_array { Builtin.tenv; pdesc; prop_; path; args; } - : Builtin.ret_typ = +let execute___require_allocated_array {Builtin.tenv; pdesc; prop_; path; args} : Builtin.ret_typ = match args with - | [(lexp, typ)] -> - (match add_array_to_prop tenv pdesc prop_ lexp typ with - | None -> [] - | Some (_, prop) -> [(prop, path)]) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute___get_array_length { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = + | [(lexp, typ)] -> ( + match add_array_to_prop tenv pdesc prop_ lexp typ with + | None + -> [] + | Some (_, prop) + -> [(prop, path)] ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute___get_array_length {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(lexp, typ)] -> - (match add_array_to_prop tenv pdesc prop_ lexp typ with - | None -> [] - | Some (len, prop) -> [(return_result tenv len prop ret_id, path)]) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute___set_array_length { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, typ); (len, _)], None -> - (match add_array_to_prop tenv pdesc prop_ lexp typ with - | None -> [] - | Some (_, prop_a) -> (* Invariant: prop_a has an array pointed to by lexp *) - let pname = Procdesc.get_proc_name pdesc in - let n_lexp, prop__ = check_arith_norm_exp tenv pname lexp prop_a in - let n_len, prop = check_arith_norm_exp tenv pname len prop__ in - let hpred, sigma' = List.partition_tf ~f:(function - | Sil.Hpointsto(e, _, _) -> Exp.equal e n_lexp - | _ -> false) prop.Prop.sigma in - (match hpred with - | [Sil.Hpointsto(e, Sil.Earray(_, esel, inst), t)] -> - let hpred' = Sil.Hpointsto (e, Sil.Earray (n_len, esel, inst), t) in - let prop' = Prop.set prop ~sigma:(hpred':: sigma') in - [(Prop.normalize tenv prop', path)] - | _ -> [] (* by construction of prop_a this case is impossible *) )) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute___print_value { Builtin.tenv; pdesc; prop_; path; args; } - : Builtin.ret_typ = - L.(debug Analysis Medium) "__print_value: "; + | [(lexp, typ)] -> ( + match add_array_to_prop tenv pdesc prop_ lexp typ with + | None + -> [] + | Some (len, prop) + -> [(return_result tenv len prop ret_id, path)] ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute___set_array_length {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, typ); (len, _)], None -> ( + match add_array_to_prop tenv pdesc prop_ lexp typ with + | None + -> [] + | Some (_, prop_a) + -> (* Invariant: prop_a has an array pointed to by lexp *) + let pname = Procdesc.get_proc_name pdesc in + let n_lexp, prop__ = check_arith_norm_exp tenv pname lexp prop_a in + let n_len, prop = check_arith_norm_exp tenv pname len prop__ in + let hpred, sigma' = + List.partition_tf + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e n_lexp | _ -> false) + prop.Prop.sigma + in + match hpred with + | [(Sil.Hpointsto (e, Sil.Earray (_, esel, inst), t))] + -> let hpred' = Sil.Hpointsto (e, Sil.Earray (n_len, esel, inst), t) in + let prop' = Prop.set prop ~sigma:(hpred' :: sigma') in + [(Prop.normalize tenv prop', path)] + | _ + -> [] + (* by construction of prop_a this case is impossible *) ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute___print_value {Builtin.tenv; pdesc; prop_; path; args} : Builtin.ret_typ = + L.(debug Analysis Medium) "__print_value: " ; let pname = Procdesc.get_proc_name pdesc in let do_arg (lexp, _) = let n_lexp, _ = check_arith_norm_exp tenv pname lexp prop_ in - L.(debug Analysis Medium) "%a " Exp.pp n_lexp in - List.iter ~f:do_arg args; - L.(debug Analysis Medium) "@."; - [(prop_, path)] + L.(debug Analysis Medium) "%a " Exp.pp n_lexp + in + List.iter ~f:do_arg args ; L.(debug Analysis Medium) "@." ; [(prop_, path)] let is_undefined_opt tenv prop n_lexp = - let is_undef = - Option.is_some (Attribute.get_undef tenv prop n_lexp) in + let is_undef = Option.is_some (Attribute.get_undef tenv prop n_lexp) in is_undef && Config.angelic_execution (** Creates an object in the heap with a given type, when the object is not known to be null or when @@ -149,319 +154,342 @@ let is_undefined_opt tenv prop n_lexp = let create_type tenv n_lexp typ prop = let prop_type = match - List.find ~f:(function - | Sil.Hpointsto(e, _, _) -> Exp.equal e n_lexp - | _ -> false) prop.Prop.sigma with - | Some _ -> - prop - | None -> - let mhpred = + List.find + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e n_lexp | _ -> false) + prop.Prop.sigma + with + | Some _ + -> prop + | None + -> let mhpred = match typ.Typ.desc with - | Typ.Tptr (typ', _) -> - let sexp = Sil.Estruct ([], Sil.inst_none) in - let texp = Exp.Sizeof {typ=typ'; nbytes=None; - dynamic_length=None; subtype=Subtype.subtypes} in + | Typ.Tptr (typ', _) + -> let sexp = Sil.Estruct ([], Sil.inst_none) in + let texp = + Exp.Sizeof + {typ= typ'; nbytes= None; dynamic_length= None; subtype= Subtype.subtypes} + in let hpred = Prop.mk_ptsto tenv n_lexp sexp texp in Some hpred - | Typ.Tarray _ -> - let len = Exp.Var (Ident.create_fresh Ident.kfootprint) in + | Typ.Tarray _ + -> let len = Exp.Var (Ident.create_fresh Ident.kfootprint) in let sexp = mk_empty_array len in - let texp = Exp.Sizeof {typ; nbytes=None; - dynamic_length=None; subtype=Subtype.subtypes} in + let texp = + Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.subtypes} + in let hpred = Prop.mk_ptsto tenv n_lexp sexp texp in Some hpred - | _ -> None in + | _ + -> None + in match mhpred with - | Some hpred -> - let sigma = prop.Prop.sigma in + | Some hpred + -> let sigma = prop.Prop.sigma in let sigma_fp = prop.Prop.sigma_fp in - let prop'= Prop.set prop ~sigma:(hpred:: sigma) in - let prop''= - let has_normal_variables = - Sil.fav_exists (Sil.exp_fav n_lexp) Ident.is_normal in - if (is_undefined_opt tenv prop n_lexp) || has_normal_variables - then prop' - else Prop.set prop' ~sigma_fp:(hpred:: sigma_fp) in - let prop''= Prop.normalize tenv prop'' in + let prop' = Prop.set prop ~sigma:(hpred :: sigma) in + let prop'' = + let has_normal_variables = Sil.fav_exists (Sil.exp_fav n_lexp) Ident.is_normal in + if is_undefined_opt tenv prop n_lexp || has_normal_variables then prop' + else Prop.set prop' ~sigma_fp:(hpred :: sigma_fp) + in + let prop'' = Prop.normalize tenv prop'' in prop'' - | None -> prop in + | None + -> prop + in let sil_is_null = Exp.BinOp (Binop.Eq, n_lexp, Exp.zero) in let sil_is_nonnull = Exp.UnOp (Unop.LNot, sil_is_null, None) in let null_case = Propset.to_proplist (prune tenv ~positive:true sil_is_null prop) in let non_null_case = Propset.to_proplist (prune tenv ~positive:true sil_is_nonnull prop_type) in - if ((List.length non_null_case) > 0) && (!Config.footprint) then - non_null_case - else if ((List.length non_null_case) > 0) && (is_undefined_opt tenv prop n_lexp) then - non_null_case + if List.length non_null_case > 0 && !Config.footprint then non_null_case + else if List.length non_null_case > 0 && is_undefined_opt tenv prop n_lexp then non_null_case else null_case @ non_null_case -let execute___get_type_of { Builtin.pdesc; tenv; prop_; path; ret_id; args; } - : Builtin.ret_typ = +let execute___get_type_of {Builtin.pdesc; tenv; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(lexp, typ)] -> - let pname = Procdesc.get_proc_name pdesc in + | [(lexp, typ)] + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in let props = create_type tenv n_lexp typ prop in let aux prop = let hpred_opt = - List.find_map ~f:(function - | Sil.Hpointsto(e, _, texp) when Exp.equal e n_lexp -> Some texp - | _ -> None) prop.Prop.sigma in + List.find_map + ~f:(function + | Sil.Hpointsto (e, _, texp) when Exp.equal e n_lexp -> Some texp | _ -> None) + prop.Prop.sigma + in match hpred_opt with - | Some texp -> - ((return_result tenv texp prop ret_id), path) - | None -> - ((return_result tenv Exp.zero prop ret_id), path) in - (List.map ~f:aux props) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | Some texp + -> (return_result tenv texp prop ret_id, path) + | None + -> (return_result tenv Exp.zero prop ret_id, path) + in + List.map ~f:aux props + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** replace the type of the ptsto rooted at [root_e] with [texp] in [prop] *) let replace_ptsto_texp tenv prop root_e texp = let process_sigma sigma = let sigma1, sigma2 = - List.partition_tf ~f:(function - | Sil.Hpointsto(e, _, _) -> Exp.equal e root_e - | _ -> false) sigma in + List.partition_tf + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e root_e | _ -> false) + sigma + in match sigma1 with - | [Sil.Hpointsto(e, se, _)] -> (Sil.Hpointsto (e, se, texp)) :: sigma2 - | _ -> sigma in + | [(Sil.Hpointsto (e, se, _))] + -> Sil.Hpointsto (e, se, texp) :: sigma2 + | _ + -> sigma + in let sigma = prop.Prop.sigma in let sigma_fp = prop.Prop.sigma_fp in - let prop'= Prop.set prop ~sigma:(process_sigma sigma) in - let prop''= Prop.set prop' ~sigma_fp:(process_sigma sigma_fp) in + let prop' = Prop.set prop ~sigma:(process_sigma sigma) in + let prop'' = Prop.set prop' ~sigma_fp:(process_sigma sigma_fp) in Prop.normalize tenv prop'' -let execute___instanceof_cast ~instof - { Builtin.pdesc; tenv; prop_; path; ret_id; args; } - : Builtin.ret_typ = +let execute___instanceof_cast ~instof {Builtin.pdesc; tenv; prop_; path; ret_id; args} + : Builtin.ret_typ = match args with - | [(val1_, typ1); (texp2_, _)] -> - let pname = Procdesc.get_proc_name pdesc in + | [(val1_, typ1); (texp2_, _)] + -> let pname = Procdesc.get_proc_name pdesc in let val1, prop__ = check_arith_norm_exp tenv pname val1_ prop_ in let texp2, prop = check_arith_norm_exp tenv pname texp2_ prop__ in let is_cast_to_reference = - match typ1.desc with - | Typ.Tptr (_, Typ.Pk_reference) -> true - | _ -> false in + match typ1.desc with Typ.Tptr (_, Typ.Pk_reference) -> true | _ -> false + in (* In Java, we throw an exception, in C++ we return 0 in case of a cast to a pointer, *) (* and throw an exception in case of a cast to a reference. *) - let should_throw_exception = - Config.curr_language_is Config.Java || is_cast_to_reference in + let should_throw_exception = Config.curr_language_is Config.Java || is_cast_to_reference in let deal_with_failed_cast val1 texp1 texp2 = - raise - (Tabulation.create_cast_exception - tenv __POS__ None texp1 texp2 val1) in + raise (Tabulation.create_cast_exception tenv __POS__ None texp1 texp2 val1) + in let exe_one_prop prop = - if Exp.equal texp2 Exp.zero then - [(return_result tenv Exp.zero prop ret_id, path)] + if Exp.equal texp2 Exp.zero then [(return_result tenv Exp.zero prop ret_id, path)] else let res_opt = - List.find ~f:(function - | Sil.Hpointsto (e1, _, _) -> Exp.equal e1 val1 - | _ -> false) prop.Prop.sigma |> - Option.map ~f:(function - | Sil.Hpointsto (_, _, texp1) -> - let pos_type_opt, neg_type_opt = - Prover.Subtyping_check.subtype_case_analysis tenv texp1 texp2 in - let mk_res type_opt res_e = match type_opt with - | None -> [] - | Some texp1' -> - let prop' = - if Exp.equal texp1 texp1' then prop - else replace_ptsto_texp tenv prop val1 texp1' in - [(return_result tenv res_e prop' ret_id, path)] in - if instof then (* instanceof *) - let pos_res = mk_res pos_type_opt Exp.one in - let neg_res = mk_res neg_type_opt Exp.zero in - pos_res @ neg_res - else (* cast *) - if not should_throw_exception then (* C++ case when negative cast returns 0 *) - let pos_res = mk_res pos_type_opt val1 in - let neg_res = mk_res neg_type_opt Exp.zero in - pos_res @ neg_res - else - begin - if !Config.footprint then - match pos_type_opt with - | None -> deal_with_failed_cast val1 texp1 texp2 - | Some _ -> mk_res pos_type_opt val1 - else (* !Config.footprint is false *) - match neg_type_opt with - | Some _ -> - if is_undefined_opt tenv prop val1 then mk_res pos_type_opt val1 - else deal_with_failed_cast val1 texp1 texp2 - | None -> mk_res pos_type_opt val1 - end - | _ -> [] - ) in + List.find + ~f:(function Sil.Hpointsto (e1, _, _) -> Exp.equal e1 val1 | _ -> false) + prop.Prop.sigma + |> Option.map ~f:(function + | Sil.Hpointsto (_, _, texp1) + -> ( + let pos_type_opt, neg_type_opt = + Prover.Subtyping_check.subtype_case_analysis tenv texp1 texp2 + in + let mk_res type_opt res_e = + match type_opt with + | None + -> [] + | Some texp1' + -> let prop' = + if Exp.equal texp1 texp1' then prop + else replace_ptsto_texp tenv prop val1 texp1' + in + [(return_result tenv res_e prop' ret_id, path)] + in + if instof then + (* instanceof *) + let pos_res = mk_res pos_type_opt Exp.one in + let neg_res = mk_res neg_type_opt Exp.zero in + pos_res @ neg_res + else if (* cast *) + not should_throw_exception then + (* C++ case when negative cast returns 0 *) + let pos_res = mk_res pos_type_opt val1 in + let neg_res = mk_res neg_type_opt Exp.zero in + pos_res @ neg_res + else if !Config.footprint then + match pos_type_opt with + | None + -> deal_with_failed_cast val1 texp1 texp2 + | Some _ + -> mk_res pos_type_opt val1 + else + (* !Config.footprint is false *) + match neg_type_opt with + | Some _ + -> if is_undefined_opt tenv prop val1 then mk_res pos_type_opt val1 + else deal_with_failed_cast val1 texp1 texp2 + | None + -> mk_res pos_type_opt val1 ) + | _ + -> [] ) + in match res_opt with - | Some res -> - res - | None -> - [(return_result tenv val1 prop ret_id, path)] in + | Some res + -> res + | None + -> [(return_result tenv val1 prop ret_id, path)] + in let props = create_type tenv val1 typ1 prop in List.concat_map ~f:exe_one_prop props - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) -let execute___instanceof builtin_args - : Builtin.ret_typ = +let execute___instanceof builtin_args : Builtin.ret_typ = execute___instanceof_cast ~instof:true builtin_args -let execute___cast builtin_args - : Builtin.ret_typ = +let execute___cast builtin_args : Builtin.ret_typ = execute___instanceof_cast ~instof:false builtin_args let set_resource_attribute tenv prop path n_lexp loc ra_res = - let prop' = match Attribute.get_resource tenv prop n_lexp with - | Some (Apred (Aresource ra, _)) -> - Attribute.add_or_replace tenv prop (Apred (Aresource { ra with ra_res }, [n_lexp])) - | _ -> - let pname = PredSymb.mem_alloc_pname PredSymb.Mnew in + let prop' = + match Attribute.get_resource tenv prop n_lexp with + | Some Apred (Aresource ra, _) + -> Attribute.add_or_replace tenv prop (Apred (Aresource {ra with ra_res}, [n_lexp])) + | _ + -> let pname = PredSymb.mem_alloc_pname PredSymb.Mnew in let ra = - { PredSymb. - ra_kind = Racquire; - ra_res = ra_res; - ra_pname = pname; - ra_loc = loc; - ra_vpath = None } in - Attribute.add_or_replace tenv prop (Apred (Aresource ra, [n_lexp])) in + {PredSymb.ra_kind= Racquire; ra_res; ra_pname= pname; ra_loc= loc; ra_vpath= None} + in + Attribute.add_or_replace tenv prop (Apred (Aresource ra, [n_lexp])) + in [(prop', path)] (** Set the attibute of the value as file *) -let execute___set_file_attribute { Builtin.tenv; pdesc; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, _)], _ -> - let pname = Procdesc.get_proc_name pdesc in +let execute___set_file_attribute {Builtin.tenv; pdesc; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, _)], _ + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in set_resource_attribute tenv prop path n_lexp loc PredSymb.Rfile - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Set the attibute of the value as lock *) -let execute___set_lock_attribute { Builtin.tenv; pdesc; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, _)], _ -> - let pname = Procdesc.get_proc_name pdesc in +let execute___set_lock_attribute {Builtin.tenv; pdesc; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, _)], _ + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in set_resource_attribute tenv prop path n_lexp loc PredSymb.Rlock - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Set the resource attribute of the first real argument of method as ignore, the first argument is assumed to be "this" *) -let execute___method_set_ignore_attribute { Builtin.tenv; pdesc; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = - match args, ret_id with - | [_ ; (lexp, _)], _ -> - let pname = Procdesc.get_proc_name pdesc in +let execute___method_set_ignore_attribute {Builtin.tenv; pdesc; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = + match (args, ret_id) with + | [_; (lexp, _)], _ + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in set_resource_attribute tenv prop path n_lexp loc PredSymb.Rignore - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Set the attibute of the value as memory *) -let execute___set_mem_attribute { Builtin.tenv; pdesc; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, _)], _ -> - let pname = Procdesc.get_proc_name pdesc in +let execute___set_mem_attribute {Builtin.tenv; pdesc; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, _)], _ + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in set_resource_attribute tenv prop path n_lexp loc (PredSymb.Rmemory PredSymb.Mnew) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** report an error if [lexp] is tainted; otherwise, add untained([lexp]) as a precondition *) let execute___check_untainted - { Builtin.tenv; pdesc; prop_; path; ret_id; args; proc_name = callee_pname; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, _)], _ -> - let caller_pname = Procdesc.get_proc_name pdesc in + {Builtin.tenv; pdesc; prop_; path; ret_id; args; proc_name= callee_pname} : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, _)], _ + -> let caller_pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv caller_pname lexp prop_ in [(check_untainted tenv n_lexp PredSymb.Tk_unknown caller_pname callee_pname prop, path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** take a pointer to a struct, and return the value of a hidden field in the struct *) -let execute___get_hidden_field { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = +let execute___get_hidden_field {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(lexp, _)] -> - let pname = Procdesc.get_proc_name pdesc in + | [(lexp, _)] + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in let ret_val = ref None in - let return_val p = match !ret_val with - | Some e -> return_result tenv e p ret_id - | None -> p in - let foot_var = lazy (Exp.Var (Ident.create_fresh Ident.kfootprint)) in - let filter_fld_hidden (f, _ ) = Typ.Fieldname.is_hidden f in + let return_val p = + match !ret_val with Some e -> return_result tenv e p ret_id | None -> p + in + let foot_var = (lazy (Exp.Var (Ident.create_fresh Ident.kfootprint))) in + let filter_fld_hidden (f, _) = Typ.Fieldname.is_hidden f in let has_fld_hidden fsel = List.exists ~f:filter_fld_hidden fsel in - let do_hpred in_foot hpred = match hpred with - | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) - when Exp.equal e n_lexp && (not (has_fld_hidden fsel)) -> - let foot_e = Lazy.force foot_var in - ret_val := Some foot_e; - let se = Sil.Eexp(foot_e, Sil.inst_none) in + let do_hpred in_foot hpred = + match hpred with + | Sil.Hpointsto (e, Sil.Estruct (fsel, inst), texp) + when Exp.equal e n_lexp && not (has_fld_hidden fsel) + -> let foot_e = Lazy.force foot_var in + ret_val := Some foot_e ; + let se = Sil.Eexp (foot_e, Sil.inst_none) in let fsel' = (Typ.Fieldname.hidden, se) :: fsel in - Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) - | Sil.Hpointsto(e, Sil.Estruct (fsel, _), _) - when Exp.equal e n_lexp && not in_foot && has_fld_hidden fsel -> - let set_ret_val () = + Sil.Hpointsto (e, Sil.Estruct (fsel', inst), texp) + | Sil.Hpointsto (e, Sil.Estruct (fsel, _), _) + when Exp.equal e n_lexp && not in_foot && has_fld_hidden fsel + -> let set_ret_val () = match List.find ~f:filter_fld_hidden fsel with - | Some (_, Sil.Eexp(e, _)) -> - ret_val := Some e - | _ -> - () in - set_ret_val(); - hpred - | _ -> hpred in + | Some (_, Sil.Eexp (e, _)) + -> ret_val := Some e + | _ + -> () + in + set_ret_val () ; hpred + | _ + -> hpred + in let sigma' = List.map ~f:(do_hpred false) prop.Prop.sigma in let sigma_fp' = List.map ~f:(do_hpred true) prop.Prop.sigma_fp in let prop' = Prop.set prop ~sigma:sigma' ~sigma_fp:sigma_fp' in let prop'' = return_val (Prop.normalize tenv prop') in [(prop'', path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** take a pointer to a struct and a value, and set a hidden field in the struct to the given value *) -let execute___set_hidden_field { Builtin.tenv; pdesc; prop_; path; args; } - : Builtin.ret_typ = +let execute___set_hidden_field {Builtin.tenv; pdesc; prop_; path; args} : Builtin.ret_typ = match args with - | [(lexp1, _); (lexp2, _)] -> - let pname = Procdesc.get_proc_name pdesc in + | [(lexp1, _); (lexp2, _)] + -> let pname = Procdesc.get_proc_name pdesc in let n_lexp1, prop__ = check_arith_norm_exp tenv pname lexp1 prop_ in let n_lexp2, prop = check_arith_norm_exp tenv pname lexp2 prop__ in - let foot_var = lazy (Exp.Var (Ident.create_fresh Ident.kfootprint)) in - let filter_fld_hidden (f, _ ) = Typ.Fieldname.is_hidden f in + let foot_var = (lazy (Exp.Var (Ident.create_fresh Ident.kfootprint))) in + let filter_fld_hidden (f, _) = Typ.Fieldname.is_hidden f in let has_fld_hidden fsel = List.exists ~f:filter_fld_hidden fsel in - let do_hpred in_foot hpred = match hpred with - | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) - when Exp.equal e n_lexp1 && not in_foot -> - let se = Sil.Eexp(n_lexp2, Sil.inst_none) in + let do_hpred in_foot hpred = + match hpred with + | Sil.Hpointsto (e, Sil.Estruct (fsel, inst), texp) when Exp.equal e n_lexp1 && not in_foot + -> let se = Sil.Eexp (n_lexp2, Sil.inst_none) in let fsel' = - (Typ.Fieldname.hidden, se) :: - (List.filter ~f:(fun x -> not (filter_fld_hidden x)) fsel) in - Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) - | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) - when Exp.equal e n_lexp1 && in_foot && not (has_fld_hidden fsel) -> - let foot_e = Lazy.force foot_var in - let se = Sil.Eexp(foot_e, Sil.inst_none) in + (Typ.Fieldname.hidden, se) + :: List.filter ~f:(fun x -> not (filter_fld_hidden x)) fsel + in + Sil.Hpointsto (e, Sil.Estruct (fsel', inst), texp) + | Sil.Hpointsto (e, Sil.Estruct (fsel, inst), texp) + when Exp.equal e n_lexp1 && in_foot && not (has_fld_hidden fsel) + -> let foot_e = Lazy.force foot_var in + let se = Sil.Eexp (foot_e, Sil.inst_none) in let fsel' = (Typ.Fieldname.hidden, se) :: fsel in - Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) - | _ -> hpred in + Sil.Hpointsto (e, Sil.Estruct (fsel', inst), texp) + | _ + -> hpred + in let sigma' = List.map ~f:(do_hpred false) prop.Prop.sigma in let sigma_fp' = List.map ~f:(do_hpred true) prop.Prop.sigma_fp in let prop' = Prop.set prop ~sigma:sigma' ~sigma_fp:sigma_fp' in let prop'' = Prop.normalize tenv prop' in [(prop'', path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (* Update the objective-c hidden counter by applying the operation op and the operand delta.*) (* Eg. op=+/- delta is an integer *) -let execute___objc_counter_update - ~mask_errors op delta - { Builtin.pdesc; tenv; prop_; path; args; loc; } - : Builtin.ret_typ = +let execute___objc_counter_update ~mask_errors op delta + {Builtin.pdesc; tenv; prop_; path; args; loc} : Builtin.ret_typ = match args with - | [(lexp, ({Typ.desc=Tstruct _} as typ | {desc=Tptr ({desc=Tstruct _} as typ, _)}))] -> - (* Assumes that lexp is a temp n$1 that has the value of the object. *) + | [(lexp, ({Typ.desc= Tstruct _} as typ | {desc= Tptr (({desc= Tstruct _} as typ), _)}))] + -> (* Assumes that lexp is a temp n$1 that has the value of the object. *) (* This is the case as a call f(o) it's translates as n$1=*&o; f(n$1) *) (* n$2 = *n$1.hidden *) let tmp = Ident.create_fresh Ident.knormal in @@ -469,105 +497,99 @@ let execute___objc_counter_update let counter_to_tmp = Sil.Load (tmp, hidden_field, typ, loc) in (* *n$1.hidden = (n$2 +/- delta) *) let update_counter = - Sil.Store (hidden_field, typ, BinOp (op, Var tmp, Const (Cint delta)), loc) in + Sil.Store (hidden_field, typ, BinOp (op, Var tmp, Const (Cint delta)), loc) + in let update_counter_instrs = - [ counter_to_tmp; update_counter; Sil.Remove_temps([tmp], loc) ] in + [counter_to_tmp; update_counter; Sil.Remove_temps ([tmp], loc)] + in SymExec.instrs ~mask_errors tenv pdesc update_counter_instrs [(prop_, path)] - | [(_, typ)] -> - L.d_str ("Trying to update hidden field of non-struct value. Type: " ^ (Typ.to_string typ)); + | [(_, typ)] + -> L.d_str ("Trying to update hidden field of non-struct value. Type: " ^ Typ.to_string typ) ; assert false - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (* Given a list of args checks if the first is the flag indicating whether is a call to retain/release for which we have to suppress NPE report or not. If the flag is present it is removed from the list of args. *) let get_suppress_npe_flag args = match args with - | (Exp.Const (Const.Cint i), {Typ.desc=Tint Typ.IBool}):: args' when IntLit.isone i -> - false, args' (* this is a CFRelease/CFRetain *) - | _ -> true, args + | (Exp.Const Const.Cint i, {Typ.desc= Tint Typ.IBool}) :: args' when IntLit.isone i + -> (false, args') (* this is a CFRelease/CFRetain *) + | _ + -> (true, args) -let execute___objc_retain_impl ({ Builtin.tenv; prop_; args; ret_id; } as builtin_args) - : Builtin.ret_typ = +let execute___objc_retain_impl ({Builtin.tenv; prop_; args; ret_id} as builtin_args) + : Builtin.ret_typ = let mask_errors, args' = get_suppress_npe_flag args in match args' with - | [(lexp, _)] -> - let prop = return_result tenv lexp prop_ ret_id in - execute___objc_counter_update - ~mask_errors (Binop.PlusA) (IntLit.one) - { builtin_args with Builtin.prop_ = prop; args = args'; } - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute___objc_retain builtin_args - : Builtin.ret_typ = - if Config.objc_memory_model_on then - execute___objc_retain_impl builtin_args + | [(lexp, _)] + -> let prop = return_result tenv lexp prop_ ret_id in + execute___objc_counter_update ~mask_errors Binop.PlusA IntLit.one + {builtin_args with Builtin.prop_= prop; args= args'} + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute___objc_retain builtin_args : Builtin.ret_typ = + if Config.objc_memory_model_on then execute___objc_retain_impl builtin_args else execute___no_op builtin_args.Builtin.prop_ builtin_args.Builtin.path -let execute___objc_retain_cf builtin_args - : Builtin.ret_typ = +let execute___objc_retain_cf builtin_args : Builtin.ret_typ = execute___objc_retain_impl builtin_args -let execute___objc_release_impl - ({ Builtin.args; } - as builtin_args) - : Builtin.ret_typ = +let execute___objc_release_impl ({Builtin.args} as builtin_args) : Builtin.ret_typ = let mask_errors, args' = get_suppress_npe_flag args in - execute___objc_counter_update - ~mask_errors Binop.MinusA IntLit.one - { builtin_args with Builtin.args = args'; } + execute___objc_counter_update ~mask_errors Binop.MinusA IntLit.one + {builtin_args with Builtin.args= args'} -let execute___objc_release builtin_args - : Builtin.ret_typ = - if Config.objc_memory_model_on then - execute___objc_release_impl builtin_args +let execute___objc_release builtin_args : Builtin.ret_typ = + if Config.objc_memory_model_on then execute___objc_release_impl builtin_args else execute___no_op builtin_args.Builtin.prop_ builtin_args.Builtin.path -let execute___objc_release_cf builtin_args - : Builtin.ret_typ = +let execute___objc_release_cf builtin_args : Builtin.ret_typ = execute___objc_release_impl builtin_args (** Set the attibute of the value as objc autoreleased *) -let execute___set_autorelease_attribute - { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = - match args, ret_id with - | [(lexp, _)], _ -> - let pname = Procdesc.get_proc_name pdesc in +let execute___set_autorelease_attribute {Builtin.tenv; pdesc; prop_; path; ret_id; args} + : Builtin.ret_typ = + match (args, ret_id) with + | [(lexp, _)], _ + -> let pname = Procdesc.get_proc_name pdesc in let prop = return_result tenv lexp prop_ ret_id in if Config.objc_memory_model_on then let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop in let prop' = Attribute.add_or_replace tenv prop (Apred (Aautorelease, [n_lexp])) in [(prop', path)] else execute___no_op prop path - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Release all the objects in the pool *) -let execute___release_autorelease_pool - ({ Builtin.tenv; prop_; path; } as builtin_args) - : Builtin.ret_typ = +let execute___release_autorelease_pool ({Builtin.tenv; prop_; path} as builtin_args) + : Builtin.ret_typ = if Config.objc_memory_model_on then let autoreleased_objects = Attribute.get_for_symb prop_ Aautorelease in let prop_without_attribute = Attribute.remove_for_attr tenv prop_ Aautorelease in let call_release res atom = - match res, atom with - | ((prop', path') :: _, Sil.Apred (_, exp :: _)) -> - List.find ~f:(function - | Sil.Hpointsto(e1, _, _) -> Exp.equal e1 exp - | _ -> false) prop_.Prop.sigma |> - Option.value_map ~f:(function - | Sil.Hpointsto (_, _, Exp.Sizeof {typ}) -> - let res1 = - execute___objc_release - { builtin_args with - Builtin.args = [(exp, typ)]; - prop_ = prop'; - path = path'; } in - res1 - | _ -> res - ) - ~default:res - | _ -> res in + match (res, atom) with + | (prop', path') :: _, Sil.Apred (_, exp :: _) + -> List.find + ~f:(function Sil.Hpointsto (e1, _, _) -> Exp.equal e1 exp | _ -> false) + prop_.Prop.sigma + |> Option.value_map + ~f:(function + | Sil.Hpointsto (_, _, Exp.Sizeof {typ}) + -> let res1 = + execute___objc_release + {builtin_args with Builtin.args= [(exp, typ)]; prop_= prop'; path= path'} + in + res1 + | _ + -> res) + ~default:res + | _ + -> res + in List.fold ~f:call_release ~init:[(prop_without_attribute, path)] autoreleased_objects else execute___no_op prop_ path @@ -581,424 +603,469 @@ let delete_attr tenv pdesc prop path exp attr = let n_lexp, prop = check_arith_norm_exp tenv pname exp prop in [(Attribute.remove tenv prop (Apred (attr, [n_lexp])), path)] - (** Set attibute att *) -let execute___set_attr attr { Builtin.tenv; pdesc; prop_; path; args; } - : Builtin.ret_typ = +let execute___set_attr attr {Builtin.tenv; pdesc; prop_; path; args} : Builtin.ret_typ = match args with - | [(lexp, _)] -> set_attr tenv pdesc prop_ path lexp attr - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | [(lexp, _)] + -> set_attr tenv pdesc prop_ path lexp attr + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Delete the locked attibute of the value*) -let execute___delete_locked_attribute { Builtin.tenv; prop_; pdesc; path; args; } - : Builtin.ret_typ = +let execute___delete_locked_attribute {Builtin.tenv; prop_; pdesc; path; args} : Builtin.ret_typ = match args with - | [(lexp, _)] -> delete_attr tenv pdesc prop_ path lexp PredSymb.Alocked - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - + | [(lexp, _)] + -> delete_attr tenv pdesc prop_ path lexp PredSymb.Alocked + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (** Set the attibute of the value as locked*) -let execute___set_locked_attribute builtin_args - : Builtin.ret_typ = - execute___set_attr (PredSymb.Alocked) builtin_args +let execute___set_locked_attribute builtin_args : Builtin.ret_typ = + execute___set_attr PredSymb.Alocked builtin_args (** Set the attibute of the value as resource/unlocked*) -let execute___set_unlocked_attribute - ({ Builtin.pdesc; loc; } as builtin_args) - : Builtin.ret_typ = +let execute___set_unlocked_attribute ({Builtin.pdesc; loc} as builtin_args) : Builtin.ret_typ = let pname = Procdesc.get_proc_name pdesc in (* ra_kind = Rrelease in following indicates unlocked *) - let ra = { - PredSymb.ra_kind = PredSymb.Rrelease; - ra_res = PredSymb.Rlock; - ra_pname = pname; - ra_loc = loc; - ra_vpath = None; } in + let ra = + { PredSymb.ra_kind= PredSymb.Rrelease + ; ra_res= PredSymb.Rlock + ; ra_pname= pname + ; ra_loc= loc + ; ra_vpath= None } + in execute___set_attr (PredSymb.Aresource ra) builtin_args (** Set the attibute of the value as tainted *) -let execute___set_taint_attribute - ({ Builtin.tenv; pdesc; args; prop_; path; }) - : Builtin.ret_typ = +let execute___set_taint_attribute {Builtin.tenv; pdesc; args; prop_; path} : Builtin.ret_typ = match args with - | (exp, _) :: [(Exp.Const (Const.Cstr taint_kind_str), _)] -> - let taint_source = Procdesc.get_proc_name pdesc in - let taint_kind = match taint_kind_str with - | "UnverifiedSSLSocket" -> PredSymb.Tk_unverified_SSL_socket - | "SharedPreferenceData" -> PredSymb.Tk_shared_preferences_data - | other_str -> failwith ("Unrecognized taint kind " ^ other_str) in - set_attr tenv pdesc prop_ path exp (PredSymb.Ataint { PredSymb.taint_source; taint_kind}) - | _ -> - (* note: we can also get this if [taint_kind] is not a string literal *) + | [(exp, _); (Exp.Const Const.Cstr taint_kind_str, _)] + -> let taint_source = Procdesc.get_proc_name pdesc in + let taint_kind = + match taint_kind_str with + | "UnverifiedSSLSocket" + -> PredSymb.Tk_unverified_SSL_socket + | "SharedPreferenceData" + -> PredSymb.Tk_shared_preferences_data + | other_str + -> failwith ("Unrecognized taint kind " ^ other_str) + in + set_attr tenv pdesc prop_ path exp + (PredSymb.Ataint {PredSymb.taint_source= taint_source; taint_kind}) + | _ + -> (* note: we can also get this if [taint_kind] is not a string literal *) raise (Exceptions.Wrong_argument_number __POS__) (** Set the attibute of the value as tainted *) -let execute___set_untaint_attribute - ({ Builtin.tenv; pdesc; args; prop_; path; }) - : Builtin.ret_typ = +let execute___set_untaint_attribute {Builtin.tenv; pdesc; args; prop_; path} : Builtin.ret_typ = match args with - | (exp, _) :: [] -> - let taint_source = Procdesc.get_proc_name pdesc in - let taint_kind = PredSymb.Tk_unknown in (* TODO: change builtin to specify taint kind *) - set_attr tenv pdesc prop_ path exp (PredSymb.Auntaint { PredSymb.taint_source; taint_kind}) - | _ -> - raise (Exceptions.Wrong_argument_number __POS__) - -let execute___objc_cast { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = + | [(exp, _)] + -> let taint_source = Procdesc.get_proc_name pdesc in + let taint_kind = PredSymb.Tk_unknown in + (* TODO: change builtin to specify taint kind *) + set_attr tenv pdesc prop_ path exp + (PredSymb.Auntaint {PredSymb.taint_source= taint_source; taint_kind}) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute___objc_cast {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(val1_, _); (texp2_, _)] -> + | [(val1_, _); (texp2_, _)] + -> ( let pname = Procdesc.get_proc_name pdesc in let val1, prop__ = check_arith_norm_exp tenv pname val1_ prop_ in let texp2, prop = check_arith_norm_exp tenv pname texp2_ prop__ in - (match - List.find ~f:(function - | Sil.Hpointsto(e1, _, _) -> Exp.equal e1 val1 - | _ -> false) prop.Prop.sigma |> - Option.map ~f:(fun hpred -> match hpred, texp2 with - | Sil.Hpointsto (val1, _, _), Exp.Sizeof _ -> - let prop' = replace_ptsto_texp tenv prop val1 texp2 in - [(return_result tenv val1 prop' ret_id, path)] - | _ -> [(return_result tenv val1 prop ret_id, path)] - ) - with - | Some res -> - res - | None -> - [(return_result tenv val1 prop ret_id, path)]) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute_abort { Builtin.proc_name; } - : Builtin.ret_typ = + match + List.find + ~f:(function Sil.Hpointsto (e1, _, _) -> Exp.equal e1 val1 | _ -> false) + prop.Prop.sigma + |> Option.map ~f:(fun hpred -> + match (hpred, texp2) with + | Sil.Hpointsto (val1, _, _), Exp.Sizeof _ + -> let prop' = replace_ptsto_texp tenv prop val1 texp2 in + [(return_result tenv val1 prop' ret_id, path)] + | _ + -> [(return_result tenv val1 prop ret_id, path)] ) + with + | Some res + -> res + | None + -> [(return_result tenv val1 prop ret_id, path)] ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute_abort {Builtin.proc_name} : Builtin.ret_typ = raise (Exceptions.Precondition_not_found (Localise.verbatim_desc (Typ.Procname.to_string proc_name), __POS__)) -let execute_exit { Builtin.prop_; path; } - : Builtin.ret_typ = - SymExec.diverge prop_ path +let execute_exit {Builtin.prop_; path} : Builtin.ret_typ = SymExec.diverge prop_ path let _execute_free tenv mk loc acc iter = match Prop.prop_iter_current tenv iter with - | (Sil.Hpointsto(lexp, _, _), []) -> - let prop = Prop.prop_iter_remove_curr_then_to_prop tenv iter in + | Sil.Hpointsto (lexp, _, _), [] + -> let prop = Prop.prop_iter_remove_curr_then_to_prop tenv iter in let pname = PredSymb.mem_dealloc_pname mk in let ra = - { PredSymb.ra_kind = PredSymb.Rrelease; - PredSymb.ra_res = PredSymb.Rmemory mk; - PredSymb.ra_pname = pname; - PredSymb.ra_loc = loc; - PredSymb.ra_vpath = None } in + { PredSymb.ra_kind= PredSymb.Rrelease + ; PredSymb.ra_res= PredSymb.Rmemory mk + ; PredSymb.ra_pname= pname + ; PredSymb.ra_loc= loc + ; PredSymb.ra_vpath= None } + in (* mark value as freed *) let p_res = - Attribute.add_or_replace_check_changed tenv - Tabulation.check_attr_dealloc_mismatch prop (Apred (Aresource ra, [lexp])) in + Attribute.add_or_replace_check_changed tenv Tabulation.check_attr_dealloc_mismatch prop + (Apred (Aresource ra, [lexp])) + in p_res :: acc - | (Sil.Hpointsto _, _ :: _) -> assert false (* alignment error *) - | _ -> assert false (* should not happen *) + | Sil.Hpointsto _, _ :: _ + -> assert false (* alignment error *) + | _ + -> assert false + +(* should not happen *) let _execute_free_nonzero mk pdesc tenv instr prop lexp typ loc = try - begin - match Prover.is_root tenv prop lexp lexp with - | None -> - L.d_strln ".... Alignment Error: Freed a non root ...."; - assert false - | Some _ -> - let prop_list = - List.fold ~f:(_execute_free tenv mk loc) ~init:[] - (Rearrange.rearrange pdesc tenv lexp typ prop loc) in - List.rev prop_list - end + match Prover.is_root tenv prop lexp lexp with + | None + -> L.d_strln ".... Alignment Error: Freed a non root ...." ; + assert false + | Some _ + -> let prop_list = + List.fold ~f:(_execute_free tenv mk loc) ~init:[] + (Rearrange.rearrange pdesc tenv lexp typ prop loc) + in + List.rev prop_list with Rearrange.ARRAY_ACCESS -> - if (Int.equal Config.array_level 0) then assert false - else begin - L.d_strln ".... Array containing allocated heap cells ...."; - L.d_str " Instr: "; Sil.d_instr instr; L.d_ln (); - L.d_str " PROP: "; Prop.d_prop prop; L.d_ln (); - raise (Exceptions.Array_of_pointsto __POS__) - end - -let execute_free mk { Builtin.pdesc; instr; tenv; prop_; path; args; loc; } - : Builtin.ret_typ = + if Int.equal Config.array_level 0 then assert false + else ( + L.d_strln ".... Array containing allocated heap cells ...." ; + L.d_str " Instr: " ; + Sil.d_instr instr ; + L.d_ln () ; + L.d_str " PROP: " ; + Prop.d_prop prop ; + L.d_ln () ; + raise (Exceptions.Array_of_pointsto __POS__) ) + +let execute_free mk {Builtin.pdesc; instr; tenv; prop_; path; args; loc} : Builtin.ret_typ = match args with - | [(lexp, typ)] -> - begin - let pname = Procdesc.get_proc_name pdesc in - let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in - let prop_nonzero = (* case n_lexp!=0 *) - Propset.to_proplist (prune tenv ~positive:true n_lexp prop) in - let prop_zero = (* case n_lexp==0 *) - Propset.to_proplist (prune tenv ~positive:false n_lexp prop) in - let plist = - prop_zero @ (* model: if 0 then skip else _execute_free_nonzero *) + | [(lexp, typ)] + -> let pname = Procdesc.get_proc_name pdesc in + let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in + let prop_nonzero = + (* case n_lexp!=0 *) + Propset.to_proplist (prune tenv ~positive:true n_lexp prop) + in + let prop_zero = + (* case n_lexp==0 *) + Propset.to_proplist (prune tenv ~positive:false n_lexp prop) + in + let plist = + prop_zero + @ (* model: if 0 then skip else _execute_free_nonzero *) List.concat_map ~f:(fun p -> - _execute_free_nonzero mk pdesc tenv instr p - (Prop.exp_normalize_prop tenv p lexp) typ loc) - prop_nonzero in - List.map ~f:(fun p -> (p, path)) plist - end - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute_alloc mk can_return_null - { Builtin.pdesc; tenv; prop_; path; ret_id; args; loc; } - : Builtin.ret_typ = + _execute_free_nonzero mk pdesc tenv instr p (Prop.exp_normalize_prop tenv p lexp) typ + loc) + prop_nonzero + in + List.map ~f:(fun p -> (p, path)) plist + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute_alloc mk can_return_null {Builtin.pdesc; tenv; prop_; path; ret_id; args; loc} + : Builtin.ret_typ = let pname = Procdesc.get_proc_name pdesc in - let rec evaluate_char_sizeof e = match e with - | Exp.Var _ -> e - | Exp.UnOp (uop, e', typ) -> - Exp.UnOp (uop, evaluate_char_sizeof e', typ) - | Exp.BinOp (bop, e1', e2') -> - Exp.BinOp (bop, evaluate_char_sizeof e1', evaluate_char_sizeof e2') - | Exp.Exn _ | Exp.Closure _ | Exp.Const _ | Exp.Cast _ | Exp.Lvar _ | Exp.Lfield _ - | Exp.Lindex _ -> e - | Exp.Sizeof {typ={Typ.desc=Tarray ({Typ.desc=Tint ik}, _, _)}; dynamic_length=Some len} - when Typ.ikind_is_char ik -> - evaluate_char_sizeof len - | Exp.Sizeof {typ={Typ.desc=Tarray ({Typ.desc=Tint ik}, Some len, _)}; dynamic_length=None} - when Typ.ikind_is_char ik -> - evaluate_char_sizeof (Exp.Const (Const.Cint len)) - | Exp.Sizeof _ -> e in - let size_exp, procname = match args with - | [(Exp.Sizeof ({typ={Typ.desc=Tstruct (ObjcClass _ as name)}} as sizeof_data) as e, _)] -> - let e' = match AttributesTable.get_correct_type_from_objc_class_name name with - | Some struct_type -> Exp.Sizeof {sizeof_data with typ=struct_type} - | None -> e in - e', pname - | [(size_exp, _)] -> (* for malloc and __new *) - size_exp, PredSymb.mem_alloc_pname mk - | [(size_exp, _); (Exp.Const (Const.Cfun pname), _)] -> - size_exp, pname - | _ -> - raise (Exceptions.Wrong_argument_number __POS__) in - let ret_id = match ret_id with - | Some (ret_id, _) -> ret_id - | _ -> Ident.create_fresh Ident.kprimed in + let rec evaluate_char_sizeof e = + match e with + | Exp.Var _ + -> e + | Exp.UnOp (uop, e', typ) + -> Exp.UnOp (uop, evaluate_char_sizeof e', typ) + | Exp.BinOp (bop, e1', e2') + -> Exp.BinOp (bop, evaluate_char_sizeof e1', evaluate_char_sizeof e2') + | Exp.Exn _ + | Exp.Closure _ + | Exp.Const _ + | Exp.Cast _ + | Exp.Lvar _ + | Exp.Lfield _ + | Exp.Lindex _ + -> e + | Exp.Sizeof {typ= {Typ.desc= Tarray ({Typ.desc= Tint ik}, _, _)}; dynamic_length= Some len} + when Typ.ikind_is_char ik + -> evaluate_char_sizeof len + | Exp.Sizeof {typ= {Typ.desc= Tarray ({Typ.desc= Tint ik}, Some len, _)}; dynamic_length= None} + when Typ.ikind_is_char ik + -> evaluate_char_sizeof (Exp.Const (Const.Cint len)) + | Exp.Sizeof _ + -> e + in + let size_exp, procname = + match args with + | [((Exp.Sizeof ({typ= {Typ.desc= Tstruct (ObjcClass _ as name)}} as sizeof_data) as e), _)] + -> let e' = + match AttributesTable.get_correct_type_from_objc_class_name name with + | Some struct_type + -> Exp.Sizeof {sizeof_data with typ= struct_type} + | None + -> e + in + (e', pname) + | [(size_exp, _)] + -> (* for malloc and __new *) + (size_exp, PredSymb.mem_alloc_pname mk) + | [(size_exp, _); (Exp.Const Const.Cfun pname, _)] + -> (size_exp, pname) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + in + let ret_id = + match ret_id with Some (ret_id, _) -> ret_id | _ -> Ident.create_fresh Ident.kprimed + in let size_exp', prop = let n_size_exp, prop = check_arith_norm_exp tenv pname size_exp prop_ in let n_size_exp' = evaluate_char_sizeof n_size_exp in - Prop.exp_normalize_prop tenv prop n_size_exp', prop in + (Prop.exp_normalize_prop tenv prop n_size_exp', prop) + in let cnt_te = - Exp.Sizeof {typ=Typ.mk (Tarray (Typ.mk (Tint Typ.IChar), None, Some (IntLit.of_int 1))); - nbytes=None; dynamic_length=Some size_exp'; subtype=Subtype.exact} in + Exp.Sizeof + { typ= Typ.mk (Tarray (Typ.mk (Tint Typ.IChar), None, Some (IntLit.of_int 1))) + ; nbytes= None + ; dynamic_length= Some size_exp' + ; subtype= Subtype.exact } + in let id_new = Ident.create_fresh Ident.kprimed in let exp_new = Exp.Var id_new in - let ptsto_new = - Prop.mk_ptsto_exp tenv Prop.Fld_init (exp_new, cnt_te, None) Sil.Ialloc in + let ptsto_new = Prop.mk_ptsto_exp tenv Prop.Fld_init (exp_new, cnt_te, None) Sil.Ialloc in let prop_plus_ptsto = let prop' = Prop.normalize tenv (Prop.prop_sigma_star prop [ptsto_new]) in let ra = - { PredSymb.ra_kind = PredSymb.Racquire; - PredSymb.ra_res = PredSymb.Rmemory mk; - PredSymb.ra_pname = procname; - PredSymb.ra_loc = loc; - PredSymb.ra_vpath = None } in + { PredSymb.ra_kind= PredSymb.Racquire + ; PredSymb.ra_res= PredSymb.Rmemory mk + ; PredSymb.ra_pname= procname + ; PredSymb.ra_loc= loc + ; PredSymb.ra_vpath= None } + in (* mark value as allocated *) - Attribute.add_or_replace tenv prop' (Apred (Aresource ra, [exp_new])) in + Attribute.add_or_replace tenv prop' (Apred (Aresource ra, [exp_new])) + in let prop_alloc = Prop.conjoin_eq tenv (Exp.Var ret_id) exp_new prop_plus_ptsto in if can_return_null then let prop_null = Prop.conjoin_eq tenv (Exp.Var ret_id) Exp.zero prop in [(prop_alloc, path); (prop_null, path)] else [(prop_alloc, path)] -let execute___cxx_typeid ({ Builtin.pdesc; tenv; prop_; args; loc} as r) - : Builtin.ret_typ = +let execute___cxx_typeid ({Builtin.pdesc; tenv; prop_; args; loc} as r) : Builtin.ret_typ = match args with - | type_info_exp :: rest -> - (let res = execute_alloc PredSymb.Mnew false { r with args = [type_info_exp] } in - match rest with - | [(field_exp, _); (lexp, typ_)] -> - let pname = Procdesc.get_proc_name pdesc in - let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in - let typ = - List.find ~f:(function - | Sil.Hpointsto (e, _, _) -> Exp.equal e n_lexp - | _ -> false) prop.Prop.sigma |> - Option.value_map ~f:(function - | Sil.Hpointsto (_, _, Exp.Sizeof {typ}) -> typ - | _ -> typ_ - ) - ~default:typ_ in - let typ_string = Typ.to_string typ in - let set_instr = - Sil.Store (field_exp, Typ.mk Tvoid, Exp.Const (Const.Cstr typ_string), loc) in - SymExec.instrs ~mask_errors:true tenv pdesc [set_instr] res - | _ -> res) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute_pthread_create ({ Builtin.tenv; prop_; path; args; } as builtin_args) - : Builtin.ret_typ = + | type_info_exp :: rest + -> ( + let res = execute_alloc PredSymb.Mnew false {r with args= [type_info_exp]} in + match rest with + | [(field_exp, _); (lexp, typ_)] + -> let pname = Procdesc.get_proc_name pdesc in + let n_lexp, prop = check_arith_norm_exp tenv pname lexp prop_ in + let typ = + List.find + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e n_lexp | _ -> false) + prop.Prop.sigma + |> Option.value_map + ~f:(function Sil.Hpointsto (_, _, Exp.Sizeof {typ}) -> typ | _ -> typ_) + ~default:typ_ + in + let typ_string = Typ.to_string typ in + let set_instr = + Sil.Store (field_exp, Typ.mk Tvoid, Exp.Const (Const.Cstr typ_string), loc) + in + SymExec.instrs ~mask_errors:true tenv pdesc [set_instr] res + | _ + -> res ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute_pthread_create ({Builtin.tenv; prop_; path; args} as builtin_args) : Builtin.ret_typ = match args with - | [_; _; start_routine; arg] -> + | [_; _; start_routine; arg] + -> ( let routine_name = Prop.exp_normalize_prop tenv prop_ (fst start_routine) in let routine_arg = Prop.exp_normalize_prop tenv prop_ (fst arg) in - (match routine_name, (snd start_routine) with - | Exp.Lvar pvar, _ -> - let fun_name = Pvar.get_name pvar in - let fun_string = Mangled.to_string fun_name in - L.d_strln ("pthread_create: calling function " ^ fun_string); - begin - match Specs.get_summary (Typ.Procname.from_string_c_fun fun_string) with - | None -> assert false - | Some callee_summary -> - SymExec.proc_call callee_summary - { builtin_args with args = [(routine_arg, snd arg)] } - end - | _ -> - L.d_str "pthread_create: unknown function "; - Sil.d_exp routine_name; L.d_strln ", skipping call."; - [(prop_, path)]) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute_skip { Builtin.prop_; path; } : Builtin.ret_typ = - [(prop_, path)] - -let execute_scan_function skip_n_arguments ({ Builtin.args } as call_args) - : Builtin.ret_typ = + match (routine_name, snd start_routine) with + | Exp.Lvar pvar, _ + -> ( + let fun_name = Pvar.get_name pvar in + let fun_string = Mangled.to_string fun_name in + L.d_strln ("pthread_create: calling function " ^ fun_string) ; + match Specs.get_summary (Typ.Procname.from_string_c_fun fun_string) with + | None + -> assert false + | Some callee_summary + -> SymExec.proc_call callee_summary {builtin_args with args= [(routine_arg, snd arg)]} ) + | _ + -> L.d_str "pthread_create: unknown function " ; + Sil.d_exp routine_name ; + L.d_strln ", skipping call." ; + [(prop_, path)] ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute_skip {Builtin.prop_; path} : Builtin.ret_typ = [(prop_, path)] + +let execute_scan_function skip_n_arguments ({Builtin.args} as call_args) : Builtin.ret_typ = match args with - | _ when List.length args >= skip_n_arguments -> - let varargs = ref args in - varargs := List.drop !varargs skip_n_arguments; - SymExec.unknown_or_scan_call - ~is_scan:true - None - Annot.Item.empty - { call_args with args = !varargs } - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute__unwrap_exception { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = + | _ when List.length args >= skip_n_arguments + -> let varargs = ref args in + varargs := List.drop !varargs skip_n_arguments ; + SymExec.unknown_or_scan_call ~is_scan:true None Annot.Item.empty + {call_args with args= !varargs} + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute__unwrap_exception {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(ret_exn, _)] -> - begin - let pname = Procdesc.get_proc_name pdesc in - let n_ret_exn, prop = check_arith_norm_exp tenv pname ret_exn prop_ in - match n_ret_exn with - | Exp.Exn exp -> - let prop_with_exn = return_result tenv exp prop ret_id in - [(prop_with_exn, path)] - | _ -> assert false - end - | _ -> raise (Exceptions.Wrong_argument_number __POS__) - -let execute_return_first_argument { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = - match args with - | (arg1_, _):: _ -> + | [(ret_exn, _)] + -> ( let pname = Procdesc.get_proc_name pdesc in + let n_ret_exn, prop = check_arith_norm_exp tenv pname ret_exn prop_ in + match n_ret_exn with + | Exp.Exn exp + -> let prop_with_exn = return_result tenv exp prop ret_id in + [(prop_with_exn, path)] + | _ + -> assert false ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + +let execute_return_first_argument {Builtin.tenv; pdesc; prop_; path; ret_id; args} + : Builtin.ret_typ = + match args with + | (arg1_, _) :: _ + -> let pname = Procdesc.get_proc_name pdesc in let arg1, prop = check_arith_norm_exp tenv pname arg1_ prop_ in let prop' = return_result tenv arg1 prop ret_id in [(prop', path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) -let execute___split_get_nth { Builtin.tenv; pdesc; prop_; path; ret_id; args; } - : Builtin.ret_typ = +let execute___split_get_nth {Builtin.tenv; pdesc; prop_; path; ret_id; args} : Builtin.ret_typ = match args with - | [(lexp1, _); (lexp2, _); (lexp3, _)] -> + | [(lexp1, _); (lexp2, _); (lexp3, _)] + -> ( let pname = Procdesc.get_proc_name pdesc in let n_lexp1, prop__ = check_arith_norm_exp tenv pname lexp1 prop_ in let n_lexp2, prop___ = check_arith_norm_exp tenv pname lexp2 prop__ in let n_lexp3, prop = check_arith_norm_exp tenv pname lexp3 prop___ in - (match n_lexp1, n_lexp2, n_lexp3 with - | Exp.Const (Const.Cstr str1), Exp.Const (Const.Cstr str2), Exp.Const (Const.Cint n_sil) -> - (let n = IntLit.to_int n_sil in - try - let parts = Str.split (Str.regexp_string str2) str1 in - let n_part = List.nth_exn parts n in - let res = Exp.Const (Const.Cstr n_part) in - [(return_result tenv res prop ret_id, path)] - with Not_found -> assert false) - | _ -> [(prop, path)]) - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + match (n_lexp1, n_lexp2, n_lexp3) with + | Exp.Const Const.Cstr str1, Exp.Const Const.Cstr str2, Exp.Const Const.Cint n_sil + -> ( + let n = IntLit.to_int n_sil in + try + let parts = Str.split (Str.regexp_string str2) str1 in + let n_part = List.nth_exn parts n in + let res = Exp.Const (Const.Cstr n_part) in + [(return_result tenv res prop ret_id, path)] + with Not_found -> assert false ) + | _ + -> [(prop, path)] ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (* forces the expression passed as parameter to be assumed true at the point where this builtin is called, diverges if this causes an inconsistency *) -let execute___infer_assume { Builtin.tenv; prop_; path; args; } - : Builtin.ret_typ = +let execute___infer_assume {Builtin.tenv; prop_; path; args} : Builtin.ret_typ = match args with - | [(lexp, _)] -> - let prop_assume = Prop.conjoin_eq tenv lexp (Exp.bool true) prop_ in - if Prover.check_inconsistency tenv prop_assume - then SymExec.diverge prop_assume path + | [(lexp, _)] + -> let prop_assume = Prop.conjoin_eq tenv lexp (Exp.bool true) prop_ in + if Prover.check_inconsistency tenv prop_assume then SymExec.diverge prop_assume path else [(prop_assume, path)] - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) (* creates a named error state *) -let execute___infer_fail { Builtin.pdesc; tenv; prop_; path; args; loc; } - : Builtin.ret_typ = +let execute___infer_fail {Builtin.pdesc; tenv; prop_; path; args; loc} : Builtin.ret_typ = let error_str = match args with - | [(lexp_msg, _)] -> - begin - match Prop.exp_normalize_prop tenv prop_ lexp_msg with - | Exp.Const (Const.Cstr str) -> str - | _ -> assert false - end - | _ -> - raise (Exceptions.Wrong_argument_number __POS__) in + | [(lexp_msg, _)] -> ( + match Prop.exp_normalize_prop tenv prop_ lexp_msg with + | Exp.Const Const.Cstr str + -> str + | _ + -> assert false ) + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + in let set_instr = - Sil.Store (Exp.Lvar Sil.custom_error, Typ.mk Tvoid, Exp.Const (Const.Cstr error_str), loc) in + Sil.Store (Exp.Lvar Sil.custom_error, Typ.mk Tvoid, Exp.Const (Const.Cstr error_str), loc) + in SymExec.instrs ~mask_errors:true tenv pdesc [set_instr] [(prop_, path)] (* translate builtin assertion failure *) -let execute___assert_fail { Builtin.pdesc; tenv; prop_; path; args; loc; } - : Builtin.ret_typ = +let execute___assert_fail {Builtin.pdesc; tenv; prop_; path; args; loc} : Builtin.ret_typ = let error_str = match List.length args with - | 4 -> - Config.default_failure_name - | _ -> - raise (Exceptions.Wrong_argument_number __POS__) in + | 4 + -> Config.default_failure_name + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) + in let set_instr = - Sil.Store (Exp.Lvar Sil.custom_error, Typ.mk Tvoid, Exp.Const (Const.Cstr error_str), loc) in + Sil.Store (Exp.Lvar Sil.custom_error, Typ.mk Tvoid, Exp.Const (Const.Cstr error_str), loc) + in SymExec.instrs ~mask_errors:true tenv pdesc [set_instr] [(prop_, path)] -let execute_objc_alloc_no_fail - symb_state typ alloc_fun_opt - { Builtin.pdesc; tenv; ret_id; loc; } = +let execute_objc_alloc_no_fail symb_state typ alloc_fun_opt {Builtin.pdesc; tenv; ret_id; loc} = let alloc_fun = Exp.Const (Const.Cfun BuiltinDecl.__objc_alloc_no_fail) in let ptr_typ = Typ.mk (Tptr (typ, Typ.Pk_pointer)) in - let sizeof_typ = Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact} in + let sizeof_typ = Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} in let alloc_fun_exp = match alloc_fun_opt with - | Some pname -> [Exp.Const (Const.Cfun pname), Typ.mk Tvoid] - | None -> [] in + | Some pname + -> [(Exp.Const (Const.Cfun pname), Typ.mk Tvoid)] + | None + -> [] + in let alloc_instr = - Sil.Call - (ret_id, alloc_fun, [(sizeof_typ, ptr_typ)] @ alloc_fun_exp, loc, CallFlags.default) in + Sil.Call (ret_id, alloc_fun, [(sizeof_typ, ptr_typ)] @ alloc_fun_exp, loc, CallFlags.default) + in SymExec.instrs tenv pdesc [alloc_instr] symb_state (* NSArray models *) let execute_objc_NSArray_alloc_no_fail builtin_args symb_state pname = let ret_typ = match builtin_args.Builtin.ret_id with - | Some (_, typ) -> typ - | None -> Typ.mk (Tptr (Typ.mk Tvoid, Pk_pointer)) in + | Some (_, typ) + -> typ + | None + -> Typ.mk (Tptr (Typ.mk Tvoid, Pk_pointer)) + in execute_objc_alloc_no_fail symb_state ret_typ (Some pname) builtin_args let execute_NSArray_arrayWithObjects_count builtin_args = let n_formals = 1 in - let res = SymExec.check_variadic_sentinel ~fails_on_nil: true n_formals (0,1) builtin_args in + let res = SymExec.check_variadic_sentinel ~fails_on_nil:true n_formals (0, 1) builtin_args in execute_objc_NSArray_alloc_no_fail builtin_args res BuiltinDecl.nsArray_arrayWithObjectsCount let execute_NSArray_arrayWithObjects builtin_args = let n_formals = 1 in - let res = SymExec.check_variadic_sentinel n_formals (0,1) builtin_args in + let res = SymExec.check_variadic_sentinel n_formals (0, 1) builtin_args in execute_objc_NSArray_alloc_no_fail builtin_args res BuiltinDecl.nsArray_arrayWithObjects - (* NSDictionary models *) let execute_objc_NSDictionary_alloc_no_fail symb_state pname builtin_args = let ret_typ = match builtin_args.Builtin.ret_id with - | Some (_, typ) -> typ - | None -> Typ.mk (Tptr (Typ.mk Tvoid, Pk_pointer)) in + | Some (_, typ) + -> typ + | None + -> Typ.mk (Tptr (Typ.mk Tvoid, Pk_pointer)) + in execute_objc_alloc_no_fail symb_state ret_typ (Some pname) builtin_args let execute___objc_dictionary_literal builtin_args = let n_formals = 1 in - let res' = SymExec.check_variadic_sentinel ~fails_on_nil: true n_formals (0,1) builtin_args in + let res' = SymExec.check_variadic_sentinel ~fails_on_nil:true n_formals (0, 1) builtin_args in let pname = BuiltinDecl.__objc_dictionary_literal in execute_objc_NSDictionary_alloc_no_fail res' pname builtin_args @@ -1022,11 +1089,10 @@ let __cxx_typeid = Builtin.register BuiltinDecl.__cxx_typeid execute___cxx_typei let __delete = Builtin.register BuiltinDecl.__delete (execute_free PredSymb.Mnew) -let __delete_array = Builtin.register BuiltinDecl.__delete_array - (execute_free PredSymb.Mnew_array) +let __delete_array = Builtin.register BuiltinDecl.__delete_array (execute_free PredSymb.Mnew_array) -let __delete_locked_attribute = Builtin.register BuiltinDecl.__delete_locked_attribute - execute___delete_locked_attribute +let __delete_locked_attribute = + Builtin.register BuiltinDecl.__delete_locked_attribute execute___delete_locked_attribute let __exit = Builtin.register BuiltinDecl.__exit execute_exit @@ -1046,28 +1112,29 @@ let __infer_fail = Builtin.register BuiltinDecl.__infer_fail execute___infer_fai (* [__instanceof(val,typ)] implements java's [val instanceof typ] *) let __instanceof = Builtin.register BuiltinDecl.__instanceof execute___instanceof -let __method_set_ignore_attribute = Builtin.register BuiltinDecl.__method_set_ignore_attribute - execute___method_set_ignore_attribute +let __method_set_ignore_attribute = + Builtin.register BuiltinDecl.__method_set_ignore_attribute execute___method_set_ignore_attribute let __new = Builtin.register BuiltinDecl.__new (execute_alloc PredSymb.Mnew false) -let __new_array = Builtin.register BuiltinDecl.__new_array (execute_alloc PredSymb.Mnew_array false) +let __new_array = + Builtin.register BuiltinDecl.__new_array (execute_alloc PredSymb.Mnew_array false) let __objc_alloc = Builtin.register BuiltinDecl.__objc_alloc (execute_alloc PredSymb.Mobjc true) (* like __objc_alloc, but does not return nil *) -let __objc_alloc_no_fail = Builtin.register BuiltinDecl.__objc_alloc_no_fail - (execute_alloc PredSymb.Mobjc false) +let __objc_alloc_no_fail = + Builtin.register BuiltinDecl.__objc_alloc_no_fail (execute_alloc PredSymb.Mobjc false) let __objc_cast = Builtin.register BuiltinDecl.__objc_cast execute___objc_cast -let __objc_dictionary_literal = Builtin.register BuiltinDecl.__objc_dictionary_literal - execute___objc_dictionary_literal +let __objc_dictionary_literal = + Builtin.register BuiltinDecl.__objc_dictionary_literal execute___objc_dictionary_literal let __objc_release = Builtin.register BuiltinDecl.__objc_release execute___objc_release -let __objc_release_autorelease_pool = Builtin.register BuiltinDecl.__objc_release_autorelease_pool - execute___release_autorelease_pool +let __objc_release_autorelease_pool = + Builtin.register BuiltinDecl.__objc_release_autorelease_pool execute___release_autorelease_pool let __objc_release_cf = Builtin.register BuiltinDecl.__objc_release_cf execute___objc_release_cf @@ -1083,44 +1150,44 @@ let __placement_new = Builtin.register BuiltinDecl.__placement_new execute_retur let __print_value = Builtin.register BuiltinDecl.__print_value execute___print_value (* require the parameter to point to an allocated array *) -let __require_allocated_array = Builtin.register BuiltinDecl.__require_allocated_array - execute___require_allocated_array +let __require_allocated_array = + Builtin.register BuiltinDecl.__require_allocated_array execute___require_allocated_array let __set_array_length = Builtin.register BuiltinDecl.__set_array_length execute___set_array_length -let __set_autorelease_attribute = Builtin.register BuiltinDecl.__set_autorelease_attribute - execute___set_autorelease_attribute +let __set_autorelease_attribute = + Builtin.register BuiltinDecl.__set_autorelease_attribute execute___set_autorelease_attribute -let __set_file_attribute = Builtin.register BuiltinDecl.__set_file_attribute - execute___set_file_attribute +let __set_file_attribute = + Builtin.register BuiltinDecl.__set_file_attribute execute___set_file_attribute (* set a hidden field in the struct to the given value *) let __set_hidden_field = Builtin.register BuiltinDecl.__set_hidden_field execute___set_hidden_field -let __set_lock_attribute = Builtin.register BuiltinDecl.__set_lock_attribute - execute___set_lock_attribute +let __set_lock_attribute = + Builtin.register BuiltinDecl.__set_lock_attribute execute___set_lock_attribute -let __set_locked_attribute = Builtin.register BuiltinDecl.__set_locked_attribute - execute___set_locked_attribute +let __set_locked_attribute = + Builtin.register BuiltinDecl.__set_locked_attribute execute___set_locked_attribute -let __set_mem_attribute = Builtin.register BuiltinDecl.__set_mem_attribute - execute___set_mem_attribute +let __set_mem_attribute = + Builtin.register BuiltinDecl.__set_mem_attribute execute___set_mem_attribute -let __set_observer_attribute = Builtin.register BuiltinDecl.__set_observer_attribute - (execute___set_attr PredSymb.Aobserver) +let __set_observer_attribute = + Builtin.register BuiltinDecl.__set_observer_attribute (execute___set_attr PredSymb.Aobserver) -let __set_taint_attribute = Builtin.register BuiltinDecl.__set_taint_attribute - execute___set_taint_attribute +let __set_taint_attribute = + Builtin.register BuiltinDecl.__set_taint_attribute execute___set_taint_attribute -let __set_unlocked_attribute = Builtin.register BuiltinDecl.__set_unlocked_attribute - execute___set_unlocked_attribute +let __set_unlocked_attribute = + Builtin.register BuiltinDecl.__set_unlocked_attribute execute___set_unlocked_attribute -let __set_unsubscribed_observer_attribute = Builtin.register - BuiltinDecl.__set_unsubscribed_observer_attribute +let __set_unsubscribed_observer_attribute = + Builtin.register BuiltinDecl.__set_unsubscribed_observer_attribute (execute___set_attr PredSymb.Aunsubscribed_observer) -let __set_untaint_attribute = Builtin.register - BuiltinDecl.__set_untaint_attribute execute___set_untaint_attribute +let __set_untaint_attribute = + Builtin.register BuiltinDecl.__set_untaint_attribute execute___set_untaint_attribute (* splits a string given a separator and returns the nth string *) let __split_get_nth = Builtin.register BuiltinDecl.__split_get_nth execute___split_get_nth @@ -1139,17 +1206,17 @@ let fscanf = Builtin.register BuiltinDecl.fscanf (execute_scan_function 2) let fwscanf = Builtin.register BuiltinDecl.fwscanf (execute_scan_function 2) -let malloc = Builtin.register BuiltinDecl.malloc (execute_alloc PredSymb.Mmalloc - (not Config.unsafe_malloc)) +let malloc = + Builtin.register BuiltinDecl.malloc (execute_alloc PredSymb.Mmalloc (not Config.unsafe_malloc)) -let malloc_no_fail = Builtin.register BuiltinDecl.malloc_no_fail - (execute_alloc PredSymb.Mmalloc false) +let malloc_no_fail = + Builtin.register BuiltinDecl.malloc_no_fail (execute_alloc PredSymb.Mmalloc false) -let nsArray_arrayWithObjects = Builtin.register BuiltinDecl.nsArray_arrayWithObjects - execute_NSArray_arrayWithObjects +let nsArray_arrayWithObjects = + Builtin.register BuiltinDecl.nsArray_arrayWithObjects execute_NSArray_arrayWithObjects -let nsArray_arrayWithObjectsCount = Builtin.register BuiltinDecl.nsArray_arrayWithObjectsCount - execute_NSArray_arrayWithObjects_count +let nsArray_arrayWithObjectsCount = + Builtin.register BuiltinDecl.nsArray_arrayWithObjectsCount execute_NSArray_arrayWithObjects_count (* model throwing exception in objc/c++ as divergence *) let objc_cpp_throw = Builtin.register BuiltinDecl.objc_cpp_throw execute_exit diff --git a/infer/src/backend/BuiltinDefn.mli b/infer/src/backend/BuiltinDefn.mli index e1b0e7d6b..13d3051a6 100644 --- a/infer/src/backend/BuiltinDefn.mli +++ b/infer/src/backend/BuiltinDefn.mli @@ -12,7 +12,7 @@ open! IStd (** Models for the builtin functions supported *) include BUILTINS.S with type t = Builtin.registered +val init : unit -> unit (** Clients of Builtin module should call this before Builtin module is used. WARNING: builtins are not guaranteed to be registered with the Builtin module until after init has been called. *) -val init : unit -> unit diff --git a/infer/src/backend/Differential.ml b/infer/src/backend/Differential.ml index c91fe1728..fcc22add4 100644 --- a/infer/src/backend/Differential.ml +++ b/infer/src/backend/Differential.ml @@ -9,36 +9,32 @@ open! IStd -type t = { - introduced: Jsonbug_t.report; - fixed: Jsonbug_t.report; - preexisting: Jsonbug_t.report; -} +type t = {introduced: Jsonbug_t.report; fixed: Jsonbug_t.report; preexisting: Jsonbug_t.report} (** Set operations should keep duplicated issues with identical hashes *) let of_reports ~(current_report: Jsonbug_t.report) ~(previous_report: Jsonbug_t.report) : t = let to_map report = List.fold_left ~f:(fun map issue -> Map.add_multi map ~key:issue.Jsonbug_t.hash ~data:issue) - ~init:Int.Map.empty - report in + ~init:Int.Map.empty report + in let fold_aux ~key:_ ~data (left, both, right) = match data with - | `Left left' -> (List.rev_append left' left, both, right) - | `Both (both', _) -> (left, List.rev_append both' both, right) - | `Right right' -> (left, both, List.rev_append right' right) in + | `Left left' + -> (List.rev_append left' left, both, right) + | `Both (both', _) + -> (left, List.rev_append both' both, right) + | `Right right' + -> (left, both, List.rev_append right' right) + in let introduced, preexisting, fixed = - Map.fold2 (to_map current_report) (to_map previous_report) ~f:fold_aux ~init:([],[],[]) in + Map.fold2 (to_map current_report) (to_map previous_report) ~f:fold_aux ~init:([], [], []) + in {introduced; fixed; preexisting} - let to_files {introduced; fixed; preexisting} destdir = - Out_channel.write_all - (destdir ^/ "introduced.json") - ~data:(Jsonbug_j.string_of_report introduced); - Out_channel.write_all - (destdir ^/ "fixed.json") - ~data:(Jsonbug_j.string_of_report fixed); - Out_channel.write_all - (destdir ^/ "preexisting.json") + Out_channel.write_all (destdir ^/ "introduced.json") + ~data:(Jsonbug_j.string_of_report introduced) ; + Out_channel.write_all (destdir ^/ "fixed.json") ~data:(Jsonbug_j.string_of_report fixed) ; + Out_channel.write_all (destdir ^/ "preexisting.json") ~data:(Jsonbug_j.string_of_report preexisting) diff --git a/infer/src/backend/Differential.mli b/infer/src/backend/Differential.mli index 3ab55d0df..6a2fd69ae 100644 --- a/infer/src/backend/Differential.mli +++ b/infer/src/backend/Differential.mli @@ -9,11 +9,7 @@ open! IStd -type t = { - introduced : Jsonbug_t.report; - fixed : Jsonbug_t.report; - preexisting : Jsonbug_t.report; -} +type t = {introduced: Jsonbug_t.report; fixed: Jsonbug_t.report; preexisting: Jsonbug_t.report} val of_reports : current_report:Jsonbug_t.report -> previous_report:Jsonbug_t.report -> t diff --git a/infer/src/backend/DifferentialFilters.ml b/infer/src/backend/DifferentialFilters.ml index 799039e8b..032ff9595 100644 --- a/infer/src/backend/DifferentialFilters.ml +++ b/infer/src/backend/DifferentialFilters.ml @@ -10,10 +10,7 @@ open! IStd module FileRenamings = struct - type renaming = { - current: string; - previous: string; - } [@@deriving compare] + type renaming = {current: string; previous: string} [@@deriving compare] type t = renaming list [@@deriving compare] @@ -29,13 +26,18 @@ module FileRenamings = struct let j = Yojson.Basic.from_string input in let renaming_of_assoc assoc : renaming = match assoc with - | `Assoc [("current", `String current); ("previous", `String previous)] -> {current; previous} - | _ -> failwithf "Expected JSON object of the following form: '%s', but instead got: '%s'" - "{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}" - (Yojson.Basic.to_string assoc) in + | `Assoc [("current", `String current); ("previous", `String previous)] + -> {current; previous} + | _ + -> failwithf "Expected JSON object of the following form: '%s', but instead got: '%s'" + "{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}" + (Yojson.Basic.to_string assoc) + in match j with - | `List json_renamings -> List.map ~f:renaming_of_assoc json_renamings - | _ -> failwithf "Expected JSON list but got '%s'" input + | `List json_renamings + -> List.map ~f:renaming_of_assoc json_renamings + | _ + -> failwithf "Expected JSON list but got '%s'" input let from_json_file file : t = from_json (In_channel.read_all file) @@ -45,12 +47,15 @@ module FileRenamings = struct let pp fmt t = let pp_tuple fmt {current; previous} = - Format.fprintf fmt "{\"current\": \"%s\", \"previous\": \"%s\"}" current previous in + Format.fprintf fmt "{\"current\": \"%s\", \"previous\": \"%s\"}" current previous + in Format.fprintf fmt "[%a]" (Pp.comma_seq pp_tuple) t module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct let from_renamings = from_renamings + let equal = equal + let pp = pp end end @@ -58,56 +63,65 @@ end (** Returns a triple [(l1', dups, l2')] where [dups] is the set of elements of that are in the intersection of [l1] and [l2] according to [cmd] and additionally satisfy [pred], and [lN'] is [lN] minus [dups]. [dups] contains only one witness for each removed issue, taken from [l1]. *) -let relative_complements ~cmp ?(pred=(fun _ -> true)) l1 l2 = - let rec aux ((out_l1, dups, out_l2) as out) in_l1 in_l2 = - let is_last_seen_dup v = match dups with - | ld::_ -> Int.equal (cmp ld v) 0 - | [] -> false in - match in_l1, in_l2 with - | i::is, f::fs when Int.equal (cmp i f) 0 -> (* i = f *) - if pred i then aux (out_l1, i::dups, out_l2) is fs - else aux (i::out_l1, dups, f::out_l2) is fs - | i::is, f::_ when cmp i f < 0 -> (* i < f *) - let out_l1' = if is_last_seen_dup i then out_l1 else i::out_l1 in +let relative_complements ~cmp ?(pred= fun _ -> true) l1 l2 = + let rec aux (out_l1, dups, out_l2 as out) in_l1 in_l2 = + let is_last_seen_dup v = match dups with ld :: _ -> Int.equal (cmp ld v) 0 | [] -> false in + match (in_l1, in_l2) with + | i :: is, f :: fs when Int.equal (cmp i f) 0 + -> (* i = f *) + if pred i then aux (out_l1, i :: dups, out_l2) is fs + else aux (i :: out_l1, dups, f :: out_l2) is fs + | i :: is, f :: _ when cmp i f < 0 + -> (* i < f *) + let out_l1' = if is_last_seen_dup i then out_l1 else i :: out_l1 in aux (out_l1', dups, out_l2) is in_l2 - | _::_, f::fs -> (* i > f *) - let out_l2' = if is_last_seen_dup f then out_l2 else f::out_l2 in + | _ :: _, f :: fs + -> (* i > f *) + let out_l2' = if is_last_seen_dup f then out_l2 else f :: out_l2 in aux (out_l1, dups, out_l2') in_l1 fs - | i::is, [] when is_last_seen_dup i -> aux out is in_l2 - | [], f::fs when is_last_seen_dup f -> aux out in_l1 fs - | _, _ -> List.rev_append in_l1 out_l1, dups, List.rev_append in_l2 out_l2 in + | i :: is, [] when is_last_seen_dup i + -> aux out is in_l2 + | [], f :: fs when is_last_seen_dup f + -> aux out in_l1 fs + | _, _ + -> (List.rev_append in_l1 out_l1, dups, List.rev_append in_l2 out_l2) + in let l1_sorted = List.sort ~cmp l1 in let l2_sorted = List.sort ~cmp l2 in aux ([], [], []) l1_sorted l2_sorted -type issue_file_with_renaming = Jsonbug_t.jsonbug * (string option) +type issue_file_with_renaming = Jsonbug_t.jsonbug * string option -let skip_duplicated_types_on_filenames - renamings - (diff: Differential.t) : Differential.t = +let skip_duplicated_types_on_filenames renamings (diff: Differential.t) : Differential.t = let compare_issue_file_with_renaming (issue1, previous_file1) (issue2, previous_file2) = let f1, f2 = - Option.value previous_file1 ~default:issue1.Jsonbug_t.file, - Option.value previous_file2 ~default:issue2.Jsonbug_t.file in - String.compare f1 f2 in - let cmp ((issue1, _) as issue_with_previous_file1) ((issue2, _) as issue_with_previous_file2) = + ( Option.value previous_file1 ~default:issue1.Jsonbug_t.file + , Option.value previous_file2 ~default:issue2.Jsonbug_t.file ) + in + String.compare f1 f2 + in + let cmp (issue1, _ as issue_with_previous_file1) (issue2, _ as issue_with_previous_file2) = [%compare : string * issue_file_with_renaming] (issue1.Jsonbug_t.bug_type, issue_with_previous_file1) - (issue2.Jsonbug_t.bug_type, issue_with_previous_file2) in + (issue2.Jsonbug_t.bug_type, issue_with_previous_file2) + in let introduced, preexisting, fixed = (* All comparisons will be made against filenames *before* renamings. This way, all introduced and fixed issues can be sorted independently over the same domain. *) let introduced_normalized = - List.map diff.introduced - ~f:(fun i -> i, FileRenamings.find_previous renamings i.Jsonbug_t.file) in - let fixed_normalized = List.map diff.fixed ~f:(fun f -> f, None) in + List.map diff.introduced ~f:(fun i -> + (i, FileRenamings.find_previous renamings i.Jsonbug_t.file) ) + in + let fixed_normalized = List.map diff.fixed ~f:(fun f -> (f, None)) in let introduced_normalized', preexisting', fixed_normalized' = - relative_complements ~cmp introduced_normalized fixed_normalized in + relative_complements ~cmp introduced_normalized fixed_normalized + in let list_map_fst = List.map ~f:fst in - list_map_fst introduced_normalized', - (list_map_fst preexisting') @ diff.preexisting, - list_map_fst fixed_normalized' in + ( list_map_fst introduced_normalized' + , list_map_fst preexisting' @ diff.preexisting + , list_map_fst fixed_normalized' ) + in {introduced; fixed; preexisting} let java_anon_class_pattern = Str.regexp "\\$[0-9]+" @@ -118,7 +132,8 @@ let compare_procedure_id pid1 pid2 = (* THIS COMPARISON FUNCTION IS INTENDED FOR JAVA ONLY *) let normalize_procedure_id pid = let anon_token = "$ANON" in - Str.global_replace java_anon_class_pattern anon_token pid in + Str.global_replace java_anon_class_pattern anon_token pid + in let pid1_norm = normalize_procedure_id pid1 in let pid2_norm = normalize_procedure_id pid2 in (* NOTE: The CRC may swallow some extra chars if the anon class has more @@ -127,18 +142,20 @@ let compare_procedure_id pid1 pid2 = * Cut the length to the min_length to match the 2 strings *) let pid1_norm_trimmed, pid2_norm_trimmed = let min_length = min (String.length pid1_norm) (String.length pid2_norm) in - String.sub pid1_norm ~pos:0 ~len:min_length, - String.sub pid2_norm ~pos:0 ~len:min_length in + (String.sub pid1_norm ~pos:0 ~len:min_length, String.sub pid2_norm ~pos:0 ~len:min_length) + in String.compare pid1_norm_trimmed pid2_norm_trimmed let value_of_qualifier_tag qts tag = match List.find ~f:(fun elem -> String.equal elem.Jsonbug_t.tag tag) qts with - | Some qt -> Some qt.Jsonbug_t.value - | None -> None + | Some qt + -> Some qt.Jsonbug_t.value + | None + -> None type file_extension = string [@@deriving compare] -type weak_hash = string * string * string * int * (string option) [@@deriving compare] +type weak_hash = string * string * string * int * string option [@@deriving compare] let skip_anonymous_class_renamings (diff: Differential.t) : Differential.t = (* @@ -151,103 +168,118 @@ let skip_anonymous_class_renamings (diff: Differential.t) : Differential.t = *) let string_of_procedure_id issue = DB.strip_crc issue.Jsonbug_t.procedure_id in let extension fname = snd (Filename.split_extension fname) in - let cmp (i1:Jsonbug_t.jsonbug) (i2:Jsonbug_t.jsonbug) = - [%compare : - (file_extension option) * weak_hash * procedure_id] - (extension i1.file, - (i1.kind, i1.bug_type, i1.file, i1.key, - value_of_qualifier_tag i1.qualifier_tags "call_procedure"), - string_of_procedure_id i1) - (extension i2.file, - (i2.kind, i2.bug_type, i2.file, i2.key, - value_of_qualifier_tag i2.qualifier_tags "call_procedure"), - string_of_procedure_id i2) in + let cmp (i1: Jsonbug_t.jsonbug) (i2: Jsonbug_t.jsonbug) = + [%compare : file_extension option * weak_hash * procedure_id] + ( extension i1.file + , ( i1.kind + , i1.bug_type + , i1.file + , i1.key + , value_of_qualifier_tag i1.qualifier_tags "call_procedure" ) + , string_of_procedure_id i1 ) + ( extension i2.file + , ( i2.kind + , i2.bug_type + , i2.file + , i2.key + , value_of_qualifier_tag i2.qualifier_tags "call_procedure" ) + , string_of_procedure_id i2 ) + in let pred (issue: Jsonbug_t.jsonbug) = let is_java_file () = match extension issue.file with - | Some ext -> String.equal (String.lowercase ext) "java" - | None -> false in + | Some ext + -> String.equal (String.lowercase ext) "java" + | None + -> false + in let has_anonymous_class_token () = try - ignore (Str.search_forward java_anon_class_pattern issue.procedure_id 0); + ignore (Str.search_forward java_anon_class_pattern issue.procedure_id 0) ; true - with Not_found -> false in - is_java_file () && has_anonymous_class_token () in - let introduced, preexisting, fixed = relative_complements ~cmp ~pred diff.introduced diff.fixed in - {introduced; fixed; preexisting = preexisting @ diff.preexisting} + with Not_found -> false + in + is_java_file () && has_anonymous_class_token () + in + let introduced, preexisting, fixed = + relative_complements ~cmp ~pred diff.introduced diff.fixed + in + {introduced; fixed; preexisting= preexisting @ diff.preexisting} (* Filter out null dereferences reported by infer if file has eradicate enabled, to avoid double reporting. *) -let resolve_infer_eradicate_conflict - (analyzer: Config.analyzer) - (filters_of_analyzer: Config.analyzer -> Inferconfig.filters) - (diff: Differential.t) : Differential.t = +let resolve_infer_eradicate_conflict (analyzer: Config.analyzer) + (filters_of_analyzer: Config.analyzer -> Inferconfig.filters) (diff: Differential.t) + : Differential.t = let should_discard_issue (issue: Jsonbug_t.jsonbug) = let file_is_whitelisted () = let source_file = SourceFile.UNSAFE.from_string issue.file in let filters = filters_of_analyzer Config.Eradicate in - filters.path_filter source_file in - Config.equal_analyzer analyzer Config.BiAbduction && - String.equal issue.bug_type (Localise.to_issue_id Localise.null_dereference) && - file_is_whitelisted () in + filters.path_filter source_file + in + Config.equal_analyzer analyzer Config.BiAbduction + && String.equal issue.bug_type (Localise.to_issue_id Localise.null_dereference) + && file_is_whitelisted () + in let filter issues = List.filter ~f:(Fn.non should_discard_issue) issues in - { - introduced = filter diff.introduced; - fixed = filter diff.fixed; - preexisting = filter diff.preexisting; - } + { introduced= filter diff.introduced + ; fixed= filter diff.fixed + ; preexisting= filter diff.preexisting } (* Strip issues whose paths are not among those we're interested in *) let interesting_paths_filter (interesting_paths: SourceFile.t list option) = match interesting_paths with - | Some (paths: SourceFile.t list) -> - let interesting_paths_set = + | Some (paths: SourceFile.t list) + -> let interesting_paths_set = paths - |> List.filter_map - ~f:(fun p -> - if not (SourceFile.is_invalid p) && SourceFile.is_under_project_root p then - Some (SourceFile.to_string p) - else None) - |> String.Set.of_list in + |> List.filter_map ~f:(fun p -> + if not (SourceFile.is_invalid p) && SourceFile.is_under_project_root p then + Some (SourceFile.to_string p) + else None ) + |> String.Set.of_list + in fun report -> List.filter - ~f:(fun issue -> String.Set.mem interesting_paths_set issue.Jsonbug_t.file) report - | None -> Fn.id - -let do_filter - (diff: Differential.t) - (renamings: FileRenamings.t) - ~(skip_duplicated_types: bool) - ~(interesting_paths: SourceFile.t list option): Differential.t = + ~f:(fun issue -> String.Set.mem interesting_paths_set issue.Jsonbug_t.file) + report + | None + -> Fn.id + +let do_filter (diff: Differential.t) (renamings: FileRenamings.t) ~(skip_duplicated_types: bool) + ~(interesting_paths: SourceFile.t list option) : Differential.t = let paths_filter = interesting_paths_filter interesting_paths in let apply_paths_filter_if_needed label issues = - if List.exists ~f:(PVariant.(=) label) Config.differential_filter_set then + if List.exists ~f:(PVariant.( = ) label) Config.differential_filter_set then paths_filter issues - else issues in + else issues + in let diff' = diff - |> (if Config.equal_analyzer Config.analyzer Config.BiAbduction then - skip_anonymous_class_renamings - else Fn.id) - |> (if skip_duplicated_types then - skip_duplicated_types_on_filenames renamings - else Fn.id) - |> (if Config.resolve_infer_eradicate_conflict then - resolve_infer_eradicate_conflict Config.analyzer Inferconfig.create_filters - else Fn.id) in - { - introduced = apply_paths_filter_if_needed `Introduced diff'.introduced; - fixed = apply_paths_filter_if_needed `Fixed diff'.fixed; - preexisting = apply_paths_filter_if_needed `Preexisting diff'.preexisting; - } - + |> ( if Config.equal_analyzer Config.analyzer Config.BiAbduction then + skip_anonymous_class_renamings + else Fn.id ) + |> (if skip_duplicated_types then skip_duplicated_types_on_filenames renamings else Fn.id) + |> + if Config.resolve_infer_eradicate_conflict then + resolve_infer_eradicate_conflict Config.analyzer Inferconfig.create_filters + else Fn.id + in + { introduced= apply_paths_filter_if_needed `Introduced diff'.introduced + ; fixed= apply_paths_filter_if_needed `Fixed diff'.fixed + ; preexisting= apply_paths_filter_if_needed `Preexisting diff'.preexisting } module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct let relative_complements = relative_complements + let skip_duplicated_types_on_filenames = skip_duplicated_types_on_filenames + let java_anon_class_pattern = java_anon_class_pattern + let value_of_qualifier_tag = value_of_qualifier_tag + let skip_anonymous_class_renamings = skip_anonymous_class_renamings + let resolve_infer_eradicate_conflict = resolve_infer_eradicate_conflict + let interesting_paths_filter = interesting_paths_filter end diff --git a/infer/src/backend/DifferentialFilters.mli b/infer/src/backend/DifferentialFilters.mli index 00514ff30..aff8fd90b 100644 --- a/infer/src/backend/DifferentialFilters.mli +++ b/infer/src/backend/DifferentialFilters.mli @@ -9,39 +9,48 @@ open! IStd -module FileRenamings : -sig - type renaming = { - current: string; - previous: string; - } +module FileRenamings : sig + type renaming = {current: string; previous: string} + type t + val empty : t + val from_json : string -> t + val from_json_file : string -> t + val find_previous : t -> string -> string option module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig val from_renamings : renaming list -> t + val equal : t -> t -> bool + val pp : Format.formatter -> t -> unit end end -val do_filter : Differential.t -> FileRenamings.t -> skip_duplicated_types:bool -> - interesting_paths:SourceFile.t list option -> Differential.t +val do_filter : + Differential.t -> FileRenamings.t -> skip_duplicated_types:bool + -> interesting_paths:SourceFile.t list option -> Differential.t module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig val relative_complements : - cmp:('a -> 'a -> int) -> ?pred:('a -> bool) -> 'a list -> 'a list -> 'a list * 'a list * 'a list + cmp:('a -> 'a -> int) -> ?pred:('a -> bool) -> 'a list -> 'a list + -> 'a list * 'a list * 'a list + val skip_duplicated_types_on_filenames : FileRenamings.t -> Differential.t -> Differential.t + val java_anon_class_pattern : Str.regexp + val value_of_qualifier_tag : Jsonbug_t.tag_value_record list -> string -> string option + val skip_anonymous_class_renamings : Differential.t -> Differential.t + val resolve_infer_eradicate_conflict : - Config.analyzer -> - (Config.analyzer -> Inferconfig.filters) -> - Differential.t -> Differential.t + Config.analyzer -> (Config.analyzer -> Inferconfig.filters) -> Differential.t -> Differential.t + val interesting_paths_filter : SourceFile.t list option -> Jsonbug_t.jsonbug list -> Jsonbug_t.jsonbug list end diff --git a/infer/src/backend/InferAnalyze.ml b/infer/src/backend/InferAnalyze.ml new file mode 100644 index 000000000..34c6082e7 --- /dev/null +++ b/infer/src/backend/InferAnalyze.ml @@ -0,0 +1,173 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Main module for the analysis after the capture phase *) +open! IStd +module L = Logging +module F = Format + +(** Create tasks to analyze an execution environment *) +let analyze_exe_env_tasks cluster exe_env : Tasks.t = + L.progressbar_file () ; + Specs.clear_spec_tbl () ; + Random.self_init () ; + let biabduction_only = Config.equal_analyzer Config.analyzer Config.BiAbduction in + if biabduction_only then + (* run the biabduction analysis only *) + Tasks.create (Interproc.do_analysis_closures exe_env) + ~continuation: + ( if Config.write_html || Config.developer_mode then + Some + (fun () -> + if Config.write_html then Printer.write_all_html_files cluster ; + if Config.developer_mode then Interproc.print_stats cluster) + else None ) + else + (* run the registered checkers *) + Tasks.create + [ (fun () -> + let call_graph = Exe_env.get_cg exe_env in + Callbacks.iterate_callbacks call_graph exe_env ; + if Config.write_html then Printer.write_all_html_files cluster) ] + +(** Create tasks to analyze a cluster *) +let analyze_cluster_tasks cluster_num (cluster: Cluster.t) : Tasks.t = + let exe_env = Exe_env.from_cluster cluster in + let defined_procs = Cg.get_defined_nodes (Exe_env.get_cg exe_env) in + let num_procs = List.length defined_procs in + L.(debug Analysis Medium) + "@\nProcessing cluster #%d with %d procedures@." (cluster_num + 1) num_procs ; + analyze_exe_env_tasks cluster exe_env + +let analyze_cluster cluster_num cluster = Tasks.run (analyze_cluster_tasks cluster_num cluster) + +let output_json_makefile_stats clusters = + let num_files = List.length clusters in + let num_procs = 0 in + (* can't compute it at this stage *) + let num_lines = 0 in + let file_stats = + `Assoc [("files", `Int num_files); ("procedures", `Int num_procs); ("lines", `Int num_lines)] + in + (* write stats file to disk, intentionally overwriting old file if it already exists *) + let f = Out_channel.create (Filename.concat Config.results_dir Config.proc_stats_filename) in + Yojson.Basic.pretty_to_channel f file_stats + +let process_cluster_cmdline fname = + match Cluster.load_from_file (DB.filename_from_string fname) with + | None + -> L.internal_error "Cannot find cluster file %s@." fname + | Some (nr, cluster) + -> analyze_cluster (nr - 1) cluster + +let print_legend () = + L.progress "Starting analysis...@\n" ; + L.progress "@\n" ; + L.progress "legend:@." ; + L.progress " \"%s\" analyzing a file@\n" Config.log_analysis_file ; + L.progress " \"%s\" analyzing a procedure@\n" Config.log_analysis_procedure ; + if Config.stats_mode || Config.debug_mode then ( + L.progress " \"%s\" analyzer crashed@\n" Config.log_analysis_crash ; + L.progress " \"%s\" timeout: procedure analysis took too much time@\n" + Config.log_analysis_wallclock_timeout ; + L.progress " \"%s\" timeout: procedure analysis took too many symbolic execution steps@\n" + Config.log_analysis_symops_timeout ; + L.progress " \"%s\" timeout: procedure analysis took too many recursive iterations@\n" + Config.log_analysis_recursion_timeout ) ; + L.progress "@\n@?" + +let cluster_should_be_analyzed ~changed_files cluster = + let fname = DB.source_dir_to_string cluster in + (* whether [fname] is one of the [changed_files] *) + let is_changed_file = + (* set of source dirs to analyze inside infer-out/captured/ *) + let source_dirs_to_analyze changed_files = + SourceFile.Set.fold + (fun source_file source_dir_set -> + let source_dir = DB.source_dir_from_source_file source_file in + String.Set.add source_dir_set (DB.source_dir_to_string source_dir)) + changed_files String.Set.empty + in + Option.map ~f:source_dirs_to_analyze changed_files + |> fun dirs_opt -> Option.map dirs_opt ~f:(fun dirs -> String.Set.mem dirs fname) + in + let check_modified () = + let modified = DB.file_was_updated_after_start (DB.filename_from_string fname) in + if modified && Config.developer_mode then L.progress "Modified: %s@." fname ; + modified + in + match is_changed_file with + | Some b + -> b + | None when Config.reactive_mode + -> check_modified () + | None + -> true + +let main ~changed_files ~makefile = + BuiltinDefn.init () ; + RegisterCheckers.register () ; + ( match Config.modified_targets with + | Some file + -> MergeCapture.record_modified_targets_from_file file + | None + -> () ) ; + match Config.cluster_cmdline with + | Some fname + -> process_cluster_cmdline fname + | None + -> if Config.allow_specs_cleanup then DB.Results_dir.clean_specs_dir () ; + if Config.merge then MergeCapture.merge_captured_targets () ; + let all_clusters = DB.find_source_dirs () in + let clusters_to_analyze = + List.filter ~f:(cluster_should_be_analyzed ~changed_files) all_clusters + in + let n_clusters_to_analyze = List.length clusters_to_analyze in + L.progress "Found %d%s source file%s to analyze in %s@." n_clusters_to_analyze + ( if Config.reactive_mode || Option.is_some changed_files then " (out of " + ^ string_of_int (List.length all_clusters) ^ ")" + else "" ) + (if Int.equal n_clusters_to_analyze 1 then "" else "s") + Config.results_dir ; + let is_java () = + List.exists + ~f:(fun cl -> DB.string_crc_has_extension ~ext:"java" (DB.source_dir_to_string cl)) + all_clusters + in + if Config.print_active_checkers then + L.result "Active checkers: %a@." RegisterCheckers.pp_active_checkers () ; + print_legend () ; + if Config.per_procedure_parallelism && not (is_java ()) then ( + (* Java uses ZipLib which is incompatible with forking *) + (* per-procedure parallelism *) + L.environment_info "Per-procedure parallelism jobs: %d@." Config.jobs ; + if makefile <> "" then ClusterMakefile.create_cluster_makefile [] makefile ; + (* Prepare tasks one cluster at a time while executing in parallel *) + let runner = Tasks.Runner.create ~jobs:Config.jobs in + let cluster_start_tasks i cluster = + let tasks = analyze_cluster_tasks i cluster in + let aggregate_tasks = Tasks.aggregate ~size:Config.procedures_per_process tasks in + Tasks.Runner.start runner ~tasks:aggregate_tasks + in + List.iteri ~f:cluster_start_tasks clusters_to_analyze ; Tasks.Runner.complete runner ) + else if makefile <> "" then + ClusterMakefile.create_cluster_makefile clusters_to_analyze makefile + else ( + (* This branch is reached when -j 1 is used *) + List.iteri ~f:analyze_cluster clusters_to_analyze ; + L.progress "@\nAnalysis finished in %as@." Pp.elapsed_time () ) ; + output_json_makefile_stats clusters_to_analyze + +let register_perf_stats_report () = + let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name in + let cluster = match Config.cluster_cmdline with Some cl -> "_" ^ cl | None -> "" in + let stats_base = Config.perf_stats_prefix ^ Filename.basename cluster ^ ".json" in + let stats_file = Filename.concat stats_dir stats_base in + PerfStats.register_report_at_exit stats_file diff --git a/infer/src/backend/InferAnalyze.rei b/infer/src/backend/InferAnalyze.mli similarity index 54% rename from infer/src/backend/InferAnalyze.rei rename to infer/src/backend/InferAnalyze.mli index 9dda254c4..940f687d6 100644 --- a/infer/src/backend/InferAnalyze.rei +++ b/infer/src/backend/InferAnalyze.mli @@ -1,4 +1,4 @@ -/* +(* * Copyright (c) 2009 - 2013 Monoidics ltd. * Copyright (c) 2013 - present Facebook, Inc. * All rights reserved. @@ -6,13 +6,14 @@ * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) +open! IStd -/** Main module for the analysis after the capture phase */ +(** Main module for the analysis after the capture phase *) -/** Given a name of the Makefile to use for multicore analysis, analyze the captured code */ -let main: changed_files::option SourceFile.Set.t => makefile::string => unit; +(** Given a name of the Makefile to use for multicore analysis, analyze the captured code *) -let register_perf_stats_report: unit => unit; +val main : changed_files:SourceFile.Set.t option -> makefile:string -> unit + +val register_perf_stats_report : unit -> unit diff --git a/infer/src/backend/InferAnalyze.re b/infer/src/backend/InferAnalyze.re deleted file mode 100644 index c5c9e7d7e..000000000 --- a/infer/src/backend/InferAnalyze.re +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** Main module for the analysis after the capture phase */ -module L = Logging; - -module F = Format; - - -/** Create tasks to analyze an execution environment */ -let analyze_exe_env_tasks cluster exe_env :Tasks.t => { - L.progressbar_file (); - Specs.clear_spec_tbl (); - Random.self_init (); - let biabduction_only = Config.equal_analyzer Config.analyzer Config.BiAbduction; - if biabduction_only { - /* run the biabduction analysis only */ - Tasks.create - (Interproc.do_analysis_closures exe_env) - continuation::( - if (Config.write_html || Config.developer_mode) { - Some ( - fun () => { - if Config.write_html { - Printer.write_all_html_files cluster - }; - if Config.developer_mode { - Interproc.print_stats cluster - } - } - ) - } else { - None - } - ) - } else { - /* run the registered checkers */ - Tasks.create [ - fun () => { - let call_graph = Exe_env.get_cg exe_env; - Callbacks.iterate_callbacks call_graph exe_env; - if Config.write_html { - Printer.write_all_html_files cluster - } - } - ] - } -}; - - -/** Create tasks to analyze a cluster */ -let analyze_cluster_tasks cluster_num (cluster: Cluster.t) :Tasks.t => { - let exe_env = Exe_env.from_cluster cluster; - let defined_procs = Cg.get_defined_nodes (Exe_env.get_cg exe_env); - let num_procs = List.length defined_procs; - L.(debug Analysis Medium) - "@\nProcessing cluster #%d with %d procedures@." (cluster_num + 1) num_procs; - analyze_exe_env_tasks cluster exe_env -}; - -let analyze_cluster cluster_num cluster => Tasks.run (analyze_cluster_tasks cluster_num cluster); - -let output_json_makefile_stats clusters => { - let num_files = List.length clusters; - let num_procs = 0; - /* can't compute it at this stage */ - let num_lines = 0; - let file_stats = - `Assoc [("files", `Int num_files), ("procedures", `Int num_procs), ("lines", `Int num_lines)]; - /* write stats file to disk, intentionally overwriting old file if it already exists */ - let f = Out_channel.create (Filename.concat Config.results_dir Config.proc_stats_filename); - Yojson.Basic.pretty_to_channel f file_stats -}; - -let process_cluster_cmdline fname => - switch (Cluster.load_from_file (DB.filename_from_string fname)) { - | None => L.internal_error "Cannot find cluster file %s@." fname - | Some (nr, cluster) => analyze_cluster (nr - 1) cluster - }; - -let print_legend () => { - L.progress "Starting analysis...@\n"; - L.progress "@\n"; - L.progress "legend:@."; - L.progress " \"%s\" analyzing a file@\n" Config.log_analysis_file; - L.progress " \"%s\" analyzing a procedure@\n" Config.log_analysis_procedure; - if (Config.stats_mode || Config.debug_mode) { - L.progress " \"%s\" analyzer crashed@\n" Config.log_analysis_crash; - L.progress - " \"%s\" timeout: procedure analysis took too much time@\n" - Config.log_analysis_wallclock_timeout; - L.progress - " \"%s\" timeout: procedure analysis took too many symbolic execution steps@\n" - Config.log_analysis_symops_timeout; - L.progress - " \"%s\" timeout: procedure analysis took too many recursive iterations@\n" - Config.log_analysis_recursion_timeout - }; - L.progress "@\n@?" -}; - -let cluster_should_be_analyzed ::changed_files cluster => { - let fname = DB.source_dir_to_string cluster; - /* whether [fname] is one of the [changed_files] */ - let is_changed_file = { - /* set of source dirs to analyze inside infer-out/captured/ */ - let source_dirs_to_analyze changed_files => - SourceFile.Set.fold - ( - fun source_file source_dir_set => { - let source_dir = DB.source_dir_from_source_file source_file; - String.Set.add source_dir_set (DB.source_dir_to_string source_dir) - } - ) - changed_files - String.Set.empty; - Option.map f::source_dirs_to_analyze changed_files |> ( - fun dirs_opt => Option.map dirs_opt f::(fun dirs => String.Set.mem dirs fname) - ) - }; - let check_modified () => { - let modified = DB.file_was_updated_after_start (DB.filename_from_string fname); - if (modified && Config.developer_mode) { - L.progress "Modified: %s@." fname - }; - modified - }; - switch is_changed_file { - | Some b => b - | None when Config.reactive_mode => check_modified () - | None => true - } -}; - -let main ::changed_files ::makefile => { - BuiltinDefn.init (); - RegisterCheckers.register (); - switch Config.modified_targets { - | Some file => MergeCapture.record_modified_targets_from_file file - | None => () - }; - switch Config.cluster_cmdline { - | Some fname => process_cluster_cmdline fname - | None => - if Config.allow_specs_cleanup { - DB.Results_dir.clean_specs_dir () - }; - if Config.merge { - MergeCapture.merge_captured_targets () - }; - let all_clusters = DB.find_source_dirs (); - let clusters_to_analyze = - List.filter f::(cluster_should_be_analyzed ::changed_files) all_clusters; - let n_clusters_to_analyze = List.length clusters_to_analyze; - L.progress - "Found %d%s source file%s to analyze in %s@." - n_clusters_to_analyze - ( - if (Config.reactive_mode || Option.is_some changed_files) { - " (out of " ^ string_of_int (List.length all_clusters) ^ ")" - } else { - "" - } - ) - ( - if (Int.equal n_clusters_to_analyze 1) { - "" - } else { - "s" - } - ) - Config.results_dir; - let is_java () => - List.exists - f::(fun cl => DB.string_crc_has_extension ext::"java" (DB.source_dir_to_string cl)) - all_clusters; - if Config.print_active_checkers { - L.result "Active checkers: %a@." RegisterCheckers.pp_active_checkers () - }; - print_legend (); - if (Config.per_procedure_parallelism && not (is_java ())) { - /* Java uses ZipLib which is incompatible with forking */ - /* per-procedure parallelism */ - L.environment_info "Per-procedure parallelism jobs: %d@." Config.jobs; - if (makefile != "") { - ClusterMakefile.create_cluster_makefile [] makefile - }; - /* Prepare tasks one cluster at a time while executing in parallel */ - let runner = Tasks.Runner.create jobs::Config.jobs; - let cluster_start_tasks i cluster => { - let tasks = analyze_cluster_tasks i cluster; - let aggregate_tasks = Tasks.aggregate size::Config.procedures_per_process tasks; - Tasks.Runner.start runner tasks::aggregate_tasks - }; - List.iteri f::cluster_start_tasks clusters_to_analyze; - Tasks.Runner.complete runner - } else if ( - makefile != "" - ) { - ClusterMakefile.create_cluster_makefile clusters_to_analyze makefile - } else { - /* This branch is reached when -j 1 is used */ - List.iteri f::analyze_cluster clusters_to_analyze; - L.progress "@\nAnalysis finished in %as@." Pp.elapsed_time () - }; - output_json_makefile_stats clusters_to_analyze - } -}; - -let register_perf_stats_report () => { - let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name; - let cluster = - switch Config.cluster_cmdline { - | Some cl => "_" ^ cl - | None => "" - }; - let stats_base = Config.perf_stats_prefix ^ Filename.basename cluster ^ ".json"; - let stats_file = Filename.concat stats_dir stats_base; - PerfStats.register_report_at_exit stats_file -}; diff --git a/infer/src/backend/InferPrint.ml b/infer/src/backend/InferPrint.ml new file mode 100644 index 000000000..de7ddccba --- /dev/null +++ b/infer/src/backend/InferPrint.ml @@ -0,0 +1,1110 @@ +(* + * Copyright (c) 2009 - 2013 Monoidics ltd. + * Copyright (c) 2013 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module CLOpt = CommandLineOption +module Hashtbl = Caml.Hashtbl +module L = Logging +module F = Format + +let print_usage_exit err_s = L.user_error "Load Error: %s@\n@." err_s ; Config.print_usage_exit () + +(** return the list of the .specs files in the results dir and libs, if they're defined *) +let load_specfiles () = + let specs_files_in_dir dir = + let is_specs_file fname = + Sys.is_directory fname <> `Yes && Filename.check_suffix fname Config.specs_files_suffix + in + let all_filenames = + try Array.to_list (Sys.readdir dir) + with Sys_error _ -> [] + in + let all_filepaths = List.map ~f:(fun fname -> Filename.concat dir fname) all_filenames in + List.filter ~f:is_specs_file all_filepaths + in + let result_specs_dir = DB.filename_to_string DB.Results_dir.specs_dir in + specs_files_in_dir result_specs_dir + +(** Create and initialize latex file *) +let begin_latex_file fmt = + let author = "Infer " ^ Version.versionString in + let title = "Report on Analysis Results" in + let table_of_contents = true in + Latex.pp_begin fmt (author, title, table_of_contents) + +let error_desc_to_csv_string error_desc = + let pp fmt = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in + Escape.escape_csv (F.asprintf "%t" pp) + +let error_advice_to_csv_string error_desc = + let pp fmt = F.fprintf fmt "%a" Localise.pp_error_advice error_desc in + Escape.escape_csv (F.asprintf "%t" pp) + +let error_desc_to_plain_string error_desc = + let pp fmt = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in + let s = F.asprintf "%t" pp in + let s = String.strip s in + let s = + (* end error description with a dot *) + if String.is_suffix ~suffix:"." s then s else s ^ "." + in + s + +let error_desc_to_dotty_string error_desc = Localise.error_desc_get_dotty error_desc + +let error_desc_to_xml_tags error_desc = + let tags = Localise.error_desc_get_tags error_desc in + let subtree label contents = Io_infer.Xml.create_tree label [] [Io_infer.Xml.String contents] in + List.map ~f:(fun (tag, value) -> subtree tag (Escape.escape_xml value)) tags + +let get_bug_hash (kind: string) (type_str: string) (procedure_id: string) (filename: string) + (node_key: int) (error_desc: Localise.error_desc) = + let qualifier_tag_call_procedure = Localise.error_desc_get_tag_call_procedure error_desc in + let qualifier_tag_value = Localise.error_desc_get_tag_value error_desc in + Hashtbl.hash + ( kind + , type_str + , procedure_id + , filename + , node_key + , qualifier_tag_call_procedure + , qualifier_tag_value ) + +let exception_value = "exception" + +let loc_trace_to_jsonbug_record trace_list ekind = + match ekind with + | Exceptions.Kinfo + -> [] + | _ + -> let tag_value_records_of_node_tag nt = + match nt with + | Errlog.Condition cond + -> [ {Jsonbug_j.tag= Io_infer.Xml.tag_kind; value= "condition"} + ; {Jsonbug_j.tag= Io_infer.Xml.tag_branch; value= Printf.sprintf "%B" cond} ] + | Errlog.Exception exn_name + -> let res = [{Jsonbug_j.tag= Io_infer.Xml.tag_kind; value= exception_value}] in + let exn_str = Typ.Name.name exn_name in + if String.is_empty exn_str then res + else {Jsonbug_j.tag= Io_infer.Xml.tag_name; value= exn_str} :: res + | Errlog.Procedure_start pname + -> [ {Jsonbug_j.tag= Io_infer.Xml.tag_kind; value= "procedure_start"} + ; {Jsonbug_j.tag= Io_infer.Xml.tag_name; value= Typ.Procname.to_string pname} + ; {Jsonbug_j.tag= Io_infer.Xml.tag_name_id; value= Typ.Procname.to_filename pname} ] + | Errlog.Procedure_end pname + -> [ {Jsonbug_j.tag= Io_infer.Xml.tag_kind; value= "procedure_end"} + ; {Jsonbug_j.tag= Io_infer.Xml.tag_name; value= Typ.Procname.to_string pname} + ; {Jsonbug_j.tag= Io_infer.Xml.tag_name_id; value= Typ.Procname.to_filename pname} ] + in + let trace_item_to_record trace_item = + { Jsonbug_j.level= trace_item.Errlog.lt_level + ; filename= SourceFile.to_string trace_item.Errlog.lt_loc.Location.file + ; line_number= trace_item.Errlog.lt_loc.Location.line + ; column_number= trace_item.Errlog.lt_loc.Location.col + ; description= trace_item.Errlog.lt_description + ; node_tags= + List.concat_map ~f:tag_value_records_of_node_tag trace_item.Errlog.lt_node_tags } + in + let record_list = List.rev (List.rev_map ~f:trace_item_to_record trace_list) in + record_list + +type summary_val = + { vname: string + ; vname_id: string + ; vspecs: int + ; vto: string + ; vsymop: int + ; verr: int + ; vfile: string + ; vflags: ProcAttributes.proc_flags + ; vline: int + ; vsignature: string + ; vweight: int + ; vproof_coverage: string + ; vproof_trace: string } + +(** compute values from summary data to export to csv format *) +let summary_values summary = + let stats = summary.Specs.stats in + let attributes = summary.Specs.attributes in + let err_log = attributes.ProcAttributes.err_log in + let proc_name = Specs.get_proc_name summary in + let signature = Specs.get_signature summary in + let nodes_nr = List.length summary.Specs.nodes in + let specs = Specs.get_specs_from_payload summary in + let nr_nodes_visited, lines_visited = + let visited = ref Specs.Visitedset.empty in + let do_spec spec = visited := Specs.Visitedset.union spec.Specs.visited !visited in + List.iter ~f:do_spec specs ; + let visited_lines = ref Int.Set.empty in + Specs.Visitedset.iter + (fun (_, ls) -> List.iter ~f:(fun l -> visited_lines := Int.Set.add !visited_lines l) ls) + !visited ; + (Specs.Visitedset.cardinal !visited, Int.Set.elements !visited_lines) + in + let proof_trace = + let pp_line fmt l = F.fprintf fmt "%d" l in + let pp fmt = F.fprintf fmt "%a" (Pp.seq pp_line) lines_visited in + F.asprintf "%t" pp + in + let node_coverage = + if Int.equal nodes_nr 0 then 0.0 else float_of_int nr_nodes_visited /. float_of_int nodes_nr + in + let pp_failure failure = F.asprintf "%a" SymOp.pp_failure_kind failure in + { vname= Typ.Procname.to_string proc_name + ; vname_id= Typ.Procname.to_filename proc_name + ; vspecs= List.length specs + ; vto= Option.value_map ~f:pp_failure ~default:"NONE" stats.Specs.stats_failure + ; vsymop= stats.Specs.symops + ; verr= + Errlog.size + (fun ekind in_footprint -> + Exceptions.equal_err_kind ekind Exceptions.Kerror && in_footprint) + err_log + ; vflags= attributes.ProcAttributes.proc_flags + ; vfile= SourceFile.to_string attributes.ProcAttributes.loc.Location.file + ; vline= attributes.ProcAttributes.loc.Location.line + ; vsignature= signature + ; vweight= nodes_nr + ; vproof_coverage= Printf.sprintf "%2.2f" node_coverage + ; vproof_trace= proof_trace } + +module ProcsCsv = struct + (** Print the header of the procedures csv file, with column names *) + let pp_header fmt () = + Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" + Io_infer.Xml.tag_name Io_infer.Xml.tag_name_id Io_infer.Xml.tag_specs Io_infer.Xml.tag_time + Io_infer.Xml.tag_to Io_infer.Xml.tag_symop Io_infer.Xml.tag_err Io_infer.Xml.tag_file + Io_infer.Xml.tag_line Io_infer.Xml.tag_loc Io_infer.Xml.tag_top Io_infer.Xml.tag_signature + Io_infer.Xml.tag_weight Io_infer.Xml.tag_proof_coverage Io_infer.Xml.tag_rank + Io_infer.Xml.tag_in_calls Io_infer.Xml.tag_out_calls Io_infer.Xml.tag_proof_trace + + (** Write proc summary stats in csv format *) + let pp_summary fmt summary = + let pp x = F.fprintf fmt x in + let sv = summary_values summary in + pp "\"%s\"," (Escape.escape_csv sv.vname) ; + pp "\"%s\"," (Escape.escape_csv sv.vname_id) ; + pp "%d," sv.vspecs ; + pp "%s," sv.vto ; + pp "%d," sv.vsymop ; + pp "%d," sv.verr ; + pp "%s," sv.vfile ; + pp "%d," sv.vline ; + pp "\"%s\"," (Escape.escape_csv sv.vsignature) ; + pp "%d," sv.vweight ; + pp "%s," sv.vproof_coverage ; + pp "%s@\n" sv.vproof_trace +end + +let should_report (issue_kind: Exceptions.err_kind) issue_type error_desc eclass = + if not Config.filtering || Exceptions.equal_err_class eclass Exceptions.Linters then true + else + let analyzer_is_whitelisted = + match Config.analyzer with + | Eradicate + -> true + | BiAbduction | CaptureOnly | Checkers | CompileOnly | Crashcontext | Linters + -> false + in + if analyzer_is_whitelisted then true + else + let issue_kind_is_blacklisted = + match issue_kind with Kinfo -> true | Kerror | Kwarning | Kadvice | Klike -> false + in + if issue_kind_is_blacklisted then false + else + let issue_type_is_null_deref = + let null_deref_issue_types = + let open Localise in + [ field_not_null_checked + ; null_dereference + ; parameter_not_null_checked + ; premature_nil_termination + ; empty_vector_access ] + in + List.mem ~equal:Localise.equal null_deref_issue_types issue_type + in + let issue_type_is_buffer_overrun = Localise.equal issue_type Localise.buffer_overrun in + if issue_type_is_null_deref || issue_type_is_buffer_overrun then + let issue_bucket_is_high = + let issue_bucket = Localise.error_desc_get_bucket error_desc in + let high_buckets = Localise.BucketLevel.([b1; b2]) in + Option.value_map issue_bucket ~default:false ~f:(fun b -> + List.mem ~equal:String.equal high_buckets b ) + in + issue_bucket_is_high + else true + +module IssuesCsv = struct + let csv_issues_id = ref 0 + + let pp_header fmt () = + Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" Io_infer.Xml.tag_class + Io_infer.Xml.tag_kind Io_infer.Xml.tag_type Io_infer.Xml.tag_qualifier + Io_infer.Xml.tag_severity Io_infer.Xml.tag_line Io_infer.Xml.tag_procedure + Io_infer.Xml.tag_procedure_id Io_infer.Xml.tag_file Io_infer.Xml.tag_trace + Io_infer.Xml.tag_key Io_infer.Xml.tag_qualifier_tags Io_infer.Xml.tag_hash "bug_id" + "always_report" "advice" + + (** Write bug report in csv format *) + let pp_issues_of_error_log fmt error_filter _ proc_loc_opt procname err_log = + let pp x = F.fprintf fmt x in + let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) = + let source_file = + match proc_loc_opt with + | Some proc_loc + -> proc_loc.Location.file + | None + -> err_data.loc.Location.file + in + if key.in_footprint && error_filter source_file key.err_desc key.err_name + && should_report key.err_kind key.err_name key.err_desc err_data.err_class + then + let err_desc_string = error_desc_to_csv_string key.err_desc in + let err_advice_string = error_advice_to_csv_string key.err_desc in + let qualifier_tag_xml = + let xml_node = + Io_infer.Xml.create_tree Io_infer.Xml.tag_qualifier_tags [] + (error_desc_to_xml_tags key.err_desc) + in + let p fmt = F.fprintf fmt "%a" (Io_infer.Xml.pp_document false) xml_node in + let s = F.asprintf "%t" p in + Escape.escape_csv s + in + let kind = Exceptions.err_kind_string key.err_kind in + let type_str = Localise.to_issue_id key.err_name in + let procedure_id = Typ.Procname.to_filename procname in + let filename = SourceFile.to_string source_file in + let always_report = + match Localise.error_desc_extract_tag_value key.err_desc "always_report" with + | "" + -> "false" + | v + -> v + in + let trace = + Jsonbug_j.string_of_json_trace + {trace= loc_trace_to_jsonbug_record err_data.loc_trace key.err_kind} + in + incr csv_issues_id ; + pp "%s," (Exceptions.err_class_string err_data.err_class) ; + pp "%s," kind ; + pp "%s," type_str ; + pp "\"%s\"," err_desc_string ; + pp "%s," key.severity ; + pp "%d," err_data.loc.Location.line ; + pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string procname)) ; + pp "\"%s\"," (Escape.escape_csv procedure_id) ; + pp "%s," filename ; + pp "\"%s\"," (Escape.escape_csv trace) ; + pp "\"%d\"," err_data.node_id_key.node_key ; + pp "\"%s\"," qualifier_tag_xml ; + pp "\"%d\"," + (get_bug_hash kind type_str procedure_id filename err_data.node_id_key.node_key + key.err_desc) ; + pp "\"%d\"," !csv_issues_id ; + (* bug id *) + pp "\"%s\"," always_report ; + pp "\"%s\"@\n" err_advice_string + in + Errlog.iter pp_row err_log +end + +let potential_exception_message = "potential exception at line" + +module IssuesJson = struct + let is_first_item = ref true + + let pp_json_open fmt () = F.fprintf fmt "[@?" + + let pp_json_close fmt () = F.fprintf fmt "]@\n@?" + + (** Write bug report in JSON format *) + let pp_issues_of_error_log fmt error_filter _ proc_loc_opt procname err_log = + let pp x = F.fprintf fmt x in + let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) = + let source_file, procedure_start_line = + match proc_loc_opt with + | Some proc_loc + -> (proc_loc.Location.file, proc_loc.Location.line) + | None + -> (err_data.loc.Location.file, 0) + in + if SourceFile.is_invalid source_file then + failwithf "Invalid source file for %a %a@.Trace: %a@." Localise.pp key.err_name + Localise.pp_error_desc key.err_desc Errlog.pp_loc_trace err_data.loc_trace ; + let should_report_source_file = + not (SourceFile.is_infer_model source_file) || Config.debug_mode || Config.debug_exceptions + in + if key.in_footprint && error_filter source_file key.err_desc key.err_name + && should_report_source_file + && should_report key.err_kind key.err_name key.err_desc err_data.err_class + then + let kind = Exceptions.err_kind_string key.err_kind in + let bug_type = Localise.to_issue_id key.err_name in + let procedure_id = Typ.Procname.to_filename procname in + let file = SourceFile.to_string source_file in + let json_ml_loc = + match err_data.loc_in_ml_source with + | Some (file, lnum, cnum, enum) when Config.reports_include_ml_loc + -> Some Jsonbug_j.{file; lnum; cnum; enum} + | _ + -> None + in + let visibility = Exceptions.string_of_visibility err_data.visibility in + let qualifier = + let base_qualifier = error_desc_to_plain_string key.err_desc in + if Localise.equal key.err_name Localise.resource_leak then + match Errlog.compute_local_exception_line err_data.loc_trace with + | None + -> base_qualifier + | Some line + -> let potential_exception_message = + Format.asprintf "%a: %s %d" MarkupFormatter.pp_bold "Note" + potential_exception_message line + in + Format.sprintf "%s@\n%s" base_qualifier potential_exception_message + else base_qualifier + in + let bug = + { Jsonbug_j.bug_class= Exceptions.err_class_string err_data.err_class + ; kind + ; bug_type + ; qualifier + ; severity= key.severity + ; visibility + ; line= err_data.loc.Location.line + ; column= err_data.loc.Location.col + ; procedure= Typ.Procname.to_string procname + ; procedure_id + ; procedure_start_line + ; file + ; bug_trace= loc_trace_to_jsonbug_record err_data.loc_trace key.err_kind + ; key= err_data.node_id_key.node_key + ; qualifier_tags= Localise.Tags.tag_value_records_of_tags key.err_desc.tags + ; hash= + get_bug_hash kind bug_type procedure_id file err_data.node_id_key.node_key + key.err_desc + ; dotty= error_desc_to_dotty_string key.err_desc + ; infer_source_loc= json_ml_loc + ; bug_type_hum= Localise.to_human_readable_string key.err_name + ; linters_def_file= err_data.linters_def_file + ; doc_url= err_data.doc_url + ; traceview_id= None } + in + if not !is_first_item then pp "," else is_first_item := false ; + pp "%s@?" (Jsonbug_j.string_of_jsonbug bug) + in + Errlog.iter pp_row err_log +end + +let pp_custom_of_report fmt report fields = + let pp_custom_of_issue fmt issue = + let open Jsonbug_t in + let comma_separator index = if index > 0 then ", " else "" in + let pp_trace fmt trace comma = + let pp_trace_elem fmt {description} = F.fprintf fmt "%s" description in + let trace_without_empty_descs = + List.filter ~f:(fun {description} -> description <> "") trace + in + F.fprintf fmt "%s[%a]" comma (Pp.comma_seq pp_trace_elem) trace_without_empty_descs + in + let pp_field index field = + match field with + | `Issue_field_bug_class + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.bug_class + | `Issue_field_kind + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.kind + | `Issue_field_bug_type + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.bug_type + | `Issue_field_qualifier + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.qualifier + | `Issue_field_severity + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.severity + | `Issue_field_visibility + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.visibility + | `Issue_field_line + -> Format.fprintf fmt "%s%d" (comma_separator index) issue.line + | `Issue_field_column + -> Format.fprintf fmt "%s%d" (comma_separator index) issue.column + | `Issue_field_procedure + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.procedure + | `Issue_field_procedure_id + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.procedure_id + | `Issue_field_procedure_start_line + -> Format.fprintf fmt "%s%d" (comma_separator index) issue.procedure_start_line + | `Issue_field_file + -> Format.fprintf fmt "%s%s" (comma_separator index) issue.file + | `Issue_field_bug_trace + -> pp_trace fmt issue.bug_trace (comma_separator index) + | `Issue_field_key + -> Format.fprintf fmt "%s%d" (comma_separator index) issue.key + | `Issue_field_hash + -> Format.fprintf fmt "%s%d" (comma_separator index) issue.hash + | `Issue_field_line_offset + -> Format.fprintf fmt "%s%d" (comma_separator index) + (issue.line - issue.procedure_start_line) + | `Issue_field_procedure_id_without_crc + -> Format.fprintf fmt "%s%s" (comma_separator index) (DB.strip_crc issue.procedure_id) + | `Issue_field_qualifier_contains_potential_exception_note + -> Format.fprintf fmt "%B" + (String.is_substring issue.qualifier ~substring:potential_exception_message) + in + List.iteri ~f:pp_field fields ; Format.fprintf fmt "@." + in + List.iter ~f:(pp_custom_of_issue fmt) report + +let tests_jsonbug_compare bug1 bug2 = + let open Jsonbug_t in + [%compare : string * string * int * string * int] + (bug1.file, bug1.procedure, bug1.line - bug1.procedure_start_line, bug1.bug_type, bug1.hash) + (bug2.file, bug2.procedure, bug2.line - bug2.procedure_start_line, bug2.bug_type, bug2.hash) + +module IssuesTxt = struct + (** Write bug report in text format *) + let pp_issues_of_error_log fmt error_filter _ proc_loc_opt _ err_log = + let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) = + let source_file = + match proc_loc_opt with + | Some proc_loc + -> proc_loc.Location.file + | None + -> err_data.loc.Location.file + in + if key.in_footprint && error_filter source_file key.err_desc key.err_name then + Exceptions.pp_err ~node_key:err_data.node_id_key.node_key err_data.loc key.err_kind + key.err_name key.err_desc None fmt () + in + Errlog.iter pp_row err_log +end + +let pp_text_of_report fmt report = + let pp_row jsonbug = + let open Jsonbug_t in + F.fprintf fmt "%s:%d: %s: %s %s@\n" jsonbug.file jsonbug.line jsonbug.kind jsonbug.bug_type + jsonbug.qualifier + in + List.iter ~f:pp_row report + +module CallsCsv = struct + (** Write proc summary stats in csv format *) + let pp_calls fmt summary = + let pp x = F.fprintf fmt x in + let stats = summary.Specs.stats in + let caller_name = Specs.get_proc_name summary in + let do_call (callee_name, loc) trace = + pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string caller_name)) ; + pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_filename caller_name)) ; + pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string callee_name)) ; + pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_filename callee_name)) ; + pp "%s," (SourceFile.to_string summary.Specs.attributes.ProcAttributes.loc.Location.file) ; + pp "%d," loc.Location.line ; + pp "%a@\n" Specs.CallStats.pp_trace trace + in + Specs.CallStats.iter do_call stats.Specs.call_stats +end + +module Stats = struct + type t = + { files: (SourceFile.t, unit) Hashtbl.t + ; mutable nchecked: int + ; mutable ndefective: int + ; mutable nerrors: int + ; mutable ninfos: int + ; mutable nadvice: int + ; mutable nlikes: int + ; mutable nprocs: int + ; mutable nspecs: int + ; mutable ntimeouts: int + ; mutable nverified: int + ; mutable nwarnings: int + ; mutable saved_errors: string list } + + let create () = + { files= Hashtbl.create 3 + ; nchecked= 0 + ; ndefective= 0 + ; nerrors= 0 + ; ninfos= 0 + ; nadvice= 0 + ; nlikes= 0 + ; nprocs= 0 + ; nspecs= 0 + ; ntimeouts= 0 + ; nverified= 0 + ; nwarnings= 0 + ; saved_errors= [] } + + let process_loc loc stats = + try Hashtbl.find stats.files loc.Location.file + with Not_found -> Hashtbl.add stats.files loc.Location.file () + + let loc_trace_to_string_list linereader indent_num ltr = + let res = ref [] in + let indent_string n = + let s = ref "" in + for _ = 1 to n do s := " " ^ !s done ; + !s + in + let num = ref 0 in + let loc_to_string lt = + incr num ; + let loc = lt.Errlog.lt_loc in + let level = lt.Errlog.lt_level in + let description = lt.Errlog.lt_description in + let code = + match Printer.LineReader.from_loc linereader loc with Some s -> s | None -> "" + in + let line = + let pp fmt = + if description <> "" then + F.fprintf fmt "%s%4s // %s@\n" (indent_string (level + indent_num)) " " description ; + F.fprintf fmt "%s%04d: %s" (indent_string (level + indent_num)) loc.Location.line code + in + F.asprintf "%t" pp + in + res := line :: "" :: !res + in + List.iter ~f:loc_to_string ltr ; + List.rev !res + + let process_err_log error_filter linereader err_log stats = + let found_errors = ref false in + let process_row (key: Errlog.err_key) (err_data: Errlog.err_data) = + let type_str = Localise.to_issue_id key.err_name in + if key.in_footprint && error_filter key.err_desc key.err_name then + match key.err_kind with + | Exceptions.Kerror + -> found_errors := true ; + stats.nerrors <- stats.nerrors + 1 ; + let error_strs = + let pp1 fmt = F.fprintf fmt "%d: %s" stats.nerrors type_str in + let pp2 fmt = + F.fprintf fmt " %a:%d" SourceFile.pp err_data.loc.Location.file + err_data.loc.Location.line + in + let pp3 fmt = F.fprintf fmt " (%a)" Localise.pp_error_desc key.err_desc in + [F.asprintf "%t" pp1; F.asprintf "%t" pp2; F.asprintf "%t" pp3] + in + let trace = loc_trace_to_string_list linereader 1 err_data.loc_trace in + stats.saved_errors <- List.rev_append (error_strs @ trace @ [""]) stats.saved_errors + | Exceptions.Kwarning + -> stats.nwarnings <- stats.nwarnings + 1 + | Exceptions.Kinfo + -> stats.ninfos <- stats.ninfos + 1 + | Exceptions.Kadvice + -> stats.nadvice <- stats.nadvice + 1 + | Exceptions.Klike + -> stats.nlikes <- stats.nlikes + 1 + in + Errlog.iter process_row err_log ; !found_errors + + let process_summary error_filter summary linereader stats = + let specs = Specs.get_specs_from_payload summary in + let found_errors = + process_err_log error_filter linereader summary.Specs.attributes.ProcAttributes.err_log stats + in + let is_defective = found_errors in + let is_verified = specs <> [] && not is_defective in + let is_checked = not (is_defective || is_verified) in + let is_timeout = + match Specs.(summary.stats.stats_failure) with None | Some FKcrash _ -> false | _ -> true + in + stats.nprocs <- stats.nprocs + 1 ; + stats.nspecs <- stats.nspecs + List.length specs ; + if is_verified then stats.nverified <- stats.nverified + 1 ; + if is_checked then stats.nchecked <- stats.nchecked + 1 ; + if is_timeout then stats.ntimeouts <- stats.ntimeouts + 1 ; + if is_defective then stats.ndefective <- stats.ndefective + 1 ; + process_loc summary.Specs.attributes.ProcAttributes.loc stats + + let num_files stats = Hashtbl.length stats.files + + let pp fmt stats = + F.fprintf fmt "Files: %d@\n" (num_files stats) ; + F.fprintf fmt "Specs: %d@\n" stats.nspecs ; + F.fprintf fmt "Timeouts: %d@\n" stats.ntimeouts ; + F.fprintf fmt "Procedures: %d@\n" stats.nprocs ; + F.fprintf fmt " Verified: %d@\n" stats.nverified ; + F.fprintf fmt " Checked: %d@\n" stats.nchecked ; + F.fprintf fmt " Defective: %d@\n" stats.ndefective ; + F.fprintf fmt "Errors: %d@\n" stats.nerrors ; + F.fprintf fmt "Warnings: %d@\n" stats.nwarnings ; + F.fprintf fmt "Infos: %d@\n" stats.ninfos ; + F.fprintf fmt "@\n -------------------@\n" ; + F.fprintf fmt "@\nDetailed Errors@\n@\n" ; + List.iter ~f:(fun s -> F.fprintf fmt "%s@\n" s) (List.rev stats.saved_errors) +end + +module Report = struct + let pp_header fmt () = + F.fprintf fmt "Infer Analysis Results -- generated %a@\n@\n" Pp.current_time () ; + F.fprintf fmt "Summary Report@\n@\n" + + let pp_stats fmt stats = Stats.pp fmt stats +end + +module Summary = struct + let pp_summary_out summary = + let proc_name = Specs.get_proc_name summary in + if CLOpt.equal_command Config.command CLOpt.Report && not Config.quiet then + L.result "Procedure: %a@\n%a@." Typ.Procname.pp proc_name Specs.pp_summary_text summary + + (** Write proc summary to latex file *) + let write_summary_latex fmt summary = + let proc_name = Specs.get_proc_name summary in + Latex.pp_section fmt + ("Analysis of function " ^ Latex.convert_string (Typ.Procname.to_string proc_name)) ; + F.fprintf fmt "@[%a@]" (Specs.pp_summary_latex Black) summary + + let pp_summary_xml summary fname = + if Config.xml_specs then + let base = DB.chop_extension (DB.filename_from_string fname) in + let xml_file = DB.filename_add_suffix base ".xml" in + let specs = Specs.get_specs_from_payload summary in + if not (DB.file_exists xml_file) + || DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time xml_file + then + let xml_out = Utils.create_outfile (DB.filename_to_string xml_file) in + Utils.do_outf xml_out (fun outf -> + Dotty.print_specs_xml (Specs.get_signature summary) specs + summary.Specs.attributes.ProcAttributes.loc outf.fmt ; + Utils.close_outf outf ) + + let print_summary_dot_svg summary fname = + if Config.svg then + let base = DB.chop_extension (DB.filename_from_string fname) in + let specs = Specs.get_specs_from_payload summary in + let dot_file = DB.filename_add_suffix base ".dot" in + let svg_file = DB.filename_add_suffix base ".svg" in + if not (DB.file_exists dot_file) + || DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time dot_file + then Dotty.pp_speclist_dotty_file base specs ; + if not (DB.file_exists svg_file) + || DB.file_modified_time dot_file > DB.file_modified_time svg_file + then + ignore + (Sys.command + ( "dot -Tsvg \"" ^ DB.filename_to_string dot_file ^ "\" >\"" + ^ DB.filename_to_string svg_file ^ "\"" )) +end + +(** Categorize the preconditions of specs and print stats *) +module PreconditionStats = struct + let nr_nopres = ref 0 + + let nr_empty = ref 0 + + let nr_onlyallocation = ref 0 + + let nr_dataconstraints = ref 0 + + let do_summary proc_name summary = + let specs = Specs.get_specs_from_payload summary in + let preconditions = List.map ~f:(fun spec -> Specs.Jprop.to_prop spec.Specs.pre) specs in + match Prop.CategorizePreconditions.categorize preconditions with + | Prop.CategorizePreconditions.Empty + -> incr nr_empty ; L.result "Procedure: %a footprint:Empty@." Typ.Procname.pp proc_name + | Prop.CategorizePreconditions.OnlyAllocation + -> incr nr_onlyallocation ; + L.result "Procedure: %a footprint:OnlyAllocation@." Typ.Procname.pp proc_name + | Prop.CategorizePreconditions.NoPres + -> incr nr_nopres ; L.result "Procedure: %a footprint:NoPres@." Typ.Procname.pp proc_name + | Prop.CategorizePreconditions.DataConstraints + -> incr nr_dataconstraints ; + L.result "Procedure: %a footprint:DataConstraints@." Typ.Procname.pp proc_name + + let pp_stats () = + L.result "@.Precondition stats@." ; + L.result "Procedures with no preconditions: %d@." !nr_nopres ; + L.result "Procedures with empty precondition: %d@." !nr_empty ; + L.result "Procedures with only allocation conditions: %d@." !nr_onlyallocation ; + L.result "Procedures with data constraints: %d@." !nr_dataconstraints +end + +let error_filter filters proc_name file error_desc error_name = + let always_report () = + String.equal (Localise.error_desc_extract_tag_value error_desc "always_report") "true" + in + (Config.write_html || not (Localise.equal error_name Localise.skip_function)) + && (filters.Inferconfig.path_filter file || always_report ()) + && filters.Inferconfig.error_filter error_name && filters.Inferconfig.proc_filter proc_name + +type report_kind = Issues | Procs | Stats | Calls | Summary [@@deriving compare] + +type bug_format_kind = Json | Csv | Tests | Text | Latex [@@deriving compare] + +let pp_issues_in_format (format_kind, (outf: Utils.outfile)) = + match format_kind with + | Json + -> IssuesJson.pp_issues_of_error_log outf.fmt + | Csv + -> IssuesCsv.pp_issues_of_error_log outf.fmt + | Tests + -> failwith "Print issues as tests is not implemented" + | Text + -> IssuesTxt.pp_issues_of_error_log outf.fmt + | Latex + -> failwith "Printing issues in latex is not implemented" + +let pp_procs_in_format (format_kind, (outf: Utils.outfile)) = + match format_kind with + | Csv + -> ProcsCsv.pp_summary outf.fmt + | Json | Latex | Tests | Text + -> failwith "Printing procs in json/latex/tests/text is not implemented" + +let pp_calls_in_format (format_kind, (outf: Utils.outfile)) = + match format_kind with + | Csv + -> CallsCsv.pp_calls outf.fmt + | Json | Tests | Text | Latex + -> failwith "Printing calls in json/tests/text/latex is not implemented" + +let pp_stats_in_format (format_kind, _) = + match format_kind with + | Csv + -> Stats.process_summary + | Json | Tests | Text | Latex + -> failwith "Printing stats in json/tests/text/latex is not implemented" + +let pp_summary_in_format (format_kind, (outf: Utils.outfile)) = + match format_kind with + | Latex + -> Summary.write_summary_latex outf.fmt + | Json | Csv | Tests | Text + -> failwith "Printing summary in json/csv/tests/text is not implemented" + +let pp_issues_of_error_log error_filter linereader proc_loc_opt procname err_log bug_format_list = + let pp_issues_in_format format = + pp_issues_in_format format error_filter linereader proc_loc_opt procname err_log + in + List.iter ~f:pp_issues_in_format bug_format_list + +let pp_issues error_filter linereader summary bug_format_list = + let err_log = summary.Specs.attributes.ProcAttributes.err_log in + let procname = Specs.get_proc_name summary in + let loc = summary.Specs.attributes.ProcAttributes.loc in + pp_issues_of_error_log error_filter linereader (Some loc) procname err_log bug_format_list + +let pp_procs summary procs_format_list = + let pp_procs_in_format format = + let pp_procs = pp_procs_in_format format in + pp_procs summary + in + List.iter ~f:pp_procs_in_format procs_format_list + +let pp_calls summary calls_format_list = + let pp_calls_in_format format = + let pp_calls = pp_calls_in_format format in + pp_calls summary + in + List.iter ~f:pp_calls_in_format calls_format_list + +let pp_stats error_filter linereader summary stats stats_format_list = + let pp_stats_in_format format = + let pp_stats = pp_stats_in_format format in + pp_stats error_filter summary linereader stats + in + List.iter ~f:pp_stats_in_format stats_format_list + +let pp_summary summary fname summary_format_list = + let pp_summary_in_format format = + let pp_summary = pp_summary_in_format format in + pp_summary summary + in + List.iter ~f:pp_summary_in_format summary_format_list ; + Summary.pp_summary_out summary ; + Summary.pp_summary_xml summary fname ; + Summary.print_summary_dot_svg summary fname + +let pp_summary_by_report_kind formats_by_report_kind summary fname error_filter linereader stats + file = + let pp_summary_by_report_kind (report_kind, format_list) = + match (report_kind, format_list) with + | Issues, _ :: _ + -> pp_issues error_filter linereader summary format_list + | Procs, _ :: _ + -> pp_procs summary format_list + | Stats, _ :: _ + -> pp_stats (error_filter file) linereader summary stats format_list + | Calls, _ :: _ + -> pp_calls summary format_list + | Summary, _ + -> pp_summary summary fname format_list + | _ + -> () + in + List.iter ~f:pp_summary_by_report_kind formats_by_report_kind + +let pp_json_report_by_report_kind formats_by_report_kind fname = + match Utils.read_file fname with + | Ok report_lines + -> let pp_json_issues format_list report = + let pp_json_issue (format_kind, (outf: Utils.outfile)) = + match format_kind with + | Tests + -> pp_custom_of_report outf.fmt report Config.issues_fields + | Text + -> pp_text_of_report outf.fmt report + | Json + -> failwith "Printing issues from json does not support json output" + | Csv + -> failwith "Printing issues from json does not support csv output" + | Latex + -> failwith "Printing issues from json does not support latex output" + in + List.iter ~f:pp_json_issue format_list + in + let sorted_report = + let report = Jsonbug_j.report_of_string (String.concat ~sep:"\n" report_lines) in + List.sort ~cmp:tests_jsonbug_compare report + in + let pp_report_by_report_kind (report_kind, format_list) = + match (report_kind, format_list) with + | Issues, _ :: _ + -> pp_json_issues format_list sorted_report + | _ + -> () + in + List.iter ~f:pp_report_by_report_kind formats_by_report_kind + | Error error + -> failwithf "Error reading '%s': %s" fname error + +let pp_lint_issues_by_report_kind formats_by_report_kind error_filter linereader procname error_log = + let pp_summary_by_report_kind (report_kind, format_list) = + match (report_kind, format_list) with + | Issues, _ :: _ + -> pp_issues_of_error_log error_filter linereader None procname error_log format_list + | _ + -> () + in + List.iter ~f:pp_summary_by_report_kind formats_by_report_kind + +(** Process lint issues of a procedure *) +let pp_lint_issues filters formats_by_report_kind linereader procname error_log = + let error_filter = error_filter filters procname in + pp_lint_issues_by_report_kind formats_by_report_kind error_filter linereader procname error_log + +(** Process a summary *) +let process_summary filters formats_by_report_kind linereader stats (fname, summary) = + let file = summary.Specs.attributes.ProcAttributes.loc.Location.file in + let proc_name = Specs.get_proc_name summary in + let error_filter = error_filter filters proc_name in + let pp_simple_saved = !Config.pp_simple in + Config.pp_simple := true ; + pp_summary_by_report_kind formats_by_report_kind summary fname error_filter linereader stats file ; + if Config.precondition_stats then PreconditionStats.do_summary proc_name summary ; + Config.pp_simple := pp_simple_saved + +module AnalysisResults = struct + type t = (string * Specs.summary) list + + let spec_files_from_cmdline () = + if CLOpt.is_originator then ( + (* Find spec files specified by command-line arguments. Not run at init time since the specs + files may be generated between init and report time. *) + List.iter + ~f:(fun arg -> + if not (Filename.check_suffix arg Config.specs_files_suffix) && arg <> "." then + print_usage_exit ("file " ^ arg ^ ": arguments must be .specs files")) + Config.anon_args ; + if Config.test_filtering then ( Inferconfig.test () ; exit 0 ) ; + if List.is_empty Config.anon_args then load_specfiles () else List.rev Config.anon_args ) + else load_specfiles () + + (** apply [f] to [arg] with the gc compaction disabled during the execution *) + let apply_without_gc f arg = + let stat = Gc.get () in + let space_oh = stat.space_overhead in + Gc.set {stat with space_overhead= 10000} ; + let res = f arg in + Gc.set {stat with space_overhead= space_oh} ; + res + + (** Load .specs files in memory and return list of summaries *) + let load_summaries_in_memory () : t = + let summaries = ref [] in + let load_file fname = + match Specs.load_summary (DB.filename_from_string fname) with + | None + -> L.user_error "Error: cannot open file %s@." fname ; exit 1 + | Some summary + -> summaries := (fname, summary) :: !summaries + in + apply_without_gc (List.iter ~f:load_file) (spec_files_from_cmdline ()) ; + let summ_cmp (_, summ1) (_, summ2) = + let n = + SourceFile.compare summ1.Specs.attributes.ProcAttributes.loc.Location.file + summ2.Specs.attributes.ProcAttributes.loc.Location.file + in + if n <> 0 then n + else + Int.compare summ1.Specs.attributes.ProcAttributes.loc.Location.line + summ2.Specs.attributes.ProcAttributes.loc.Location.line + in + List.sort ~cmp:summ_cmp !summaries + + (** Create an iterator which loads spec files one at a time *) + let iterator_of_spec_files () = + let sorted_spec_files = List.sort ~cmp:String.compare (spec_files_from_cmdline ()) in + let do_spec f fname = + match Specs.load_summary (DB.filename_from_string fname) with + | None + -> L.user_error "Error: cannot open file %s@." fname ; exit 1 + | Some summary + -> f (fname, summary) + in + let iterate f = List.iter ~f:(do_spec f) sorted_spec_files in + iterate + + (** Serializer for analysis results *) + let analysis_results_serializer : t Serialization.serializer = + Serialization.create_serializer Serialization.Key.analysis_results + + (** Load analysis_results from a file *) + let load_analysis_results_from_file (filename: DB.filename) : t option = + Serialization.read_from_file analysis_results_serializer filename + + (** Save analysis_results into a file *) + let store_analysis_results_to_file (filename: DB.filename) (analysis_results: t) = + Serialization.write_to_file analysis_results_serializer filename ~data:analysis_results + + (** Return an iterator over all the summaries. + If options - load_results or - save_results are used, + all the summaries are loaded in memory *) + let get_summary_iterator () = + let iterator_of_summary_list r f = List.iter ~f r in + match Config.load_analysis_results with + | None -> ( + match Config.save_analysis_results with + | None + -> iterator_of_spec_files () + | Some s + -> let r = load_summaries_in_memory () in + store_analysis_results_to_file (DB.filename_from_string s) r ; + iterator_of_summary_list r ) + | Some fname -> + match load_analysis_results_from_file (DB.filename_from_string fname) with + | Some r + -> iterator_of_summary_list r + | None + -> L.user_error "Error: cannot open analysis results file %s@." fname ; exit 1 +end + +let register_perf_stats_report () = + let stats_dir = Filename.concat Config.results_dir Config.reporting_stats_dir_name in + let stats_file = Filename.concat stats_dir (Config.perf_stats_prefix ^ ".json") in + PerfStats.register_report_at_exit stats_file + +let mk_format format_kind fname = + Option.value_map + ~f:(fun out_file -> [(format_kind, out_file)]) + ~default:[] (Utils.create_outfile fname) + +let init_issues_format_list report_csv report_json = + let csv_format = Option.value_map ~f:(mk_format Csv) ~default:[] report_csv in + let json_format = Option.value_map ~f:(mk_format Json) ~default:[] report_json in + let tests_format = Option.value_map ~f:(mk_format Tests) ~default:[] Config.bugs_tests in + let txt_format = Option.value_map ~f:(mk_format Text) ~default:[] Config.bugs_txt in + csv_format @ json_format @ tests_format @ txt_format + +let init_procs_format_list () = Option.value_map ~f:(mk_format Csv) ~default:[] Config.procs_csv + +let init_calls_format_list () = + let csv_format = Option.value_map ~f:(mk_format Csv) ~default:[] Config.calls_csv in + csv_format + +let init_stats_format_list () = + let csv_format = Option.value_map ~f:(mk_format Csv) ~default:[] Config.stats_report in + csv_format + +let init_summary_format_list () = + let latex_format = Option.value_map ~f:(mk_format Latex) ~default:[] Config.latex in + latex_format + +let init_files format_list_by_kind = + let init_files_of_report_kind (report_kind, format_list) = + let init_files_of_format (format_kind, (outfile: Utils.outfile)) = + match (format_kind, report_kind) with + | Csv, Issues + -> IssuesCsv.pp_header outfile.fmt () + | Csv, Procs + -> ProcsCsv.pp_header outfile.fmt () + | Csv, Stats + -> Report.pp_header outfile.fmt () + | Json, Issues + -> IssuesJson.pp_json_open outfile.fmt () + | Latex, Summary + -> begin_latex_file outfile.fmt + | (Csv | Json | Latex | Tests | Text), _ + -> () + in + List.iter ~f:init_files_of_format format_list + in + List.iter ~f:init_files_of_report_kind format_list_by_kind + +let finalize_and_close_files format_list_by_kind stats pdflatex = + let close_files_of_report_kind (report_kind, format_list) = + let close_files_of_format (format_kind, (outfile: Utils.outfile)) = + ( match (format_kind, report_kind) with + | Csv, Stats + -> F.fprintf outfile.fmt "%a@?" Report.pp_stats stats + | Json, Issues + -> IssuesJson.pp_json_close outfile.fmt () + | Latex, Summary + -> Latex.pp_end outfile.fmt () + | (Csv | Latex | Tests | Text | Json), _ + -> () ) ; + Utils.close_outf outfile ; + (* bug_format_kind report_kind *) + if [%compare.equal : bug_format_kind * report_kind] + (format_kind, report_kind) (Latex, Summary) + then ( + pdflatex outfile.fname ; + let pdf_name = Filename.chop_extension outfile.fname ^ ".pdf" in + ignore (Sys.command ("open " ^ pdf_name)) ) + in + List.iter ~f:close_files_of_format format_list ; () + in + List.iter ~f:close_files_of_report_kind format_list_by_kind + +let pp_summary_and_issues formats_by_report_kind = + let pdflatex fname = ignore (Sys.command ("pdflatex " ^ fname)) in + let stats = Stats.create () in + let linereader = Printer.LineReader.create () in + let filters = Inferconfig.create_filters Config.analyzer in + let iterate_summaries = AnalysisResults.get_summary_iterator () in + iterate_summaries (process_summary filters formats_by_report_kind linereader stats) ; + if Config.precondition_stats then PreconditionStats.pp_stats () ; + LintIssues.load_issues_to_errlog_map Config.lint_issues_dir_name ; + Typ.Procname.Map.iter (pp_lint_issues filters formats_by_report_kind linereader) + !LintIssues.errLogMap ; + finalize_and_close_files formats_by_report_kind stats pdflatex + +let print_issues formats_by_report_kind = + init_files formats_by_report_kind ; + match Config.from_json_report with + | Some fname + -> pp_json_report_by_report_kind formats_by_report_kind fname + | None + -> pp_summary_and_issues formats_by_report_kind + +let main ~report_csv ~report_json = + let formats_by_report_kind = + [ (Issues, init_issues_format_list report_csv report_json) + ; (Procs, init_procs_format_list ()) + ; (Calls, init_calls_format_list ()) + ; (Stats, init_stats_format_list ()) + ; (Summary, init_summary_format_list ()) ] + in + register_perf_stats_report () ; print_issues formats_by_report_kind + +let main_from_config () = main ~report_csv:Config.bugs_csv ~report_json:Config.bugs_json diff --git a/infer/src/backend/InferPrint.rei b/infer/src/backend/InferPrint.mli similarity index 65% rename from infer/src/backend/InferPrint.rei rename to infer/src/backend/InferPrint.mli index d94180fc1..1223bc30b 100644 --- a/infer/src/backend/InferPrint.rei +++ b/infer/src/backend/InferPrint.mli @@ -1,15 +1,16 @@ -/* +(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) -let exception_value: string; +open! IStd -let main: report_csv::option string => report_json::option string => unit; +val exception_value : string -let main_from_config: unit => unit; +val main : report_csv:string option -> report_json:string option -> unit + +val main_from_config : unit -> unit diff --git a/infer/src/backend/InferPrint.re b/infer/src/backend/InferPrint.re deleted file mode 100644 index e62e4af71..000000000 --- a/infer/src/backend/InferPrint.re +++ /dev/null @@ -1,1300 +0,0 @@ -/* - * Copyright (c) 2009 - 2013 Monoidics ltd. - * Copyright (c) 2013 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module CLOpt = CommandLineOption; - -module Hashtbl = Caml.Hashtbl; - -module L = Logging; - -module F = Format; - -let print_usage_exit err_s => { - L.user_error "Load Error: %s@\n@." err_s; - Config.print_usage_exit () -}; - - -/** return the list of the .specs files in the results dir and libs, if they're defined */ -let load_specfiles () => { - let specs_files_in_dir dir => { - let is_specs_file fname => - Sys.is_directory fname != `Yes && Filename.check_suffix fname Config.specs_files_suffix; - let all_filenames = - try (Array.to_list (Sys.readdir dir)) { - | Sys_error _ => [] - }; - let all_filepaths = List.map f::(fun fname => Filename.concat dir fname) all_filenames; - List.filter f::is_specs_file all_filepaths - }; - let result_specs_dir = DB.filename_to_string DB.Results_dir.specs_dir; - specs_files_in_dir result_specs_dir -}; - - -/** Create and initialize latex file */ -let begin_latex_file fmt => { - let author = "Infer " ^ Version.versionString; - let title = "Report on Analysis Results"; - let table_of_contents = true; - Latex.pp_begin fmt (author, title, table_of_contents) -}; - -let error_desc_to_csv_string error_desc => { - let pp fmt => F.fprintf fmt "%a" Localise.pp_error_desc error_desc; - Escape.escape_csv (F.asprintf "%t" pp) -}; - -let error_advice_to_csv_string error_desc => { - let pp fmt => F.fprintf fmt "%a" Localise.pp_error_advice error_desc; - Escape.escape_csv (F.asprintf "%t" pp) -}; - -let error_desc_to_plain_string error_desc => { - let pp fmt => F.fprintf fmt "%a" Localise.pp_error_desc error_desc; - let s = F.asprintf "%t" pp; - let s = String.strip s; - let s = - /* end error description with a dot */ - if (String.is_suffix suffix::"." s) { - s - } else { - s ^ "." - }; - s -}; - -let error_desc_to_dotty_string error_desc => Localise.error_desc_get_dotty error_desc; - -let error_desc_to_xml_tags error_desc => { - let tags = Localise.error_desc_get_tags error_desc; - let subtree label contents => Io_infer.Xml.create_tree label [] [Io_infer.Xml.String contents]; - List.map f::(fun (tag, value) => subtree tag (Escape.escape_xml value)) tags -}; - -let get_bug_hash - (kind: string) - (type_str: string) - (procedure_id: string) - (filename: string) - (node_key: int) - (error_desc: Localise.error_desc) => { - let qualifier_tag_call_procedure = Localise.error_desc_get_tag_call_procedure error_desc; - let qualifier_tag_value = Localise.error_desc_get_tag_value error_desc; - Hashtbl.hash ( - kind, - type_str, - procedure_id, - filename, - node_key, - qualifier_tag_call_procedure, - qualifier_tag_value - ) -}; - -let exception_value = "exception"; - -let loc_trace_to_jsonbug_record trace_list ekind => - switch ekind { - | Exceptions.Kinfo => [] - | _ => - let tag_value_records_of_node_tag nt => - switch nt { - | Errlog.Condition cond => [ - {Jsonbug_j.tag: Io_infer.Xml.tag_kind, value: "condition"}, - {Jsonbug_j.tag: Io_infer.Xml.tag_branch, value: Printf.sprintf "%B" cond} - ] - | Errlog.Exception exn_name => - let res = [{Jsonbug_j.tag: Io_infer.Xml.tag_kind, value: exception_value}]; - let exn_str = Typ.Name.name exn_name; - if (String.is_empty exn_str) { - res - } else { - [{Jsonbug_j.tag: Io_infer.Xml.tag_name, value: exn_str}, ...res] - } - | Errlog.Procedure_start pname => [ - {Jsonbug_j.tag: Io_infer.Xml.tag_kind, value: "procedure_start"}, - {Jsonbug_j.tag: Io_infer.Xml.tag_name, value: Typ.Procname.to_string pname}, - {Jsonbug_j.tag: Io_infer.Xml.tag_name_id, value: Typ.Procname.to_filename pname} - ] - | Errlog.Procedure_end pname => [ - {Jsonbug_j.tag: Io_infer.Xml.tag_kind, value: "procedure_end"}, - {Jsonbug_j.tag: Io_infer.Xml.tag_name, value: Typ.Procname.to_string pname}, - {Jsonbug_j.tag: Io_infer.Xml.tag_name_id, value: Typ.Procname.to_filename pname} - ] - }; - let trace_item_to_record trace_item => { - Jsonbug_j.level: trace_item.Errlog.lt_level, - filename: SourceFile.to_string trace_item.Errlog.lt_loc.Location.file, - line_number: trace_item.Errlog.lt_loc.Location.line, - column_number: trace_item.Errlog.lt_loc.Location.col, - description: trace_item.Errlog.lt_description, - node_tags: List.concat_map f::tag_value_records_of_node_tag trace_item.Errlog.lt_node_tags - }; - let record_list = List.rev (List.rev_map f::trace_item_to_record trace_list); - record_list - }; - -type summary_val = { - vname: string, - vname_id: string, - vspecs: int, - vto: string, - vsymop: int, - verr: int, - vfile: string, - vflags: ProcAttributes.proc_flags, - vline: int, - vsignature: string, - vweight: int, - vproof_coverage: string, - vproof_trace: string -}; - - -/** compute values from summary data to export to csv format */ -let summary_values summary => { - let stats = summary.Specs.stats; - let attributes = summary.Specs.attributes; - let err_log = attributes.ProcAttributes.err_log; - let proc_name = Specs.get_proc_name summary; - let signature = Specs.get_signature summary; - let nodes_nr = List.length summary.Specs.nodes; - let specs = Specs.get_specs_from_payload summary; - let (nr_nodes_visited, lines_visited) = { - let visited = ref Specs.Visitedset.empty; - let do_spec spec => visited := Specs.Visitedset.union spec.Specs.visited !visited; - List.iter f::do_spec specs; - let visited_lines = ref Int.Set.empty; - Specs.Visitedset.iter - (fun (_, ls) => List.iter f::(fun l => visited_lines := Int.Set.add !visited_lines l) ls) - !visited; - (Specs.Visitedset.cardinal !visited, Int.Set.elements !visited_lines) - }; - let proof_trace = { - let pp_line fmt l => F.fprintf fmt "%d" l; - let pp fmt => F.fprintf fmt "%a" (Pp.seq pp_line) lines_visited; - F.asprintf "%t" pp - }; - let node_coverage = - if (Int.equal nodes_nr 0) { - 0.0 - } else { - float_of_int nr_nodes_visited /. float_of_int nodes_nr - }; - let pp_failure failure => F.asprintf "%a" SymOp.pp_failure_kind failure; - { - vname: Typ.Procname.to_string proc_name, - vname_id: Typ.Procname.to_filename proc_name, - vspecs: List.length specs, - vto: Option.value_map f::pp_failure default::"NONE" stats.Specs.stats_failure, - vsymop: stats.Specs.symops, - verr: - Errlog.size - ( - fun ekind in_footprint => - Exceptions.equal_err_kind ekind Exceptions.Kerror && in_footprint - ) - err_log, - vflags: attributes.ProcAttributes.proc_flags, - vfile: SourceFile.to_string attributes.ProcAttributes.loc.Location.file, - vline: attributes.ProcAttributes.loc.Location.line, - vsignature: signature, - vweight: nodes_nr, - vproof_coverage: Printf.sprintf "%2.2f" node_coverage, - vproof_trace: proof_trace - } -}; - -module ProcsCsv = { - - /** Print the header of the procedures csv file, with column names */ - let pp_header fmt () => - Format.fprintf - fmt - "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" - Io_infer.Xml.tag_name - Io_infer.Xml.tag_name_id - Io_infer.Xml.tag_specs - Io_infer.Xml.tag_time - Io_infer.Xml.tag_to - Io_infer.Xml.tag_symop - Io_infer.Xml.tag_err - Io_infer.Xml.tag_file - Io_infer.Xml.tag_line - Io_infer.Xml.tag_loc - Io_infer.Xml.tag_top - Io_infer.Xml.tag_signature - Io_infer.Xml.tag_weight - Io_infer.Xml.tag_proof_coverage - Io_infer.Xml.tag_rank - Io_infer.Xml.tag_in_calls - Io_infer.Xml.tag_out_calls - Io_infer.Xml.tag_proof_trace; - - /** Write proc summary stats in csv format */ - let pp_summary fmt summary => { - let pp x => F.fprintf fmt x; - let sv = summary_values summary; - pp "\"%s\"," (Escape.escape_csv sv.vname); - pp "\"%s\"," (Escape.escape_csv sv.vname_id); - pp "%d," sv.vspecs; - pp "%s," sv.vto; - pp "%d," sv.vsymop; - pp "%d," sv.verr; - pp "%s," sv.vfile; - pp "%d," sv.vline; - pp "\"%s\"," (Escape.escape_csv sv.vsignature); - pp "%d," sv.vweight; - pp "%s," sv.vproof_coverage; - pp "%s@\n" sv.vproof_trace - }; -}; - -let should_report (issue_kind: Exceptions.err_kind) issue_type error_desc eclass => - if (not Config.filtering || Exceptions.equal_err_class eclass Exceptions.Linters) { - true - } else { - let analyzer_is_whitelisted = - switch Config.analyzer { - | Eradicate => true - | BiAbduction - | CaptureOnly - | Checkers - | CompileOnly - | Crashcontext - | Linters => false - }; - if analyzer_is_whitelisted { - true - } else { - let issue_kind_is_blacklisted = - switch issue_kind { - | Kinfo => true - | Kerror - | Kwarning - | Kadvice - | Klike => false - }; - if issue_kind_is_blacklisted { - false - } else { - let issue_type_is_null_deref = { - let null_deref_issue_types = - Localise.[ - field_not_null_checked, - null_dereference, - parameter_not_null_checked, - premature_nil_termination, - empty_vector_access - ]; - List.mem equal::Localise.equal null_deref_issue_types issue_type - }; - let issue_type_is_buffer_overrun = Localise.equal issue_type Localise.buffer_overrun; - if (issue_type_is_null_deref || issue_type_is_buffer_overrun) { - let issue_bucket_is_high = { - let issue_bucket = Localise.error_desc_get_bucket error_desc; - let high_buckets = Localise.BucketLevel.[b1, b2]; - Option.value_map - issue_bucket default::false f::(fun b => List.mem equal::String.equal high_buckets b) - }; - issue_bucket_is_high - } else { - true - } - } - } - }; - -module IssuesCsv = { - let csv_issues_id = ref 0; - let pp_header fmt () => - Format.fprintf - fmt - "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" - Io_infer.Xml.tag_class - Io_infer.Xml.tag_kind - Io_infer.Xml.tag_type - Io_infer.Xml.tag_qualifier - Io_infer.Xml.tag_severity - Io_infer.Xml.tag_line - Io_infer.Xml.tag_procedure - Io_infer.Xml.tag_procedure_id - Io_infer.Xml.tag_file - Io_infer.Xml.tag_trace - Io_infer.Xml.tag_key - Io_infer.Xml.tag_qualifier_tags - Io_infer.Xml.tag_hash - "bug_id" - "always_report" - "advice"; - - /** Write bug report in csv format */ - let pp_issues_of_error_log fmt error_filter _ proc_loc_opt procname err_log => { - let pp x => F.fprintf fmt x; - let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) => { - let source_file = - switch proc_loc_opt { - | Some proc_loc => proc_loc.Location.file - | None => err_data.loc.Location.file - }; - if ( - key.in_footprint && - error_filter source_file key.err_desc key.err_name && - should_report key.err_kind key.err_name key.err_desc err_data.err_class - ) { - let err_desc_string = error_desc_to_csv_string key.err_desc; - let err_advice_string = error_advice_to_csv_string key.err_desc; - let qualifier_tag_xml = { - let xml_node = - Io_infer.Xml.create_tree - Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags key.err_desc); - let p fmt => F.fprintf fmt "%a" (Io_infer.Xml.pp_document false) xml_node; - let s = F.asprintf "%t" p; - Escape.escape_csv s - }; - let kind = Exceptions.err_kind_string key.err_kind; - let type_str = Localise.to_issue_id key.err_name; - let procedure_id = Typ.Procname.to_filename procname; - let filename = SourceFile.to_string source_file; - let always_report = - switch (Localise.error_desc_extract_tag_value key.err_desc "always_report") { - | "" => "false" - | v => v - }; - let trace = - Jsonbug_j.string_of_json_trace { - trace: loc_trace_to_jsonbug_record err_data.loc_trace key.err_kind - }; - incr csv_issues_id; - pp "%s," (Exceptions.err_class_string err_data.err_class); - pp "%s," kind; - pp "%s," type_str; - pp "\"%s\"," err_desc_string; - pp "%s," key.severity; - pp "%d," err_data.loc.Location.line; - pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string procname)); - pp "\"%s\"," (Escape.escape_csv procedure_id); - pp "%s," filename; - pp "\"%s\"," (Escape.escape_csv trace); - pp "\"%d\"," err_data.node_id_key.node_key; - pp "\"%s\"," qualifier_tag_xml; - pp - "\"%d\"," - ( - get_bug_hash - kind type_str procedure_id filename err_data.node_id_key.node_key key.err_desc - ); - pp "\"%d\"," !csv_issues_id; /* bug id */ - pp "\"%s\"," always_report; - pp "\"%s\"@\n" err_advice_string - } - }; - Errlog.iter pp_row err_log - }; -}; - -let potential_exception_message = "potential exception at line"; - -module IssuesJson = { - let is_first_item = ref true; - let pp_json_open fmt () => F.fprintf fmt "[@?"; - let pp_json_close fmt () => F.fprintf fmt "]@\n@?"; - - /** Write bug report in JSON format */ - let pp_issues_of_error_log fmt error_filter _ proc_loc_opt procname err_log => { - let pp x => F.fprintf fmt x; - let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) => { - let (source_file, procedure_start_line) = - switch proc_loc_opt { - | Some proc_loc => (proc_loc.Location.file, proc_loc.Location.line) - | None => (err_data.loc.Location.file, 0) - }; - if (SourceFile.is_invalid source_file) { - failwithf - "Invalid source file for %a %a@.Trace: %a@." - Localise.pp - key.err_name - Localise.pp_error_desc - key.err_desc - Errlog.pp_loc_trace - err_data.loc_trace - }; - let should_report_source_file = - not (SourceFile.is_infer_model source_file) || - Config.debug_mode || Config.debug_exceptions; - if ( - key.in_footprint && - error_filter source_file key.err_desc key.err_name && - should_report_source_file && - should_report key.err_kind key.err_name key.err_desc err_data.err_class - ) { - let kind = Exceptions.err_kind_string key.err_kind; - let bug_type = Localise.to_issue_id key.err_name; - let procedure_id = Typ.Procname.to_filename procname; - let file = SourceFile.to_string source_file; - let json_ml_loc = - switch err_data.loc_in_ml_source { - | Some (file, lnum, cnum, enum) when Config.reports_include_ml_loc => - Some Jsonbug_j.{file, lnum, cnum, enum} - | _ => None - }; - let visibility = Exceptions.string_of_visibility err_data.visibility; - let qualifier = { - let base_qualifier = error_desc_to_plain_string key.err_desc; - if (Localise.equal key.err_name Localise.resource_leak) { - switch (Errlog.compute_local_exception_line err_data.loc_trace) { - | None => base_qualifier - | Some line => - let potential_exception_message = - Format.asprintf - "%a: %s %d" MarkupFormatter.pp_bold "Note" potential_exception_message line; - Format.sprintf "%s@\n%s" base_qualifier potential_exception_message - } - } else { - base_qualifier - } - }; - let bug = { - Jsonbug_j.bug_class: Exceptions.err_class_string err_data.err_class, - kind, - bug_type, - qualifier, - severity: key.severity, - visibility, - line: err_data.loc.Location.line, - column: err_data.loc.Location.col, - procedure: Typ.Procname.to_string procname, - procedure_id, - procedure_start_line, - file, - bug_trace: loc_trace_to_jsonbug_record err_data.loc_trace key.err_kind, - key: err_data.node_id_key.node_key, - qualifier_tags: Localise.Tags.tag_value_records_of_tags key.err_desc.tags, - hash: - get_bug_hash kind bug_type procedure_id file err_data.node_id_key.node_key key.err_desc, - dotty: error_desc_to_dotty_string key.err_desc, - infer_source_loc: json_ml_loc, - bug_type_hum: Localise.to_human_readable_string key.err_name, - linters_def_file: err_data.linters_def_file, - doc_url: err_data.doc_url, - traceview_id: None - }; - if (not !is_first_item) { - pp "," - } else { - is_first_item := false - }; - pp "%s@?" (Jsonbug_j.string_of_jsonbug bug) - } - }; - Errlog.iter pp_row err_log - }; -}; - -let pp_custom_of_report fmt report fields => { - let pp_custom_of_issue fmt issue => { - open Jsonbug_t; - let comma_separator index => - if (index > 0) { - ", " - } else { - "" - }; - let pp_trace fmt trace comma => { - let pp_trace_elem fmt {description} => F.fprintf fmt "%s" description; - let trace_without_empty_descs = - List.filter f::(fun {description} => description != "") trace; - F.fprintf fmt "%s[%a]" comma (Pp.comma_seq pp_trace_elem) trace_without_empty_descs - }; - let pp_field index field => - switch field { - | `Issue_field_bug_class => Format.fprintf fmt "%s%s" (comma_separator index) issue.bug_class - | `Issue_field_kind => Format.fprintf fmt "%s%s" (comma_separator index) issue.kind - | `Issue_field_bug_type => Format.fprintf fmt "%s%s" (comma_separator index) issue.bug_type - | `Issue_field_qualifier => Format.fprintf fmt "%s%s" (comma_separator index) issue.qualifier - | `Issue_field_severity => Format.fprintf fmt "%s%s" (comma_separator index) issue.severity - | `Issue_field_visibility => - Format.fprintf fmt "%s%s" (comma_separator index) issue.visibility - | `Issue_field_line => Format.fprintf fmt "%s%d" (comma_separator index) issue.line - | `Issue_field_column => Format.fprintf fmt "%s%d" (comma_separator index) issue.column - | `Issue_field_procedure => Format.fprintf fmt "%s%s" (comma_separator index) issue.procedure - | `Issue_field_procedure_id => - Format.fprintf fmt "%s%s" (comma_separator index) issue.procedure_id - | `Issue_field_procedure_start_line => - Format.fprintf fmt "%s%d" (comma_separator index) issue.procedure_start_line - | `Issue_field_file => Format.fprintf fmt "%s%s" (comma_separator index) issue.file - | `Issue_field_bug_trace => pp_trace fmt issue.bug_trace (comma_separator index) - | `Issue_field_key => Format.fprintf fmt "%s%d" (comma_separator index) issue.key - | `Issue_field_hash => Format.fprintf fmt "%s%d" (comma_separator index) issue.hash - | `Issue_field_line_offset => - Format.fprintf fmt "%s%d" (comma_separator index) (issue.line - issue.procedure_start_line) - | `Issue_field_procedure_id_without_crc => - Format.fprintf fmt "%s%s" (comma_separator index) (DB.strip_crc issue.procedure_id) - | `Issue_field_qualifier_contains_potential_exception_note => - Format.fprintf - fmt "%B" (String.is_substring issue.qualifier substring::potential_exception_message) - }; - List.iteri f::pp_field fields; - Format.fprintf fmt "@." - }; - List.iter f::(pp_custom_of_issue fmt) report -}; - -let tests_jsonbug_compare bug1 bug2 => - Jsonbug_t.( - [%compare : (string, string, int, string, int)] - (bug1.file, bug1.procedure, bug1.line - bug1.procedure_start_line, bug1.bug_type, bug1.hash) - (bug2.file, bug2.procedure, bug2.line - bug2.procedure_start_line, bug2.bug_type, bug2.hash) - ); - -module IssuesTxt = { - - /** Write bug report in text format */ - let pp_issues_of_error_log fmt error_filter _ proc_loc_opt _ err_log => { - let pp_row (key: Errlog.err_key) (err_data: Errlog.err_data) => { - let source_file = - switch proc_loc_opt { - | Some proc_loc => proc_loc.Location.file - | None => err_data.loc.Location.file - }; - if (key.in_footprint && error_filter source_file key.err_desc key.err_name) { - Exceptions.pp_err - node_key::err_data.node_id_key.node_key - err_data.loc - key.err_kind - key.err_name - key.err_desc - None - fmt - () - } - }; - Errlog.iter pp_row err_log - }; -}; - -let pp_text_of_report fmt report => { - let pp_row jsonbug => - Jsonbug_t.( - F.fprintf - fmt - "%s:%d: %s: %s %s@\n" - jsonbug.file - jsonbug.line - jsonbug.kind - jsonbug.bug_type - jsonbug.qualifier - ); - List.iter f::pp_row report -}; - -module CallsCsv = { - - /** Write proc summary stats in csv format */ - let pp_calls fmt summary => { - let pp x => F.fprintf fmt x; - let stats = summary.Specs.stats; - let caller_name = Specs.get_proc_name summary; - let do_call (callee_name, loc) trace => { - pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string caller_name)); - pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_filename caller_name)); - pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_string callee_name)); - pp "\"%s\"," (Escape.escape_csv (Typ.Procname.to_filename callee_name)); - pp "%s," (SourceFile.to_string summary.Specs.attributes.ProcAttributes.loc.Location.file); - pp "%d," loc.Location.line; - pp "%a@\n" Specs.CallStats.pp_trace trace - }; - Specs.CallStats.iter do_call stats.Specs.call_stats - }; -}; - -module Stats = { - type t = { - files: Hashtbl.t SourceFile.t unit, - mutable nchecked: int, - mutable ndefective: int, - mutable nerrors: int, - mutable ninfos: int, - mutable nadvice: int, - mutable nlikes: int, - mutable nprocs: int, - mutable nspecs: int, - mutable ntimeouts: int, - mutable nverified: int, - mutable nwarnings: int, - mutable saved_errors: list string - }; - let create () => { - files: Hashtbl.create 3, - nchecked: 0, - ndefective: 0, - nerrors: 0, - ninfos: 0, - nadvice: 0, - nlikes: 0, - nprocs: 0, - nspecs: 0, - ntimeouts: 0, - nverified: 0, - nwarnings: 0, - saved_errors: [] - }; - let process_loc loc stats => - try (Hashtbl.find stats.files loc.Location.file) { - | Not_found => Hashtbl.add stats.files loc.Location.file () - }; - let loc_trace_to_string_list linereader indent_num ltr => { - let res = ref []; - let indent_string n => { - let s = ref ""; - for _ in 1 to n { - s := " " ^ !s - }; - !s - }; - let num = ref 0; - let loc_to_string lt => { - incr num; - let loc = lt.Errlog.lt_loc; - let level = lt.Errlog.lt_level; - let description = lt.Errlog.lt_description; - let code = - switch (Printer.LineReader.from_loc linereader loc) { - | Some s => s - | None => "" - }; - let line = { - let pp fmt => { - if (description != "") { - F.fprintf fmt "%s%4s // %s@\n" (indent_string (level + indent_num)) " " description - }; - F.fprintf fmt "%s%04d: %s" (indent_string (level + indent_num)) loc.Location.line code - }; - F.asprintf "%t" pp - }; - res := [line, "", ...!res] - }; - List.iter f::loc_to_string ltr; - List.rev !res - }; - let process_err_log error_filter linereader err_log stats => { - let found_errors = ref false; - let process_row (key: Errlog.err_key) (err_data: Errlog.err_data) => { - let type_str = Localise.to_issue_id key.err_name; - if (key.in_footprint && error_filter key.err_desc key.err_name) { - switch key.err_kind { - | Exceptions.Kerror => - found_errors := true; - stats.nerrors = stats.nerrors + 1; - let error_strs = { - let pp1 fmt => F.fprintf fmt "%d: %s" stats.nerrors type_str; - let pp2 fmt => - F.fprintf - fmt " %a:%d" SourceFile.pp err_data.loc.Location.file err_data.loc.Location.line; - let pp3 fmt => F.fprintf fmt " (%a)" Localise.pp_error_desc key.err_desc; - [F.asprintf "%t" pp1, F.asprintf "%t" pp2, F.asprintf "%t" pp3] - }; - let trace = loc_trace_to_string_list linereader 1 err_data.loc_trace; - stats.saved_errors = List.rev_append (error_strs @ trace @ [""]) stats.saved_errors - | Exceptions.Kwarning => stats.nwarnings = stats.nwarnings + 1 - | Exceptions.Kinfo => stats.ninfos = stats.ninfos + 1 - | Exceptions.Kadvice => stats.nadvice = stats.nadvice + 1 - | Exceptions.Klike => stats.nlikes = stats.nlikes + 1 - } - } - }; - Errlog.iter process_row err_log; - !found_errors - }; - let process_summary error_filter summary linereader stats => { - let specs = Specs.get_specs_from_payload summary; - let found_errors = - process_err_log - error_filter linereader summary.Specs.attributes.ProcAttributes.err_log stats; - let is_defective = found_errors; - let is_verified = specs != [] && not is_defective; - let is_checked = not (is_defective || is_verified); - let is_timeout = - switch Specs.(summary.stats.stats_failure) { - | None - | Some (FKcrash _) => false - | _ => true - }; - stats.nprocs = stats.nprocs + 1; - stats.nspecs = stats.nspecs + List.length specs; - if is_verified { - stats.nverified = stats.nverified + 1 - }; - if is_checked { - stats.nchecked = stats.nchecked + 1 - }; - if is_timeout { - stats.ntimeouts = stats.ntimeouts + 1 - }; - if is_defective { - stats.ndefective = stats.ndefective + 1 - }; - process_loc summary.Specs.attributes.ProcAttributes.loc stats - }; - let num_files stats => Hashtbl.length stats.files; - let pp fmt stats => { - F.fprintf fmt "Files: %d@\n" (num_files stats); - F.fprintf fmt "Specs: %d@\n" stats.nspecs; - F.fprintf fmt "Timeouts: %d@\n" stats.ntimeouts; - F.fprintf fmt "Procedures: %d@\n" stats.nprocs; - F.fprintf fmt " Verified: %d@\n" stats.nverified; - F.fprintf fmt " Checked: %d@\n" stats.nchecked; - F.fprintf fmt " Defective: %d@\n" stats.ndefective; - F.fprintf fmt "Errors: %d@\n" stats.nerrors; - F.fprintf fmt "Warnings: %d@\n" stats.nwarnings; - F.fprintf fmt "Infos: %d@\n" stats.ninfos; - F.fprintf fmt "@\n -------------------@\n"; - F.fprintf fmt "@\nDetailed Errors@\n@\n"; - List.iter f::(fun s => F.fprintf fmt "%s@\n" s) (List.rev stats.saved_errors) - }; -}; - -module Report = { - let pp_header fmt () => { - F.fprintf fmt "Infer Analysis Results -- generated %a@\n@\n" Pp.current_time (); - F.fprintf fmt "Summary Report@\n@\n" - }; - let pp_stats fmt stats => Stats.pp fmt stats; -}; - -module Summary = { - let pp_summary_out summary => { - let proc_name = Specs.get_proc_name summary; - if (CLOpt.equal_command Config.command CLOpt.Report && not Config.quiet) { - L.result "Procedure: %a@\n%a@." Typ.Procname.pp proc_name Specs.pp_summary_text summary - } - }; - - /** Write proc summary to latex file */ - let write_summary_latex fmt summary => { - let proc_name = Specs.get_proc_name summary; - Latex.pp_section - fmt ("Analysis of function " ^ Latex.convert_string (Typ.Procname.to_string proc_name)); - F.fprintf fmt "@[%a@]" (Specs.pp_summary_latex Black) summary - }; - let pp_summary_xml summary fname => - if Config.xml_specs { - let base = DB.chop_extension (DB.filename_from_string fname); - let xml_file = DB.filename_add_suffix base ".xml"; - let specs = Specs.get_specs_from_payload summary; - if ( - not (DB.file_exists xml_file) || - DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time xml_file - ) { - let xml_out = Utils.create_outfile (DB.filename_to_string xml_file); - Utils.do_outf - xml_out - ( - fun outf => { - Dotty.print_specs_xml - (Specs.get_signature summary) - specs - summary.Specs.attributes.ProcAttributes.loc - outf.fmt; - Utils.close_outf outf - } - ) - } - }; - let print_summary_dot_svg summary fname => - if Config.svg { - let base = DB.chop_extension (DB.filename_from_string fname); - let specs = Specs.get_specs_from_payload summary; - let dot_file = DB.filename_add_suffix base ".dot"; - let svg_file = DB.filename_add_suffix base ".svg"; - if ( - not (DB.file_exists dot_file) || - DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time dot_file - ) { - Dotty.pp_speclist_dotty_file base specs - }; - if ( - not (DB.file_exists svg_file) || - DB.file_modified_time dot_file > DB.file_modified_time svg_file - ) { - ignore ( - Sys.command ( - "dot -Tsvg \"" ^ - DB.filename_to_string dot_file ^ "\" >\"" ^ DB.filename_to_string svg_file ^ "\"" - ) - ) - } - }; -}; - - -/** Categorize the preconditions of specs and print stats */ -module PreconditionStats = { - let nr_nopres = ref 0; - let nr_empty = ref 0; - let nr_onlyallocation = ref 0; - let nr_dataconstraints = ref 0; - let do_summary proc_name summary => { - let specs = Specs.get_specs_from_payload summary; - let preconditions = List.map f::(fun spec => Specs.Jprop.to_prop spec.Specs.pre) specs; - switch (Prop.CategorizePreconditions.categorize preconditions) { - | Prop.CategorizePreconditions.Empty => - incr nr_empty; - L.result "Procedure: %a footprint:Empty@." Typ.Procname.pp proc_name - | Prop.CategorizePreconditions.OnlyAllocation => - incr nr_onlyallocation; - L.result "Procedure: %a footprint:OnlyAllocation@." Typ.Procname.pp proc_name - | Prop.CategorizePreconditions.NoPres => - incr nr_nopres; - L.result "Procedure: %a footprint:NoPres@." Typ.Procname.pp proc_name - | Prop.CategorizePreconditions.DataConstraints => - incr nr_dataconstraints; - L.result "Procedure: %a footprint:DataConstraints@." Typ.Procname.pp proc_name - } - }; - let pp_stats () => { - L.result "@.Precondition stats@."; - L.result "Procedures with no preconditions: %d@." !nr_nopres; - L.result "Procedures with empty precondition: %d@." !nr_empty; - L.result "Procedures with only allocation conditions: %d@." !nr_onlyallocation; - L.result "Procedures with data constraints: %d@." !nr_dataconstraints - }; -}; - -let error_filter filters proc_name file error_desc error_name => { - let always_report () => - String.equal (Localise.error_desc_extract_tag_value error_desc "always_report") "true"; - (Config.write_html || not (Localise.equal error_name Localise.skip_function)) && - (filters.Inferconfig.path_filter file || always_report ()) && - filters.Inferconfig.error_filter error_name && filters.Inferconfig.proc_filter proc_name -}; - -type report_kind = - | Issues - | Procs - | Stats - | Calls - | Summary -[@@deriving compare]; - -type bug_format_kind = - | Json - | Csv - | Tests - | Text - | Latex -[@@deriving compare]; - -let pp_issues_in_format (format_kind, outf: Utils.outfile) => - switch format_kind { - | Json => IssuesJson.pp_issues_of_error_log outf.fmt - | Csv => IssuesCsv.pp_issues_of_error_log outf.fmt - | Tests => failwith "Print issues as tests is not implemented" - | Text => IssuesTxt.pp_issues_of_error_log outf.fmt - | Latex => failwith "Printing issues in latex is not implemented" - }; - -let pp_procs_in_format (format_kind, outf: Utils.outfile) => - switch format_kind { - | Csv => ProcsCsv.pp_summary outf.fmt - | Json - | Latex - | Tests - | Text => failwith "Printing procs in json/latex/tests/text is not implemented" - }; - -let pp_calls_in_format (format_kind, outf: Utils.outfile) => - switch format_kind { - | Csv => CallsCsv.pp_calls outf.fmt - | Json - | Tests - | Text - | Latex => failwith "Printing calls in json/tests/text/latex is not implemented" - }; - -let pp_stats_in_format (format_kind, _) => - switch format_kind { - | Csv => Stats.process_summary - | Json - | Tests - | Text - | Latex => failwith "Printing stats in json/tests/text/latex is not implemented" - }; - -let pp_summary_in_format (format_kind, outf: Utils.outfile) => - switch format_kind { - | Latex => Summary.write_summary_latex outf.fmt - | Json - | Csv - | Tests - | Text => failwith "Printing summary in json/csv/tests/text is not implemented" - }; - -let pp_issues_of_error_log error_filter linereader proc_loc_opt procname err_log bug_format_list => { - let pp_issues_in_format format => - pp_issues_in_format format error_filter linereader proc_loc_opt procname err_log; - List.iter f::pp_issues_in_format bug_format_list -}; - -let pp_issues error_filter linereader summary bug_format_list => { - let err_log = summary.Specs.attributes.ProcAttributes.err_log; - let procname = Specs.get_proc_name summary; - let loc = summary.Specs.attributes.ProcAttributes.loc; - pp_issues_of_error_log error_filter linereader (Some loc) procname err_log bug_format_list -}; - -let pp_procs summary procs_format_list => { - let pp_procs_in_format format => { - let pp_procs = pp_procs_in_format format; - pp_procs summary - }; - List.iter f::pp_procs_in_format procs_format_list -}; - -let pp_calls summary calls_format_list => { - let pp_calls_in_format format => { - let pp_calls = pp_calls_in_format format; - pp_calls summary - }; - List.iter f::pp_calls_in_format calls_format_list -}; - -let pp_stats error_filter linereader summary stats stats_format_list => { - let pp_stats_in_format format => { - let pp_stats = pp_stats_in_format format; - pp_stats error_filter summary linereader stats - }; - List.iter f::pp_stats_in_format stats_format_list -}; - -let pp_summary summary fname summary_format_list => { - let pp_summary_in_format format => { - let pp_summary = pp_summary_in_format format; - pp_summary summary - }; - List.iter f::pp_summary_in_format summary_format_list; - Summary.pp_summary_out summary; - Summary.pp_summary_xml summary fname; - Summary.print_summary_dot_svg summary fname -}; - -let pp_summary_by_report_kind - formats_by_report_kind - summary - fname - error_filter - linereader - stats - file => { - let pp_summary_by_report_kind (report_kind, format_list) => - switch (report_kind, format_list) { - | (Issues, [_, ..._]) => pp_issues error_filter linereader summary format_list - | (Procs, [_, ..._]) => pp_procs summary format_list - | (Stats, [_, ..._]) => pp_stats (error_filter file) linereader summary stats format_list - | (Calls, [_, ..._]) => pp_calls summary format_list - | (Summary, _) => pp_summary summary fname format_list - | _ => () - }; - List.iter f::pp_summary_by_report_kind formats_by_report_kind -}; - -let pp_json_report_by_report_kind formats_by_report_kind fname => - switch (Utils.read_file fname) { - | Ok report_lines => - let pp_json_issues format_list report => { - let pp_json_issue (format_kind, outf: Utils.outfile) => - switch format_kind { - | Tests => pp_custom_of_report outf.fmt report Config.issues_fields - | Text => pp_text_of_report outf.fmt report - | Json => failwith "Printing issues from json does not support json output" - | Csv => failwith "Printing issues from json does not support csv output" - | Latex => failwith "Printing issues from json does not support latex output" - }; - List.iter f::pp_json_issue format_list - }; - let sorted_report = { - let report = Jsonbug_j.report_of_string (String.concat sep::"\n" report_lines); - List.sort cmp::tests_jsonbug_compare report - }; - let pp_report_by_report_kind (report_kind, format_list) => - switch (report_kind, format_list) { - | (Issues, [_, ..._]) => pp_json_issues format_list sorted_report - | _ => () - }; - List.iter f::pp_report_by_report_kind formats_by_report_kind - | Error error => failwithf "Error reading '%s': %s" fname error - }; - -let pp_lint_issues_by_report_kind formats_by_report_kind error_filter linereader procname error_log => { - let pp_summary_by_report_kind (report_kind, format_list) => - switch (report_kind, format_list) { - | (Issues, [_, ..._]) => - pp_issues_of_error_log error_filter linereader None procname error_log format_list - | _ => () - }; - List.iter f::pp_summary_by_report_kind formats_by_report_kind -}; - - -/** Process lint issues of a procedure */ -let pp_lint_issues filters formats_by_report_kind linereader procname error_log => { - let error_filter = error_filter filters procname; - pp_lint_issues_by_report_kind formats_by_report_kind error_filter linereader procname error_log -}; - - -/** Process a summary */ -let process_summary filters formats_by_report_kind linereader stats (fname, summary) => { - let file = summary.Specs.attributes.ProcAttributes.loc.Location.file; - let proc_name = Specs.get_proc_name summary; - let error_filter = error_filter filters proc_name; - let pp_simple_saved = !Config.pp_simple; - Config.pp_simple := true; - pp_summary_by_report_kind - formats_by_report_kind summary fname error_filter linereader stats file; - if Config.precondition_stats { - PreconditionStats.do_summary proc_name summary - }; - Config.pp_simple := pp_simple_saved -}; - -module AnalysisResults = { - type t = list (string, Specs.summary); - let spec_files_from_cmdline () => - if CLOpt.is_originator { - /* Find spec files specified by command-line arguments. Not run at init time since the specs - files may be generated between init and report time. */ - List.iter - f::( - fun arg => - if (not (Filename.check_suffix arg Config.specs_files_suffix) && arg != ".") { - print_usage_exit ("file " ^ arg ^ ": arguments must be .specs files") - } - ) - Config.anon_args; - if Config.test_filtering { - Inferconfig.test (); - exit 0 - }; - if (List.is_empty Config.anon_args) { - load_specfiles () - } else { - List.rev Config.anon_args - } - } else { - load_specfiles () - }; - - /** apply [f] to [arg] with the gc compaction disabled during the execution */ - let apply_without_gc f arg => { - let stat = Gc.get (); - let space_oh = stat.space_overhead; - Gc.set {...stat, space_overhead: 10000}; - let res = f arg; - Gc.set {...stat, space_overhead: space_oh}; - res - }; - - /** Load .specs files in memory and return list of summaries */ - let load_summaries_in_memory () :t => { - let summaries = ref []; - let load_file fname => - switch (Specs.load_summary (DB.filename_from_string fname)) { - | None => - L.user_error "Error: cannot open file %s@." fname; - exit 1 - | Some summary => summaries := [(fname, summary), ...!summaries] - }; - apply_without_gc (List.iter f::load_file) (spec_files_from_cmdline ()); - let summ_cmp (_, summ1) (_, summ2) => { - let n = - SourceFile.compare - summ1.Specs.attributes.ProcAttributes.loc.Location.file - summ2.Specs.attributes.ProcAttributes.loc.Location.file; - if (n != 0) { - n - } else { - Int.compare - summ1.Specs.attributes.ProcAttributes.loc.Location.line - summ2.Specs.attributes.ProcAttributes.loc.Location.line - } - }; - List.sort cmp::summ_cmp !summaries - }; - - /** Create an iterator which loads spec files one at a time */ - let iterator_of_spec_files () => { - let sorted_spec_files = List.sort cmp::String.compare (spec_files_from_cmdline ()); - let do_spec f fname => - switch (Specs.load_summary (DB.filename_from_string fname)) { - | None => - L.user_error "Error: cannot open file %s@." fname; - exit 1 - | Some summary => f (fname, summary) - }; - let iterate f => List.iter f::(do_spec f) sorted_spec_files; - iterate - }; - - /** Serializer for analysis results */ - let analysis_results_serializer: Serialization.serializer t = - Serialization.create_serializer Serialization.Key.analysis_results; - - /** Load analysis_results from a file */ - let load_analysis_results_from_file (filename: DB.filename) :option t => - Serialization.read_from_file analysis_results_serializer filename; - - /** Save analysis_results into a file */ - let store_analysis_results_to_file (filename: DB.filename) (analysis_results: t) => - Serialization.write_to_file analysis_results_serializer filename data::analysis_results; - - /** Return an iterator over all the summaries. - If options - load_results or - save_results are used, - all the summaries are loaded in memory */ - let get_summary_iterator () => { - let iterator_of_summary_list r f => List.iter ::f r; - switch Config.load_analysis_results { - | None => - switch Config.save_analysis_results { - | None => iterator_of_spec_files () - | Some s => - let r = load_summaries_in_memory (); - store_analysis_results_to_file (DB.filename_from_string s) r; - iterator_of_summary_list r - } - | Some fname => - switch (load_analysis_results_from_file (DB.filename_from_string fname)) { - | Some r => iterator_of_summary_list r - | None => - L.user_error "Error: cannot open analysis results file %s@." fname; - exit 1 - } - } - }; -}; - -let register_perf_stats_report () => { - let stats_dir = Filename.concat Config.results_dir Config.reporting_stats_dir_name; - let stats_file = Filename.concat stats_dir (Config.perf_stats_prefix ^ ".json"); - PerfStats.register_report_at_exit stats_file -}; - -let mk_format format_kind fname => - Option.value_map - f::(fun out_file => [(format_kind, out_file)]) default::[] (Utils.create_outfile fname); - -let init_issues_format_list report_csv report_json => { - let csv_format = Option.value_map f::(mk_format Csv) default::[] report_csv; - let json_format = Option.value_map f::(mk_format Json) default::[] report_json; - let tests_format = Option.value_map f::(mk_format Tests) default::[] Config.bugs_tests; - let txt_format = Option.value_map f::(mk_format Text) default::[] Config.bugs_txt; - csv_format @ json_format @ tests_format @ txt_format -}; - -let init_procs_format_list () => Option.value_map f::(mk_format Csv) default::[] Config.procs_csv; - -let init_calls_format_list () => { - let csv_format = Option.value_map f::(mk_format Csv) default::[] Config.calls_csv; - csv_format -}; - -let init_stats_format_list () => { - let csv_format = Option.value_map f::(mk_format Csv) default::[] Config.stats_report; - csv_format -}; - -let init_summary_format_list () => { - let latex_format = Option.value_map f::(mk_format Latex) default::[] Config.latex; - latex_format -}; - -let init_files format_list_by_kind => { - let init_files_of_report_kind (report_kind, format_list) => { - let init_files_of_format (format_kind, outfile: Utils.outfile) => - switch (format_kind, report_kind) { - | (Csv, Issues) => IssuesCsv.pp_header outfile.fmt () - | (Csv, Procs) => ProcsCsv.pp_header outfile.fmt () - | (Csv, Stats) => Report.pp_header outfile.fmt () - | (Json, Issues) => IssuesJson.pp_json_open outfile.fmt () - | (Latex, Summary) => begin_latex_file outfile.fmt - | (Csv | Json | Latex | Tests | Text, _) => () - }; - List.iter f::init_files_of_format format_list - }; - List.iter f::init_files_of_report_kind format_list_by_kind -}; - -let finalize_and_close_files format_list_by_kind stats pdflatex => { - let close_files_of_report_kind (report_kind, format_list) => { - let close_files_of_format (format_kind, outfile: Utils.outfile) => { - switch (format_kind, report_kind) { - | (Csv, Stats) => F.fprintf outfile.fmt "%a@?" Report.pp_stats stats - | (Json, Issues) => IssuesJson.pp_json_close outfile.fmt () - | (Latex, Summary) => Latex.pp_end outfile.fmt () - | (Csv | Latex | Tests | Text | Json, _) => () - }; - Utils.close_outf outfile; - /* bug_format_kind report_kind */ - if ( - [%compare.equal : (bug_format_kind, report_kind)] - (format_kind, report_kind) (Latex, Summary) - ) { - pdflatex outfile.fname; - let pdf_name = Filename.chop_extension outfile.fname ^ ".pdf"; - ignore (Sys.command ("open " ^ pdf_name)) - } - }; - List.iter f::close_files_of_format format_list; - () - }; - List.iter f::close_files_of_report_kind format_list_by_kind -}; - -let pp_summary_and_issues formats_by_report_kind => { - let pdflatex fname => ignore (Sys.command ("pdflatex " ^ fname)); - let stats = Stats.create (); - let linereader = Printer.LineReader.create (); - let filters = Inferconfig.create_filters Config.analyzer; - let iterate_summaries = AnalysisResults.get_summary_iterator (); - iterate_summaries (process_summary filters formats_by_report_kind linereader stats); - if Config.precondition_stats { - PreconditionStats.pp_stats () - }; - { - LintIssues.load_issues_to_errlog_map Config.lint_issues_dir_name; - Typ.Procname.Map.iter - (pp_lint_issues filters formats_by_report_kind linereader) !LintIssues.errLogMap - }; - finalize_and_close_files formats_by_report_kind stats pdflatex -}; - -let print_issues formats_by_report_kind => { - init_files formats_by_report_kind; - switch Config.from_json_report { - | Some fname => pp_json_report_by_report_kind formats_by_report_kind fname - | None => pp_summary_and_issues formats_by_report_kind - } -}; - -let main ::report_csv ::report_json => { - let formats_by_report_kind = [ - (Issues, init_issues_format_list report_csv report_json), - (Procs, init_procs_format_list ()), - (Calls, init_calls_format_list ()), - (Stats, init_stats_format_list ()), - (Summary, init_summary_format_list ()) - ]; - register_perf_stats_report (); - print_issues formats_by_report_kind -}; - -let main_from_config () => main report_csv::Config.bugs_csv report_json::Config.bugs_json; diff --git a/infer/src/backend/OndemandCapture.ml b/infer/src/backend/OndemandCapture.ml index c8e72cbc8..40aa0da34 100644 --- a/infer/src/backend/OndemandCapture.ml +++ b/infer/src/backend/OndemandCapture.ml @@ -7,57 +7,54 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module L = Logging -let compilation_db = lazy (CompilationDatabase.from_json_files !Config.clang_compilation_dbs) +let compilation_db = (lazy (CompilationDatabase.from_json_files !Config.clang_compilation_dbs)) (** Given proc_attributes try to produce proc_attributes' where proc_attributes'.is_defined = true It may trigger capture of extra files to do so and when it does, it waits for frontend to finish before returning *) -let try_capture (attributes : ProcAttributes.t) : ProcAttributes.t option = +let try_capture (attributes: ProcAttributes.t) : ProcAttributes.t option = let lazy cdb = compilation_db in - if Option.is_none - (AttributesTable.load_defined_attributes ~cache_none:false attributes.proc_name) then ( - let decl_file = attributes.loc.file in - let definition_file_opt = SourceFile.of_header decl_file in - let try_compile definition_file = - let source_dir = DB.source_dir_from_source_file definition_file in - (* Use cfg_filename as a proxy to find out whether definition_file was already captured. + ( if Option.is_none + (AttributesTable.load_defined_attributes ~cache_none:false attributes.proc_name) + then + let decl_file = attributes.loc.file in + let definition_file_opt = SourceFile.of_header decl_file in + let try_compile definition_file = + let source_dir = DB.source_dir_from_source_file definition_file in + (* Use cfg_filename as a proxy to find out whether definition_file was already captured. If it was, there is no point in trying to capture it again. Treat existance of cfg_filename as a barrier - if it exists it means that all attributes files have been created - write logic is defined in Cfg.store_cfg_to_file *) - let cfg_filename = DB.source_dir_get_internal_file source_dir ".cfg" in - if not (DB.file_exists cfg_filename) then ( - L.(debug Capture Verbose) "Started capture of %a...@\n" SourceFile.pp definition_file; - Timeout.suspend_existing_timeout ~keep_symop_total:true; - protect - ~f:(fun () -> CaptureCompilationDatabase.capture_file_in_database cdb definition_file) - ~finally:Timeout.resume_previous_timeout; - if Config.debug_mode && - Option.is_none - (AttributesTable.load_defined_attributes ~cache_none:false attributes.proc_name) then ( - (* peek at the results to know if capture succeeded, but only in debug mode *) + let cfg_filename = DB.source_dir_get_internal_file source_dir ".cfg" in + if not (DB.file_exists cfg_filename) then ( + L.(debug Capture Verbose) "Started capture of %a...@\n" SourceFile.pp definition_file ; + Timeout.suspend_existing_timeout ~keep_symop_total:true ; + protect + ~f:(fun () -> CaptureCompilationDatabase.capture_file_in_database cdb definition_file) + ~finally:Timeout.resume_previous_timeout ; + if Config.debug_mode + && Option.is_none + (AttributesTable.load_defined_attributes ~cache_none:false attributes.proc_name) + then + (* peek at the results to know if capture succeeded, but only in debug mode *) + L.(debug Capture Verbose) + "Captured file %a to get procedure %a but it wasn't found there@\n" SourceFile.pp + definition_file Typ.Procname.pp attributes.proc_name ) + else L.(debug Capture Verbose) - "Captured file %a to get procedure %a but it wasn't found there@\n" - SourceFile.pp definition_file - Typ.Procname.pp attributes.proc_name - ) - ) else ( - L.(debug Capture Verbose) - "Wanted to capture file %a to get procedure %a but file was already captured@\n" - SourceFile.pp definition_file - Typ.Procname.pp attributes.proc_name - ) - in - match definition_file_opt with - | None -> - L.(debug Capture Medium) "Couldn't find source file for %a (declared in %a)@\n" - Typ.Procname.pp attributes.proc_name - SourceFile.pp decl_file - | Some file -> try_compile file - ); + "Wanted to capture file %a to get procedure %a but file was already captured@\n" + SourceFile.pp definition_file Typ.Procname.pp attributes.proc_name + in + match definition_file_opt with + | None + -> L.(debug Capture Medium) + "Couldn't find source file for %a (declared in %a)@\n" Typ.Procname.pp + attributes.proc_name SourceFile.pp decl_file + | Some file + -> try_compile file ) ; (* It's important to call load_defined_attributes again in all cases to make sure we try reading from disk again no matter which condition happened. If previous call to load_defined_attributes is None, it may mean couple of things: diff --git a/infer/src/backend/PerfStats.ml b/infer/src/backend/PerfStats.ml index 25bc0c2df..cf7fdca61 100644 --- a/infer/src/backend/PerfStats.ml +++ b/infer/src/backend/PerfStats.ml @@ -10,69 +10,64 @@ (** Performance Statistics gathering and reporting *) open! IStd - module L = Logging -type perf_stats = { - rtime : float; - utime : float; - stime : float; - cutime : float; - cstime : float; - minor_gb : float; - promoted_gb : float; - major_gb : float; - allocated_gb : float; - minor_collections : int; - major_collections : int; - compactions : int; - top_heap_gb : float; - stack_kb : float; - minor_heap_kb : float; - attributes_table : AttributesTable.t; -} +type perf_stats = + { rtime: float + ; utime: float + ; stime: float + ; cutime: float + ; cstime: float + ; minor_gb: float + ; promoted_gb: float + ; major_gb: float + ; allocated_gb: float + ; minor_collections: int + ; major_collections: int + ; compactions: int + ; top_heap_gb: float + ; stack_kb: float + ; minor_heap_kb: float + ; attributes_table: AttributesTable.t } let to_json ps = let attributes_table = AttributesTable.stats () in - `Assoc [ - ("rtime", `Float ps.rtime); - ("utime", `Float ps.utime); - ("stime", `Float ps.stime); - ("cutime", `Float ps.cutime); - ("cstime", `Float ps.cstime); - ("minor_gb", `Float ps.minor_gb); - ("promoted_gb", `Float ps.promoted_gb); - ("major_gb", `Float ps.major_gb); - ("allocated_gb", `Float ps.allocated_gb); - ("minor_collections", `Int ps.minor_collections); - ("major_collections", `Int ps.major_collections); - ("compactions", `Int ps.compactions); - ("top_heap_gb", `Float ps.top_heap_gb); - ("stack_kb", `Float ps.stack_kb); - ("minor_heap_kb", `Float ps.minor_heap_kb); - ("attributes_table", AttributesTable.to_json attributes_table); - ] + `Assoc + [ ("rtime", `Float ps.rtime) + ; ("utime", `Float ps.utime) + ; ("stime", `Float ps.stime) + ; ("cutime", `Float ps.cutime) + ; ("cstime", `Float ps.cstime) + ; ("minor_gb", `Float ps.minor_gb) + ; ("promoted_gb", `Float ps.promoted_gb) + ; ("major_gb", `Float ps.major_gb) + ; ("allocated_gb", `Float ps.allocated_gb) + ; ("minor_collections", `Int ps.minor_collections) + ; ("major_collections", `Int ps.major_collections) + ; ("compactions", `Int ps.compactions) + ; ("top_heap_gb", `Float ps.top_heap_gb) + ; ("stack_kb", `Float ps.stack_kb) + ; ("minor_heap_kb", `Float ps.minor_heap_kb) + ; ("attributes_table", AttributesTable.to_json attributes_table) ] let from_json json = let open! Yojson.Basic.Util in - { - rtime = json |> member "rtime" |> to_float; - utime = json |> member "utime" |> to_float; - stime = json |> member "stime" |> to_float; - cutime = json |> member "cutime" |> to_float; - cstime = json |> member "cstime" |> to_float; - minor_gb = json |> member "minor_gb" |> to_float; - promoted_gb = json |> member "promoted_gb" |> to_float; - major_gb = json |> member "major_gb" |> to_float; - allocated_gb = json |> member "allocated_gb" |> to_float; - minor_collections = json |> member "minor_collections" |> to_int; - major_collections = json |> member "major_collections" |> to_int; - compactions = json |> member "compactions" |> to_int; - top_heap_gb = json |> member "top_heap_gb" |> to_float; - stack_kb = json |> member "stack_kb" |> to_float; - minor_heap_kb = json |> member "minor_heap_kb" |> to_float; - attributes_table = json |> member "attributes_table" |> AttributesTable.from_json; - } + { rtime= json |> member "rtime" |> to_float + ; utime= json |> member "utime" |> to_float + ; stime= json |> member "stime" |> to_float + ; cutime= json |> member "cutime" |> to_float + ; cstime= json |> member "cstime" |> to_float + ; minor_gb= json |> member "minor_gb" |> to_float + ; promoted_gb= json |> member "promoted_gb" |> to_float + ; major_gb= json |> member "major_gb" |> to_float + ; allocated_gb= json |> member "allocated_gb" |> to_float + ; minor_collections= json |> member "minor_collections" |> to_int + ; major_collections= json |> member "major_collections" |> to_int + ; compactions= json |> member "compactions" |> to_int + ; top_heap_gb= json |> member "top_heap_gb" |> to_float + ; stack_kb= json |> member "stack_kb" |> to_float + ; minor_heap_kb= json |> member "minor_heap_kb" |> to_float + ; attributes_table= json |> member "attributes_table" |> AttributesTable.from_json } let aggregate s = let mk_stats f = StatisticsToolbox.compute_statistics (List.map ~f s) in @@ -92,79 +87,73 @@ let aggregate s = let aggr_stack_kb = mk_stats (fun stats -> stats.stack_kb) in let aggr_minor_heap_kb = mk_stats (fun stats -> stats.minor_heap_kb) in let aggr_attributes_table = - AttributesTable.aggregate (List.map ~f:(fun stats -> stats.attributes_table) s) in - `Assoc [ - ("rtime", StatisticsToolbox.to_json aggr_rtime); - ("utime", StatisticsToolbox.to_json aggr_utime); - ("stime", StatisticsToolbox.to_json aggr_stime); - ("cutime", StatisticsToolbox.to_json aggr_cutime); - ("cstime", StatisticsToolbox.to_json aggr_cstime); - ("minor_gb", StatisticsToolbox.to_json aggr_minor_gb); - ("promoted_gb", StatisticsToolbox.to_json aggr_promoted_gb); - ("major_gb", StatisticsToolbox.to_json aggr_major_gb); - ("allocated_gb", StatisticsToolbox.to_json aggr_allocated_gb); - ("minor_collections", StatisticsToolbox.to_json aggr_minor_collections); - ("major_collections", StatisticsToolbox.to_json aggr_major_collections); - ("compactions", StatisticsToolbox.to_json aggr_compactions); - ("top_heap_gb", StatisticsToolbox.to_json aggr_top_heap_gb); - ("stack_kb", StatisticsToolbox.to_json aggr_stack_kb); - ("minor_heap_kb", StatisticsToolbox.to_json aggr_minor_heap_kb); - ("attributes_table", aggr_attributes_table); - ] + AttributesTable.aggregate (List.map ~f:(fun stats -> stats.attributes_table) s) + in + `Assoc + [ ("rtime", StatisticsToolbox.to_json aggr_rtime) + ; ("utime", StatisticsToolbox.to_json aggr_utime) + ; ("stime", StatisticsToolbox.to_json aggr_stime) + ; ("cutime", StatisticsToolbox.to_json aggr_cutime) + ; ("cstime", StatisticsToolbox.to_json aggr_cstime) + ; ("minor_gb", StatisticsToolbox.to_json aggr_minor_gb) + ; ("promoted_gb", StatisticsToolbox.to_json aggr_promoted_gb) + ; ("major_gb", StatisticsToolbox.to_json aggr_major_gb) + ; ("allocated_gb", StatisticsToolbox.to_json aggr_allocated_gb) + ; ("minor_collections", StatisticsToolbox.to_json aggr_minor_collections) + ; ("major_collections", StatisticsToolbox.to_json aggr_major_collections) + ; ("compactions", StatisticsToolbox.to_json aggr_compactions) + ; ("top_heap_gb", StatisticsToolbox.to_json aggr_top_heap_gb) + ; ("stack_kb", StatisticsToolbox.to_json aggr_stack_kb) + ; ("minor_heap_kb", StatisticsToolbox.to_json aggr_minor_heap_kb) + ; ("attributes_table", aggr_attributes_table) ] let stats () = let words_to_kb n = n *. float_of_int (Sys.word_size / 8) /. 1024. in let words_to_mb n = words_to_kb n /. 1024. in let words_to_gb n = words_to_mb n /. 1024. in let gc_stats = Gc.quick_stat () in - let allocated_words = - gc_stats.minor_words +. gc_stats.major_words -. gc_stats.promoted_words in + let allocated_words = gc_stats.minor_words +. gc_stats.major_words -. gc_stats.promoted_words in let gc_ctrl = Gc.get () in let exit_timeofday = Unix.gettimeofday () in let exit_times = Unix.times () in let at = AttributesTable.stats () in - { - rtime = exit_timeofday -. Utils.initial_timeofday; - utime = exit_times.tms_utime -. Utils.initial_times.tms_utime; - stime = exit_times.tms_stime -. Utils.initial_times.tms_stime; - cutime = exit_times.tms_cutime -. Utils.initial_times.tms_cutime; - cstime = exit_times.tms_cstime -. Utils.initial_times.tms_cstime; - minor_gb = words_to_gb gc_stats.minor_words; - promoted_gb = words_to_gb gc_stats.promoted_words; - major_gb = words_to_gb gc_stats.major_words; - allocated_gb = words_to_gb allocated_words; - minor_collections = gc_stats.minor_collections; - major_collections = gc_stats.major_collections; - compactions = gc_stats.compactions; - top_heap_gb = words_to_gb (float_of_int gc_stats.top_heap_words); - stack_kb = words_to_kb (float_of_int gc_stats.stack_size); - minor_heap_kb = words_to_kb (float_of_int gc_ctrl.minor_heap_size); - attributes_table = at - } + { rtime= exit_timeofday -. Utils.initial_timeofday + ; utime= exit_times.tms_utime -. Utils.initial_times.tms_utime + ; stime= exit_times.tms_stime -. Utils.initial_times.tms_stime + ; cutime= exit_times.tms_cutime -. Utils.initial_times.tms_cutime + ; cstime= exit_times.tms_cstime -. Utils.initial_times.tms_cstime + ; minor_gb= words_to_gb gc_stats.minor_words + ; promoted_gb= words_to_gb gc_stats.promoted_words + ; major_gb= words_to_gb gc_stats.major_words + ; allocated_gb= words_to_gb allocated_words + ; minor_collections= gc_stats.minor_collections + ; major_collections= gc_stats.major_collections + ; compactions= gc_stats.compactions + ; top_heap_gb= words_to_gb (float_of_int gc_stats.top_heap_words) + ; stack_kb= words_to_kb (float_of_int gc_stats.stack_size) + ; minor_heap_kb= words_to_kb (float_of_int gc_ctrl.minor_heap_size) + ; attributes_table= at } let report_at_exit file () = try let json_stats = to_json (stats ()) in try - Unix.mkdir_p (Filename.dirname file); + Unix.mkdir_p (Filename.dirname file) ; (* the same report may be registered across different infer processes *) Utils.write_file_with_locking file ~f:(fun stats_oc -> - Yojson.Basic.pretty_to_channel stats_oc json_stats; - ); + Yojson.Basic.pretty_to_channel stats_oc json_stats ) with exc -> - L.internal_error "Info: failed to write stats to %s@\n%s@\n%s@\n%s@." - file (Exn.to_string exc) (Yojson.Basic.pretty_to_string json_stats) - (Printexc.get_backtrace ()) + L.internal_error "Info: failed to write stats to %s@\n%s@\n%s@\n%s@." file + (Exn.to_string exc) (Yojson.Basic.pretty_to_string json_stats) (Printexc.get_backtrace ()) with exc -> - L.internal_error "Info: failed to compute stats for %s@\n%s@\n%s@." - file (Exn.to_string exc) (Printexc.get_backtrace ()) + L.internal_error "Info: failed to compute stats for %s@\n%s@\n%s@." file (Exn.to_string exc) + (Printexc.get_backtrace ()) let register_report_at_exit = (* take care of not double-registering the same perf stat report *) let registered_files = String.Table.create ~size:4 () in fun file -> if not (Hashtbl.mem registered_files file) then ( - String.Table.set registered_files ~key:file ~data:(); + String.Table.set registered_files ~key:file ~data:() ; if not Config.buck_cache_mode then - Epilogues.register ~f:(report_at_exit file) ("stats reporting in " ^ file) - ) + Epilogues.register ~f:(report_at_exit file) ("stats reporting in " ^ file) ) diff --git a/infer/src/backend/PerfStats.mli b/infer/src/backend/PerfStats.mli index 7aac6afea..3529d6088 100644 --- a/infer/src/backend/PerfStats.mli +++ b/infer/src/backend/PerfStats.mli @@ -17,6 +17,6 @@ val from_json : Yojson.Basic.json -> perf_stats val aggregate : perf_stats list -> Yojson.Basic.json +val register_report_at_exit : string -> unit (** Create performance report when the current process terminates. Automatically disabled when [Config.buck_cache_mode] is true. *) -val register_report_at_exit : string -> unit diff --git a/infer/src/backend/PropUtil.ml b/infer/src/backend/PropUtil.ml new file mode 100644 index 000000000..19fe2f7f3 --- /dev/null +++ b/infer/src/backend/PropUtil.ml @@ -0,0 +1,181 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd + +let get_name_of_local (curr_f: Procdesc.t) (x, _) = Pvar.mk x (Procdesc.get_proc_name curr_f) + +(* returns a list of local static variables (ie local variables defined static) in a proposition *) +let get_name_of_objc_static_locals (curr_f: Procdesc.t) p = + let pname = Typ.Procname.to_string (Procdesc.get_proc_name curr_f) in + let local_static e = + match e with + | Exp.Lvar (* is a local static if it's a global and it has a static local name *) + pvar + when Pvar.is_global pvar && Sil.is_static_local_name pname pvar + -> [pvar] + | _ + -> [] + in + let hpred_local_static hpred = + match hpred with Sil.Hpointsto (e, _, _) -> [local_static e] | _ -> [] + in + let vars_sigma = List.map ~f:hpred_local_static p.Prop.sigma in + List.concat (List.concat vars_sigma) + +(* returns a list of local variables that points to an objc block in a proposition *) +let get_name_of_objc_block_locals p = + let local_blocks e = + match e with Exp.Lvar pvar when Sil.is_block_pvar pvar -> [pvar] | _ -> [] + in + let hpred_local_blocks hpred = + match hpred with Sil.Hpointsto (e, _, _) -> [local_blocks e] | _ -> [] + in + let vars_sigma = List.map ~f:hpred_local_blocks p.Prop.sigma in + List.concat (List.concat vars_sigma) + +let remove_abduced_retvars tenv p = + (* compute the hpreds and pure atoms reachable from the set of seed expressions in [exps] *) + let compute_reachable p seed_exps = + let sigma, pi = (p.Prop.sigma, p.Prop.pi) in + let rec collect_exps exps = function + | Sil.Eexp (Exp.Exn e, _) + -> Exp.Set.add e exps + | Sil.Eexp (e, _) + -> Exp.Set.add e exps + | Sil.Estruct (flds, _) + -> List.fold ~f:(fun exps (_, strexp) -> collect_exps exps strexp) ~init:exps flds + | Sil.Earray (_, elems, _) + -> List.fold ~f:(fun exps (_, strexp) -> collect_exps exps strexp) ~init:exps elems + in + let rec compute_reachable_hpreds_rec sigma (reach, exps) = + let add_hpred_if_reachable (reach, exps) = function + | Sil.Hpointsto (lhs, rhs, _) as hpred when Exp.Set.mem lhs exps + -> let reach' = Sil.HpredSet.add hpred reach in + let exps' = collect_exps exps rhs in + (reach', exps') + | Sil.Hlseg (_, _, exp1, exp2, exp_l) as hpred + -> let reach' = Sil.HpredSet.add hpred reach in + let exps' = + List.fold + ~f:(fun exps_acc exp -> Exp.Set.add exp exps_acc) + ~init:exps (exp1 :: exp2 :: exp_l) + in + (reach', exps') + | Sil.Hdllseg (_, _, exp1, exp2, exp3, exp4, exp_l) as hpred + -> let reach' = Sil.HpredSet.add hpred reach in + let exps' = + List.fold + ~f:(fun exps_acc exp -> Exp.Set.add exp exps_acc) + ~init:exps (exp1 :: exp2 :: exp3 :: exp4 :: exp_l) + in + (reach', exps') + | _ + -> (reach, exps) + in + let reach', exps' = List.fold ~f:add_hpred_if_reachable ~init:(reach, exps) sigma in + if Int.equal (Sil.HpredSet.cardinal reach) (Sil.HpredSet.cardinal reach') then (reach, exps) + else compute_reachable_hpreds_rec sigma (reach', exps') + in + let reach_hpreds, reach_exps = + compute_reachable_hpreds_rec sigma (Sil.HpredSet.empty, seed_exps) + in + (* filter away the pure atoms without reachable exps *) + let reach_pi = + let rec exp_contains = function + | exp when Exp.Set.mem exp reach_exps + -> true + | Exp.UnOp (_, e, _) | Exp.Cast (_, e) | Exp.Lfield (e, _, _) + -> exp_contains e + | Exp.BinOp (_, e0, e1) | Exp.Lindex (e0, e1) + -> exp_contains e0 || exp_contains e1 + | _ + -> false + in + List.filter + ~f:(function + | Sil.Aeq (lhs, rhs) | Sil.Aneq (lhs, rhs) + -> exp_contains lhs || exp_contains rhs + | Sil.Apred (_, es) | Sil.Anpred (_, es) + -> List.exists ~f:exp_contains es) + pi + in + (Sil.HpredSet.elements reach_hpreds, reach_pi) + in + (* separate the abduced pvars from the normal ones, deallocate the abduced ones*) + let abduceds, normal_pvars = + List.fold + ~f:(fun pvars hpred -> + match hpred with + | Sil.Hpointsto (Exp.Lvar pvar, _, _) + -> let abduceds, normal_pvars = pvars in + if Pvar.is_abduced pvar then (pvar :: abduceds, normal_pvars) + else (abduceds, pvar :: normal_pvars) + | _ + -> pvars) + ~init:([], []) p.Prop.sigma + in + let _, p' = Attribute.deallocate_stack_vars tenv p abduceds in + let normal_pvar_set = + List.fold + ~f:(fun normal_pvar_set pvar -> Exp.Set.add (Exp.Lvar pvar) normal_pvar_set) + ~init:Exp.Set.empty normal_pvars + in + (* walk forward from non-abduced pvars, keep everything reachable. remove everything else *) + let sigma_reach, pi_reach = compute_reachable p' normal_pvar_set in + Prop.normalize tenv (Prop.set p' ~pi:pi_reach ~sigma:sigma_reach) + +let remove_locals tenv (curr_f: Procdesc.t) p = + let names_of_locals = List.map ~f:(get_name_of_local curr_f) (Procdesc.get_locals curr_f) in + let names_of_locals' = + match !Config.curr_language with + | Config.Clang + -> (* in ObjC to deal with block we need to remove static locals *) + let names_of_static_locals = get_name_of_objc_static_locals curr_f p in + let names_of_block_locals = get_name_of_objc_block_locals p in + names_of_block_locals @ names_of_locals @ names_of_static_locals + | _ + -> names_of_locals + in + let removed, p' = Attribute.deallocate_stack_vars tenv p names_of_locals' in + (removed, if Config.angelic_execution then remove_abduced_retvars tenv p' else p') + +let remove_formals tenv (curr_f: Procdesc.t) p = + let pname = Procdesc.get_proc_name curr_f in + let formal_vars = List.map ~f:(fun (n, _) -> Pvar.mk n pname) (Procdesc.get_formals curr_f) in + Attribute.deallocate_stack_vars tenv p formal_vars + +(** remove the return variable from the prop *) +let remove_ret tenv (curr_f: Procdesc.t) (p: Prop.normal Prop.t) = + let pname = Procdesc.get_proc_name curr_f in + let name_of_ret = Procdesc.get_ret_var curr_f in + let _, p' = Attribute.deallocate_stack_vars tenv p [Pvar.to_callee pname name_of_ret] in + p' + +(** remove locals and return variable from the prop *) +let remove_locals_ret tenv (curr_f: Procdesc.t) p = + snd (remove_locals tenv curr_f (remove_ret tenv curr_f p)) + +(** Remove locals and formal parameters from the prop. + Return the list of stack variables whose address was still present after deallocation. *) +let remove_locals_formals tenv (curr_f: Procdesc.t) p = + let pvars1, p1 = remove_locals tenv curr_f p in + let pvars2, p2 = remove_formals tenv curr_f p1 in + (pvars1 @ pvars2, p2) + +(** remove seed vars from a prop *) +let remove_seed_vars tenv (prop: 'a Prop.t) : Prop.normal Prop.t = + let hpred_not_seed = function + | Sil.Hpointsto (Exp.Lvar pv, _, _) + -> not (Pvar.is_seed pv) + | _ + -> true + in + let sigma = prop.sigma in + let sigma' = List.filter ~f:hpred_not_seed sigma in + Prop.normalize tenv (Prop.set prop ~sigma:sigma') diff --git a/infer/src/backend/PropUtil.mli b/infer/src/backend/PropUtil.mli new file mode 100644 index 000000000..5106cfbee --- /dev/null +++ b/infer/src/backend/PropUtil.mli @@ -0,0 +1,28 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(** remove the return variable from the prop *) + +val remove_ret : Tenv.t -> Procdesc.t -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** remove locals and return variable from the prop *) + +val remove_locals_ret : Tenv.t -> Procdesc.t -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Deallocate the stack variables in [pvars], and replace them by normal variables. + Return the list of stack variables whose address was still present after deallocation. *) + +val remove_locals_formals : + Tenv.t -> Procdesc.t -> Prop.normal Prop.t -> Pvar.t list * Prop.normal Prop.t + +(** remove seed vars from a prop *) + +val remove_seed_vars : Tenv.t -> 'a Prop.t -> Prop.normal Prop.t diff --git a/infer/src/backend/PropUtil.re b/infer/src/backend/PropUtil.re deleted file mode 100644 index 3495c7bad..000000000 --- a/infer/src/backend/PropUtil.re +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -let get_name_of_local (curr_f: Procdesc.t) (x, _) => Pvar.mk x (Procdesc.get_proc_name curr_f); - -/* returns a list of local static variables (ie local variables defined static) in a proposition */ -let get_name_of_objc_static_locals (curr_f: Procdesc.t) p => { - let pname = Typ.Procname.to_string (Procdesc.get_proc_name curr_f); - let local_static e => - switch e { - /* is a local static if it's a global and it has a static local name */ - | Exp.Lvar pvar when Pvar.is_global pvar && Sil.is_static_local_name pname pvar => [pvar] - | _ => [] - }; - let hpred_local_static hpred => - switch hpred { - | Sil.Hpointsto e _ _ => [local_static e] - | _ => [] - }; - let vars_sigma = List.map f::hpred_local_static p.Prop.sigma; - List.concat (List.concat vars_sigma) -}; - -/* returns a list of local variables that points to an objc block in a proposition */ -let get_name_of_objc_block_locals p => { - let local_blocks e => - switch e { - | Exp.Lvar pvar when Sil.is_block_pvar pvar => [pvar] - | _ => [] - }; - let hpred_local_blocks hpred => - switch hpred { - | Sil.Hpointsto e _ _ => [local_blocks e] - | _ => [] - }; - let vars_sigma = List.map f::hpred_local_blocks p.Prop.sigma; - List.concat (List.concat vars_sigma) -}; - -let remove_abduced_retvars tenv p => { - /* compute the hpreds and pure atoms reachable from the set of seed expressions in [exps] */ - let compute_reachable p seed_exps => { - let (sigma, pi) = (p.Prop.sigma, p.Prop.pi); - let rec collect_exps exps => - fun - | Sil.Eexp (Exp.Exn e) _ => Exp.Set.add e exps - | Sil.Eexp e _ => Exp.Set.add e exps - | Sil.Estruct flds _ => - List.fold f::(fun exps (_, strexp) => collect_exps exps strexp) init::exps flds - | Sil.Earray _ elems _ => - List.fold f::(fun exps (_, strexp) => collect_exps exps strexp) init::exps elems; - let rec compute_reachable_hpreds_rec sigma (reach, exps) => { - let add_hpred_if_reachable (reach, exps) => - fun - | Sil.Hpointsto lhs rhs _ as hpred when Exp.Set.mem lhs exps => { - let reach' = Sil.HpredSet.add hpred reach; - let exps' = collect_exps exps rhs; - (reach', exps') - } - | Sil.Hlseg _ _ exp1 exp2 exp_l as hpred => { - let reach' = Sil.HpredSet.add hpred reach; - let exps' = - List.fold - f::(fun exps_acc exp => Exp.Set.add exp exps_acc) - init::exps - [exp1, exp2, ...exp_l]; - (reach', exps') - } - | Sil.Hdllseg _ _ exp1 exp2 exp3 exp4 exp_l as hpred => { - let reach' = Sil.HpredSet.add hpred reach; - let exps' = - List.fold - f::(fun exps_acc exp => Exp.Set.add exp exps_acc) - init::exps - [exp1, exp2, exp3, exp4, ...exp_l]; - (reach', exps') - } - | _ => (reach, exps); - let (reach', exps') = List.fold f::add_hpred_if_reachable init::(reach, exps) sigma; - if (Int.equal (Sil.HpredSet.cardinal reach) (Sil.HpredSet.cardinal reach')) { - (reach, exps) - } else { - compute_reachable_hpreds_rec sigma (reach', exps') - } - }; - let (reach_hpreds, reach_exps) = - compute_reachable_hpreds_rec sigma (Sil.HpredSet.empty, seed_exps); - /* filter away the pure atoms without reachable exps */ - let reach_pi = { - let rec exp_contains = - fun - | exp when Exp.Set.mem exp reach_exps => true - | Exp.UnOp _ e _ - | Exp.Cast _ e - | Exp.Lfield e _ _ => exp_contains e - | Exp.BinOp _ e0 e1 - | Exp.Lindex e0 e1 => exp_contains e0 || exp_contains e1 - | _ => false; - List.filter - f::( - fun - | Sil.Aeq lhs rhs - | Sil.Aneq lhs rhs => exp_contains lhs || exp_contains rhs - | Sil.Apred _ es - | Sil.Anpred _ es => List.exists f::exp_contains es - ) - pi - }; - (Sil.HpredSet.elements reach_hpreds, reach_pi) - }; - /* separate the abduced pvars from the normal ones, deallocate the abduced ones*/ - let (abduceds, normal_pvars) = - List.fold - f::( - fun pvars hpred => - switch hpred { - | Sil.Hpointsto (Exp.Lvar pvar) _ _ => - let (abduceds, normal_pvars) = pvars; - if (Pvar.is_abduced pvar) { - ([pvar, ...abduceds], normal_pvars) - } else { - (abduceds, [pvar, ...normal_pvars]) - } - | _ => pvars - } - ) - init::([], []) - p.Prop.sigma; - let (_, p') = Attribute.deallocate_stack_vars tenv p abduceds; - let normal_pvar_set = - List.fold - f::(fun normal_pvar_set pvar => Exp.Set.add (Exp.Lvar pvar) normal_pvar_set) - init::Exp.Set.empty - normal_pvars; - /* walk forward from non-abduced pvars, keep everything reachable. remove everything else */ - let (sigma_reach, pi_reach) = compute_reachable p' normal_pvar_set; - Prop.normalize tenv (Prop.set p' pi::pi_reach sigma::sigma_reach) -}; - -let remove_locals tenv (curr_f: Procdesc.t) p => { - let names_of_locals = List.map f::(get_name_of_local curr_f) (Procdesc.get_locals curr_f); - let names_of_locals' = - switch !Config.curr_language { - | Config.Clang => - /* in ObjC to deal with block we need to remove static locals */ - let names_of_static_locals = get_name_of_objc_static_locals curr_f p; - let names_of_block_locals = get_name_of_objc_block_locals p; - names_of_block_locals @ names_of_locals @ names_of_static_locals - | _ => names_of_locals - }; - let (removed, p') = Attribute.deallocate_stack_vars tenv p names_of_locals'; - ( - removed, - if Config.angelic_execution { - remove_abduced_retvars tenv p' - } else { - p' - } - ) -}; - -let remove_formals tenv (curr_f: Procdesc.t) p => { - let pname = Procdesc.get_proc_name curr_f; - let formal_vars = List.map f::(fun (n, _) => Pvar.mk n pname) (Procdesc.get_formals curr_f); - Attribute.deallocate_stack_vars tenv p formal_vars -}; - - -/** remove the return variable from the prop */ -let remove_ret tenv (curr_f: Procdesc.t) (p: Prop.t Prop.normal) => { - let pname = Procdesc.get_proc_name curr_f; - let name_of_ret = Procdesc.get_ret_var curr_f; - let (_, p') = Attribute.deallocate_stack_vars tenv p [Pvar.to_callee pname name_of_ret]; - p' -}; - - -/** remove locals and return variable from the prop */ -let remove_locals_ret tenv (curr_f: Procdesc.t) p => - snd (remove_locals tenv curr_f (remove_ret tenv curr_f p)); - - -/** Remove locals and formal parameters from the prop. - Return the list of stack variables whose address was still present after deallocation. */ -let remove_locals_formals tenv (curr_f: Procdesc.t) p => { - let (pvars1, p1) = remove_locals tenv curr_f p; - let (pvars2, p2) = remove_formals tenv curr_f p1; - (pvars1 @ pvars2, p2) -}; - - -/** remove seed vars from a prop */ -let remove_seed_vars tenv (prop: Prop.t 'a) :Prop.t Prop.normal => { - let hpred_not_seed = - fun - | Sil.Hpointsto (Exp.Lvar pv) _ _ => not (Pvar.is_seed pv) - | _ => true; - let sigma = prop.sigma; - let sigma' = List.filter f::hpred_not_seed sigma; - Prop.normalize tenv (Prop.set prop sigma::sigma') -}; diff --git a/infer/src/backend/PropUtil.rei b/infer/src/backend/PropUtil.rei deleted file mode 100644 index 7c01a33c2..000000000 --- a/infer/src/backend/PropUtil.rei +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - - -/** remove the return variable from the prop */ -let remove_ret: Tenv.t => Procdesc.t => Prop.t Prop.normal => Prop.t Prop.normal; - - -/** remove locals and return variable from the prop */ -let remove_locals_ret: Tenv.t => Procdesc.t => Prop.t Prop.normal => Prop.t Prop.normal; - - -/** Deallocate the stack variables in [pvars], and replace them by normal variables. - Return the list of stack variables whose address was still present after deallocation. */ -let remove_locals_formals: - Tenv.t => Procdesc.t => Prop.t Prop.normal => (list Pvar.t, Prop.t Prop.normal); - - -/** remove seed vars from a prop */ -let remove_seed_vars: Tenv.t => Prop.t 'a => Prop.t Prop.normal; diff --git a/infer/src/backend/StatsAggregator.ml b/infer/src/backend/StatsAggregator.ml new file mode 100644 index 000000000..dd0776be6 --- /dev/null +++ b/infer/src/backend/StatsAggregator.ml @@ -0,0 +1,171 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +open! PVariant + +let aggregated_stats_filename = "aggregated_stats.json" + +let aggregated_stats_by_target_filename = "aggregated_stats_by_target.json" + +let json_files_to_ignore_regex = + Str.regexp + ( ".*\\(" ^ Str.quote aggregated_stats_filename ^ "\\|" + ^ Str.quote aggregated_stats_by_target_filename ^ "\\)$" ) + +let dir_exists dir = Sys.is_directory dir = `Yes + +let find_json_files_in_dir dir = + let is_valid_json_file path = + let s = Unix.lstat path in + let json_regex = Str.regexp_case_fold ".*\\.json$" in + not (Str.string_match json_files_to_ignore_regex path 0) && Str.string_match json_regex path 0 + && Polymorphic_compare.( = ) s.st_kind Unix.S_REG + in + match dir_exists dir with + | true + -> let content = Array.to_list (Sys.readdir dir) in + let content_with_path = List.map ~f:(fun p -> Filename.concat dir p) content in + List.filter ~f:is_valid_json_file content_with_path + | false + -> [] + +type stats_paths = + {frontend_paths: string list; backend_paths: string list; reporting_paths: string list} + +type origin = Buck_out of (string * stats_paths) list | Infer_out of stats_paths + +let find_stats_files_in_dir dir = + let frontend_paths = + find_json_files_in_dir (Filename.concat dir Config.frontend_stats_dir_name) + in + let backend_paths = find_json_files_in_dir (Filename.concat dir Config.backend_stats_dir_name) in + let reporting_paths = + find_json_files_in_dir (Filename.concat dir Config.reporting_stats_dir_name) + in + {frontend_paths; backend_paths; reporting_paths} + +let load_data_from_infer_deps file = + let extract_target_and_path line = + match Str.split_delim (Str.regexp (Str.quote "\t")) line with + | target :: _ :: path :: _ + -> if dir_exists path then (target, path) + else raise (Failure ("path '" ^ path ^ "' is not a valid directory")) + | _ + -> raise (Failure "malformed input") + in + let lines = Utils.read_file file in + try + match lines with + | Ok l + -> Ok (List.map ~f:extract_target_and_path l) + | Error error + -> raise (Failure (Printf.sprintf "Error reading '%s': %s" file error)) + with Failure msg -> Error msg + +let collect_all_stats_files () = + let infer_out = Config.results_dir in + let concatenate_paths p1 p2 = if Filename.is_relative p2 then Filename.concat p1 p2 else p2 in + match Config.buck_out with + | Some p + -> if dir_exists p then + let data = + load_data_from_infer_deps (Filename.concat infer_out Config.buck_infer_deps_file_name) + in + match data with + | Ok r + -> let buck_out_parent = Filename.concat p Filename.parent_dir_name in + let targets_files = + List.map + ~f:(fun (t, p) -> + (t, find_stats_files_in_dir (concatenate_paths buck_out_parent p))) + r + in + Ok (Buck_out targets_files) + | Error _ as e + -> e + else Error ("buck-out path '" ^ p ^ "' not found") + | None + -> Ok (Infer_out (find_stats_files_in_dir infer_out)) + +let aggregate_stats_files paths = + let open_json_file file = Yojson.Basic.from_file file in + let load_stats paths = + List.map ~f:(fun path -> PerfStats.from_json (open_json_file path)) paths + in + let all_perf_stats = load_stats paths in + match all_perf_stats with [] -> None | _ -> Some (PerfStats.aggregate all_perf_stats) + +type json_aggregated_stats = + { frontend_json_data: Yojson.Basic.json option + ; backend_json_data: Yojson.Basic.json option + ; reporting_json_data: Yojson.Basic.json option } + +let aggregate_all_stats origin = + let accumulate_paths acc paths = + { frontend_paths= paths.frontend_paths @ acc.frontend_paths + ; backend_paths= paths.backend_paths @ acc.backend_paths + ; reporting_paths= paths.reporting_paths @ acc.reporting_paths } + in + let empty_stats_paths = {frontend_paths= []; backend_paths= []; reporting_paths= []} in + let stats_paths = + match origin with + | Buck_out tf + -> List.fold ~f:(fun acc (_, paths) -> accumulate_paths acc paths) ~init:empty_stats_paths tf + | Infer_out paths + -> paths + in + { frontend_json_data= aggregate_stats_files stats_paths.frontend_paths + ; backend_json_data= aggregate_stats_files stats_paths.backend_paths + ; reporting_json_data= aggregate_stats_files stats_paths.reporting_paths } + +let aggregate_stats_by_target tp = + let to_json f aggr_stats = + let collect_valid_stats acc t p = match p with Some v -> (t, v) :: acc | None -> acc in + let l = List.fold ~f:(fun acc (t, p) -> collect_valid_stats acc t (f p)) ~init:[] aggr_stats in + match l with [] -> None | _ as v -> Some (`Assoc v) + in + let frontend_json_data = to_json (fun p -> aggregate_stats_files p.frontend_paths) tp in + let backend_json_data = to_json (fun p -> aggregate_stats_files p.backend_paths) tp in + let reporting_json_data = to_json (fun p -> aggregate_stats_files p.reporting_paths) tp in + {frontend_json_data; backend_json_data; reporting_json_data} + +let generate_files () = + let infer_out = Config.results_dir in + let stats_files = collect_all_stats_files () in + let origin = match stats_files with Ok origin -> origin | Error e -> failwith e in + let aggregated_frontend_stats_dir = Filename.concat infer_out Config.frontend_stats_dir_name in + let aggregated_backend_stats_dir = Filename.concat infer_out Config.backend_stats_dir_name in + let aggregated_reporting_stats_dir = Filename.concat infer_out Config.reporting_stats_dir_name in + Utils.create_dir aggregated_frontend_stats_dir ; + Utils.create_dir aggregated_backend_stats_dir ; + Utils.create_dir aggregated_reporting_stats_dir ; + let write_to_json_file_opt destfile json = + match json with Some j -> Utils.write_json_to_file destfile j | None -> () + in + ( match origin with + | Buck_out tp + -> let j = aggregate_stats_by_target tp in + write_to_json_file_opt + (Filename.concat aggregated_frontend_stats_dir aggregated_stats_by_target_filename) + j.frontend_json_data ; + write_to_json_file_opt + (Filename.concat aggregated_backend_stats_dir aggregated_stats_by_target_filename) + j.backend_json_data ; + write_to_json_file_opt + (Filename.concat aggregated_reporting_stats_dir aggregated_stats_by_target_filename) + j.reporting_json_data + | Infer_out _ + -> () ) ; + let j = aggregate_all_stats origin in + write_to_json_file_opt (Filename.concat aggregated_frontend_stats_dir aggregated_stats_filename) + j.frontend_json_data ; + write_to_json_file_opt (Filename.concat aggregated_backend_stats_dir aggregated_stats_filename) + j.backend_json_data ; + write_to_json_file_opt (Filename.concat aggregated_reporting_stats_dir aggregated_stats_filename) + j.reporting_json_data diff --git a/infer/src/backend/StatsAggregator.rei b/infer/src/backend/StatsAggregator.mli similarity index 69% rename from infer/src/backend/StatsAggregator.rei rename to infer/src/backend/StatsAggregator.mli index f5d087721..4a62faac5 100644 --- a/infer/src/backend/StatsAggregator.rei +++ b/infer/src/backend/StatsAggregator.mli @@ -1,14 +1,15 @@ -/* +(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) +open! IStd -/** Generate files containing statistics aggregated from individual statistics files under - Config.results_dir */ -let generate_files: unit => unit; +(** Generate files containing statistics aggregated from individual statistics files under + Config.results_dir *) + +val generate_files : unit -> unit diff --git a/infer/src/backend/StatsAggregator.re b/infer/src/backend/StatsAggregator.re deleted file mode 100644 index bf4783e83..000000000 --- a/infer/src/backend/StatsAggregator.re +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -open! PVariant; - -let aggregated_stats_filename = "aggregated_stats.json"; - -let aggregated_stats_by_target_filename = "aggregated_stats_by_target.json"; - -let json_files_to_ignore_regex = - Str.regexp ( - ".*\\(" ^ - Str.quote aggregated_stats_filename ^ - "\\|" ^ Str.quote aggregated_stats_by_target_filename ^ "\\)$" - ); - -let dir_exists dir => Sys.is_directory dir == `Yes; - -let find_json_files_in_dir dir => { - let is_valid_json_file path => { - let s = Unix.lstat path; - let json_regex = Str.regexp_case_fold ".*\\.json$"; - not (Str.string_match json_files_to_ignore_regex path 0) && - Str.string_match json_regex path 0 && Polymorphic_compare.(==) s.st_kind Unix.S_REG - }; - dir_exists dir ? - { - let content = Array.to_list (Sys.readdir dir); - let content_with_path = List.map f::(fun p => Filename.concat dir p) content; - List.filter f::is_valid_json_file content_with_path - } : - [] -}; - -type stats_paths = { - frontend_paths: list string, - backend_paths: list string, - reporting_paths: list string -}; - -type origin = - | Buck_out (list (string, stats_paths)) - | Infer_out stats_paths; - -let find_stats_files_in_dir dir => { - let frontend_paths = find_json_files_in_dir (Filename.concat dir Config.frontend_stats_dir_name); - let backend_paths = find_json_files_in_dir (Filename.concat dir Config.backend_stats_dir_name); - let reporting_paths = - find_json_files_in_dir (Filename.concat dir Config.reporting_stats_dir_name); - {frontend_paths, backend_paths, reporting_paths} -}; - -let load_data_from_infer_deps file => { - let extract_target_and_path line => - switch (Str.split_delim (Str.regexp (Str.quote "\t")) line) { - | [target, _, path, ..._] => - if (dir_exists path) { - (target, path) - } else { - raise (Failure ("path '" ^ path ^ "' is not a valid directory")) - } - | _ => raise (Failure "malformed input") - }; - let lines = Utils.read_file file; - try ( - switch lines { - | Ok l => Ok (List.map f::extract_target_and_path l) - | Error error => raise (Failure (Printf.sprintf "Error reading '%s': %s" file error)) - } - ) { - | Failure msg => Error msg - } -}; - -let collect_all_stats_files () => { - let infer_out = Config.results_dir; - let concatenate_paths p1 p2 => - if (Filename.is_relative p2) { - Filename.concat p1 p2 - } else { - p2 - }; - switch Config.buck_out { - | Some p => - if (dir_exists p) { - let data = - load_data_from_infer_deps (Filename.concat infer_out Config.buck_infer_deps_file_name); - switch data { - | Ok r => - let buck_out_parent = Filename.concat p Filename.parent_dir_name; - let targets_files = - List.map - f::(fun (t, p) => (t, find_stats_files_in_dir (concatenate_paths buck_out_parent p))) - r; - Ok (Buck_out targets_files) - | Error _ as e => e - } - } else { - Error ("buck-out path '" ^ p ^ "' not found") - } - | None => Ok (Infer_out (find_stats_files_in_dir infer_out)) - } -}; - -let aggregate_stats_files paths => { - let open_json_file file => Yojson.Basic.from_file file; - let load_stats paths => - List.map f::(fun path => PerfStats.from_json (open_json_file path)) paths; - let all_perf_stats = load_stats paths; - switch all_perf_stats { - | [] => None - | _ => Some (PerfStats.aggregate all_perf_stats) - } -}; - -type json_aggregated_stats = { - frontend_json_data: option Yojson.Basic.json, - backend_json_data: option Yojson.Basic.json, - reporting_json_data: option Yojson.Basic.json -}; - -let aggregate_all_stats origin => { - let accumulate_paths acc paths => { - frontend_paths: paths.frontend_paths @ acc.frontend_paths, - backend_paths: paths.backend_paths @ acc.backend_paths, - reporting_paths: paths.reporting_paths @ acc.reporting_paths - }; - let empty_stats_paths = {frontend_paths: [], backend_paths: [], reporting_paths: []}; - let stats_paths = - switch origin { - | Buck_out tf => - List.fold f::(fun acc (_, paths) => accumulate_paths acc paths) init::empty_stats_paths tf - | Infer_out paths => paths - }; - { - frontend_json_data: aggregate_stats_files stats_paths.frontend_paths, - backend_json_data: aggregate_stats_files stats_paths.backend_paths, - reporting_json_data: aggregate_stats_files stats_paths.reporting_paths - } -}; - -let aggregate_stats_by_target tp => { - let to_json f aggr_stats => { - let collect_valid_stats acc t p => - switch p { - | Some v => [(t, v), ...acc] - | None => acc - }; - let l = List.fold f::(fun acc (t, p) => collect_valid_stats acc t (f p)) init::[] aggr_stats; - switch l { - | [] => None - | _ as v => Some (`Assoc v) - } - }; - let frontend_json_data = to_json (fun p => aggregate_stats_files p.frontend_paths) tp; - let backend_json_data = to_json (fun p => aggregate_stats_files p.backend_paths) tp; - let reporting_json_data = to_json (fun p => aggregate_stats_files p.reporting_paths) tp; - {frontend_json_data, backend_json_data, reporting_json_data} -}; - -let generate_files () => { - let infer_out = Config.results_dir; - let stats_files = collect_all_stats_files (); - let origin = - switch stats_files { - | Ok origin => origin - | Error e => failwith e - }; - let aggregated_frontend_stats_dir = Filename.concat infer_out Config.frontend_stats_dir_name; - let aggregated_backend_stats_dir = Filename.concat infer_out Config.backend_stats_dir_name; - let aggregated_reporting_stats_dir = Filename.concat infer_out Config.reporting_stats_dir_name; - Utils.create_dir aggregated_frontend_stats_dir; - Utils.create_dir aggregated_backend_stats_dir; - Utils.create_dir aggregated_reporting_stats_dir; - let write_to_json_file_opt destfile json => - switch json { - | Some j => Utils.write_json_to_file destfile j - | None => () - }; - switch origin { - | Buck_out tp => - let j = aggregate_stats_by_target tp; - write_to_json_file_opt - (Filename.concat aggregated_frontend_stats_dir aggregated_stats_by_target_filename) - j.frontend_json_data; - write_to_json_file_opt - (Filename.concat aggregated_backend_stats_dir aggregated_stats_by_target_filename) - j.backend_json_data; - write_to_json_file_opt - (Filename.concat aggregated_reporting_stats_dir aggregated_stats_by_target_filename) - j.reporting_json_data - | Infer_out _ => () - }; - let j = aggregate_all_stats origin; - write_to_json_file_opt - (Filename.concat aggregated_frontend_stats_dir aggregated_stats_filename) j.frontend_json_data; - write_to_json_file_opt - (Filename.concat aggregated_backend_stats_dir aggregated_stats_filename) j.backend_json_data; - write_to_json_file_opt - (Filename.concat aggregated_reporting_stats_dir aggregated_stats_filename) - j.reporting_json_data -}; diff --git a/infer/src/backend/Tasks.ml b/infer/src/backend/Tasks.ml index 129001108..0bacaf923 100644 --- a/infer/src/backend/Tasks.ml +++ b/infer/src/backend/Tasks.ml @@ -8,64 +8,50 @@ *) open! IStd - module F = Format - module L = Logging type closure = unit -> unit -type t = { - closures: closure list; - continuations: closure Queue.t; -} +type t = {closures: closure list; continuations: closure Queue.t} type tasks = t -let create ?(continuation = None) closures = - let continuations = match continuation with - | None -> - Queue.create () - | Some closure -> - Queue.singleton closure in - { closures; continuations } +let create ?(continuation= None) closures = + let continuations = + match continuation with None -> Queue.create () | Some closure -> Queue.singleton closure + in + {closures; continuations} -let empty = { closures = []; continuations = Queue.create () } +let empty = {closures= []; continuations= Queue.create ()} (* Aggregate closures into groups of the given size *) let aggregate ~size t = - let group_to_closure group = - fun () -> List.iter ~f:(fun closure -> closure ()) group in - let group_size = if size > 0 then size else (List.length t.closures) / Config.jobs in - if group_size > 1 - then + let group_to_closure group () = List.iter ~f:(fun closure -> closure ()) group in + let group_size = if size > 0 then size else List.length t.closures / Config.jobs in + if group_size > 1 then let groups = List.groupi ~break:(fun n _ _ -> Int.equal (n mod group_size) 0) t.closures in let closures = List.map ~f:group_to_closure groups in - { t with closures } - else - t + {t with closures} + else t let run t = - List.iter ~f:(fun f -> f ()) t.closures; + List.iter ~f:(fun f -> f ()) t.closures ; Queue.iter ~f:(fun closure -> closure ()) t.continuations module Runner = struct - type runner = - { pool : ProcessPool.t; - all_continuations : closure Queue.t } + type runner = {pool: ProcessPool.t; all_continuations: closure Queue.t} - let create ~jobs = - { pool = ProcessPool.create ~jobs; - all_continuations = Queue.create () } + let create ~jobs = {pool= ProcessPool.create ~jobs; all_continuations= Queue.create ()} let start runner ~tasks = let pool = runner.pool in - Queue.enqueue_all runner.all_continuations (Queue.to_list tasks.continuations); + Queue.enqueue_all runner.all_continuations (Queue.to_list tasks.continuations) ; List.iter - ~f:(fun x -> ProcessPool.start_child ~f:(fun f -> L.reset_formatters (); f ()) ~pool x) + ~f:(fun x -> ProcessPool.start_child ~f:(fun f -> L.reset_formatters () ; f ()) ~pool x) tasks.closures let complete runner = - ProcessPool.wait_all runner.pool; + ProcessPool.wait_all runner.pool ; Queue.iter ~f:(fun f -> f ()) runner.all_continuations end diff --git a/infer/src/backend/Tasks.mli b/infer/src/backend/Tasks.mli index cc5b6b63f..2e6ec854c 100644 --- a/infer/src/backend/Tasks.mli +++ b/infer/src/backend/Tasks.mli @@ -19,28 +19,29 @@ type tasks = t type closure = unit -> unit (* Aggregate closures into groups of the given size *) + val aggregate : size:int -> t -> t +val create : ?continuation:closure option -> closure list -> t (** Create tasks with a list of closures to be executed in parallel, and an optional continuation to be executed afterwards *) -val create : ?continuation:(closure option) -> closure list -> t -(** No-op tasks *) val empty : t +(** No-op tasks *) -(** Run the closures and continuation *) val run : t -> unit +(** Run the closures and continuation *) module Runner : sig (** A runner accepts new tasks repeatedly for parallel execution *) type runner + val create : jobs:int -> runner (** Create a runner *) - val create: jobs:int -> runner - (** Start the given tasks with the runner *) val start : runner -> tasks:t -> unit + (** Start the given tasks with the runner *) - (** Complete all the outstanding tasks *) val complete : runner -> unit + (** Complete all the outstanding tasks *) end diff --git a/infer/src/backend/abs.ml b/infer/src/backend/abs.ml index 38acede89..a4ba059d2 100644 --- a/infer/src/backend/abs.ml +++ b/infer/src/backend/abs.ml @@ -18,18 +18,20 @@ module F = Format (** {2 Abstraction} *) type rule = - { r_vars: Ident.t list; - r_root: Match.hpred_pat; - r_sigma: Match.hpred_pat list; (* sigma should be in a specific order *) - r_new_sigma: Sil.hpred list; - r_new_pi: Prop.normal Prop.t -> Prop.normal Prop.t -> Sil.exp_subst -> Sil.atom list; - r_condition: Prop.normal Prop.t -> Sil.exp_subst -> bool } + { r_vars: Ident.t list + ; r_root: Match.hpred_pat + ; r_sigma: Match.hpred_pat list + ; (* sigma should be in a specific order *) + r_new_sigma: Sil.hpred list + ; r_new_pi: Prop.normal Prop.t -> Prop.normal Prop.t -> Sil.exp_subst -> Sil.atom list + ; r_condition: Prop.normal Prop.t -> Sil.exp_subst -> bool } let sigma_rewrite tenv p r : Prop.normal Prop.t option = - match (Match.prop_match_with_impl tenv p r.r_condition r.r_vars r.r_root r.r_sigma) with - | None -> None - | Some(sub, p_leftover) -> - if not (r.r_condition p_leftover sub) then None + match Match.prop_match_with_impl tenv p r.r_condition r.r_vars r.r_root r.r_sigma with + | None + -> None + | Some (sub, p_leftover) + -> if not (r.r_condition p_leftover sub) then None else let res_pi = r.r_new_pi p p_leftover sub in let res_sigma = Prop.sigma_sub (`Exp sub) r.r_new_sigma in @@ -37,14 +39,11 @@ let sigma_rewrite tenv p r : Prop.normal Prop.t option = let p_new = Prop.prop_sigma_star p_with_res_pi res_sigma in Some (Prop.normalize tenv p_new) -let sigma_fav_list sigma = - Sil.fav_to_list (Prop.sigma_fav sigma) +let sigma_fav_list sigma = Sil.fav_to_list (Prop.sigma_fav sigma) -let sigma_fav_in_pvars = - Sil.fav_imperative_to_functional Prop.sigma_fav_in_pvars_add +let sigma_fav_in_pvars = Sil.fav_imperative_to_functional Prop.sigma_fav_in_pvars_add -let sigma_fav_in_pvars_list sigma = - Sil.fav_to_list (sigma_fav_in_pvars sigma) +let sigma_fav_in_pvars_list sigma = Sil.fav_to_list (sigma_fav_in_pvars sigma) (******************** Start of SLL abstraction rules *****************) let create_fresh_primeds_ls para = @@ -54,7 +53,8 @@ let create_fresh_primeds_ls para = let ids_shared = let svars = para.Sil.svars in let f _ = Ident.create_fresh Ident.kprimed in - List.map ~f svars in + List.map ~f svars + in let ids_tuple = (id_base, id_next, id_end, ids_shared) in let exp_base = Exp.Var id_base in let exp_next = Exp.Var id_next in @@ -64,18 +64,23 @@ let create_fresh_primeds_ls para = (ids_tuple, exps_tuple) let create_condition_ls ids_private id_base p_leftover (inst: Sil.exp_subst) = - let (insts_of_private_ids, insts_of_public_ids, inst_of_base) = + let insts_of_private_ids, insts_of_public_ids, inst_of_base = let f id' = List.exists ~f:(fun id'' -> Ident.equal id' id'') ids_private in - let (inst_private, inst_public) = Sil.sub_domain_partition f inst in + let inst_private, inst_public = Sil.sub_domain_partition f inst in let insts_of_public_ids = Sil.sub_range inst_public in - let inst_of_base = try Sil.sub_find (Ident.equal id_base) inst_public with Not_found -> assert false in + let inst_of_base = + try Sil.sub_find (Ident.equal id_base) inst_public + with Not_found -> assert false + in let insts_of_private_ids = Sil.sub_range inst_private in - (insts_of_private_ids, insts_of_public_ids, inst_of_base) in + (insts_of_private_ids, insts_of_public_ids, inst_of_base) + in let fav_insts_of_public_ids = List.concat_map ~f:Sil.exp_fav_list insts_of_public_ids in let fav_insts_of_private_ids = List.concat_map ~f:Sil.exp_fav_list insts_of_private_ids in - let (fav_p_leftover, _) = + let fav_p_leftover, _ = let sigma = p_leftover.Prop.sigma in - (sigma_fav_list sigma, sigma_fav_in_pvars_list sigma) in + (sigma_fav_list sigma, sigma_fav_in_pvars_list sigma) + in let fpv_inst_of_base = Sil.exp_fpv inst_of_base in let fpv_insts_of_private_ids = List.concat_map ~f:Sil.exp_fpv insts_of_private_ids in (* @@ -85,108 +90,124 @@ let create_condition_ls ids_private id_base p_leftover (inst: Sil.exp_subst) = L.out "@[<4> public ids : %a@\n@." pp_exp_list insts_of_public_ids; *) (* (not (IList.intersect compare fav_inst_of_base fav_in_pvars)) && *) - (List.is_empty fpv_inst_of_base) && - (List.is_empty fpv_insts_of_private_ids) && - (not (List.exists ~f:Ident.is_normal fav_insts_of_private_ids)) && - (not (IList.intersect Ident.compare fav_insts_of_private_ids fav_p_leftover)) && - (not (IList.intersect Ident.compare fav_insts_of_private_ids fav_insts_of_public_ids)) + List.is_empty fpv_inst_of_base && List.is_empty fpv_insts_of_private_ids + && not (List.exists ~f:Ident.is_normal fav_insts_of_private_ids) + && not (IList.intersect Ident.compare fav_insts_of_private_ids fav_p_leftover) + && not (IList.intersect Ident.compare fav_insts_of_private_ids fav_insts_of_public_ids) let mk_rule_ptspts_ls tenv impl_ok1 impl_ok2 (para: Sil.hpara) = - let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in - let (id_base, id_next, id_end, ids_shared) = ids_tuple in - let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in - let (ids_exist_fst, para_fst) = Sil.hpara_instantiate para exp_base exp_next exps_shared in - let (para_fst_start, para_fst_rest) = - let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + let ids_tuple, exps_tuple = create_fresh_primeds_ls para in + let id_base, id_next, id_end, ids_shared = ids_tuple in + let exp_base, exp_next, exp_end, exps_shared = exps_tuple in + let ids_exist_fst, para_fst = Sil.hpara_instantiate para exp_base exp_next exps_shared in + let para_fst_start, para_fst_rest = + let mark_impl_flag hpred = {Match.hpred= hpred; Match.flag= impl_ok1} in match para_fst with - | [] -> - L.internal_error "@\n@\nERROR (Empty Para): %a@\n@." (Sil.pp_hpara Pp.text) para; + | [] + -> L.internal_error "@\n@\nERROR (Empty Para): %a@\n@." (Sil.pp_hpara Pp.text) para ; assert false - | hpred :: hpreds -> - let hpat = mark_impl_flag hpred in + | hpred :: hpreds + -> let hpat = mark_impl_flag hpred in let hpats = List.map ~f:mark_impl_flag hpreds in - (hpat, hpats) in - let (ids_exist_snd, para_snd) = - let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in - let (ids, para_body) = Sil.hpara_instantiate para exp_next exp_end exps_shared in + (hpat, hpats) + in + let ids_exist_snd, para_snd = + let mark_impl_flag hpred = {Match.hpred= hpred; Match.flag= impl_ok2} in + let ids, para_body = Sil.hpara_instantiate para exp_next exp_end exps_shared in let para_body_hpats = List.map ~f:mark_impl_flag para_body in - (ids, para_body_hpats) in + (ids, para_body_hpats) + in let lseg_res = Prop.mk_lseg tenv Sil.Lseg_NE para exp_base exp_end exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = let ids_private = id_next :: (ids_exist_fst @ ids_exist_snd) in - create_condition_ls ids_private id_base in - { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist_fst @ ids_exist_snd; - r_root = para_fst_start; - r_sigma = para_fst_rest @ para_snd; - r_new_sigma = [lseg_res]; - r_new_pi = gen_pi_res; - r_condition = condition } + create_condition_ls ids_private id_base + in + { r_vars= id_base :: id_next :: id_end :: ids_shared @ ids_exist_fst @ ids_exist_snd + ; r_root= para_fst_start + ; r_sigma= para_fst_rest @ para_snd + ; r_new_sigma= [lseg_res] + ; r_new_pi= gen_pi_res + ; r_condition= condition } let mk_rule_ptsls_ls tenv k2 impl_ok1 impl_ok2 para = - let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in - let (id_base, id_next, id_end, ids_shared) = ids_tuple in - let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in - let (ids_exist, para_inst) = Sil.hpara_instantiate para exp_base exp_next exps_shared in - let (para_inst_start, para_inst_rest) = + let ids_tuple, exps_tuple = create_fresh_primeds_ls para in + let id_base, id_next, id_end, ids_shared = ids_tuple in + let exp_base, exp_next, exp_end, exps_shared = exps_tuple in + let ids_exist, para_inst = Sil.hpara_instantiate para exp_base exp_next exps_shared in + let para_inst_start, para_inst_rest = match para_inst with - | [] -> - L.internal_error "@\n@\nERROR (Empty Para): %a@\n@." (Sil.pp_hpara Pp.text) para; + | [] + -> L.internal_error "@\n@\nERROR (Empty Para): %a@\n@." (Sil.pp_hpara Pp.text) para ; assert false - | hpred :: hpreds -> - let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in - (allow_impl hpred, List.map ~f:allow_impl hpreds) in - let lseg_pat = { Match.hpred = Prop.mk_lseg tenv k2 para exp_next exp_end exps_shared; Match.flag = impl_ok2 } in + | hpred :: hpreds + -> let allow_impl hpred = {Match.hpred= hpred; Match.flag= impl_ok1} in + (allow_impl hpred, List.map ~f:allow_impl hpreds) + in + let lseg_pat = + {Match.hpred= Prop.mk_lseg tenv k2 para exp_next exp_end exps_shared; Match.flag= impl_ok2} + in let lseg_res = Prop.mk_lseg tenv Sil.Lseg_NE para exp_base exp_end exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = let ids_private = id_next :: ids_exist in - create_condition_ls ids_private id_base in - { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist; - r_root = para_inst_start; - r_sigma = para_inst_rest @ [lseg_pat]; - r_new_pi = gen_pi_res; - r_new_sigma = [lseg_res]; - r_condition = condition } + create_condition_ls ids_private id_base + in + { r_vars= id_base :: id_next :: id_end :: ids_shared @ ids_exist + ; r_root= para_inst_start + ; r_sigma= para_inst_rest @ [lseg_pat] + ; r_new_pi= gen_pi_res + ; r_new_sigma= [lseg_res] + ; r_condition= condition } let mk_rule_lspts_ls tenv k1 impl_ok1 impl_ok2 para = - let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in - let (id_base, id_next, id_end, ids_shared) = ids_tuple in - let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in - let lseg_pat = { Match.hpred = Prop.mk_lseg tenv k1 para exp_base exp_next exps_shared; Match.flag = impl_ok1 } in - let (ids_exist, para_inst_pat) = - let (ids, para_body) = Sil.hpara_instantiate para exp_next exp_end exps_shared in - let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in + let ids_tuple, exps_tuple = create_fresh_primeds_ls para in + let id_base, id_next, id_end, ids_shared = ids_tuple in + let exp_base, exp_next, exp_end, exps_shared = exps_tuple in + let lseg_pat = + {Match.hpred= Prop.mk_lseg tenv k1 para exp_base exp_next exps_shared; Match.flag= impl_ok1} + in + let ids_exist, para_inst_pat = + let ids, para_body = Sil.hpara_instantiate para exp_next exp_end exps_shared in + let allow_impl hpred = {Match.hpred= hpred; Match.flag= impl_ok2} in let para_body_pat = List.map ~f:allow_impl para_body in - (ids, para_body_pat) in + (ids, para_body_pat) + in let lseg_res = Prop.mk_lseg tenv Sil.Lseg_NE para exp_base exp_end exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = let ids_private = id_next :: ids_exist in - create_condition_ls ids_private id_base in - { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist; - r_root = lseg_pat; - r_sigma = para_inst_pat; - r_new_sigma = [lseg_res]; - r_new_pi = gen_pi_res; - r_condition = condition } - -let lseg_kind_add k1 k2 = match k1, k2 with - | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_NE -> Sil.Lseg_NE - | Sil.Lseg_PE, Sil.Lseg_PE -> Sil.Lseg_PE + create_condition_ls ids_private id_base + in + { r_vars= id_base :: id_next :: id_end :: ids_shared @ ids_exist + ; r_root= lseg_pat + ; r_sigma= para_inst_pat + ; r_new_sigma= [lseg_res] + ; r_new_pi= gen_pi_res + ; r_condition= condition } + +let lseg_kind_add k1 k2 = + match (k1, k2) with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_NE + -> Sil.Lseg_NE + | Sil.Lseg_PE, Sil.Lseg_PE + -> Sil.Lseg_PE let mk_rule_lsls_ls tenv k1 k2 impl_ok1 impl_ok2 para = - let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in - let (id_base, id_next, id_end, ids_shared) = ids_tuple in - let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in + let ids_tuple, exps_tuple = create_fresh_primeds_ls para in + let id_base, id_next, id_end, ids_shared = ids_tuple in + let exp_base, exp_next, exp_end, exps_shared = exps_tuple in let lseg_fst_pat = - { Match.hpred = Prop.mk_lseg tenv k1 para exp_base exp_next exps_shared; Match.flag = impl_ok1 } in + {Match.hpred= Prop.mk_lseg tenv k1 para exp_base exp_next exps_shared; Match.flag= impl_ok1} + in let lseg_snd_pat = - { Match.hpred = Prop.mk_lseg tenv k2 para exp_next exp_end exps_shared; Match.flag = impl_ok2 } in + {Match.hpred= Prop.mk_lseg tenv k2 para exp_next exp_end exps_shared; Match.flag= impl_ok2} + in let k_res = lseg_kind_add k1 k2 in let lseg_res = Prop.mk_lseg tenv k_res para exp_base exp_end exps_shared in - let gen_pi_res _ _ (_: Sil.exp_subst) = [] - (* + let gen_pi_res _ _ (_: Sil.exp_subst) = + [] + (* let inst_base, inst_next, inst_end = let find x = sub_find (equal x) inst in try @@ -206,35 +227,32 @@ let mk_rule_lsls_ls tenv k1 k2 impl_ok1 impl_ok2 para = in let condition = let ids_private = [id_next] in - create_condition_ls ids_private id_base in - { r_vars = id_base :: id_next :: id_end :: ids_shared ; - r_root = lseg_fst_pat; - r_sigma = [lseg_snd_pat]; - r_new_sigma = [lseg_res]; - r_new_pi = gen_pi_res; - r_condition = condition } - -let mk_rules_for_sll tenv (para : Sil.hpara) : rule list = + create_condition_ls ids_private id_base + in + { r_vars= id_base :: id_next :: id_end :: ids_shared + ; r_root= lseg_fst_pat + ; r_sigma= [lseg_snd_pat] + ; r_new_sigma= [lseg_res] + ; r_new_pi= gen_pi_res + ; r_condition= condition } + +let mk_rules_for_sll tenv (para: Sil.hpara) : rule list = if not Config.nelseg then - begin - let pts_pts = mk_rule_ptspts_ls tenv true true para in - let pts_pels = mk_rule_ptsls_ls tenv Sil.Lseg_PE true false para in - let pels_pts = mk_rule_lspts_ls tenv Sil.Lseg_PE false true para in - let pels_nels = mk_rule_lsls_ls tenv Sil.Lseg_PE Sil.Lseg_NE false false para in - let nels_pels = mk_rule_lsls_ls tenv Sil.Lseg_NE Sil.Lseg_PE false false para in - let pels_pels = mk_rule_lsls_ls tenv Sil.Lseg_PE Sil.Lseg_PE false false para in - [pts_pts; pts_pels; pels_pts; pels_nels; nels_pels; pels_pels] - end + let pts_pts = mk_rule_ptspts_ls tenv true true para in + let pts_pels = mk_rule_ptsls_ls tenv Sil.Lseg_PE true false para in + let pels_pts = mk_rule_lspts_ls tenv Sil.Lseg_PE false true para in + let pels_nels = mk_rule_lsls_ls tenv Sil.Lseg_PE Sil.Lseg_NE false false para in + let nels_pels = mk_rule_lsls_ls tenv Sil.Lseg_NE Sil.Lseg_PE false false para in + let pels_pels = mk_rule_lsls_ls tenv Sil.Lseg_PE Sil.Lseg_PE false false para in + [pts_pts; pts_pels; pels_pts; pels_nels; nels_pels; pels_pels] else - begin - let pts_pts = mk_rule_ptspts_ls tenv true true para in - let pts_nels = mk_rule_ptsls_ls tenv Sil.Lseg_NE true false para in - let nels_pts = mk_rule_lspts_ls tenv Sil.Lseg_NE false true para in - let nels_nels = mk_rule_lsls_ls tenv Sil.Lseg_NE Sil.Lseg_NE false false para in - [pts_pts; pts_nels; nels_pts; nels_nels] - end -(****************** End of SLL abstraction rules ******************) + let pts_pts = mk_rule_ptspts_ls tenv true true para in + let pts_nels = mk_rule_ptsls_ls tenv Sil.Lseg_NE true false para in + let nels_pts = mk_rule_lspts_ls tenv Sil.Lseg_NE false true para in + let nels_nels = mk_rule_lsls_ls tenv Sil.Lseg_NE Sil.Lseg_NE false false para in + [pts_pts; pts_nels; nels_pts; nels_nels] +(****************** End of SLL abstraction rules ******************) (****************** Start of DLL abstraction rules ******************) let create_condition_dll = create_condition_ls @@ -246,46 +264,50 @@ let mk_rule_ptspts_dll tenv impl_ok1 impl_ok2 para = let ids_shared = let svars = para.Sil.svars_dll in let f _ = Ident.create_fresh Ident.kprimed in - List.map ~f svars in + List.map ~f svars + in let exp_iF = Exp.Var id_iF in let exp_iF' = Exp.Var id_iF' in let exp_oB = Exp.Var id_oB in let exp_oF = Exp.Var id_oF in let exps_shared = List.map ~f:(fun id -> Exp.Var id) ids_shared in - let (ids_exist_fst, para_fst) = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in - let (para_fst_start, para_fst_rest) = - let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + let ids_exist_fst, para_fst = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in + let para_fst_start, para_fst_rest = + let mark_impl_flag hpred = {Match.hpred= hpred; Match.flag= impl_ok1} in match para_fst with - | [] -> - L.internal_error "@\n@\nERROR (Empty DLL Para): %a@\n@." (Sil.pp_hpara_dll Pp.text) para; + | [] + -> L.internal_error "@\n@\nERROR (Empty DLL Para): %a@\n@." (Sil.pp_hpara_dll Pp.text) para ; assert false - | hpred :: hpreds -> - let hpat = mark_impl_flag hpred in + | hpred :: hpreds + -> let hpat = mark_impl_flag hpred in let hpats = List.map ~f:mark_impl_flag hpreds in - (hpat, hpats) in - let (ids_exist_snd, para_snd) = - let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in - let (ids, para_body) = Sil.hpara_dll_instantiate para exp_iF' exp_iF exp_oF exps_shared in + (hpat, hpats) + in + let ids_exist_snd, para_snd = + let mark_impl_flag hpred = {Match.hpred= hpred; Match.flag= impl_ok2} in + let ids, para_body = Sil.hpara_dll_instantiate para exp_iF' exp_iF exp_oF exps_shared in let para_body_hpats = List.map ~f:mark_impl_flag para_body in - (ids, para_body_hpats) in + (ids, para_body_hpats) + in let dllseg_res = Prop.mk_dllseg tenv Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iF' exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = (* for the case of ptspts since iF'=iB therefore iF' cannot be private*) let ids_private = ids_exist_fst @ ids_exist_snd in - create_condition_dll ids_private id_iF in + create_condition_dll ids_private id_iF + in (* L.out "r_root/para_fst_start=%a @.@." pp_hpat para_fst_start; L.out "para_fst_rest=%a @.@." pp_hpat_list para_fst_rest; L.out "para_snd=%a @.@." pp_hpat_list para_snd; L.out "dllseg_res=%a @.@." pp_hpred dllseg_res; *) - { r_vars = id_iF :: id_oB :: id_iF':: id_oF :: ids_shared @ ids_exist_fst @ ids_exist_snd; - r_root = para_fst_start; - r_sigma = para_fst_rest @ para_snd; - r_new_sigma = [dllseg_res]; - r_new_pi = gen_pi_res; - r_condition = condition } + { r_vars= id_iF :: id_oB :: id_iF' :: id_oF :: ids_shared @ ids_exist_fst @ ids_exist_snd + ; r_root= para_fst_start + ; r_sigma= para_fst_rest @ para_snd + ; r_new_sigma= [dllseg_res] + ; r_new_pi= gen_pi_res + ; r_condition= condition } let mk_rule_ptsdll_dll tenv k2 impl_ok1 impl_ok2 para = let id_iF = Ident.create_fresh Ident.kprimed in @@ -296,32 +318,39 @@ let mk_rule_ptsdll_dll tenv k2 impl_ok1 impl_ok2 para = let ids_shared = let svars = para.Sil.svars_dll in let f _ = Ident.create_fresh Ident.kprimed in - List.map ~f svars in + List.map ~f svars + in let exp_iF = Exp.Var id_iF in let exp_iF' = Exp.Var id_iF' in let exp_oB = Exp.Var id_oB in let exp_oF = Exp.Var id_oF in let exp_iB = Exp.Var id_iB in let exps_shared = List.map ~f:(fun id -> Exp.Var id) ids_shared in - let (ids_exist, para_inst) = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in - let (para_inst_start, para_inst_rest) = + let ids_exist, para_inst = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in + let para_inst_start, para_inst_rest = match para_inst with - | [] -> assert false - | hpred :: hpreds -> - let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in - (allow_impl hpred, List.map ~f:allow_impl hpreds) in - let dllseg_pat = { Match.hpred = Prop.mk_dllseg tenv k2 para exp_iF' exp_iF exp_oF exp_iB exps_shared; Match.flag = impl_ok2 } in + | [] + -> assert false + | hpred :: hpreds + -> let allow_impl hpred = {Match.hpred= hpred; Match.flag= impl_ok1} in + (allow_impl hpred, List.map ~f:allow_impl hpreds) + in + let dllseg_pat = + { Match.hpred= Prop.mk_dllseg tenv k2 para exp_iF' exp_iF exp_oF exp_iB exps_shared + ; Match.flag= impl_ok2 } + in let dllseg_res = Prop.mk_dllseg tenv Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iB exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = - let ids_private = id_iF':: ids_exist in - create_condition_dll ids_private id_iF in - { r_vars = id_iF :: id_oB :: id_iF':: id_oF:: id_iB:: ids_shared @ ids_exist; - r_root = para_inst_start; - r_sigma = para_inst_rest @ [dllseg_pat]; - r_new_pi = gen_pi_res; - r_new_sigma = [dllseg_res]; - r_condition = condition } + let ids_private = id_iF' :: ids_exist in + create_condition_dll ids_private id_iF + in + { r_vars= id_iF :: id_oB :: id_iF' :: id_oF :: id_iB :: ids_shared @ ids_exist + ; r_root= para_inst_start + ; r_sigma= para_inst_rest @ [dllseg_pat] + ; r_new_pi= gen_pi_res + ; r_new_sigma= [dllseg_res] + ; r_condition= condition } let mk_rule_dllpts_dll tenv k1 impl_ok1 impl_ok2 para = let id_iF = Ident.create_fresh Ident.kprimed in @@ -332,29 +361,35 @@ let mk_rule_dllpts_dll tenv k1 impl_ok1 impl_ok2 para = let ids_shared = let svars = para.Sil.svars_dll in let f _ = Ident.create_fresh Ident.kprimed in - List.map ~f svars in + List.map ~f svars + in let exp_iF = Exp.Var id_iF in let exp_iF' = Exp.Var id_iF' in let exp_oB = Exp.Var id_oB in let exp_oB' = Exp.Var id_oB' in let exp_oF = Exp.Var id_oF in let exps_shared = List.map ~f:(fun id -> Exp.Var id) ids_shared in - let (ids_exist, para_inst) = Sil.hpara_dll_instantiate para exp_iF' exp_oB' exp_oF exps_shared in + let ids_exist, para_inst = Sil.hpara_dll_instantiate para exp_iF' exp_oB' exp_oF exps_shared in let para_inst_pat = - let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in - List.map ~f:allow_impl para_inst in - let dllseg_pat = { Match.hpred = Prop.mk_dllseg tenv k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared; Match.flag = impl_ok1 } in + let allow_impl hpred = {Match.hpred= hpred; Match.flag= impl_ok2} in + List.map ~f:allow_impl para_inst + in + let dllseg_pat = + { Match.hpred= Prop.mk_dllseg tenv k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared + ; Match.flag= impl_ok1 } + in let dllseg_res = Prop.mk_dllseg tenv Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iF' exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = - let ids_private = id_oB':: ids_exist in - create_condition_dll ids_private id_iF in - { r_vars = id_iF :: id_oB :: id_iF':: id_oB':: id_oF:: ids_shared @ ids_exist; - r_root = dllseg_pat; - r_sigma = para_inst_pat; - r_new_pi = gen_pi_res; - r_new_sigma = [dllseg_res]; - r_condition = condition } + let ids_private = id_oB' :: ids_exist in + create_condition_dll ids_private id_iF + in + { r_vars= id_iF :: id_oB :: id_iF' :: id_oB' :: id_oF :: ids_shared @ ids_exist + ; r_root= dllseg_pat + ; r_sigma= para_inst_pat + ; r_new_pi= gen_pi_res + ; r_new_sigma= [dllseg_res] + ; r_condition= condition } let mk_rule_dlldll_dll tenv k1 k2 impl_ok1 impl_ok2 para = let id_iF = Ident.create_fresh Ident.kprimed in @@ -366,7 +401,8 @@ let mk_rule_dlldll_dll tenv k1 k2 impl_ok1 impl_ok2 para = let ids_shared = let svars = para.Sil.svars_dll in let f _ = Ident.create_fresh Ident.kprimed in - List.map ~f svars in + List.map ~f svars + in let exp_iF = Exp.Var id_iF in let exp_iF' = Exp.Var id_iF' in let exp_oB = Exp.Var id_oB in @@ -374,87 +410,93 @@ let mk_rule_dlldll_dll tenv k1 k2 impl_ok1 impl_ok2 para = let exp_oF = Exp.Var id_oF in let exp_iB = Exp.Var id_iB in let exps_shared = List.map ~f:(fun id -> Exp.Var id) ids_shared in - let lseg_fst_pat = { Match.hpred = Prop.mk_dllseg tenv k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared; Match.flag = impl_ok1 } in - let lseg_snd_pat = { Match.hpred = Prop.mk_dllseg tenv k2 para exp_iF' exp_oB' exp_oF exp_iB exps_shared; Match.flag = impl_ok2 } in + let lseg_fst_pat = + { Match.hpred= Prop.mk_dllseg tenv k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared + ; Match.flag= impl_ok1 } + in + let lseg_snd_pat = + { Match.hpred= Prop.mk_dllseg tenv k2 para exp_iF' exp_oB' exp_oF exp_iB exps_shared + ; Match.flag= impl_ok2 } + in let k_res = lseg_kind_add k1 k2 in let lseg_res = Prop.mk_dllseg tenv k_res para exp_iF exp_oB exp_oF exp_iB exps_shared in let gen_pi_res _ _ (_: Sil.exp_subst) = [] in let condition = let ids_private = [id_iF'; id_oB'] in - create_condition_dll ids_private id_iF in - { r_vars = id_iF :: id_iF' :: id_oB:: id_oB' :: id_oF:: id_iB:: ids_shared ; - r_root = lseg_fst_pat; - r_sigma = [lseg_snd_pat]; - r_new_sigma = [lseg_res]; - r_new_pi = gen_pi_res; - r_condition = condition } - -let mk_rules_for_dll tenv (para : Sil.hpara_dll) : rule list = + create_condition_dll ids_private id_iF + in + { r_vars= id_iF :: id_iF' :: id_oB :: id_oB' :: id_oF :: id_iB :: ids_shared + ; r_root= lseg_fst_pat + ; r_sigma= [lseg_snd_pat] + ; r_new_sigma= [lseg_res] + ; r_new_pi= gen_pi_res + ; r_condition= condition } + +let mk_rules_for_dll tenv (para: Sil.hpara_dll) : rule list = if not Config.nelseg then - begin - let pts_pts = mk_rule_ptspts_dll tenv true true para in - let pts_pedll = mk_rule_ptsdll_dll tenv Sil.Lseg_PE true false para in - let pedll_pts = mk_rule_dllpts_dll tenv Sil.Lseg_PE false true para in - let pedll_nedll = mk_rule_dlldll_dll tenv Sil.Lseg_PE Sil.Lseg_NE false false para in - let nedll_pedll = mk_rule_dlldll_dll tenv Sil.Lseg_NE Sil.Lseg_PE false false para in - let pedll_pedll = mk_rule_dlldll_dll tenv Sil.Lseg_PE Sil.Lseg_PE false false para in - [pts_pts; pts_pedll; pedll_pts; pedll_nedll; nedll_pedll; pedll_pedll] - end + let pts_pts = mk_rule_ptspts_dll tenv true true para in + let pts_pedll = mk_rule_ptsdll_dll tenv Sil.Lseg_PE true false para in + let pedll_pts = mk_rule_dllpts_dll tenv Sil.Lseg_PE false true para in + let pedll_nedll = mk_rule_dlldll_dll tenv Sil.Lseg_PE Sil.Lseg_NE false false para in + let nedll_pedll = mk_rule_dlldll_dll tenv Sil.Lseg_NE Sil.Lseg_PE false false para in + let pedll_pedll = mk_rule_dlldll_dll tenv Sil.Lseg_PE Sil.Lseg_PE false false para in + [pts_pts; pts_pedll; pedll_pts; pedll_nedll; nedll_pedll; pedll_pedll] else - begin - let ptspts_dll = mk_rule_ptspts_dll tenv true true para in - let ptsdll_dll = mk_rule_ptsdll_dll tenv Sil.Lseg_NE true false para in - let dllpts_dll = mk_rule_dllpts_dll tenv Sil.Lseg_NE false true para in - let dlldll_dll = mk_rule_dlldll_dll tenv Sil.Lseg_NE Sil.Lseg_NE false false para in - [ptspts_dll; ptsdll_dll; dllpts_dll; dlldll_dll] - end -(****************** End of DLL abstraction rules ******************) + let ptspts_dll = mk_rule_ptspts_dll tenv true true para in + let ptsdll_dll = mk_rule_ptsdll_dll tenv Sil.Lseg_NE true false para in + let dllpts_dll = mk_rule_dllpts_dll tenv Sil.Lseg_NE false true para in + let dlldll_dll = mk_rule_dlldll_dll tenv Sil.Lseg_NE Sil.Lseg_NE false false para in + [ptspts_dll; ptsdll_dll; dllpts_dll; dlldll_dll] +(****************** End of DLL abstraction rules ******************) (****************** Start of Predicate Discovery ******************) let typ_get_recursive_flds tenv typ_exp = let filter typ (_, (t: Typ.t), _) = match t.desc with - | Tstruct _ | Tint _ | Tfloat _ | Tvoid | Tfun _ | TVar _ -> - false - | Tptr ({desc=Tstruct _} as typ', _) -> - Typ.equal typ' typ - | Tptr _ | Tarray _ -> - false + | Tstruct _ | Tint _ | Tfloat _ | Tvoid | Tfun _ | TVar _ + -> false + | Tptr (({desc= Tstruct _} as typ'), _) + -> Typ.equal typ' typ + | Tptr _ | Tarray _ + -> false in match typ_exp with | Exp.Sizeof {typ} -> ( - match typ.desc with - | Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { fields } -> List.map ~f:fst3 (List.filter ~f:(filter typ) fields) - | None -> - L.(debug Analysis Quiet) - "@\ntyp_get_recursive_flds: unexpected %a unknown struct type: %a@." - Exp.pp typ_exp - Typ.Name.pp name; - [] (* ToDo: assert false *) - ) - | Tint _ | Tvoid | Tfun _ | Tptr _ | Tfloat _ | Tarray _ | TVar _ -> [] - ) - | Exp.Var _ -> [] (* type of |-> not known yet *) - | Exp.Const _ -> [] - | _ -> - L.internal_error "@\ntyp_get_recursive_flds: unexpected type expr: %a@." Exp.pp typ_exp; + match typ.desc with + | Tstruct name -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> List.map ~f:fst3 (List.filter ~f:(filter typ) fields) + | None + -> L.(debug Analysis Quiet) + "@\ntyp_get_recursive_flds: unexpected %a unknown struct type: %a@." Exp.pp typ_exp + Typ.Name.pp name ; + [] + (* ToDo: assert false *) ) + | Tint _ | Tvoid | Tfun _ | Tptr _ | Tfloat _ | Tarray _ | TVar _ + -> [] ) + | Exp.Var _ + -> [] (* type of |-> not known yet *) + | Exp.Const _ + -> [] + | _ + -> L.internal_error "@\ntyp_get_recursive_flds: unexpected type expr: %a@." Exp.pp typ_exp ; assert false let discover_para_roots tenv p root1 next1 root2 next2 : Sil.hpara option = let eq_arg1 = Exp.equal root1 next1 in let eq_arg2 = Exp.equal root2 next2 in - let precondition_check = (not eq_arg1 && not eq_arg2) in + let precondition_check = not eq_arg1 && not eq_arg2 in if not precondition_check then None else let corres = [(next1, next2)] in let todos = [(root1, root2)] in let sigma = p.Prop.sigma in match Match.find_partial_iso tenv (Prover.check_equal tenv p) corres todos sigma with - | None -> None - | Some (new_corres, new_sigma1, _, _) -> - let hpara, _ = Match.hpara_create tenv new_corres new_sigma1 root1 next1 in + | None + -> None + | Some (new_corres, new_sigma1, _, _) + -> let hpara, _ = Match.hpara_create tenv new_corres new_sigma1 root1 next1 in Some hpara let discover_para_dll_roots tenv p root1 blink1 flink1 root2 blink2 flink2 : Sil.hpara_dll option = @@ -469,9 +511,10 @@ let discover_para_dll_roots tenv p root1 blink1 flink1 root2 blink2 flink2 : Sil let todos = [(root1, root2)] in let sigma = p.Prop.sigma in match Match.find_partial_iso tenv (Prover.check_equal tenv p) corres todos sigma with - | None -> None - | Some (new_corres, new_sigma1, _, _) -> - let hpara_dll, _ = Match.hpara_dll_create tenv new_corres new_sigma1 root1 blink1 flink1 in + | None + -> None + | Some (new_corres, new_sigma1, _, _) + -> let hpara_dll, _ = Match.hpara_dll_create tenv new_corres new_sigma1 root1 blink1 flink1 in Some hpara_dll let discover_para_candidates tenv p = @@ -480,84 +523,102 @@ let discover_para_candidates tenv p = let get_edges_strexp rec_flds root se = let is_rec_fld fld = List.exists ~f:(Typ.Fieldname.equal fld) rec_flds in match se with - | Sil.Eexp _ | Sil.Earray _ -> () - | Sil.Estruct (fsel, _) -> - let fsel' = List.filter ~f:(fun (fld, _) -> is_rec_fld fld) fsel in + | Sil.Eexp _ | Sil.Earray _ + -> () + | Sil.Estruct (fsel, _) + -> let fsel' = List.filter ~f:(fun (fld, _) -> is_rec_fld fld) fsel in let process (_, nextse) = - match nextse with - | Sil.Eexp (next, _) -> add_edge (root, next) - | _ -> assert false in - List.iter ~f:process fsel' in + match nextse with Sil.Eexp (next, _) -> add_edge (root, next) | _ -> assert false + in + List.iter ~f:process fsel' + in let rec get_edges_sigma = function - | [] -> () - | Sil.Hlseg _ :: sigma_rest | Sil.Hdllseg _ :: sigma_rest -> - get_edges_sigma sigma_rest - | Sil.Hpointsto (root, se, te) :: sigma_rest -> - let rec_flds = typ_get_recursive_flds tenv te in - get_edges_strexp rec_flds root se; - get_edges_sigma sigma_rest in + | [] + -> () + | (Sil.Hlseg _) :: sigma_rest | (Sil.Hdllseg _) :: sigma_rest + -> get_edges_sigma sigma_rest + | (Sil.Hpointsto (root, se, te)) :: sigma_rest + -> let rec_flds = typ_get_recursive_flds tenv te in + get_edges_strexp rec_flds root se ; get_edges_sigma sigma_rest + in let rec find_all_consecutive_edges found edges_seen = function - | [] -> List.rev found - | (e1, e2) :: edges_notseen -> - let edges_others = List.rev_append edges_seen edges_notseen in + | [] + -> List.rev found + | (e1, e2) :: edges_notseen + -> let edges_others = List.rev_append edges_seen edges_notseen in let edges_matched = List.filter ~f:(fun (e1', _) -> Exp.equal e2 e1') edges_others in let new_found = let f found_acc (_, e3) = (e1, e2, e3) :: found_acc in - List.fold ~f ~init:found edges_matched in + List.fold ~f ~init:found edges_matched + in let new_edges_seen = (e1, e2) :: edges_seen in - find_all_consecutive_edges new_found new_edges_seen edges_notseen in + find_all_consecutive_edges new_found new_edges_seen edges_notseen + in let sigma = p.Prop.sigma in - get_edges_sigma sigma; + get_edges_sigma sigma ; find_all_consecutive_edges [] [] !edges let discover_para_dll_candidates tenv p = let edges = ref [] in - let add_edge edg = (edges := edg :: !edges) in + let add_edge edg = edges := edg :: !edges in let get_edges_strexp rec_flds root se = let is_rec_fld fld = List.exists ~f:(Typ.Fieldname.equal fld) rec_flds in match se with - | Sil.Eexp _ | Sil.Earray _ -> () - | Sil.Estruct (fsel, _) -> - let fsel' = List.rev_filter ~f:(fun (fld, _) -> is_rec_fld fld) fsel in + | Sil.Eexp _ | Sil.Earray _ + -> () + | Sil.Estruct (fsel, _) + -> let fsel' = List.rev_filter ~f:(fun (fld, _) -> is_rec_fld fld) fsel in let convert_to_exp acc (_, se) = - match se with - | Sil.Eexp (e, _) -> e:: acc - | _ -> assert false in + match se with Sil.Eexp (e, _) -> e :: acc | _ -> assert false + in let links = List.fold ~f:convert_to_exp ~init:[] fsel' in let rec iter_pairs = function - | [] -> () - | x:: l -> (List.iter ~f:(fun y -> add_edge (root, x, y)) l; iter_pairs l) in - iter_pairs links in + | [] + -> () + | x :: l + -> List.iter ~f:(fun y -> add_edge (root, x, y)) l ; + iter_pairs l + in + iter_pairs links + in let rec get_edges_sigma = function - | [] -> () - | Sil.Hlseg _ :: sigma_rest | Sil.Hdllseg _ :: sigma_rest -> - get_edges_sigma sigma_rest - | Sil.Hpointsto (root, se, te) :: sigma_rest -> - let rec_flds = typ_get_recursive_flds tenv te in - get_edges_strexp rec_flds root se; - get_edges_sigma sigma_rest in + | [] + -> () + | (Sil.Hlseg _) :: sigma_rest | (Sil.Hdllseg _) :: sigma_rest + -> get_edges_sigma sigma_rest + | (Sil.Hpointsto (root, se, te)) :: sigma_rest + -> let rec_flds = typ_get_recursive_flds tenv te in + get_edges_strexp rec_flds root se ; get_edges_sigma sigma_rest + in let rec find_all_consecutive_edges found edges_seen = function - | [] -> List.rev found - | (iF, blink, flink) :: edges_notseen -> - let edges_others = List.rev_append edges_seen edges_notseen in + | [] + -> List.rev found + | (iF, blink, flink) :: edges_notseen + -> let edges_others = List.rev_append edges_seen edges_notseen in let edges_matched = List.filter ~f:(fun (e1', _, _) -> Exp.equal flink e1') edges_others in let new_found = let f found_acc (_, _, flink2) = (iF, blink, flink, flink2) :: found_acc in - List.fold ~f ~init:found edges_matched in + List.fold ~f ~init:found edges_matched + in let new_edges_seen = (iF, blink, flink) :: edges_seen in - find_all_consecutive_edges new_found new_edges_seen edges_notseen in + find_all_consecutive_edges new_found new_edges_seen edges_notseen + in let sigma = p.Prop.sigma in - get_edges_sigma sigma; + get_edges_sigma sigma ; find_all_consecutive_edges [] [] !edges let discover_para tenv p = let candidates = discover_para_candidates tenv p in let already_defined para paras = - List.exists ~f:(fun para' -> Match.hpara_iso tenv para para') paras in + List.exists ~f:(fun para' -> Match.hpara_iso tenv para para') paras + in let f paras (root, next, out) = - match (discover_para_roots tenv p root next next out) with - | None -> paras - | Some para -> if already_defined para paras then paras else para :: paras in + match discover_para_roots tenv p root next next out with + | None + -> paras + | Some para + -> if already_defined para paras then paras else para :: paras + in List.fold ~f ~init:[] candidates let discover_para_dll tenv p = @@ -567,40 +628,37 @@ let discover_para_dll tenv p = *) let candidates = discover_para_dll_candidates tenv p in let already_defined para paras = - List.exists ~f:(fun para' -> Match.hpara_dll_iso tenv para para') paras in + List.exists ~f:(fun para' -> Match.hpara_dll_iso tenv para para') paras + in let f paras (iF, oB, iF', oF) = - match (discover_para_dll_roots tenv p iF oB iF' iF' iF oF) with - | None -> paras - | Some para -> if already_defined para paras then paras else para :: paras in + match discover_para_dll_roots tenv p iF oB iF' iF' iF oF with + | None + -> paras + | Some para + -> if already_defined para paras then paras else para :: paras + in List.fold ~f ~init:[] candidates -(****************** End of Predicate Discovery ******************) +(****************** End of Predicate Discovery ******************) (****************** Start of the ADT abs_rules ******************) (** Type of parameter for abstraction rules *) -type para_ty = - | SLL of Sil.hpara - | DLL of Sil.hpara_dll +type para_ty = SLL of Sil.hpara | DLL of Sil.hpara_dll (** Rule set: a list of rules of a given type *) type rule_set = para_ty * rule list type rules = rule_set list -module Global = -struct - let current_rules : rules ref = - ref [] +module Global = struct + let current_rules : rules ref = ref [] end -let get_current_rules () = - !Global.current_rules +let get_current_rules () = !Global.current_rules -let set_current_rules rules = - Global.current_rules := rules +let set_current_rules rules = Global.current_rules := rules -let reset_current_rules () = - Global.current_rules := [] +let reset_current_rules () = Global.current_rules := [] let eqs_sub subst eqs = List.map ~f:(fun (e1, e2) -> (Sil.exp_sub (`Exp subst) e1, Sil.exp_sub (`Exp subst) e2)) eqs @@ -610,63 +668,65 @@ let eqs_solve ids_in eqs_in = let do_default id e eqs_rest = if not (List.exists ~f:(fun id' -> Ident.equal id id') ids_in) then None else - let sub' = match Sil.extend_sub sub id e with - | None -> - L.internal_error "@\n@\nERROR : Buggy Implementation.@\n@."; + let sub' = + match Sil.extend_sub sub id e with + | None + -> L.internal_error "@\n@\nERROR : Buggy Implementation.@\n@." ; assert false - | Some sub' -> sub' in + | Some sub' + -> sub' + in let eqs_rest' = eqs_sub sub' eqs_rest in - solve sub' eqs_rest' in + solve sub' eqs_rest' + in match eqs with - | [] -> Some sub - | (e1, e2) :: eqs_rest when Exp.equal e1 e2 -> - solve sub eqs_rest - | (Exp.Var id1, (Exp.Const _ as e2)) :: eqs_rest -> - do_default id1 e2 eqs_rest - | ((Exp.Const _ as e1), (Exp.Var _ as e2)) :: eqs_rest -> - solve sub ((e2, e1):: eqs_rest) - | ((Exp.Var id1 as e1), (Exp.Var id2 as e2)) :: eqs_rest -> - let n = Ident.compare id1 id2 in - begin - if Int.equal n 0 then solve sub eqs_rest - else if n > 0 then solve sub ((e2, e1):: eqs_rest) - else do_default id1 e2 eqs_rest - end - | _ :: _ -> None in + | [] + -> Some sub + | (e1, e2) :: eqs_rest when Exp.equal e1 e2 + -> solve sub eqs_rest + | (Exp.Var id1, (Exp.Const _ as e2)) :: eqs_rest + -> do_default id1 e2 eqs_rest + | ((Exp.Const _ as e1), (Exp.Var _ as e2)) :: eqs_rest + -> solve sub ((e2, e1) :: eqs_rest) + | ((Exp.Var id1 as e1), (Exp.Var id2 as e2)) :: eqs_rest + -> let n = Ident.compare id1 id2 in + if Int.equal n 0 then solve sub eqs_rest + else if n > 0 then solve sub ((e2, e1) :: eqs_rest) + else do_default id1 e2 eqs_rest + | _ :: _ + -> None + in let compute_ids sub = let sub_list = Sil.sub_to_list sub in let sub_dom = List.map ~f:fst sub_list in - let filter id = - not (List.exists ~f:(fun id' -> Ident.equal id id') sub_dom) in - List.filter ~f:filter ids_in in - match solve Sil.exp_sub_empty eqs_in with - | None -> None - | Some sub -> Some (compute_ids sub, sub) + let filter id = not (List.exists ~f:(fun id' -> Ident.equal id id') sub_dom) in + List.filter ~f:filter ids_in + in + match solve Sil.exp_sub_empty eqs_in with None -> None | Some sub -> Some (compute_ids sub, sub) let sigma_special_cases_eqs sigma = let rec f ids_acc eqs_acc sigma_acc = function - | [] -> - [(List.rev ids_acc, List.rev eqs_acc, List.rev sigma_acc)] - | Sil.Hpointsto _ as hpred :: sigma_rest -> - f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest - | Sil.Hlseg(_, para, e1, e2, es) as hpred :: sigma_rest -> - let empty_case = - f ids_acc ((e1, e2):: eqs_acc) sigma_acc sigma_rest in + | [] + -> [(List.rev ids_acc, List.rev eqs_acc, List.rev sigma_acc)] + | (Sil.Hpointsto _ as hpred) :: sigma_rest + -> f ids_acc eqs_acc (hpred :: sigma_acc) sigma_rest + | (Sil.Hlseg (_, para, e1, e2, es) as hpred) :: sigma_rest + -> let empty_case = f ids_acc ((e1, e2) :: eqs_acc) sigma_acc sigma_rest in let pointsto_case = - let (eids, para_inst) = Sil.hpara_instantiate para e1 e2 es in - f (eids@ids_acc) eqs_acc sigma_acc (para_inst@sigma_rest) in - let general_case = - f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest in + let eids, para_inst = Sil.hpara_instantiate para e1 e2 es in + f (eids @ ids_acc) eqs_acc sigma_acc (para_inst @ sigma_rest) + in + let general_case = f ids_acc eqs_acc (hpred :: sigma_acc) sigma_rest in empty_case @ pointsto_case @ general_case - | Sil.Hdllseg(_, para, e1, e2, e3, e4, es) as hpred :: sigma_rest -> - let empty_case = - f ids_acc ((e1, e3):: (e2, e4):: eqs_acc) sigma_acc sigma_rest in + | (Sil.Hdllseg (_, para, e1, e2, e3, e4, es) as hpred) :: sigma_rest + -> let empty_case = f ids_acc ((e1, e3) :: (e2, e4) :: eqs_acc) sigma_acc sigma_rest in let pointsto_case = - let (eids, para_inst) = Sil.hpara_dll_instantiate para e1 e2 e3 es in - f (eids@ids_acc) eqs_acc sigma_acc (para_inst@sigma_rest) in - let general_case = - f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest in - empty_case @ pointsto_case @ general_case in + let eids, para_inst = Sil.hpara_dll_instantiate para e1 e2 e3 es in + f (eids @ ids_acc) eqs_acc sigma_acc (para_inst @ sigma_rest) + in + let general_case = f ids_acc eqs_acc (hpred :: sigma_acc) sigma_rest in + empty_case @ pointsto_case @ general_case + in f [] [] [] sigma let sigma_special_cases ids sigma : (Ident.t list * Sil.hpred list) list = @@ -674,132 +734,145 @@ let sigma_special_cases ids sigma : (Ident.t list * Sil.hpred list) list = let special_cases_rev = let f acc (eids_cur, eqs_cur, sigma_cur) = let ids_all = ids @ eids_cur in - match (eqs_solve ids_all eqs_cur) with - | None -> acc - | Some (ids_res, sub) -> - (ids_res, List.map ~f:(Sil.hpred_sub (`Exp sub)) sigma_cur) :: acc in - List.fold ~f ~init:[] special_cases_eqs in + match eqs_solve ids_all eqs_cur with + | None + -> acc + | Some (ids_res, sub) + -> (ids_res, List.map ~f:(Sil.hpred_sub (`Exp sub)) sigma_cur) :: acc + in + List.fold ~f ~init:[] special_cases_eqs + in List.rev special_cases_rev let hpara_special_cases hpara : Sil.hpara list = - let update_para (evars', body') = { hpara with Sil.evars = evars'; Sil.body = body'} in + let update_para (evars', body') = {hpara with Sil.evars= evars'; Sil.body= body'} in let special_cases = sigma_special_cases hpara.Sil.evars hpara.Sil.body in List.map ~f:update_para special_cases let hpara_special_cases_dll hpara : Sil.hpara_dll list = - let update_para (evars', body') = { hpara with Sil.evars_dll = evars'; Sil.body_dll = body'} in + let update_para (evars', body') = {hpara with Sil.evars_dll= evars'; Sil.body_dll= body'} in let special_cases = sigma_special_cases hpara.Sil.evars_dll hpara.Sil.body_dll in List.map ~f:update_para special_cases -let abs_rules_apply_rsets tenv (rsets: rule_set list) (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = +let abs_rules_apply_rsets tenv (rsets: rule_set list) (p_in: Prop.normal Prop.t) + : Prop.normal Prop.t = let apply_rule (changed, p) r = - match (sigma_rewrite tenv p r) with - | None -> (changed, p) - | Some p' -> - (* + match sigma_rewrite tenv p r with + | None + -> (changed, p) + | Some p' + -> (* L.out "@[.... abstraction (rewritten in abs_rules) ....@."; L.out "@[<4> PROP:%a@\n@." pp_prop p'; *) - (true, p') in + (true, p') + in let rec apply_rule_set p rset = - let (_, rules) = rset in - let (changed, p') = List.fold ~f:apply_rule ~init:(false, p) rules in - if changed then apply_rule_set p' rset else p' in + let _, rules = rset in + let changed, p' = List.fold ~f:apply_rule ~init:(false, p) rules in + if changed then apply_rule_set p' rset else p' + in List.fold ~f:apply_rule_set ~init:p_in rsets let abs_rules_apply_lists tenv (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = let new_rsets = ref [] in let old_rsets = get_current_rules () in let rec discover_then_abstract p = - let (closed_paras_sll, closed_paras_dll) = + let closed_paras_sll, closed_paras_dll = let paras_sll = discover_para tenv p in let paras_dll = discover_para_dll tenv p in let closed_paras_sll = List.concat_map ~f:hpara_special_cases paras_sll in let closed_paras_dll = List.concat_map ~f:hpara_special_cases_dll paras_dll in - begin - (closed_paras_sll, closed_paras_dll) - end in - let (todo_paras_sll, todo_paras_dll) = - let eq_sll para rset = match rset with - | (SLL para', _) -> Match.hpara_iso tenv para para' - | _ -> false in - let eq_dll para rset = match rset with - | (DLL para', _) -> Match.hpara_dll_iso tenv para para' - | _ -> false in + (closed_paras_sll, closed_paras_dll) + in + let todo_paras_sll, todo_paras_dll = + let eq_sll para rset = + match rset with SLL para', _ -> Match.hpara_iso tenv para para' | _ -> false + in + let eq_dll para rset = + match rset with DLL para', _ -> Match.hpara_dll_iso tenv para para' | _ -> false + in let filter_sll para = - not (List.exists ~f:(eq_sll para) old_rsets) && - not (List.exists ~f:(eq_sll para) !new_rsets) in + not (List.exists ~f:(eq_sll para) old_rsets) + && not (List.exists ~f:(eq_sll para) !new_rsets) + in let filter_dll para = - not (List.exists ~f:(eq_dll para) old_rsets) && - not (List.exists ~f:(eq_dll para) !new_rsets) in + not (List.exists ~f:(eq_dll para) old_rsets) + && not (List.exists ~f:(eq_dll para) !new_rsets) + in let todo_paras_sll = List.filter ~f:filter_sll closed_paras_sll in let todo_paras_dll = List.filter ~f:filter_dll closed_paras_dll in - (todo_paras_sll, todo_paras_dll) in + (todo_paras_sll, todo_paras_dll) + in let f_recurse () = let todo_rsets_sll = - List.map ~f:(fun para -> (SLL para, mk_rules_for_sll tenv para)) todo_paras_sll in + List.map ~f:(fun para -> (SLL para, mk_rules_for_sll tenv para)) todo_paras_sll + in let todo_rsets_dll = - List.map ~f:(fun para -> (DLL para, mk_rules_for_dll tenv para)) todo_paras_dll in - new_rsets := !new_rsets @ todo_rsets_sll @ todo_rsets_dll; + List.map ~f:(fun para -> (DLL para, mk_rules_for_dll tenv para)) todo_paras_dll + in + new_rsets := !new_rsets @ todo_rsets_sll @ todo_rsets_dll ; let p' = abs_rules_apply_rsets tenv todo_rsets_sll p in let p'' = abs_rules_apply_rsets tenv todo_rsets_dll p' in - discover_then_abstract p'' in - match todo_paras_sll, todo_paras_dll with - | [], [] -> p - | _ -> f_recurse () in + discover_then_abstract p'' + in + match (todo_paras_sll, todo_paras_dll) with [], [] -> p | _ -> f_recurse () + in let p1 = abs_rules_apply_rsets tenv old_rsets p_in in let p2 = discover_then_abstract p1 in let new_rules = old_rsets @ !new_rsets in - set_current_rules new_rules; - p2 + set_current_rules new_rules ; p2 let abs_rules_apply tenv (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = abs_rules_apply_lists tenv p_in -(****************** End of the ADT abs_rules ******************) +(****************** End of the ADT abs_rules ******************) (****************** Start of Main Abstraction Functions ******************) let abstract_pure_part tenv p ~(from_abstract_footprint: bool) = let do_pure pure = let pi_filtered = let sigma = p.Prop.sigma in let fav_sigma = Prop.sigma_fav sigma in - let fav_nonpure = Prop.prop_fav_nonpure p in (* vars in current and footprint sigma *) + let fav_nonpure = Prop.prop_fav_nonpure p in + (* vars in current and footprint sigma *) let filter atom = let fav' = Sil.atom_fav atom in Sil.fav_for_all fav' (fun id -> if Ident.is_primed id then Sil.fav_mem fav_sigma id else if Ident.is_footprint id then Sil.fav_mem fav_nonpure id - else true) in - List.filter ~f:filter pure in + else true ) + in + List.filter ~f:filter pure + in let new_pure = List.fold ~f:(fun pi a -> - match a with - (* we only use Lt and Le because Gt and Ge are inserted in terms of Lt and Le. *) - | Sil.Aeq (Exp.Const (Const.Cint i), Exp.BinOp (Binop.Lt, _, _)) - | Sil.Aeq (Exp.BinOp (Binop.Lt, _, _), Exp.Const (Const.Cint i)) - | Sil.Aeq (Exp.Const (Const.Cint i), Exp.BinOp (Binop.Le, _, _)) - | Sil.Aeq (Exp.BinOp (Binop.Le, _, _), Exp.Const (Const.Cint i)) when IntLit.isone i -> - a :: pi - | Sil.Aeq (Exp.Var name, e) when not (Ident.is_primed name) -> - (match e with - | Exp.Var _ - | Exp.Const _ -> a :: pi - | _ -> pi) - | Sil.Aneq (Var _, _) - | Sil.Apred (_, Var _ :: _) | Anpred (_, Var _ :: _) -> a :: pi - | Sil.Aeq _ | Aneq _ | Apred _ | Anpred _ -> pi - ) - ~init:[] pi_filtered in - List.rev new_pure in - + match a with + (* we only use Lt and Le because Gt and Ge are inserted in terms of Lt and Le. *) + | Sil.Aeq (Exp.Const Const.Cint i, Exp.BinOp (Binop.Lt, _, _)) + | Sil.Aeq (Exp.BinOp (Binop.Lt, _, _), Exp.Const Const.Cint i) + | Sil.Aeq (Exp.Const Const.Cint i, Exp.BinOp (Binop.Le, _, _)) + | Sil.Aeq (Exp.BinOp (Binop.Le, _, _), Exp.Const Const.Cint i) + when IntLit.isone i + -> a :: pi + | Sil.Aeq (Exp.Var name, e) when not (Ident.is_primed name) -> ( + match e with Exp.Var _ | Exp.Const _ -> a :: pi | _ -> pi ) + | Sil.Aneq (Var _, _) | Sil.Apred (_, (Var _) :: _) | Anpred (_, (Var _) :: _) + -> a :: pi + | Sil.Aeq _ | Aneq _ | Apred _ | Anpred _ + -> pi) + ~init:[] pi_filtered + in + List.rev new_pure + in let new_pure = do_pure (Prop.get_pure p) in let eprop' = Prop.set p ~pi:new_pure ~sub:Sil.exp_sub_empty in let eprop'' = if !Config.footprint && not from_abstract_footprint then let new_pi_footprint = do_pure p.Prop.pi_fp in Prop.set eprop' ~pi_fp:new_pi_footprint - else eprop' in + else eprop' + in Prop.normalize tenv eprop'' (** Collect symbolic garbage from pi and sigma *) @@ -811,37 +884,48 @@ let abstract_gc tenv p = let fav_atom = atom_fav atom in IList.intersect compare fav_p_without_pi fav_atom in *) let strong_filter = function - | Sil.Aeq(e1, e2) | Sil.Aneq(e1, e2) -> - let fav_e1 = Sil.exp_fav e1 in + | Sil.Aeq (e1, e2) | Sil.Aneq (e1, e2) + -> let fav_e1 = Sil.exp_fav e1 in let fav_e2 = Sil.exp_fav e2 in - let intersect_e1 _ = IList.intersect Ident.compare (Sil.fav_to_list fav_e1) (Sil.fav_to_list fav_p_without_pi) in - let intersect_e2 _ = IList.intersect Ident.compare (Sil.fav_to_list fav_e2) (Sil.fav_to_list fav_p_without_pi) in + let intersect_e1 _ = + IList.intersect Ident.compare (Sil.fav_to_list fav_e1) (Sil.fav_to_list fav_p_without_pi) + in + let intersect_e2 _ = + IList.intersect Ident.compare (Sil.fav_to_list fav_e2) (Sil.fav_to_list fav_p_without_pi) + in let no_fav_e1 = Sil.fav_is_empty fav_e1 in let no_fav_e2 = Sil.fav_is_empty fav_e2 in (no_fav_e1 || intersect_e1 ()) && (no_fav_e2 || intersect_e2 ()) - | (Sil.Apred _ | Anpred _) as a -> - let fav_a = Sil.atom_fav a in + | Sil.Apred _ | Anpred _ as a + -> let fav_a = Sil.atom_fav a in Sil.fav_is_empty fav_a - || - IList.intersect Ident.compare (Sil.fav_to_list fav_a) (Sil.fav_to_list fav_p_without_pi) in + || IList.intersect Ident.compare (Sil.fav_to_list fav_a) (Sil.fav_to_list fav_p_without_pi) + in let new_pi = List.filter ~f:strong_filter pi in let prop = Prop.normalize tenv (Prop.set p ~pi:new_pi) in match Prop.prop_iter_create prop with - | None -> prop - | Some iter -> Prop.prop_iter_to_prop tenv (Prop.prop_iter_gc_fields iter) + | None + -> prop + | Some iter + -> Prop.prop_iter_to_prop tenv (Prop.prop_iter_gc_fields iter) -module IdMap = Caml.Map.Make (Ident) (** maps from identifiers *) +(** maps from identifiers *) +module IdMap = Caml.Map.Make (Ident) -module HpredSet = - Caml.Set.Make(struct - type t = Sil.hpred - let compare = Sil.compare_hpred ~inst:false - end) +module HpredSet = Caml.Set.Make (struct + type t = Sil.hpred -let hpred_entries hpred = match hpred with - | Sil.Hpointsto (e, _, _) -> [e] - | Sil.Hlseg (_, _, e, _, _) -> [e] - | Sil.Hdllseg (_, _, e1, _, _, e2, _) -> [e1; e2] + let compare = Sil.compare_hpred ~inst:false +end) + +let hpred_entries hpred = + match hpred with + | Sil.Hpointsto (e, _, _) + -> [e] + | Sil.Hlseg (_, _, e, _, _) + -> [e] + | Sil.Hdllseg (_, _, e1, _, _, e2, _) + -> [e1; e2] (** find the id's in sigma reachable from the given roots *) let sigma_reachable root_fav sigma = @@ -851,26 +935,32 @@ let sigma_reachable root_fav sigma = let do_hpred hpred = let hp_fav_set = fav_to_set (Sil.hpred_fav hpred) in let add_entry e = edges := (e, hp_fav_set) :: !edges in - List.iter ~f:add_entry (hpred_entries hpred) in - List.iter ~f:do_hpred sigma; - let edge_fires (e, _) = match e with - | Exp.Var id -> - if (Ident.is_primed id || Ident.is_footprint id) then Ident.IdentSet.mem id !reach_set + List.iter ~f:add_entry (hpred_entries hpred) + in + List.iter ~f:do_hpred sigma ; + let edge_fires (e, _) = + match e with + | Exp.Var id + -> if Ident.is_primed id || Ident.is_footprint id then Ident.IdentSet.mem id !reach_set else true - | _ -> true in - let rec apply_once edges_to_revisit edges_todo modified = match edges_todo with - | [] -> (edges_to_revisit, modified) - | edge:: edges_todo' -> - if edge_fires edge then - begin - reach_set := Ident.IdentSet.union (snd edge) !reach_set; - apply_once edges_to_revisit edges_todo' true - end - else apply_once (edge :: edges_to_revisit) edges_todo' modified in + | _ + -> true + in + let rec apply_once edges_to_revisit edges_todo modified = + match edges_todo with + | [] + -> (edges_to_revisit, modified) + | edge :: edges_todo' + -> if edge_fires edge then ( + reach_set := Ident.IdentSet.union (snd edge) !reach_set ; + apply_once edges_to_revisit edges_todo' true ) + else apply_once (edge :: edges_to_revisit) edges_todo' modified + in let rec find_fixpoint edges_todo = let edges_to_revisit, modified = apply_once [] edges_todo false in - if modified then find_fixpoint edges_to_revisit in - find_fixpoint !edges; + if modified then find_fixpoint edges_to_revisit + in + find_fixpoint !edges ; (* L.d_str "reachable: "; Ident.IdentSet.iter (fun id -> Sil.d_exp (Exp.Var id); L.d_str " ") !reach_set; L.d_ln (); *) @@ -880,121 +970,137 @@ let get_cycle root prop = let sigma = prop.Prop.sigma in let get_points_to e = match e with - | Sil.Eexp(e', _) -> - List.find - ~f:(fun hpred -> match hpred with - | Sil.Hpointsto (e'', _, _) -> Exp.equal e'' e' - | _ -> false) + | Sil.Eexp (e', _) + -> List.find + ~f:(fun hpred -> + match hpred with Sil.Hpointsto (e'', _, _) -> Exp.equal e'' e' | _ -> false) sigma - | _ -> None in + | _ + -> None + in let print_cycle cyc = - (L.d_str "Cycle= "; - List.iter ~f:(fun ((e, t), f, e') -> - match e, e' with - | Sil.Eexp (e, _), Sil.Eexp (e', _) -> - L.d_str ("("^(Exp.to_string e)^": "^(Typ.to_string t)^", " - ^(Typ.Fieldname.to_string f)^", "^(Exp.to_string e')^")") - | _ -> ()) cyc; - L.d_strln "") in + L.d_str "Cycle= " ; + List.iter + ~f:(fun ((e, t), f, e') -> + match (e, e') with + | Sil.Eexp (e, _), Sil.Eexp (e', _) + -> L.d_str + ( "(" ^ Exp.to_string e ^ ": " ^ Typ.to_string t ^ ", " ^ Typ.Fieldname.to_string f + ^ ", " ^ Exp.to_string e' ^ ")" ) + | _ + -> ()) + cyc ; + L.d_strln "" + in (* Perform a dfs of a graph stopping when e_root is reached. Returns a pair (path, bool) where path is a list of edges ((e1,type_e1),f,e2) describing the path to e_root and bool is true if e_root is reached. *) let rec dfs e_root et_src path el visited = match el with - | [] -> path, false - | (f, e):: el' -> - if Sil.equal_strexp e e_root then - (et_src, f, e):: path, true - else if List.mem ~equal:Sil.equal_strexp visited e then - path, false - else ( - let visited' = (fst et_src):: visited in - let res = (match get_points_to e with - | None -> path, false - | Some (Sil.Hpointsto (_, Sil.Estruct (fl, _), Exp.Sizeof {typ=te})) -> - dfs e_root (e, te) ((et_src, f, e):: path) fl visited' - | _ -> path, false (* check for lists *)) in - if snd res then res - else dfs e_root et_src path el' visited') in - L.d_strln "Looking for cycle with root expression: "; Sil.d_hpred root; L.d_strln ""; + | [] + -> (path, false) + | (f, e) :: el' + -> if Sil.equal_strexp e e_root then ((et_src, f, e) :: path, true) + else if List.mem ~equal:Sil.equal_strexp visited e then (path, false) + else + let visited' = fst et_src :: visited in + let res = + match get_points_to e with + | None + -> (path, false) + | Some Sil.Hpointsto (_, Sil.Estruct (fl, _), Exp.Sizeof {typ= te}) + -> dfs e_root (e, te) ((et_src, f, e) :: path) fl visited' + | _ + -> (path, false) + (* check for lists *) + in + if snd res then res else dfs e_root et_src path el' visited' + in + L.d_strln "Looking for cycle with root expression: " ; + Sil.d_hpred root ; + L.d_strln "" ; match root with - | Sil.Hpointsto (e_root, Sil.Estruct (fl, _), Exp.Sizeof {typ=te}) -> - let se_root = Sil.Eexp(e_root, Sil.Inone) in + | Sil.Hpointsto (e_root, Sil.Estruct (fl, _), Exp.Sizeof {typ= te}) + -> let se_root = Sil.Eexp (e_root, Sil.Inone) in (* start dfs with empty path and expr pointing to root *) - let (pot_cycle, res) = dfs se_root (se_root, te) [] fl [] in - if res then ( - print_cycle pot_cycle; - pot_cycle - ) else ( - L.d_strln "NO cycle found from root"; - []) - | _ -> L.d_strln "Root exp is not an allocated object. No cycle found"; [] - + let pot_cycle, res = dfs se_root (se_root, te) [] fl [] in + if res then ( print_cycle pot_cycle ; pot_cycle ) + else ( L.d_strln "NO cycle found from root" ; [] ) + | _ + -> L.d_strln "Root exp is not an allocated object. No cycle found" ; [] (** Check whether the hidden counter field of a struct representing an objective-c object is positive, and whether the leak is part of the specified buckets. In the positive case, it returns the bucket *) let should_raise_objc_leak hpred = match hpred with - | Sil.Hpointsto(_, Sil.Estruct((fn, Sil.Eexp( (Exp.Const (Const.Cint i)), _)):: _, _), - Exp.Sizeof {typ}) - when Typ.Fieldname.is_hidden fn && IntLit.gt i IntLit.zero (* counter > 0 *) -> - Mleak_buckets.should_raise_objc_leak typ - | _ -> None + | Sil.Hpointsto + (_, Sil.Estruct ((fn, Sil.Eexp (Exp.Const Const.Cint i, _)) :: _, _), Exp.Sizeof {typ}) + when Typ.Fieldname.is_hidden fn && IntLit.gt i IntLit.zero (* counter > 0 *) + -> Mleak_buckets.should_raise_objc_leak typ + | _ + -> None let get_retain_cycle_dotty _prop cycle = match _prop with - | None -> None - | Some (Some _prop) -> - Dotty.dotty_prop_to_str _prop cycle - | _ -> None + | None + -> None + | Some Some _prop + -> Dotty.dotty_prop_to_str _prop cycle + | _ + -> None let get_var_retain_cycle prop_ = let sigma = prop_.Prop.sigma in let is_pvar v h = match h with - | Sil.Hpointsto (Exp.Lvar _, v', _) when Sil.equal_strexp v v' -> true - | _ -> false in + | Sil.Hpointsto (Exp.Lvar _, v', _) when Sil.equal_strexp v v' + -> true + | _ + -> false + in let is_hpred_block v h = - match h, v with + match (h, v) with | Sil.Hpointsto (e, _, Exp.Sizeof {typ}), Sil.Eexp (e', _) - when Exp.equal e e' && Typ.is_block_type typ -> true - | _, _ -> false in - let find v = - List.find ~f:(is_pvar v) sigma |> - Option.map ~f:Sil.hpred_get_lhs in + when Exp.equal e e' && Typ.is_block_type typ + -> true + | _, _ + -> false + in + let find v = List.find ~f:(is_pvar v) sigma |> Option.map ~f:Sil.hpred_get_lhs in let find_block v = - if (List.exists ~f:(is_hpred_block v) sigma) then - Some (Exp.Lvar Sil.block_pvar) - else None in + if List.exists ~f:(is_hpred_block v) sigma then Some (Exp.Lvar Sil.block_pvar) else None + in let sexp e = Sil.Eexp (e, Sil.Inone) in let find_or_block ((e, t), f, e') = match find e with - | Some pvar -> [((sexp pvar, t), f, e')] - | _ -> (match find_block e with - | Some blk -> [((sexp blk, t), f, e')] - | _ -> - let sizeof = {Exp.typ=t; nbytes=None; dynamic_length=None; subtype=Subtype.exact} in - [((sexp (Exp.Sizeof sizeof), t), f, e')]) in + | Some pvar + -> [((sexp pvar, t), f, e')] + | _ -> + match find_block e with + | Some blk + -> [((sexp blk, t), f, e')] + | _ + -> let sizeof = {Exp.typ= t; nbytes= None; dynamic_length= None; subtype= Subtype.exact} in + [((sexp (Exp.Sizeof sizeof), t), f, e')] + in (* returns the pvars of the first cycle we find in sigma. This is an heuristic that works if there is one cycle. In case there are more than one cycle we may return not necessarily the one we are looking for. *) let rec do_sigma sigma_todo = match sigma_todo with - | [] -> [] - | hp:: sigma' -> - let cycle = get_cycle hp prop_ in - L.d_strln "Filtering pvar in cycle "; + | [] + -> [] + | hp :: sigma' + -> let cycle = get_cycle hp prop_ in + L.d_strln "Filtering pvar in cycle " ; let cycle' = List.concat_map ~f:find_or_block cycle in - if List.is_empty cycle' then do_sigma sigma' - else cycle' in + if List.is_empty cycle' then do_sigma sigma' else cycle' + in do_sigma sigma -let remove_opt _prop = - match _prop with - | Some (Some p) -> p - | _ -> Prop.prop_emp +let remove_opt _prop = match _prop with Some Some p -> p | _ -> Prop.prop_emp (** Checks if cycle has fields (derived from a property or directly defined as ivar) with attributes weak/unsafe_unretained/assing *) @@ -1002,244 +1108,306 @@ let cycle_has_weak_or_unretained_or_assign_field tenv cycle = (* returns items annotation for field fn in struct t *) let get_item_annotation (t: Typ.t) fn = match t.desc with - | Tstruct name -> ( + | Tstruct name + -> ( let equal_fn (fn', _, _) = Typ.Fieldname.equal fn fn' in match Tenv.lookup tenv name with - | Some { fields; statics } -> ( - List.find ~f:equal_fn (fields @ statics) |> - Option.value_map ~f:trd3 ~default:[] - ) - | None -> [] - ) - | _ -> [] in + | Some {fields; statics} + -> List.find ~f:equal_fn (fields @ statics) |> Option.value_map ~f:trd3 ~default:[] + | None + -> [] ) + | _ + -> [] + in let rec has_weak_or_unretained_or_assign params = match params with - | [] -> false - | att:: _ when String.equal Config.unsafe_unret att || - String.equal Config.weak att || - String.equal Config.assign att -> true - | _:: params' -> has_weak_or_unretained_or_assign params' in + | [] + -> false + | att :: _ + when String.equal Config.unsafe_unret att || String.equal Config.weak att + || String.equal Config.assign att + -> true + | _ :: params' + -> has_weak_or_unretained_or_assign params' + in let do_annotation ((a: Annot.t), _) = - ((String.equal a.class_name Config.property_attributes) || - (String.equal a.class_name Config.ivar_attributes)) - && has_weak_or_unretained_or_assign a.parameters in + ( String.equal a.class_name Config.property_attributes + || String.equal a.class_name Config.ivar_attributes ) + && has_weak_or_unretained_or_assign a.parameters + in let rec do_cycle c = match c with - | [] -> false - | ((_, t), fn, _):: c' -> - let ia = get_item_annotation t fn in - if (List.exists ~f:do_annotation ia) then true - else do_cycle c' in + | [] + -> false + | ((_, t), fn, _) :: c' + -> let ia = get_item_annotation t fn in + if List.exists ~f:do_annotation ia then true else do_cycle c' + in do_cycle cycle let check_observer_is_unsubscribed_deallocation tenv prop e = - let pvar_opt = match Attribute.get_resource tenv prop e with - | Some (Apred (Aresource ({ ra_vpath = Some (Dpvar pvar) }), _)) -> Some pvar - | _ -> None in + let pvar_opt = + match Attribute.get_resource tenv prop e with + | Some Apred (Aresource {ra_vpath= Some Dpvar pvar}, _) + -> Some pvar + | _ + -> None + in let loc = State.get_loc () in match Attribute.get_observer tenv prop e with - | Some (Apred (Aobserver, _)) -> - (match pvar_opt with - | Some pvar when Config.nsnotification_center_checker_backend -> - L.d_strln (" ERROR: Object " ^ (Pvar.to_string pvar) ^ - " is being deallocated while still registered in a notification center"); - let desc = Localise.desc_registered_observer_being_deallocated pvar loc in - raise (Exceptions.Registered_observer_being_deallocated (desc, __POS__)) - | _ -> ()) - | _ -> () + | Some Apred (Aobserver, _) -> ( + match pvar_opt with + | Some pvar when Config.nsnotification_center_checker_backend + -> L.d_strln + ( " ERROR: Object " ^ Pvar.to_string pvar + ^ " is being deallocated while still registered in a notification center" ) ; + let desc = Localise.desc_registered_observer_being_deallocated pvar loc in + raise (Exceptions.Registered_observer_being_deallocated (desc, __POS__)) + | _ + -> () ) + | _ + -> () let check_junk ?original_prop pname tenv prop = let fav_sub_sigmafp = Sil.fav_new () in - Sil.sub_fav_add fav_sub_sigmafp prop.Prop.sub; - Prop.sigma_fav_add fav_sub_sigmafp prop.Prop.sigma_fp; + Sil.sub_fav_add fav_sub_sigmafp prop.Prop.sub ; + Prop.sigma_fav_add fav_sub_sigmafp prop.Prop.sigma_fp ; let leaks_reported = ref [] in - let remove_junk_once fp_part fav_root sigma = - let id_considered_reachable = (* reachability function *) + let id_considered_reachable = + (* reachability function *) let reach_set = sigma_reachable fav_root sigma in - fun id -> Ident.IdentSet.mem id reach_set in + fun id -> Ident.IdentSet.mem id reach_set + in let should_remove_hpred entries = let predicate = function - | Exp.Var id -> - (Ident.is_primed id || Ident.is_footprint id) - && not (Sil.fav_mem fav_root id) && not (id_considered_reachable id) - | _ -> false in - List.for_all ~f:predicate entries in - let hpred_in_cycle hpred = (* check if the predicate belongs to a cycle in the heap *) + | Exp.Var id + -> (Ident.is_primed id || Ident.is_footprint id) && not (Sil.fav_mem fav_root id) + && not (id_considered_reachable id) + | _ + -> false + in + List.for_all ~f:predicate entries + in + let hpred_in_cycle hpred = + (* check if the predicate belongs to a cycle in the heap *) let id_in_cycle id = let set1 = sigma_reachable (Sil.fav_from_list [id]) sigma in let set2 = Ident.IdentSet.remove id set1 in let fav2 = Sil.fav_from_list (Ident.IdentSet.elements set2) in let set3 = sigma_reachable fav2 sigma in - Ident.IdentSet.mem id set3 in + Ident.IdentSet.mem id set3 + in let entries = hpred_entries hpred in - let predicate = function - | Exp.Var id -> id_in_cycle id - | _ -> false in - let hpred_is_loop = match hpred with (* true if hpred has a self loop, ie one field points to id *) - | Sil.Hpointsto (Exp.Var id, se, _) -> - let fav = Sil.fav_new () in - Sil.strexp_fav_add fav se; - Sil.fav_mem fav id - | _ -> false in - hpred_is_loop || List.exists ~f:predicate entries in + let predicate = function Exp.Var id -> id_in_cycle id | _ -> false in + let hpred_is_loop = + match hpred with + (* true if hpred has a self loop, ie one field points to id *) + | Sil.Hpointsto (Exp.Var id, se, _) + -> let fav = Sil.fav_new () in + Sil.strexp_fav_add fav se ; Sil.fav_mem fav id + | _ + -> false + in + hpred_is_loop || List.exists ~f:predicate entries + in let rec remove_junk_recursive sigma_done sigma_todo = match sigma_todo with - | [] -> List.rev sigma_done - | hpred :: sigma_todo' -> - let entries = hpred_entries hpred in + | [] + -> List.rev sigma_done + | hpred :: sigma_todo' + -> let entries = hpred_entries hpred in if should_remove_hpred entries then - begin - let part = if fp_part then "footprint" else "normal" in - L.d_strln (".... Prop with garbage in " ^ part ^ " part ...."); - L.d_increase_indent 1; - L.d_strln "PROP:"; - Prop.d_prop prop; L.d_ln (); - L.d_strln "PREDICATE:"; - Prop.d_sigma [hpred]; - L.d_ln (); - let alloc_attribute = - (* find the alloc attribute of one of the roots of hpred, if it exists *) - let res = ref None in - let do_entry e = - check_observer_is_unsubscribed_deallocation tenv prop e; - match Attribute.get_resource tenv prop e with - | Some (Apred (Aresource ({ ra_kind = Racquire }) as a, _)) -> - L.d_str "ATTRIBUTE: "; PredSymb.d_attribute a; L.d_ln (); - res := Some a - | _ -> - (match Attribute.get_undef tenv prop e with - | Some (Apred (Aundef _ as a, _)) -> - res := Some a - | _ -> ()) in - List.iter ~f:do_entry entries; - !res in - L.d_decrease_indent 1; - let is_undefined = - Option.value_map ~f:PredSymb.is_undef ~default:false alloc_attribute in - let resource = match Errdesc.hpred_is_open_resource tenv prop hpred with - | Some res -> res - | None -> PredSymb.Rmemory PredSymb.Mmalloc in - let ml_bucket_opt = - match resource with - | PredSymb.Rmemory PredSymb.Mobjc -> should_raise_objc_leak hpred - | PredSymb.Rmemory PredSymb.Mnew | PredSymb.Rmemory PredSymb.Mnew_array - when Config.curr_language_is Config.Clang -> - Mleak_buckets.should_raise_cpp_leak - | _ -> None in - let exn_retain_cycle cycle = - let cycle_dotty = get_retain_cycle_dotty original_prop cycle in - let desc = Errdesc.explain_retain_cycle cycle (State.get_loc ()) cycle_dotty in - Exceptions.Retain_cycle (hpred, desc, __POS__) in - let exn_leak = Exceptions.Leak - (fp_part, hpred, - Errdesc.explain_leak tenv hpred prop alloc_attribute ml_bucket_opt, - !Absarray.array_abstraction_performed, - resource, - __POS__) in - let ignore_resource, exn = - (match alloc_attribute, resource with - | Some _, Rmemory Mobjc when (hpred_in_cycle hpred) -> - (* When there is a cycle in objc we ignore it + let part = if fp_part then "footprint" else "normal" in + L.d_strln (".... Prop with garbage in " ^ part ^ " part ....") ; + L.d_increase_indent 1 ; + L.d_strln "PROP:" ; + Prop.d_prop prop ; + L.d_ln () ; + L.d_strln "PREDICATE:" ; + Prop.d_sigma [hpred] ; + L.d_ln () ; + let alloc_attribute = + (* find the alloc attribute of one of the roots of hpred, if it exists *) + let res = ref None in + let do_entry e = + check_observer_is_unsubscribed_deallocation tenv prop e ; + match Attribute.get_resource tenv prop e with + | Some Apred ((Aresource {ra_kind= Racquire} as a), _) + -> L.d_str "ATTRIBUTE: " ; + PredSymb.d_attribute a ; + L.d_ln () ; + res := Some a + | _ -> + match Attribute.get_undef tenv prop e with + | Some Apred ((Aundef _ as a), _) + -> res := Some a + | _ + -> () + in + List.iter ~f:do_entry entries ; !res + in + L.d_decrease_indent 1 ; + let is_undefined = + Option.value_map ~f:PredSymb.is_undef ~default:false alloc_attribute + in + let resource = + match Errdesc.hpred_is_open_resource tenv prop hpred with + | Some res + -> res + | None + -> PredSymb.Rmemory PredSymb.Mmalloc + in + let ml_bucket_opt = + match resource with + | PredSymb.Rmemory PredSymb.Mobjc + -> should_raise_objc_leak hpred + | PredSymb.Rmemory PredSymb.Mnew + | PredSymb.Rmemory PredSymb.Mnew_array + when Config.curr_language_is Config.Clang + -> Mleak_buckets.should_raise_cpp_leak + | _ + -> None + in + let exn_retain_cycle cycle = + let cycle_dotty = get_retain_cycle_dotty original_prop cycle in + let desc = Errdesc.explain_retain_cycle cycle (State.get_loc ()) cycle_dotty in + Exceptions.Retain_cycle (hpred, desc, __POS__) + in + let exn_leak = + Exceptions.Leak + ( fp_part + , hpred + , Errdesc.explain_leak tenv hpred prop alloc_attribute ml_bucket_opt + , !Absarray.array_abstraction_performed + , resource + , __POS__ ) + in + let ignore_resource, exn = + match (alloc_attribute, resource) with + | Some _, Rmemory Mobjc when hpred_in_cycle hpred + -> (* When there is a cycle in objc we ignore it only if it's empty or it has weak or unsafe_unretained fields. Otherwise we report a retain cycle. *) - let cycle = get_var_retain_cycle (remove_opt original_prop) in - let ignore_cycle = - (Int.equal (List.length cycle) 0) || - (cycle_has_weak_or_unretained_or_assign_field tenv cycle) in - ignore_cycle, exn_retain_cycle cycle - | Some _, Rmemory Mobjc - | Some _, Rmemory Mnew - | Some _, Rmemory Mnew_array when Config.curr_language_is Config.Clang -> - is_none ml_bucket_opt, exn_leak - | Some _, Rmemory _ -> Config.curr_language_is Config.Java, exn_leak - | Some _, Rignore -> true, exn_leak - | Some _, Rfile -> false, exn_leak - | Some _, Rlock -> false, exn_leak - | _ when hpred_in_cycle hpred && Sil.has_objc_ref_counter tenv hpred -> - (* When it's a cycle and the object has a ref counter then + let cycle = get_var_retain_cycle (remove_opt original_prop) in + let ignore_cycle = + Int.equal (List.length cycle) 0 + || cycle_has_weak_or_unretained_or_assign_field tenv cycle + in + (ignore_cycle, exn_retain_cycle cycle) + | Some _, Rmemory Mobjc + | Some _, Rmemory Mnew + | Some _, Rmemory Mnew_array + when Config.curr_language_is Config.Clang + -> (is_none ml_bucket_opt, exn_leak) + | Some _, Rmemory _ + -> (Config.curr_language_is Config.Java, exn_leak) + | Some _, Rignore + -> (true, exn_leak) + | Some _, Rfile + -> (false, exn_leak) + | Some _, Rlock + -> (false, exn_leak) + | _ when hpred_in_cycle hpred && Sil.has_objc_ref_counter tenv hpred + -> (* When it's a cycle and the object has a ref counter then we have a retain cycle. Objc object may not have the Mobjc qualifier when added in footprint doing abduction *) - let cycle = get_var_retain_cycle (remove_opt original_prop) in - Int.equal (List.length cycle) 0, exn_retain_cycle cycle - | _ -> Config.curr_language_is Config.Java, exn_leak) in - let already_reported () = - let attr_opt_equal ao1 ao2 = match ao1, ao2 with - | None, None -> true - | Some a1, Some a2 -> PredSymb.equal a1 a2 - | Some _, None - | None, Some _ -> false in - (is_none alloc_attribute && !leaks_reported <> []) || - (* None attribute only reported if it's the first one *) - List.mem ~equal:attr_opt_equal !leaks_reported alloc_attribute in - let ignore_leak = - !Config.allow_leak || ignore_resource || is_undefined || already_reported () in - let report_and_continue = - Config.curr_language_is Config.Java || !Config.footprint in - let report_leak () = - if not report_and_continue then raise exn - else - begin - Reporting.log_error_deprecated pname exn; - leaks_reported := alloc_attribute :: !leaks_reported; - end in - if not ignore_leak then report_leak (); - remove_junk_recursive sigma_done sigma_todo' - end - else - remove_junk_recursive (hpred :: sigma_done) sigma_todo' in - remove_junk_recursive [] sigma in - let rec remove_junk fp_part fav_root sigma = (* call remove_junk_once until sigma stops shrinking *) + let cycle = get_var_retain_cycle (remove_opt original_prop) in + (Int.equal (List.length cycle) 0, exn_retain_cycle cycle) + | _ + -> (Config.curr_language_is Config.Java, exn_leak) + in + let already_reported () = + let attr_opt_equal ao1 ao2 = + match (ao1, ao2) with + | None, None + -> true + | Some a1, Some a2 + -> PredSymb.equal a1 a2 + | Some _, None | None, Some _ + -> false + in + is_none alloc_attribute && !leaks_reported <> [] + || (* None attribute only reported if it's the first one *) + List.mem ~equal:attr_opt_equal !leaks_reported alloc_attribute + in + let ignore_leak = + !Config.allow_leak || ignore_resource || is_undefined || already_reported () + in + let report_and_continue = Config.curr_language_is Config.Java || !Config.footprint in + let report_leak () = + if not report_and_continue then raise exn + else ( + Reporting.log_error_deprecated pname exn ; + leaks_reported := alloc_attribute :: !leaks_reported ) + in + if not ignore_leak then report_leak () ; + remove_junk_recursive sigma_done sigma_todo' + else remove_junk_recursive (hpred :: sigma_done) sigma_todo' + in + remove_junk_recursive [] sigma + in + let rec remove_junk fp_part fav_root sigma = + (* call remove_junk_once until sigma stops shrinking *) let sigma' = remove_junk_once fp_part fav_root sigma in if Int.equal (List.length sigma') (List.length sigma) then sigma' - else remove_junk fp_part fav_root sigma' in + else remove_junk fp_part fav_root sigma' + in let sigma_new = remove_junk false fav_sub_sigmafp prop.Prop.sigma in let sigma_fp_new = remove_junk true (Sil.fav_new ()) prop.Prop.sigma_fp in - if - Prop.equal_sigma prop.Prop.sigma sigma_new - && Prop.equal_sigma prop.Prop.sigma_fp sigma_fp_new + if Prop.equal_sigma prop.Prop.sigma sigma_new && Prop.equal_sigma prop.Prop.sigma_fp sigma_fp_new then prop else Prop.normalize tenv (Prop.set prop ~sigma:sigma_new ~sigma_fp:sigma_fp_new) (** Check whether the prop contains junk. If it does, and [Config.allowleak] is true, remove the junk, otherwise raise a Leak exception. *) let abstract_junk ?original_prop pname tenv prop = - Absarray.array_abstraction_performed := false; + Absarray.array_abstraction_performed := false ; check_junk ~original_prop pname tenv prop (** Remove redundant elements in an array, and check for junk afterwards *) let remove_redundant_array_elements pname tenv prop = - Absarray.array_abstraction_performed := false; + Absarray.array_abstraction_performed := false ; let prop' = Absarray.remove_redundant_elements tenv prop in - check_junk ~original_prop: (Some(prop)) pname tenv prop' + check_junk ~original_prop:(Some prop) pname tenv prop' let abstract_prop pname tenv ~(rename_primed: bool) ~(from_abstract_footprint: bool) p = - Absarray.array_abstraction_performed := false; - let pure_abs_p = abstract_pure_part tenv ~from_abstract_footprint: true p in + Absarray.array_abstraction_performed := false ; + let pure_abs_p = abstract_pure_part tenv ~from_abstract_footprint:true p in let array_abs_p = - if from_abstract_footprint - then pure_abs_p + if from_abstract_footprint then pure_abs_p else - abstract_pure_part tenv ~from_abstract_footprint (Absarray.abstract_array_check tenv pure_abs_p) in + abstract_pure_part tenv ~from_abstract_footprint + (Absarray.abstract_array_check tenv pure_abs_p) + in let abs_p = abs_rules_apply tenv array_abs_p in - let abs_p = abstract_gc tenv abs_p in (* abstraction might enable more gc *) - let abs_p = check_junk ~original_prop: (Some(p)) pname tenv abs_p in + let abs_p = abstract_gc tenv abs_p in + (* abstraction might enable more gc *) + let abs_p = check_junk ~original_prop:(Some p) pname tenv abs_p in let ren_abs_p = - if rename_primed - then Prop.prop_rename_primed_footprint_vars tenv abs_p - else abs_p in + if rename_primed then Prop.prop_rename_primed_footprint_vars tenv abs_p else abs_p + in ren_abs_p let get_local_stack cur_sigma init_sigma = let filter_stack = function - | Sil.Hpointsto (Exp.Lvar _, _, _) -> true - | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> false in + | Sil.Hpointsto (Exp.Lvar _, _, _) + -> true + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ + -> false + in let get_stack_var = function - | Sil.Hpointsto (Exp.Lvar pvar, _, _) -> pvar - | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> assert false in + | Sil.Hpointsto (Exp.Lvar pvar, _, _) + -> pvar + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ + -> assert false + in let filter_local_stack olds = function - | Sil.Hpointsto (Exp.Lvar pvar, _, _) -> not (List.exists ~f:(Pvar.equal pvar) olds) - | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> false in + | Sil.Hpointsto (Exp.Lvar pvar, _, _) + -> not (List.exists ~f:(Pvar.equal pvar) olds) + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ + -> false + in let init_stack = List.filter ~f:filter_stack init_sigma in let init_stack_pvars = List.map ~f:get_stack_var init_stack in let cur_local_stack = List.filter ~f:(filter_local_stack init_stack_pvars) cur_sigma in @@ -1247,24 +1415,28 @@ let get_local_stack cur_sigma init_sigma = (cur_local_stack, cur_local_stack_pvars) (** Extract the footprint, add a local stack and return it as a prop *) -let extract_footprint_for_abs (p : 'a Prop.t) : Prop.exposed Prop.t * Pvar.t list = +let extract_footprint_for_abs (p: 'a Prop.t) : Prop.exposed Prop.t * Pvar.t list = let sigma = p.Prop.sigma in let pi_fp = p.Prop.pi_fp in let sigma_fp = p.Prop.sigma_fp in - let (local_stack, local_stack_pvars) = get_local_stack sigma sigma_fp in + let local_stack, local_stack_pvars = get_local_stack sigma sigma_fp in let p0 = Prop.from_sigma (local_stack @ sigma_fp) in let p1 = Prop.set p0 ~pi:pi_fp in (p1, local_stack_pvars) let remove_local_stack sigma pvars = let filter_non_stack = function - | Sil.Hpointsto (Exp.Lvar pvar, _, _) -> not (List.exists ~f:(Pvar.equal pvar) pvars) - | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> true in + | Sil.Hpointsto (Exp.Lvar pvar, _, _) + -> not (List.exists ~f:(Pvar.equal pvar) pvars) + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ + -> true + in List.filter ~f:filter_non_stack sigma (** [prop_set_fooprint p p_foot] removes a local stack from [p_foot], and sets proposition [p_foot] as footprint of [p]. *) -let set_footprint_for_abs (p : 'a Prop.t) (p_foot : 'a Prop.t) local_stack_pvars : Prop.exposed Prop.t = +let set_footprint_for_abs (p: 'a Prop.t) (p_foot: 'a Prop.t) local_stack_pvars + : Prop.exposed Prop.t = let p_foot_pure = Prop.get_pure p_foot in let p_sigma_fp = p_foot.Prop.sigma in let pi = p_foot_pure in @@ -1272,30 +1444,27 @@ let set_footprint_for_abs (p : 'a Prop.t) (p_foot : 'a Prop.t) local_stack_pvars Prop.set p ~pi_fp:pi ~sigma_fp:sigma (** Abstract the footprint of prop *) -let abstract_footprint pname (tenv : Tenv.t) (prop : Prop.normal Prop.t) : Prop.normal Prop.t = - let (p, added_local_vars) = extract_footprint_for_abs prop in +let abstract_footprint pname (tenv: Tenv.t) (prop: Prop.normal Prop.t) : Prop.normal Prop.t = + let p, added_local_vars = extract_footprint_for_abs prop in let p_abs = - abstract_prop - pname tenv ~rename_primed: false - ~from_abstract_footprint: true (Prop.normalize tenv p) in + abstract_prop pname tenv ~rename_primed:false ~from_abstract_footprint:true + (Prop.normalize tenv p) + in let prop' = set_footprint_for_abs prop p_abs added_local_vars in Prop.normalize tenv prop' let _abstract pname pay tenv p = - if pay then SymOp.pay(); (* pay one symop *) + if pay then SymOp.pay () ; + (* pay one symop *) let p' = if !Config.footprint then abstract_footprint pname tenv p else p in - abstract_prop pname tenv ~rename_primed: true ~from_abstract_footprint: false p' + abstract_prop pname tenv ~rename_primed:true ~from_abstract_footprint:false p' -let abstract pname tenv p = - _abstract pname true tenv p +let abstract pname tenv p = _abstract pname true tenv p -let abstract_no_symop pname tenv p = - _abstract pname false tenv p +let abstract_no_symop pname tenv p = _abstract pname false tenv p let lifted_abstract pname tenv pset = - let f p = - if Prover.check_inconsistency tenv p then None - else Some (abstract pname tenv p) in + let f p = if Prover.check_inconsistency tenv p then None else Some (abstract pname tenv p) in let abstracted_pset = Propset.map_option tenv f pset in abstracted_pset diff --git a/infer/src/backend/abs.mli b/infer/src/backend/abs.mli index be33027b5..d0da6a28a 100644 --- a/infer/src/backend/abs.mli +++ b/infer/src/backend/abs.mli @@ -15,31 +15,31 @@ open! IStd (** Abstraction rules discovered *) type rules -(** Abstract a proposition. *) val abstract : Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t +(** Abstract a proposition. *) +val abstract_junk : + ?original_prop:Prop.normal Prop.t -> Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t + -> Prop.normal Prop.t (** Check whether the prop contains junk. If it does, and [Config.allowleak] is true, remove the junk, otherwise raise a Leak exception. *) -val abstract_junk : - ?original_prop:Prop.normal Prop.t -> - Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t -(** Abstract a proposition but don't pay a SymOp *) val abstract_no_symop : Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t +(** Abstract a proposition but don't pay a SymOp *) -(** Get the current rules discoveres *) val get_current_rules : unit -> rules +(** Get the current rules discoveres *) -(** Abstract each proposition in [propset] *) val lifted_abstract : Typ.Procname.t -> Tenv.t -> Propset.t -> Propset.t +(** Abstract each proposition in [propset] *) -(** Remove redundant elements in an array, and check for junk afterwards *) val remove_redundant_array_elements : Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t +(** Remove redundant elements in an array, and check for junk afterwards *) -(** Reset the abstraction rules discovered *) val reset_current_rules : unit -> unit +(** Reset the abstraction rules discovered *) -(** Set the current rules discovered *) val set_current_rules : rules -> unit +(** Set the current rules discovered *) diff --git a/infer/src/backend/absarray.ml b/infer/src/backend/absarray.ml index 4010eb8ca..c61c7289e 100644 --- a/infer/src/backend/absarray.ml +++ b/infer/src/backend/absarray.ml @@ -22,11 +22,11 @@ module StrexpMatch : sig (** path through a strexp *) type path - (** convert a path into a list of expressions *) val path_to_exps : path -> Exp.t list + (** convert a path into a list of expressions *) - (** create a path from a root and a list of offsets *) val path_from_exp_offsets : Exp.t -> Sil.offset list -> path + (** create a path from a root and a list of offsets *) (** path to the root, length, elements and type of a new_array *) type strexp_data = path * Sil.strexp * Typ.t @@ -34,21 +34,21 @@ module StrexpMatch : sig (** sigma with info about a current array *) type t - (** Find a strexp at the given path. Can raise [Not_found] *) val find_path : sigma -> path -> t + (** Find a strexp at the given path. Can raise [Not_found] *) - (** Find a strexp with the given property. *) val find : Tenv.t -> sigma -> (strexp_data -> bool) -> t list + (** Find a strexp with the given property. *) - (** Get the array *) val get_data : Tenv.t -> t -> strexp_data + (** Get the array *) - (** Replace the strexp at a given position by a new strexp *) val replace_strexp : Tenv.t -> bool -> t -> Sil.strexp -> sigma + (** Replace the strexp at a given position by a new strexp *) - (** Replace the index in the array at a given position with the new index *) val replace_index : Tenv.t -> bool -> t -> Exp.t -> Exp.t -> sigma -(* + (** Replace the index in the array at a given position with the new index *) + (* (** Get the partition of the sigma: the unmatched part of the sigma and the matched hpred *) val get_sigma_partition : t -> sigma * Sil.hpred @@ -56,88 +56,96 @@ module StrexpMatch : sig val replace_strexp_sigma : bool -> t -> Sil.strexp -> sigma -> sigma *) end = struct - (** syntactic offset *) type syn_offset = Field of Typ.Fieldname.t * Typ.t | Index of Exp.t (** path through an Estruct *) - type path = Exp.t * (syn_offset list) + type path = Exp.t * syn_offset list (** Find a strexp and a type at the given syntactic offset list *) let rec get_strexp_at_syn_offsets tenv se (t: Typ.t) syn_offs = let fail () = - L.d_strln "Failure of get_strexp_at_syn_offsets"; - L.d_str "se: "; Sil.d_sexp se; L.d_ln (); - L.d_str "t: "; Typ.d_full t; L.d_ln (); + L.d_strln "Failure of get_strexp_at_syn_offsets" ; + L.d_str "se: " ; + Sil.d_sexp se ; + L.d_ln () ; + L.d_str "t: " ; + Typ.d_full t ; + L.d_ln () ; assert false in - match se, t.desc, syn_offs with - | _, _, [] -> (se, t) - | Sil.Estruct (fsel, _), Tstruct name, Field (fld, _) :: syn_offs' -> ( - match Tenv.lookup tenv name with - | Some { fields } -> - let se' = - snd (List.find_exn ~f:(fun (f', _) -> Typ.Fieldname.equal f' fld) fsel) in - let t' = - snd3 (List.find_exn ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' fld) fields) in - get_strexp_at_syn_offsets tenv se' t' syn_offs' - | None -> - fail () - ) - | Sil.Earray (_, esel, _), Typ.Tarray (t', _, _), Index ind :: syn_offs' -> - let se' = snd (List.find_exn ~f:(fun (i', _) -> Exp.equal i' ind) esel) in + match (se, t.desc, syn_offs) with + | _, _, [] + -> (se, t) + | Sil.Estruct (fsel, _), Tstruct name, (Field (fld, _)) :: syn_offs' -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> let se' = snd (List.find_exn ~f:(fun (f', _) -> Typ.Fieldname.equal f' fld) fsel) in + let t' = snd3 (List.find_exn ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' fld) fields) in + get_strexp_at_syn_offsets tenv se' t' syn_offs' + | None + -> fail () ) + | Sil.Earray (_, esel, _), Typ.Tarray (t', _, _), (Index ind) :: syn_offs' + -> let se' = snd (List.find_exn ~f:(fun (i', _) -> Exp.equal i' ind) esel) in get_strexp_at_syn_offsets tenv se' t' syn_offs' - | _ -> - fail () + | _ + -> fail () (** Replace a strexp at the given syntactic offset list *) let rec replace_strexp_at_syn_offsets tenv se (t: Typ.t) syn_offs update = - match se, t.desc, syn_offs with - | _, _, [] -> - update se - | Sil.Estruct (fsel, inst), Tstruct name, Field (fld, _) :: syn_offs' -> ( - match Tenv.lookup tenv name with - | Some { fields } -> - let se' = snd (List.find_exn ~f:(fun (f', _) -> Typ.Fieldname.equal f' fld) fsel) in - let t' = (fun (_,y,_) -> y) - (List.find_exn ~f:(fun (f', _, _) -> - Typ.Fieldname.equal f' fld) fields) in - let se_mod = replace_strexp_at_syn_offsets tenv se' t' syn_offs' update in - let fsel' = - List.map ~f:(fun (f'', se'') -> - if Typ.Fieldname.equal f'' fld then (fld, se_mod) else (f'', se'') - ) fsel in - Sil.Estruct (fsel', inst) - | None -> - assert false - ) - | Sil.Earray (len, esel, inst), Tarray (t', _, _), Index idx :: syn_offs' -> - let se' = snd (List.find_exn ~f:(fun (i', _) -> Exp.equal i' idx) esel) in + match (se, t.desc, syn_offs) with + | _, _, [] + -> update se + | Sil.Estruct (fsel, inst), Tstruct name, (Field (fld, _)) :: syn_offs' -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> let se' = snd (List.find_exn ~f:(fun (f', _) -> Typ.Fieldname.equal f' fld) fsel) in + let t' = + (fun (_, y, _) -> y) + (List.find_exn ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' fld) fields) + in + let se_mod = replace_strexp_at_syn_offsets tenv se' t' syn_offs' update in + let fsel' = + List.map + ~f:(fun (f'', se'') -> + if Typ.Fieldname.equal f'' fld then (fld, se_mod) else (f'', se'')) + fsel + in + Sil.Estruct (fsel', inst) + | None + -> assert false ) + | Sil.Earray (len, esel, inst), Tarray (t', _, _), (Index idx) :: syn_offs' + -> let se' = snd (List.find_exn ~f:(fun (i', _) -> Exp.equal i' idx) esel) in let se_mod = replace_strexp_at_syn_offsets tenv se' t' syn_offs' update in let esel' = - List.map ~f:(fun ese -> if Exp.equal (fst ese) idx then (idx, se_mod) else ese) esel in + List.map ~f:(fun ese -> if Exp.equal (fst ese) idx then (idx, se_mod) else ese) esel + in Sil.Earray (len, esel', inst) - | _ -> assert false + | _ + -> assert false (** convert a path into an expression *) let path_to_exps (root, syn_offs_in) = let rec convert acc = function - | [] -> acc - | Field (f, t) :: syn_offs' -> - let acc' = List.map ~f:(fun e -> Exp.Lfield (e, f, t)) acc in + | [] + -> acc + | (Field (f, t)) :: syn_offs' + -> let acc' = List.map ~f:(fun e -> Exp.Lfield (e, f, t)) acc in convert acc' syn_offs' - | Index idx :: syn_offs' -> - let acc' = List.map ~f:(fun e -> Exp.Lindex (e, idx)) acc in - convert acc' syn_offs' in - begin - convert [root] syn_offs_in - end + | (Index idx) :: syn_offs' + -> let acc' = List.map ~f:(fun e -> Exp.Lindex (e, idx)) acc in + convert acc' syn_offs' + in + convert [root] syn_offs_in (** create a path from a root and a list of offsets *) let path_from_exp_offsets root offs = let offset_to_syn_offset = function - | Sil.Off_fld (fld, typ) -> Field (fld, typ) - | Sil.Off_index idx -> Index idx in + | Sil.Off_fld (fld, typ) + -> Field (fld, typ) + | Sil.Off_index idx + -> Index idx + in let syn_offs = List.map ~f:offset_to_syn_offset offs in (root, syn_offs) @@ -145,162 +153,168 @@ end = struct type strexp_data = path * Sil.strexp * Typ.t (** Store hpred using physical equality, and offset list for an array *) - type t = sigma * Sil.hpred * (syn_offset list) + type t = sigma * Sil.hpred * syn_offset list (** Find an array at the given path. Can raise [Not_found] *) let find_path sigma (root, syn_offs) : t = - let filter = function - | Sil.Hpointsto (e, _, _) -> Exp.equal root e - | _ -> false in + let filter = function Sil.Hpointsto (e, _, _) -> Exp.equal root e | _ -> false in let hpred = List.find_exn ~f:filter sigma in (sigma, hpred, syn_offs) (** Find a sub strexp with the given property. Can raise [Not_found] *) - let find tenv (sigma : sigma) (pred : strexp_data -> bool) : t list = + let find tenv (sigma: sigma) (pred: strexp_data -> bool) : t list = let found = ref [] in let rec find_offset_sexp sigma_other hpred root offs se (typ: Typ.t) = let offs' = List.rev offs in let path = (root, offs') in if pred (path, se, typ) then found := (sigma, hpred, offs') :: !found - else begin - match se, typ.desc with + else + match (se, typ.desc) with | Sil.Estruct (fsel, _), Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { fields } -> - find_offset_fsel sigma_other hpred root offs fsel fields typ - | None -> - () - ) - | Sil.Earray (_, esel, _), Tarray (t, _, _) -> - find_offset_esel sigma_other hpred root offs esel t - | _ -> () - end - and find_offset_fsel sigma_other hpred root offs fsel ftal typ = match fsel with - | [] -> () - | (f, se) :: fsel' -> - begin - match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' f) ftal with - | Some (_, t, _) -> - find_offset_sexp sigma_other hpred root ((Field (f, typ)) :: offs) se t - | None -> - L.d_strln - ("Can't find field " ^ (Typ.Fieldname.to_string f) ^ " in StrexpMatch.find") - end; + match Tenv.lookup tenv name with + | Some {fields} + -> find_offset_fsel sigma_other hpred root offs fsel fields typ + | None + -> () ) + | Sil.Earray (_, esel, _), Tarray (t, _, _) + -> find_offset_esel sigma_other hpred root offs esel t + | _ + -> () + and find_offset_fsel sigma_other hpred root offs fsel ftal typ = + match fsel with + | [] + -> () + | (f, se) :: fsel' + -> ( match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' f) ftal with + | Some (_, t, _) + -> find_offset_sexp sigma_other hpred root (Field (f, typ) :: offs) se t + | None + -> L.d_strln ("Can't find field " ^ Typ.Fieldname.to_string f ^ " in StrexpMatch.find") + ) ; find_offset_fsel sigma_other hpred root offs fsel' ftal typ - and find_offset_esel sigma_other hpred root offs esel t = match esel with - | [] -> () - | (ind, se) :: esel' -> - begin - find_offset_sexp sigma_other hpred root ((Index ind):: offs) se t; - find_offset_esel sigma_other hpred root offs esel' t - end in + and find_offset_esel sigma_other hpred root offs esel t = + match esel with + | [] + -> () + | (ind, se) :: esel' + -> find_offset_sexp sigma_other hpred root (Index ind :: offs) se t ; + find_offset_esel sigma_other hpred root offs esel' t + in let rec iterate sigma_seen = function - | [] -> () - | hpred :: sigma_rest -> - begin - match hpred with - | Sil.Hpointsto (root, se, te) -> - let sigma_other = sigma_seen @ sigma_rest in - find_offset_sexp sigma_other hpred root [] se (Exp.texp_to_typ None te) - | _ -> () - end; - iterate (hpred:: sigma_seen) sigma_rest in - begin - iterate [] sigma; - !found - end + | [] + -> () + | hpred :: sigma_rest + -> ( match hpred with + | Sil.Hpointsto (root, se, te) + -> let sigma_other = sigma_seen @ sigma_rest in + find_offset_sexp sigma_other hpred root [] se (Exp.texp_to_typ None te) + | _ + -> () ) ; + iterate (hpred :: sigma_seen) sigma_rest + in + iterate [] sigma ; !found (** Get the matched strexp *) - let get_data tenv ((_ , hpred, syn_offs) : t) = match hpred with - | Sil.Hpointsto (root, se, te) -> - let t = Exp.texp_to_typ None te in + let get_data tenv ((_, hpred, syn_offs): t) = + match hpred with + | Sil.Hpointsto (root, se, te) + -> let t = Exp.texp_to_typ None te in let se', t' = get_strexp_at_syn_offsets tenv se t syn_offs in let path' = (root, syn_offs) in (path', se', t') - | _ -> assert false + | _ + -> assert false (** Replace the current hpred *) - let replace_hpred ((sigma, hpred, _) : t) hpred' = + let replace_hpred ((sigma, hpred, _): t) hpred' = List.map ~f:(fun hpred'' -> if phys_equal hpred'' hpred then hpred' else hpred'') sigma (** Replace the strexp at the given offset in the given hpred *) let hpred_replace_strexp tenv footprint_part hpred syn_offs update = let update se' = let se_in = update se' in - match se', se_in with - | Sil.Earray (len, esel, _), Sil.Earray (_, esel_in, inst2) -> - let orig_indices = List.map ~f:fst esel in + match (se', se_in) with + | Sil.Earray (len, esel, _), Sil.Earray (_, esel_in, inst2) + -> let orig_indices = List.map ~f:fst esel in let index_is_not_new idx = List.exists ~f:(Exp.equal idx) orig_indices in let process_index idx = - if index_is_not_new idx then idx else (Sil.array_clean_new_index footprint_part idx) in - let esel_in' = List.map ~f:(fun (idx, se) -> process_index idx, se) esel_in in + if index_is_not_new idx then idx else Sil.array_clean_new_index footprint_part idx + in + let esel_in' = List.map ~f:(fun (idx, se) -> (process_index idx, se)) esel_in in Sil.Earray (len, esel_in', inst2) - | _, _ -> se_in in - begin - match hpred with - | Sil.Hpointsto (root, se, te) -> - let t = Exp.texp_to_typ None te in - let se' = replace_strexp_at_syn_offsets tenv se t syn_offs update in - Sil.Hpointsto (root, se', te) - | _ -> assert false - end + | _, _ + -> se_in + in + match hpred with + | Sil.Hpointsto (root, se, te) + -> let t = Exp.texp_to_typ None te in + let se' = replace_strexp_at_syn_offsets tenv se t syn_offs update in + Sil.Hpointsto (root, se', te) + | _ + -> assert false (** Replace the strexp at a given position by a new strexp *) - let replace_strexp tenv footprint_part ((sigma, hpred, syn_offs) : t) se_in = + let replace_strexp tenv footprint_part ((sigma, hpred, syn_offs): t) se_in = let update _ = se_in in let hpred' = hpred_replace_strexp tenv footprint_part hpred syn_offs update in replace_hpred (sigma, hpred, syn_offs) hpred' (** Replace the index in the array at a given position with the new index *) - let replace_index tenv footprint_part ((sigma, hpred, syn_offs) : t) (index: Exp.t) (index': Exp.t) = + let replace_index tenv footprint_part ((sigma, hpred, syn_offs): t) (index: Exp.t) + (index': Exp.t) = let update se' = match se' with - | Sil.Earray (len, esel, inst) -> - let esel' = - List.map ~f:(fun (e', se') -> - if Exp.equal e' index then (index', se') else (e', se') - ) esel in + | Sil.Earray (len, esel, inst) + -> let esel' = + List.map + ~f:(fun (e', se') -> if Exp.equal e' index then (index', se') else (e', se')) + esel + in Sil.Earray (len, esel', inst) - | _ -> assert false in + | _ + -> assert false + in let hpred' = hpred_replace_strexp tenv footprint_part hpred syn_offs update in replace_hpred (sigma, hpred, syn_offs) hpred' end (** This function renames expressions in [p]. The renaming is, roughly speaking, to replace [path.i] by [path.i'] for all (i, i') in [map]. *) -let prop_replace_path_index tenv - (p: Prop.exposed Prop.t) - (path: StrexpMatch.path) - (map : (Exp.t * Exp.t) list) : Prop.exposed Prop.t - = +let prop_replace_path_index tenv (p: Prop.exposed Prop.t) (path: StrexpMatch.path) + (map: (Exp.t * Exp.t) list) : Prop.exposed Prop.t = let elist_path = StrexpMatch.path_to_exps path in let expmap_list = - List.fold ~f:(fun acc_outer e_path -> - List.fold ~f:(fun acc_inner (old_index, new_index) -> - let old_e_path_index = Prop.exp_normalize_prop tenv p (Exp.Lindex(e_path, old_index)) in - let new_e_path_index = Prop.exp_normalize_prop tenv p (Exp.Lindex(e_path, new_index)) in - (old_e_path_index, new_e_path_index) :: acc_inner - ) ~init:acc_outer map - ) ~init:[] elist_path in + List.fold + ~f:(fun acc_outer e_path -> + List.fold + ~f:(fun acc_inner (old_index, new_index) -> + let old_e_path_index = + Prop.exp_normalize_prop tenv p (Exp.Lindex (e_path, old_index)) + in + let new_e_path_index = + Prop.exp_normalize_prop tenv p (Exp.Lindex (e_path, new_index)) + in + (old_e_path_index, new_e_path_index) :: acc_inner) + ~init:acc_outer map) + ~init:[] elist_path + in let expmap_fun e' = - Option.value_map - ~f:snd (List.find ~f:(fun (e, _) -> Exp.equal e e') expmap_list) - ~default:e' in + Option.value_map ~f:snd (List.find ~f:(fun (e, _) -> Exp.equal e e') expmap_list) ~default:e' + in Prop.prop_expmap expmap_fun p (** This function uses [update] and transforms the two sigma parts of [p], the sigma of the current SH of [p] and that of the footprint of [p]. *) -let prop_update_sigma_and_fp_sigma tenv - (p : Prop.normal Prop.t) - (update : bool -> sigma -> sigma * bool) : Prop.normal Prop.t * bool - = +let prop_update_sigma_and_fp_sigma tenv (p: Prop.normal Prop.t) + (update: bool -> sigma -> sigma * bool) : Prop.normal Prop.t * bool = let sigma', changed = update false p.Prop.sigma in let ep1 = Prop.set p ~sigma:sigma' in let ep2, changed2 = if !Config.footprint then let sigma_fp', changed' = update true ep1.Prop.sigma_fp in (Prop.set ep1 ~sigma_fp:sigma_fp', changed') - else (ep1, false) in + else (ep1, false) + in (Prop.normalize tenv ep2, changed || changed2) (** Remember whether array abstraction was performed (to be reset before calling Abs.abstract) *) @@ -309,224 +323,236 @@ let array_abstraction_performed = ref false (** This function abstracts strexps. The parameter [can_abstract] spots strexps where the abstraction might be applicable, and the parameter [do_abstract] does the abstraction to those spotted strexps. *) -let generic_strexp_abstract tenv - (abstraction_name : string) - (p_in : Prop.normal Prop.t) - (can_abstract_ : StrexpMatch.strexp_data -> bool) - (do_abstract : bool -> Prop.normal Prop.t -> StrexpMatch.strexp_data -> Prop.normal Prop.t * bool) - : Prop.normal Prop.t - = +let generic_strexp_abstract tenv (abstraction_name: string) (p_in: Prop.normal Prop.t) + (can_abstract_: StrexpMatch.strexp_data -> bool) + (do_abstract: + bool -> Prop.normal Prop.t -> StrexpMatch.strexp_data -> Prop.normal Prop.t * bool) + : Prop.normal Prop.t = let can_abstract data = let r = can_abstract_ data in - if r then array_abstraction_performed := true; - r in + if r then array_abstraction_performed := true ; + r + in let find_strexp_to_abstract p0 = let find sigma = StrexpMatch.find tenv sigma can_abstract in let matchings_cur = find p0.Prop.sigma in let matchings_fp = find p0.Prop.sigma_fp in - matchings_cur, matchings_fp in + (matchings_cur, matchings_fp) + in let match_select_next (matchings_cur, matchings_fp) = - match matchings_cur, matchings_fp with - | [], [] -> raise Not_found - | matched :: cur', fp' -> matched, false, (cur', fp') - | [], matched :: fp' -> matched, true, ([], fp') in + match (matchings_cur, matchings_fp) with + | [], [] + -> raise Not_found + | matched :: cur', fp' + -> (matched, false, (cur', fp')) + | [], matched :: fp' + -> (matched, true, ([], fp')) + in let rec match_abstract p0 matchings_cur_fp = try let matched, footprint_part, matchings_cur_fp' = match_select_next matchings_cur_fp in let n = List.length (snd matchings_cur_fp') + 1 in - if Config.trace_absarray then (L.d_strln ("Num of fp candidates " ^ (string_of_int n))); + if Config.trace_absarray then L.d_strln ("Num of fp candidates " ^ string_of_int n) ; let strexp_data = StrexpMatch.get_data tenv matched in let p1, changed = do_abstract footprint_part p0 strexp_data in - if changed then (p1, true) - else match_abstract p0 matchings_cur_fp' - with - | Not_found -> (p0, false) in + if changed then (p1, true) else match_abstract p0 matchings_cur_fp' + with Not_found -> (p0, false) + in let rec find_then_abstract bound p0 = if Int.equal bound 0 then p0 - else begin - if Config.trace_absarray then - (L.d_strln ("Applying " ^ abstraction_name ^ " to"); Prop.d_prop p0; L.d_ln (); L.d_ln ()); + else ( + if Config.trace_absarray then ( + L.d_strln ("Applying " ^ abstraction_name ^ " to") ; + Prop.d_prop p0 ; + L.d_ln () ; + L.d_ln () ) ; let matchings_cur_fp = find_strexp_to_abstract p0 in let p1, changed = match_abstract p0 matchings_cur_fp in - if changed then find_then_abstract (bound - 1) p1 else p0 - end in + if changed then find_then_abstract (bound - 1) p1 else p0 ) + in let matchings_cur, matchings_fp = find_strexp_to_abstract p_in in - let num_matches = (List.length matchings_cur) + (List.length matchings_fp) in - begin - find_then_abstract num_matches p_in - end - + let num_matches = List.length matchings_cur + List.length matchings_fp in + find_then_abstract num_matches p_in (** Return [true] if there's a pointer to the index *) let index_is_pointed_to tenv (p: Prop.normal Prop.t) (path: StrexpMatch.path) (index: Exp.t) : bool = let indices = - let index_plus_one = Exp.BinOp(Binop.PlusA, index, Exp.one) in - [index; index_plus_one] in + let index_plus_one = Exp.BinOp (Binop.PlusA, index, Exp.one) in + [index; index_plus_one] + in let add_index_to_paths = let elist_path = StrexpMatch.path_to_exps path in - let add_index i e = Prop.exp_normalize_prop tenv p (Exp.Lindex(e, i)) in - fun i -> List.map ~f:(add_index i) elist_path in + let add_index i e = Prop.exp_normalize_prop tenv p (Exp.Lindex (e, i)) in + fun i -> List.map ~f:(add_index i) elist_path + in let pointers = List.concat_map ~f:add_index_to_paths indices in let filter = function - | Sil.Hpointsto (_, Sil.Eexp (e, _), _) -> List.exists ~f:(Exp.equal e) pointers - | _ -> false in + | Sil.Hpointsto (_, Sil.Eexp (e, _), _) + -> List.exists ~f:(Exp.equal e) pointers + | _ + -> false + in List.exists ~f:filter p.Prop.sigma - (** Given [p] containing an array at [path], blur [index] in it *) -let blur_array_index tenv - (p: Prop.normal Prop.t) - (path: StrexpMatch.path) - (index: Exp.t) : Prop.normal Prop.t - = +let blur_array_index tenv (p: Prop.normal Prop.t) (path: StrexpMatch.path) (index: Exp.t) + : Prop.normal Prop.t = try let fresh_index = - Exp.Var - (Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed)) in + Exp.Var (Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed)) + in let p2 = try if !Config.footprint then - begin - let sigma_fp = p.Prop.sigma_fp in - let matched_fp = StrexpMatch.find_path sigma_fp path in - let sigma_fp' = StrexpMatch.replace_index tenv true matched_fp index fresh_index in - Prop.set p ~sigma_fp:sigma_fp' - end + let sigma_fp = p.Prop.sigma_fp in + let matched_fp = StrexpMatch.find_path sigma_fp path in + let sigma_fp' = StrexpMatch.replace_index tenv true matched_fp index fresh_index in + Prop.set p ~sigma_fp:sigma_fp' else Prop.expose p - with Not_found -> Prop.expose p in + with Not_found -> Prop.expose p + in let p3 = let matched = StrexpMatch.find_path p.Prop.sigma path in let sigma' = StrexpMatch.replace_index tenv false matched index fresh_index in - Prop.set p2 ~sigma:sigma' in + Prop.set p2 ~sigma:sigma' + in let p4 = - let index_next = Exp.BinOp(Binop.PlusA, index, Exp.one) in + let index_next = Exp.BinOp (Binop.PlusA, index, Exp.one) in let fresh_index_next = Exp.BinOp (Binop.PlusA, fresh_index, Exp.one) in let map = [(index, fresh_index); (index_next, fresh_index_next)] in - prop_replace_path_index tenv p3 path map in + prop_replace_path_index tenv p3 path map + in Prop.normalize tenv p4 with Not_found -> p - (** Given [p] containing an array at [root], blur [indices] in it *) -let blur_array_indices tenv - (p: Prop.normal Prop.t) - (root: StrexpMatch.path) - (indices: Exp.t list) : Prop.normal Prop.t * bool = +let blur_array_indices tenv (p: Prop.normal Prop.t) (root: StrexpMatch.path) (indices: Exp.t list) + : Prop.normal Prop.t * bool = let f prop index = blur_array_index tenv prop root index in (List.fold ~f ~init:p indices, List.length indices > 0) - (** Given [p] containing an array at [root], only keep [indices] in it *) -let keep_only_indices tenv - (p: Prop.normal Prop.t) - (path: StrexpMatch.path) - (indices: Exp.t list) : Prop.normal Prop.t * bool - = +let keep_only_indices tenv (p: Prop.normal Prop.t) (path: StrexpMatch.path) (indices: Exp.t list) + : Prop.normal Prop.t * bool = let prune_sigma footprint_part sigma = try let matched = StrexpMatch.find_path sigma path in - let (_, se, _) = StrexpMatch.get_data tenv matched in + let _, se, _ = StrexpMatch.get_data tenv matched in match se with - | Sil.Earray (len, esel, inst) -> - let esel', esel_leftover' = - List.partition_tf ~f:(fun (e, _) -> List.exists ~f:(Exp.equal e) indices) esel in + | Sil.Earray (len, esel, inst) + -> let esel', esel_leftover' = + List.partition_tf ~f:(fun (e, _) -> List.exists ~f:(Exp.equal e) indices) esel + in if List.is_empty esel_leftover' then (sigma, false) - else begin + else let se' = Sil.Earray (len, esel', inst) in let sigma' = StrexpMatch.replace_strexp tenv footprint_part matched se' in (sigma', true) - end - | _ -> (sigma, false) - with Not_found -> (sigma, false) in + | _ + -> (sigma, false) + with Not_found -> (sigma, false) + in prop_update_sigma_and_fp_sigma tenv p prune_sigma - (** If the type is array, check whether we should do abstraction *) -let array_typ_can_abstract {Typ.desc} = match desc with - | Tarray ({desc=Tptr ({desc=Tfun _}, _)}, _, _) -> false (* don't abstract arrays of pointers *) - | _ -> true +let array_typ_can_abstract {Typ.desc} = + match desc with + | Tarray ({desc= Tptr ({desc= Tfun _}, _)}, _, _) + -> false (* don't abstract arrays of pointers *) + | _ + -> true (** This function checks whether we can apply an abstraction to a strexp *) -let strexp_can_abstract ((_, se, typ) : StrexpMatch.strexp_data) : bool = - let can_abstract_se = match se with - | Sil.Earray (_, esel, _) -> - let len = List.length esel in +let strexp_can_abstract ((_, se, typ): StrexpMatch.strexp_data) : bool = + let can_abstract_se = + match se with + | Sil.Earray (_, esel, _) + -> let len = List.length esel in len > 1 - | _ -> false in + | _ + -> false + in can_abstract_se && array_typ_can_abstract typ - (** This function abstracts a strexp *) -let strexp_do_abstract tenv - footprint_part p ((path, se_in, _) : StrexpMatch.strexp_data) : Prop.normal Prop.t * bool = - if Config.trace_absarray && footprint_part then - (L.d_str "strexp_do_abstract (footprint)"; L.d_ln ()); - if Config.trace_absarray && not footprint_part then - (L.d_str "strexp_do_abstract (nonfootprint)"; L.d_ln ()); +let strexp_do_abstract tenv footprint_part p ((path, se_in, _): StrexpMatch.strexp_data) + : Prop.normal Prop.t * bool = + if Config.trace_absarray && footprint_part then ( + L.d_str "strexp_do_abstract (footprint)" ; L.d_ln () ) ; + if Config.trace_absarray && not footprint_part then ( + L.d_str "strexp_do_abstract (nonfootprint)" ; L.d_ln () ) ; let prune_and_blur d_keys keep blur path keep_keys blur_keys = let p2, changed2 = - if Config.trace_absarray then (L.d_str "keep "; d_keys keep_keys; L.d_ln ()); - keep p path keep_keys in + if Config.trace_absarray then ( L.d_str "keep " ; d_keys keep_keys ; L.d_ln () ) ; + keep p path keep_keys + in let p3, changed3 = if List.is_empty blur_keys then (p2, false) - else begin - if Config.trace_absarray then (L.d_str "blur "; d_keys blur_keys; L.d_ln ()); - blur p2 path blur_keys - end in - if Config.trace_absarray then (L.d_strln "Returns"; Prop.d_prop p3; L.d_ln (); L.d_ln ()); - (p3, changed2 || changed3) in + else ( + if Config.trace_absarray then ( L.d_str "blur " ; d_keys blur_keys ; L.d_ln () ) ; + blur p2 path blur_keys ) + in + if Config.trace_absarray then ( L.d_strln "Returns" ; Prop.d_prop p3 ; L.d_ln () ; L.d_ln () ) ; + (p3, changed2 || changed3) + in let prune_and_blur_indices = - prune_and_blur Sil.d_exp_list (keep_only_indices tenv) (blur_array_indices tenv) in - + prune_and_blur Sil.d_exp_list (keep_only_indices tenv) (blur_array_indices tenv) + in let partition_abstract should_keep abstract ksel default_keys = let keep_ksel, remove_ksel = List.partition_tf ~f:should_keep ksel in let keep_keys, _, _ = - List.map ~f:fst keep_ksel, List.map ~f:fst remove_ksel, List.map ~f:fst ksel in + (List.map ~f:fst keep_ksel, List.map ~f:fst remove_ksel, List.map ~f:fst ksel) + in let keep_keys' = if List.is_empty keep_keys then default_keys else keep_keys in - abstract keep_keys' keep_keys' in + abstract keep_keys' keep_keys' + in let do_array_footprint esel = (* array case footprint: keep only the last index, and blur it *) let should_keep (i0, _) = index_is_pointed_to tenv p path i0 in let abstract = prune_and_blur_indices path in let default_indices = - match List.map ~f:fst esel with - | [] -> [] - | indices -> [List.last_exn indices] (* keep last key at least *) in - partition_abstract should_keep abstract esel default_indices in + match List.map ~f:fst esel with [] -> [] | indices -> [List.last_exn indices] + (* keep last key at least *) + in + partition_abstract should_keep abstract esel default_indices + in let do_footprint () = - match se_in with - | Sil.Earray (_, esel, _) -> do_array_footprint esel - | _ -> assert false in - + match se_in with Sil.Earray (_, esel, _) -> do_array_footprint esel | _ -> assert false + in let filter_abstract d_keys should_keep abstract ksel default_keys = let keep_ksel = List.filter ~f:should_keep ksel in let keep_keys = List.map ~f:fst keep_ksel in let keep_keys' = if List.is_empty keep_keys then default_keys else keep_keys in - if Config.trace_absarray then (L.d_str "keep "; d_keys keep_keys'; L.d_ln ()); - abstract keep_keys' [] in + if Config.trace_absarray then ( L.d_str "keep " ; d_keys keep_keys' ; L.d_ln () ) ; + abstract keep_keys' [] + in let do_array_reexecution esel = (* array case re-execution: remove and blur constant and primed indices *) let is_pointed index = index_is_pointed_to tenv p path index in - let should_keep (index, _) = match index with - | Exp.Const _ -> is_pointed index - | Exp.Var id -> Ident.is_normal id || is_pointed index - | _ -> false in + let should_keep (index, _) = + match index with + | Exp.Const _ + -> is_pointed index + | Exp.Var id + -> Ident.is_normal id || is_pointed index + | _ + -> false + in let abstract = prune_and_blur_indices path in - filter_abstract Sil.d_exp_list should_keep abstract esel [] in + filter_abstract Sil.d_exp_list should_keep abstract esel [] + in let do_reexecution () = - match se_in with - | Sil.Earray (_, esel, _) -> do_array_reexecution esel - | _ -> assert false in + match se_in with Sil.Earray (_, esel, _) -> do_array_reexecution esel | _ -> assert false + in + if !Config.footprint then do_footprint () else do_reexecution () - if !Config.footprint then do_footprint () - else do_reexecution () - -let strexp_abstract tenv (p : Prop.normal Prop.t) : Prop.normal Prop.t = +let strexp_abstract tenv (p: Prop.normal Prop.t) : Prop.normal Prop.t = generic_strexp_abstract tenv "strexp_abstract" p strexp_can_abstract (strexp_do_abstract tenv) let report_error prop = - L.d_strln "Check after array abstraction: FAIL"; - Prop.d_prop prop; L.d_ln (); + L.d_strln "Check after array abstraction: FAIL" ; + Prop.d_prop prop ; + L.d_ln () ; assert false (** Check performed after the array abstraction to see whether it was successful. Raise assert false in case of failure *) @@ -536,84 +562,103 @@ let check_after_array_abstraction tenv prop = if !Config.footprint then let path = StrexpMatch.path_from_exp_offsets root offs in index_is_pointed_to tenv prop path ind - else not (Sil.fav_exists (Sil.exp_fav ind) Ident.is_primed) in + else not (Sil.fav_exists (Sil.exp_fav ind) Ident.is_primed) + in let rec check_se root offs typ = function - | Sil.Eexp _ -> () - | Sil.Earray (_, esel, _) -> (* check that no more than 2 elements are in the array *) + | Sil.Eexp _ + -> () + | Sil.Earray (_, esel, _) + -> (* check that no more than 2 elements are in the array *) let typ_elem = Typ.array_elem (Some (Typ.mk Tvoid)) typ in if List.length esel > 2 && array_typ_can_abstract typ then - if List.for_all ~f:(check_index root offs) esel then () - else report_error prop - else List.iter + if List.for_all ~f:(check_index root offs) esel then () else report_error prop + else + List.iter ~f:(fun (ind, se) -> check_se root (offs @ [Sil.Off_index ind]) typ_elem se) esel - | Sil.Estruct (fsel, _) -> - List.iter ~f:(fun (f, se) -> + | Sil.Estruct (fsel, _) + -> List.iter + ~f:(fun (f, se) -> let typ_f = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f typ in - check_se root (offs @ [Sil.Off_fld (f, typ)]) typ_f se) fsel in + check_se root (offs @ [Sil.Off_fld (f, typ)]) typ_f se) + fsel + in let check_hpred = function - | Sil.Hpointsto (root, se, texp) -> - let typ = Exp.texp_to_typ (Some (Typ.mk Tvoid)) texp in + | Sil.Hpointsto (root, se, texp) + -> let typ = Exp.texp_to_typ (Some (Typ.mk Tvoid)) texp in check_se root [] typ se - | Sil.Hlseg _ | Sil.Hdllseg _ -> () in + | Sil.Hlseg _ | Sil.Hdllseg _ + -> () + in let check_sigma sigma = List.iter ~f:check_hpred sigma in (* check_footprint_pure prop; *) - check_sigma prop.Prop.sigma; - check_sigma prop.Prop.sigma_fp + check_sigma prop.Prop.sigma ; check_sigma prop.Prop.sigma_fp (** Apply array abstraction and check the result *) let abstract_array_check tenv p = let p_res = strexp_abstract tenv p in - check_after_array_abstraction tenv p_res; - p_res + check_after_array_abstraction tenv p_res ; p_res (** remove redundant elements in an array *) let remove_redundant_elements tenv prop = - Prop.d_prop prop; L.d_ln (); - let occurs_at_most_once : Ident.t -> bool = (* the variable occurs at most once in the footprint or current part *) + Prop.d_prop prop ; + L.d_ln () ; + let occurs_at_most_once : Ident.t -> bool = + (* the variable occurs at most once in the footprint or current part *) let fav_curr = Sil.fav_new () in let fav_foot = Sil.fav_new () in - Sil.fav_duplicates := true; - Sil.sub_fav_add fav_curr prop.Prop.sub; - Prop.pi_fav_add fav_curr prop.Prop.pi; - Prop.sigma_fav_add fav_curr prop.Prop.sigma; - Prop.pi_fav_add fav_foot prop.Prop.pi_fp; - Prop.sigma_fav_add fav_foot prop.Prop.sigma_fp; + Sil.fav_duplicates := true ; + Sil.sub_fav_add fav_curr prop.Prop.sub ; + Prop.pi_fav_add fav_curr prop.Prop.pi ; + Prop.sigma_fav_add fav_curr prop.Prop.sigma ; + Prop.pi_fav_add fav_foot prop.Prop.pi_fp ; + Prop.sigma_fav_add fav_foot prop.Prop.sigma_fp ; let favl_curr = Sil.fav_to_list fav_curr in let favl_foot = Sil.fav_to_list fav_foot in - Sil.fav_duplicates := false; + Sil.fav_duplicates := false ; let num_occur l id = List.length (List.filter ~f:(fun id' -> Ident.equal id id') l) in - let at_most_once v = - num_occur favl_curr v <= 1 && num_occur favl_foot v <= 1 in - at_most_once in + let at_most_once v = num_occur favl_curr v <= 1 && num_occur favl_foot v <= 1 in + at_most_once + in let modified = ref false in let filter_redundant_e_se fp_part (e, se) = let remove () = - L.d_strln "kill_redundant: removing "; Sil.d_exp e; L.d_str " "; Sil.d_sexp se; L.d_ln(); - array_abstraction_performed := true; - modified := true; - false in - match e, se with - | Exp.Const (Const.Cint i), Sil.Eexp (Exp.Var id, _) - when (not fp_part || IntLit.iszero i) && not (Ident.is_normal id) && occurs_at_most_once id -> - remove () (* unknown value can be removed in re-execution mode or if the index is zero *) - | Exp.Var id, Sil.Eexp _ when not (Ident.is_normal id) && occurs_at_most_once id -> - remove () (* index unknown can be removed *) - | _ -> true in + L.d_strln "kill_redundant: removing " ; + Sil.d_exp e ; + L.d_str " " ; + Sil.d_sexp se ; + L.d_ln () ; + array_abstraction_performed := true ; + modified := true ; + false + in + match (e, se) with + | Exp.Const Const.Cint i, Sil.Eexp (Exp.Var id, _) + when (not fp_part || IntLit.iszero i) && not (Ident.is_normal id) && occurs_at_most_once id + -> remove () (* unknown value can be removed in re-execution mode or if the index is zero *) + | Exp.Var id, Sil.Eexp _ when not (Ident.is_normal id) && occurs_at_most_once id + -> remove () (* index unknown can be removed *) + | _ + -> true + in let remove_redundant_se fp_part = function - | Sil.Earray (len, esel, inst) -> - let esel' = List.filter ~f:(filter_redundant_e_se fp_part) esel in + | Sil.Earray (len, esel, inst) + -> let esel' = List.filter ~f:(filter_redundant_e_se fp_part) esel in Sil.Earray (len, esel', inst) - | se -> se in + | se + -> se + in let remove_redundant_hpred fp_part = function - | Sil.Hpointsto (e, se, te) -> - let se' = remove_redundant_se fp_part se in + | Sil.Hpointsto (e, se, te) + -> let se' = remove_redundant_se fp_part se in Sil.Hpointsto (e, se', te) - | hpred -> hpred in + | hpred + -> hpred + in let remove_redundant_sigma fp_part sigma = List.map ~f:(remove_redundant_hpred fp_part) sigma in let sigma' = remove_redundant_sigma false prop.Prop.sigma in let sigma_fp' = remove_redundant_sigma true prop.Prop.sigma_fp in if !modified then - let prop' = Prop.set prop ~sigma:sigma' ~sigma_fp:sigma_fp' in + let prop' = Prop.set prop ~sigma:sigma' ~sigma_fp:sigma_fp' in Prop.normalize tenv prop' else prop diff --git a/infer/src/backend/absarray.mli b/infer/src/backend/absarray.mli index bf970c864..74d211028 100644 --- a/infer/src/backend/absarray.mli +++ b/infer/src/backend/absarray.mli @@ -12,11 +12,11 @@ open! IStd (** Abstraction for Arrays *) -(** Apply array abstraction and check the result *) val abstract_array_check : Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t +(** Apply array abstraction and check the result *) -(** Remember whether array abstraction was performed (to be reset before calling Abs.abstract) *) val array_abstraction_performed : bool ref +(** Remember whether array abstraction was performed (to be reset before calling Abs.abstract) *) -(** remove redundant elements in an array *) val remove_redundant_elements : Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t +(** remove redundant elements in an array *) diff --git a/infer/src/backend/buckets.ml b/infer/src/backend/buckets.ml index 2f716f714..ccf76d11a 100644 --- a/infer/src/backend/buckets.ml +++ b/infer/src/backend/buckets.ml @@ -22,118 +22,135 @@ let verbose = Config.trace_error let check_nested_loop path pos_opt = let trace_length = ref 0 in let loop_visits_log = ref [] in - let in_nested_loop () = match !loop_visits_log with - | true :: true :: _ -> - if verbose then L.d_strln "in nested loop"; - true (* last two loop visits were entering loops *) - | _ -> false in - let do_node_caller node = match Procdesc.Node.get_kind node with - | Procdesc.Node.Prune_node (b, (Sil.Ik_dowhile | Sil.Ik_for | Sil.Ik_while), _) -> - (* if verbose then *) + let in_nested_loop () = + match !loop_visits_log with + | true :: true :: _ + -> if verbose then L.d_strln "in nested loop" ; + true + (* last two loop visits were entering loops *) + | _ + -> false + in + let do_node_caller node = + match Procdesc.Node.get_kind node with + | Procdesc.Node.Prune_node (b, (Sil.Ik_dowhile | Sil.Ik_for | Sil.Ik_while), _) + -> (* if verbose then *) (* L.d_strln ((if b then "enter" else "exit") ^ " node " *) (* ^ (string_of_int (Procdesc.Node.get_id node))); *) loop_visits_log := b :: !loop_visits_log - | _ -> () in + | _ + -> () + in let do_any_node _level _node = - incr trace_length; + incr trace_length (* L.d_strln *) (* ("level " ^ string_of_int _level ^ *) (* " (Procdesc.Node.get_id node) " ^ string_of_int (Procdesc.Node.get_id _node)) *) in - let f level p _ _ = match Paths.Path.curr_node p with - | Some node -> - do_any_node level node; + let f level p _ _ = + match Paths.Path.curr_node p with + | Some node + -> do_any_node level node ; if Int.equal level 0 then do_node_caller node - | None -> - () in - Paths.Path.iter_shortest_sequence f pos_opt path; - in_nested_loop () + | None + -> () + in + Paths.Path.iter_shortest_sequence f pos_opt path ; in_nested_loop () (** Check that we know where the value was last assigned, and that there is a local access instruction at that line. **) let check_access access_opt de_opt = let find_bucket line_number null_case_flag = - let find_formal_ids node = (* find ids obtained by a letref on a formal parameter *) + let find_formal_ids node = + (* find ids obtained by a letref on a formal parameter *) let node_instrs = Procdesc.Node.get_instrs node in - let formals = match State.get_prop_tenv_pdesc () with - | None -> [] - | Some (_, _, pdesc) -> - Procdesc.get_formals pdesc in + let formals = + match State.get_prop_tenv_pdesc () with + | None + -> [] + | Some (_, _, pdesc) + -> Procdesc.get_formals pdesc + in let formal_names = List.map ~f:fst formals in let is_formal pvar = let name = Pvar.get_name pvar in - List.exists ~f:(Mangled.equal name) formal_names in + List.exists ~f:(Mangled.equal name) formal_names + in let formal_ids = ref [] in let process_formal_letref = function - | Sil.Load (id, Exp.Lvar pvar, _, _) -> - let is_java_this = - Config.curr_language_is Config.Java && Pvar.is_this pvar in + | Sil.Load (id, Exp.Lvar pvar, _, _) + -> let is_java_this = Config.curr_language_is Config.Java && Pvar.is_this pvar in if not is_java_this && is_formal pvar then formal_ids := id :: !formal_ids - | _ -> () in - List.iter ~f:process_formal_letref node_instrs; - !formal_ids in + | _ + -> () + in + List.iter ~f:process_formal_letref node_instrs ; !formal_ids + in let formal_param_used_in_call = ref false in let has_call_or_sets_null node = - let rec exp_is_null exp = match exp with - | Exp.Const (Const.Cint n) -> IntLit.iszero n - | Exp.Cast (_, e) -> exp_is_null e - | Exp.Var _ - | Exp.Lvar _ -> - begin - match State.get_const_map () node exp with - | Some (Const.Cint n) -> - IntLit.iszero n - | _ -> false - end - | _ -> false in + let rec exp_is_null exp = + match exp with + | Exp.Const Const.Cint n + -> IntLit.iszero n + | Exp.Cast (_, e) + -> exp_is_null e + | Exp.Var _ | Exp.Lvar _ -> ( + match State.get_const_map () node exp with + | Some Const.Cint n + -> IntLit.iszero n + | _ + -> false ) + | _ + -> false + in let filter = function - | Sil.Call (_, _, etl, _, _) -> - let formal_ids = find_formal_ids node in - let arg_is_formal_param (e, _) = match e with - | Exp.Var id -> List.exists ~f:(Ident.equal id) formal_ids - | _ -> false in - if List.exists ~f:arg_is_formal_param etl then formal_param_used_in_call := true; + | Sil.Call (_, _, etl, _, _) + -> let formal_ids = find_formal_ids node in + let arg_is_formal_param (e, _) = + match e with Exp.Var id -> List.exists ~f:(Ident.equal id) formal_ids | _ -> false + in + if List.exists ~f:arg_is_formal_param etl then formal_param_used_in_call := true ; true - | Sil.Store (_, _, e, _) -> - exp_is_null e - | _ -> false in - List.exists ~f:filter (Procdesc.Node.get_instrs node) in + | Sil.Store (_, _, e, _) + -> exp_is_null e + | _ + -> false + in + List.exists ~f:filter (Procdesc.Node.get_instrs node) + in let local_access_found = ref false in let do_node node = - if Int.equal (Procdesc.Node.get_loc node).Location.line line_number && - has_call_or_sets_null node then - begin - local_access_found := true - end in + if Int.equal (Procdesc.Node.get_loc node).Location.line line_number + && has_call_or_sets_null node + then local_access_found := true + in let path, pos_opt = State.get_path () in - Paths.Path.iter_all_nodes_nocalls do_node path; + Paths.Path.iter_all_nodes_nocalls do_node path ; if !local_access_found then let bucket = - if null_case_flag then Localise.BucketLevel.b5 else - if check_nested_loop path pos_opt then Localise.BucketLevel.b3 + if null_case_flag then Localise.BucketLevel.b5 + else if check_nested_loop path pos_opt then Localise.BucketLevel.b3 else if !formal_param_used_in_call then Localise.BucketLevel.b2 - else Localise.BucketLevel.b1 in + else Localise.BucketLevel.b1 + in Some bucket - else None in - + else None + in match access_opt with - | Some (Localise.Last_assigned (n, ncf)) -> - find_bucket n ncf - | Some (Localise.Returned_from_call n) -> - find_bucket n false - | Some (Localise.Last_accessed (_, is_nullable)) when is_nullable -> - Some Localise.BucketLevel.b1 + | Some Localise.Last_assigned (n, ncf) + -> find_bucket n ncf + | Some Localise.Returned_from_call n + -> find_bucket n false + | Some Localise.Last_accessed (_, is_nullable) when is_nullable + -> Some Localise.BucketLevel.b1 | _ -> - begin - match de_opt with - | Some (DecompiledExp.Dconst _) -> - Some Localise.BucketLevel.b1 - | _ -> None - end + match de_opt with Some DecompiledExp.Dconst _ -> Some Localise.BucketLevel.b1 | _ -> None let classify_access desc access_opt de_opt is_nullable = let default_bucket = if is_nullable then Localise.BucketLevel.b1 else Localise.BucketLevel.b5 in let show_in_message = Config.show_buckets in match check_access access_opt de_opt with - | None -> Localise.error_desc_set_bucket desc default_bucket show_in_message - | Some bucket -> Localise.error_desc_set_bucket desc bucket show_in_message + | None + -> Localise.error_desc_set_bucket desc default_bucket show_in_message + | Some bucket + -> Localise.error_desc_set_bucket desc bucket show_in_message diff --git a/infer/src/backend/buckets.mli b/infer/src/backend/buckets.mli index 89b5ffd3d..a1eb67cd8 100644 --- a/infer/src/backend/buckets.mli +++ b/infer/src/backend/buckets.mli @@ -12,7 +12,7 @@ open! IStd (** Classify bugs into buckets *) -(** Classify the bucket of an error desc using Location.access and nullable information *) val classify_access : - Localise.error_desc -> Localise.access option -> DecompiledExp.t option -> bool -> - Localise.error_desc + Localise.error_desc -> Localise.access option -> DecompiledExp.t option -> bool + -> Localise.error_desc +(** Classify the bucket of an error desc using Location.access and nullable information *) diff --git a/infer/src/backend/builtin.ml b/infer/src/backend/builtin.ml index 45dafac8b..8fd9efa10 100644 --- a/infer/src/backend/builtin.ml +++ b/infer/src/backend/builtin.ml @@ -11,17 +11,16 @@ open! IStd (** Module for builtin functions with their symbolic execution handler *) -type args = { - pdesc : Procdesc.t; - instr : Sil.instr; - tenv : Tenv.t; - prop_ : Prop.normal Prop.t; - path : Paths.Path.t; - ret_id : (Ident.t * Typ.t) option; - args : (Exp.t * Typ.t) list; - proc_name : Typ.Procname.t; - loc : Location.t; -} +type args = + { pdesc: Procdesc.t + ; instr: Sil.instr + ; tenv: Tenv.t + ; prop_: Prop.normal Prop.t + ; path: Paths.Path.t + ; ret_id: (Ident.t * Typ.t) option + ; args: (Exp.t * Typ.t) list + ; proc_name: Typ.Procname.t + ; loc: Location.t } type ret_typ = (Prop.normal Prop.t * Paths.Path.t) list @@ -39,29 +38,26 @@ let check_register_populated () = (** check if the function is a builtin *) let is_registered name = - Typ.Procname.Hash.mem builtin_functions name || (check_register_populated (); false) + Typ.Procname.Hash.mem builtin_functions name || (check_register_populated () ; false) (** get the symbolic execution handler associated to the builtin function name *) let get name : t option = try Some (Typ.Procname.Hash.find builtin_functions name) - with Not_found -> (check_register_populated (); None) + with Not_found -> check_register_populated () ; None (** register a builtin [Typ.Procname.t] and symbolic execution handler *) let register proc_name sym_exe_fun : registered = - Typ.Procname.Hash.replace builtin_functions proc_name sym_exe_fun; - sym_exe_fun + Typ.Procname.Hash.replace builtin_functions proc_name sym_exe_fun ; sym_exe_fun (** print the functions registered *) let pp_registered fmt () = let builtin_names = ref [] in - Typ.Procname.Hash.iter (fun name _ -> builtin_names := name :: !builtin_names) builtin_functions; - builtin_names := List.sort ~cmp:Typ.Procname.compare !builtin_names; + Typ.Procname.Hash.iter (fun name _ -> builtin_names := name :: !builtin_names) builtin_functions ; + builtin_names := List.sort ~cmp:Typ.Procname.compare !builtin_names ; let pp pname = Format.fprintf fmt "%a@\n" Typ.Procname.pp pname in - Format.fprintf fmt "Registered builtins:@\n @["; - List.iter ~f:pp !builtin_names; + Format.fprintf fmt "Registered builtins:@\n @[" ; + List.iter ~f:pp !builtin_names ; Format.fprintf fmt "@]@." (** print the builtin functions and exit *) -let print_and_exit () = - pp_registered Format.std_formatter (); - exit 0 +let print_and_exit () = pp_registered Format.std_formatter () ; exit 0 diff --git a/infer/src/backend/builtin.mli b/infer/src/backend/builtin.mli index 2df99e270..65fd69b39 100644 --- a/infer/src/backend/builtin.mli +++ b/infer/src/backend/builtin.mli @@ -11,17 +11,16 @@ open! IStd (** Module for builtin functions with their symbolic execution handler *) -type args = { - pdesc : Procdesc.t; - instr : Sil.instr; - tenv : Tenv.t; - prop_ : Prop.normal Prop.t; - path : Paths.Path.t; - ret_id : (Ident.t * Typ.t) option; - args : (Exp.t * Typ.t) list; - proc_name : Typ.Procname.t; - loc : Location.t; -} +type args = + { pdesc: Procdesc.t + ; instr: Sil.instr + ; tenv: Tenv.t + ; prop_: Prop.normal Prop.t + ; path: Paths.Path.t + ; ret_id: (Ident.t * Typ.t) option + ; args: (Exp.t * Typ.t) list + ; proc_name: Typ.Procname.t + ; loc: Location.t } type ret_typ = (Prop.normal Prop.t * Paths.Path.t) list diff --git a/infer/src/backend/callbacks.ml b/infer/src/backend/callbacks.ml index 359acecf9..af70179cc 100644 --- a/infer/src/backend/callbacks.ml +++ b/infer/src/backend/callbacks.ml @@ -9,180 +9,156 @@ open! IStd open! PVariant - module L = Logging (** Module to register and invoke callbacks *) -type proc_callback_args = { - get_proc_desc : Typ.Procname.t -> Procdesc.t option; - get_procs_in_file : Typ.Procname.t -> Typ.Procname.t list; - idenv : Idenv.t; - tenv : Tenv.t; - summary : Specs.summary; - proc_desc : Procdesc.t; -} +type proc_callback_args = + { get_proc_desc: Typ.Procname.t -> Procdesc.t option + ; get_procs_in_file: Typ.Procname.t -> Typ.Procname.t list + ; idenv: Idenv.t + ; tenv: Tenv.t + ; summary: Specs.summary + ; proc_desc: Procdesc.t } type proc_callback_t = proc_callback_args -> Specs.summary type cluster_callback_t = - Exe_env.t -> - Typ.Procname.t list -> - (Typ.Procname.t -> Procdesc.t option) -> - (Idenv.t * Tenv.t * Typ.Procname.t * Procdesc.t) list -> - unit + Exe_env.t -> Typ.Procname.t list -> (Typ.Procname.t -> Procdesc.t option) + -> (Idenv.t * Tenv.t * Typ.Procname.t * Procdesc.t) list -> unit let procedure_callbacks = ref [] + let cluster_callbacks = ref [] let register_procedure_callback language_opt (callback: proc_callback_t) = - procedure_callbacks := (language_opt, callback):: !procedure_callbacks + procedure_callbacks := (language_opt, callback) :: !procedure_callbacks let register_cluster_callback language_opt (callback: cluster_callback_t) = - cluster_callbacks := (language_opt, callback):: !cluster_callbacks + cluster_callbacks := (language_opt, callback) :: !cluster_callbacks (** Collect what we need to know about a procedure for the analysis. *) let get_procedure_definition exe_env proc_name = let tenv = Exe_env.get_tenv exe_env proc_name in Option.map ~f:(fun proc_desc -> - let idenv = Idenv.create proc_desc - and language = (Procdesc.get_attributes proc_desc).ProcAttributes.language in - (idenv, tenv, proc_name, proc_desc, language)) + let idenv = Idenv.create proc_desc + and language = (Procdesc.get_attributes proc_desc).ProcAttributes.language in + (idenv, tenv, proc_name, proc_desc, language)) (Exe_env.get_proc_desc exe_env proc_name) let get_language proc_name = if Typ.Procname.is_java proc_name then Config.Java else Config.Clang - (** Invoke all registered procedure callbacks on the given procedure. *) let iterate_procedure_callbacks exe_env summary caller_pname = let procedure_language = get_language caller_pname in - Config.curr_language := procedure_language; - - let get_proc_desc proc_name = - Exe_env.get_proc_desc exe_env proc_name in - + Config.curr_language := procedure_language ; + let get_proc_desc proc_name = Exe_env.get_proc_desc exe_env proc_name in let get_procs_in_file proc_name = match Exe_env.get_cfg exe_env proc_name with - | Some cfg-> - List.map ~f:Procdesc.get_proc_name (Cfg.get_defined_procs cfg) - | None -> - [] in - + | Some cfg + -> List.map ~f:Procdesc.get_proc_name (Cfg.get_defined_procs cfg) + | None + -> [] + in match get_procedure_definition exe_env caller_pname with - | None -> summary - | Some (idenv, tenv, _, proc_desc, _) -> - List.fold - ~init:summary + | None + -> summary + | Some (idenv, tenv, _, proc_desc, _) + -> List.fold ~init:summary ~f:(fun summary (language_opt, proc_callback) -> - let language_matches = match language_opt with - | Some language -> Config.equal_language language procedure_language - | None -> true in - if language_matches then - proc_callback - { - get_proc_desc; - get_procs_in_file; - idenv; - tenv; - summary; - proc_desc; - } - else - summary) + let language_matches = + match language_opt with + | Some language + -> Config.equal_language language procedure_language + | None + -> true + in + if language_matches then + proc_callback {get_proc_desc; get_procs_in_file; idenv; tenv; summary; proc_desc} + else summary) !procedure_callbacks - (** Invoke all registered cluster callbacks on a cluster of procedures. *) let iterate_cluster_callbacks all_procs exe_env proc_names = let get_procdesc = Exe_env.get_proc_desc exe_env in - - let procedure_definitions = - List.filter_map ~f:(get_procedure_definition exe_env) proc_names in - + let procedure_definitions = List.filter_map ~f:(get_procedure_definition exe_env) proc_names in let environment = List.map ~f:(fun (idenv, tenv, proc_name, proc_desc, _) -> (idenv, tenv, proc_name, proc_desc)) - procedure_definitions in - + procedure_definitions + in (* Procedures matching the given language or all if no language is specified. *) let relevant_procedures language_opt = Option.value_map ~f:(fun l -> List.filter ~f:(fun p -> Config.equal_language l (get_language p)) proc_names) - ~default:proc_names - language_opt in - + ~default:proc_names language_opt + in List.iter ~f:(fun (language_opt, cluster_callback) -> - let proc_names = relevant_procedures language_opt in - if List.length proc_names > 0 then - cluster_callback exe_env all_procs get_procdesc environment) + let proc_names = relevant_procedures language_opt in + if List.length proc_names > 0 then + cluster_callback exe_env all_procs get_procdesc environment) !cluster_callbacks - (** Invoke all procedure and cluster callbacks on a given environment. *) let iterate_callbacks call_graph exe_env = let saved_language = !Config.curr_language in let procs_to_analyze = (* analyze all the currently defined procedures *) - Cg.get_defined_nodes call_graph in - + Cg.get_defined_nodes call_graph + in let analyze_ondemand summary proc_desc = let proc_name = Procdesc.get_proc_name proc_desc in - iterate_procedure_callbacks exe_env summary proc_name in - + iterate_procedure_callbacks exe_env summary proc_name + in let get_proc_desc proc_name = match Exe_env.get_proc_desc exe_env proc_name with - | Some pdesc -> Some pdesc - | None when Config.dynamic_dispatch = `Lazy -> - Option.bind (Specs.get_summary proc_name) - ~f:(fun summary -> summary.Specs.proc_desc_option) - | None -> None in - - let callbacks = { - Ondemand.analyze_ondemand; - get_proc_desc; - } in - + | Some pdesc + -> Some pdesc + | None when Config.dynamic_dispatch = `Lazy + -> Option.bind (Specs.get_summary proc_name) ~f:(fun summary -> summary.Specs.proc_desc_option) + | None + -> None + in + let callbacks = {Ondemand.analyze_ondemand= analyze_ondemand; get_proc_desc} in (* Create and register on-demand analysis callback *) let analyze_proc_name pname = match Ondemand.get_proc_desc pname with - | None -> - failwithf "Could not find proc desc for %a" Typ.Procname.pp pname - | Some pdesc -> - ignore (Ondemand.analyze_proc_desc ~propagate_exceptions:true pdesc pdesc) in - Ondemand.set_callbacks callbacks; - + | None + -> failwithf "Could not find proc desc for %a" Typ.Procname.pp pname + | Some pdesc + -> ignore (Ondemand.analyze_proc_desc ~propagate_exceptions:true pdesc pdesc) + in + Ondemand.set_callbacks callbacks ; (* Invoke procedure callbacks using on-demand anlaysis schedulling *) - List.iter ~f:analyze_proc_name procs_to_analyze; - - let originally_defined_procs = - Cg.get_defined_nodes call_graph in - + List.iter ~f:analyze_proc_name procs_to_analyze ; + let originally_defined_procs = Cg.get_defined_nodes call_graph in let cluster_id proc_name = match proc_name with - | Typ.Procname.Java pname_java -> - Typ.Procname.java_get_class_name pname_java - | _ -> - "unknown" in + | Typ.Procname.Java pname_java + -> Typ.Procname.java_get_class_name pname_java + | _ + -> "unknown" + in let cluster proc_names = let cluster_map = List.fold ~f:(fun map proc_name -> - let proc_cluster = cluster_id proc_name in - let bucket = try String.Map.find_exn map proc_cluster with Not_found -> [] in - String.Map.add ~key:proc_cluster ~data:(proc_name:: bucket) map) - ~init:String.Map.empty - proc_names in + let proc_cluster = cluster_id proc_name in + let bucket = + try String.Map.find_exn map proc_cluster + with Not_found -> [] + in + String.Map.add ~key:proc_cluster ~data:(proc_name :: bucket) map) + ~init:String.Map.empty proc_names + in (* Return all values of the map *) - String.Map.data cluster_map in - + String.Map.data cluster_map + in (* Invoke cluster callbacks. *) - List.iter - ~f:(iterate_cluster_callbacks originally_defined_procs exe_env) - (cluster procs_to_analyze); - + List.iter ~f:(iterate_cluster_callbacks originally_defined_procs exe_env) + (cluster procs_to_analyze) ; (* Unregister callbacks *) - Ondemand.unset_callbacks (); - + Ondemand.unset_callbacks () ; Config.curr_language := saved_language diff --git a/infer/src/backend/callbacks.mli b/infer/src/backend/callbacks.mli index 65af29c17..0747a167a 100644 --- a/infer/src/backend/callbacks.mli +++ b/infer/src/backend/callbacks.mli @@ -11,14 +11,13 @@ open! IStd (** Module to register and invoke callbacks *) -type proc_callback_args = { - get_proc_desc : Typ.Procname.t -> Procdesc.t option; - get_procs_in_file : Typ.Procname.t -> Typ.Procname.t list; - idenv : Idenv.t; - tenv : Tenv.t; - summary : Specs.summary; - proc_desc : Procdesc.t; -} +type proc_callback_args = + { get_proc_desc: Typ.Procname.t -> Procdesc.t option + ; get_procs_in_file: Typ.Procname.t -> Typ.Procname.t list + ; idenv: Idenv.t + ; tenv: Tenv.t + ; summary: Specs.summary + ; proc_desc: Procdesc.t } (** Type of a procedure callback: - List of all the procedures the callback will be called on. @@ -29,17 +28,14 @@ type proc_callback_args = { type proc_callback_t = proc_callback_args -> Specs.summary type cluster_callback_t = - Exe_env.t -> - Typ.Procname.t list -> - (Typ.Procname.t -> Procdesc.t option) -> - (Idenv.t * Tenv.t * Typ.Procname.t * Procdesc.t) list -> - unit + Exe_env.t -> Typ.Procname.t list -> (Typ.Procname.t -> Procdesc.t option) + -> (Idenv.t * Tenv.t * Typ.Procname.t * Procdesc.t) list -> unit -(** register a procedure callback *) val register_procedure_callback : Config.language option -> proc_callback_t -> unit +(** register a procedure callback *) -(** register a cluster callback *) val register_cluster_callback : Config.language option -> cluster_callback_t -> unit +(** register a cluster callback *) -(** Invoke all the registered callbacks. *) val iterate_callbacks : Cg.t -> Exe_env.t -> unit +(** Invoke all the registered callbacks. *) diff --git a/infer/src/backend/cluster.ml b/infer/src/backend/cluster.ml index b7ddaf83b..4895bbf3e 100644 --- a/infer/src/backend/cluster.ml +++ b/infer/src/backend/cluster.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging module F = Format @@ -25,23 +24,25 @@ let serializer : serializer_t Serialization.serializer = Serialization.create_serializer Serialization.Key.cluster (** Load a cluster from a file *) -let load_from_file (filename : DB.filename) : serializer_t option = +let load_from_file (filename: DB.filename) : serializer_t option = Serialization.read_from_file serializer filename (** Save a cluster into a file *) -let store_to_file (filename : DB.filename) (data: serializer_t) = +let store_to_file (filename: DB.filename) (data: serializer_t) = Serialization.write_to_file serializer filename ~data let cl_name n = "cl" ^ string_of_int n -let cl_file n = "x" ^ (cl_name n) ^ ".cluster" + +let cl_file n = "x" ^ cl_name n ^ ".cluster" + let pp_cluster_name fmt n = Format.fprintf fmt "%s" (cl_name n) let pp_cluster fmt (nr, cluster) = let fname = Config.results_dir ^/ Config.multicore_dir_name ^/ cl_file nr in let pp_cl fmt n = Format.fprintf fmt "%s" (cl_name n) in - store_to_file (DB.filename_from_string fname) (nr, cluster); - F.fprintf fmt "%a: @\n" pp_cl nr; - F.fprintf fmt "\t%@$(INFERANALYZE) --cluster '%s'@\n" fname; + store_to_file (DB.filename_from_string fname) (nr, cluster) ; + F.fprintf fmt "%a: @\n" pp_cl nr ; + F.fprintf fmt "\t%@$(INFERANALYZE) --cluster '%s'@\n" fname ; (* touch the target of the rule to let `make` know that the job has been done *) - F.fprintf fmt "\t%@touch $%@@\n"; + F.fprintf fmt "\t%@touch $%@@\n" ; F.fprintf fmt "@\n" diff --git a/infer/src/backend/cluster.mli b/infer/src/backend/cluster.mli index aa67f46df..e1a6735e4 100644 --- a/infer/src/backend/cluster.mli +++ b/infer/src/backend/cluster.mli @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Module to process clusters of procedures. *) @@ -19,11 +18,11 @@ type t = DB.source_dir (** type stored in .cluster file: (n,cl) indicates cl is cluster n *) type serializer_t = int * t -(** Load a cluster from a file *) val load_from_file : DB.filename -> serializer_t option +(** Load a cluster from a file *) -(** Print a cluster *) val pp_cluster : F.formatter -> serializer_t -> unit +(** Print a cluster *) -(** Print a cluster name *) val pp_cluster_name : F.formatter -> int -> unit +(** Print a cluster name *) diff --git a/infer/src/backend/clusterMakefile.ml b/infer/src/backend/clusterMakefile.ml index d002eaf20..72b211538 100644 --- a/infer/src/backend/clusterMakefile.ml +++ b/infer/src/backend/clusterMakefile.ml @@ -8,47 +8,43 @@ *) open! IStd - module L = Logging module F = Format module CLOpt = CommandLineOption (** Module to create a makefile with dependencies between clusters *) - let pp_prolog fmt clusters = let escape = Escape.escape_map (fun c -> if Char.equal c '#' then Some "\\#" else None) in let infer_flag_of_compilation_db = function - | `Escaped f -> F.sprintf "--compilation-database-escaped '%s'" f - | `Raw f -> F.sprintf "--compilation-database '%s'" f in + | `Escaped f + -> F.sprintf "--compilation-database-escaped '%s'" f + | `Raw f + -> F.sprintf "--compilation-database '%s'" f + in let compilation_dbs_cmd = List.map ~f:infer_flag_of_compilation_db !Config.clang_compilation_dbs - |> String.concat ~sep:" " |> escape in + |> String.concat ~sep:" " |> escape + in F.fprintf fmt "INFERANALYZE = '%s' --no-report --results-dir '%s' %s@\n@\n" (Config.bin_dir ^/ CommandDoc.exe_name_of_command CLOpt.Analyze) - (escape Config.results_dir) - compilation_dbs_cmd; - F.fprintf fmt "CLUSTERS="; - - List.iteri - ~f:(fun i _ -> - F.fprintf fmt "%a " Cluster.pp_cluster_name (i+1)) - clusters; - - F.fprintf fmt "@\n@\ndefault: test@\n@\nall: test@\n@\n"; - F.fprintf fmt "test: $(CLUSTERS)@\n"; + (escape Config.results_dir) compilation_dbs_cmd ; + F.fprintf fmt "CLUSTERS=" ; + List.iteri ~f:(fun i _ -> F.fprintf fmt "%a " Cluster.pp_cluster_name (i + 1)) clusters ; + F.fprintf fmt "@\n@\ndefault: test@\n@\nall: test@\n@\n" ; + F.fprintf fmt "test: $(CLUSTERS)@\n" ; if Config.show_progress_bar then F.fprintf fmt "\t%@echo@\n@." -let pp_epilog fmt () = - F.fprintf fmt "@.clean:@.\trm -f $(CLUSTERS)@." +let pp_epilog fmt () = F.fprintf fmt "@.clean:@.\trm -f $(CLUSTERS)@." let create_cluster_makefile (clusters: Cluster.t list) (fname: string) = let outc = Out_channel.create fname in let fmt = Format.formatter_of_out_channel outc in let do_cluster cluster_nr cluster = - F.fprintf fmt "#%s@\n" (DB.source_dir_to_string cluster); - Cluster.pp_cluster fmt (cluster_nr + 1, cluster) in - pp_prolog fmt clusters; - List.iteri ~f:do_cluster clusters; + F.fprintf fmt "#%s@\n" (DB.source_dir_to_string cluster) ; + Cluster.pp_cluster fmt (cluster_nr + 1, cluster) + in + pp_prolog fmt clusters ; + List.iteri ~f:do_cluster clusters ; pp_epilog fmt () ; Out_channel.close outc diff --git a/infer/src/backend/crashcontext.ml b/infer/src/backend/crashcontext.ml index 4891cd2c0..3d885e6e8 100644 --- a/infer/src/backend/crashcontext.ml +++ b/infer/src/backend/crashcontext.ml @@ -8,109 +8,115 @@ *) open! IStd - module F = Format module L = Logging let frame_id_of_stackframe frame = - let loc_str = match frame.Stacktrace.line_num with - | None -> frame.Stacktrace.file_str - | Some line -> F.sprintf "%s:%d" frame.Stacktrace.file_str line in - F.sprintf - "%s.%s(%s)" - frame.Stacktrace.class_str - frame.Stacktrace.method_str - loc_str + let loc_str = + match frame.Stacktrace.line_num with + | None + -> frame.Stacktrace.file_str + | Some line + -> F.sprintf "%s:%d" frame.Stacktrace.file_str line + in + F.sprintf "%s.%s(%s)" frame.Stacktrace.class_str frame.Stacktrace.method_str loc_str let frame_id_of_summary stacktree = - let short_name = List.hd_exn - (Str.split (Str.regexp "(") stacktree.Stacktree_j.method_name) in + let short_name = List.hd_exn (Str.split (Str.regexp "(") stacktree.Stacktree_j.method_name) in match stacktree.Stacktree_j.location with - | None -> - failwith + | None + -> failwith "Attempted to take signature of a frame without location information. This is undefined." - | Some { line = Some line_num; file } -> - F.sprintf "%s(%s:%d)" short_name (Filename.basename file) line_num - | Some { file } -> - F.sprintf "%s(%s)" short_name (Filename.basename file) + | Some {line= Some line_num; file} + -> F.sprintf "%s(%s:%d)" short_name (Filename.basename file) line_num + | Some {file} + -> F.sprintf "%s(%s)" short_name (Filename.basename file) let stracktree_of_frame frame = - { Stacktree_j.method_name = F.sprintf - "%s.%s" - frame.Stacktrace.class_str - frame.Stacktrace.method_str; - location = Some { Stacktree_j.location_type = "call_site"; - file = frame.Stacktrace.file_str; - line = frame.Stacktrace.line_num; - blame_range = [] }; - callees = []; - } + { Stacktree_j.method_name= + F.sprintf "%s.%s" frame.Stacktrace.class_str frame.Stacktrace.method_str + ; location= + Some + { Stacktree_j.location_type= "call_site" + ; file= frame.Stacktrace.file_str + ; line= frame.Stacktrace.line_num + ; blame_range= [] } + ; callees= [] } (** k = 1 implementation, where k is the number of levels of calls inlined *) let stitch_summaries stacktrace_file summary_files out_file = let stacktrace = Stacktrace.of_json_file stacktrace_file in - let summaries = List.map - ~f:(Ag_util.Json.from_file Stacktree_j.read_stacktree) - summary_files in - let summary_map = List.fold + let summaries = List.map ~f:(Ag_util.Json.from_file Stacktree_j.read_stacktree) summary_files in + let summary_map = + List.fold ~f:(fun acc stacktree -> - String.Map.add ~key:(frame_id_of_summary stacktree) ~data:stacktree acc) - ~init:String.Map.empty - summaries in + String.Map.add ~key:(frame_id_of_summary stacktree) ~data:stacktree acc) + ~init:String.Map.empty summaries + in let expand_stack_frame frame = (* TODO: Implement k > 1 case *) let frame_id = frame_id_of_stackframe frame in if String.Map.existsi ~f:(fun ~key ~data:_ -> String.equal key frame_id) summary_map then String.Map.find_exn summary_map frame_id - else - stracktree_of_frame frame in + else stracktree_of_frame frame + in let expanded_frames = List.map ~f:expand_stack_frame stacktrace.frames in - let crashcontext = { Stacktree_j.stack = expanded_frames} in + let crashcontext = {Stacktree_j.stack= expanded_frames} in Ag_util.Json.to_file Stacktree_j.write_crashcontext_t out_file crashcontext let collect_all_summaries root_summaries_dir stacktrace_file stacktraces_dir = let method_summaries = Utils.directory_fold (fun summaries path -> - (* check if the file is a JSON file under the crashcontext dir *) - if (Sys.is_directory path) <> `Yes && Filename.check_suffix path "json" && - String.is_suffix ~suffix:"crashcontext" (Filename.dirname path) - then path :: summaries - else summaries) - [] - root_summaries_dir in - let pair_for_stacktrace_file = match stacktrace_file with - | None -> None - | Some file -> - let crashcontext_dir = Config.results_dir ^/ "crashcontext" in - Utils.create_dir crashcontext_dir; - Some (file, crashcontext_dir ^/ "crashcontext.json") in + (* check if the file is a JSON file under the crashcontext dir *) + if Sys.is_directory path <> `Yes && Filename.check_suffix path "json" + && String.is_suffix ~suffix:"crashcontext" (Filename.dirname path) + then path :: summaries + else summaries) + [] root_summaries_dir + in + let pair_for_stacktrace_file = + match stacktrace_file with + | None + -> None + | Some file + -> let crashcontext_dir = Config.results_dir ^/ "crashcontext" in + Utils.create_dir crashcontext_dir ; Some (file, crashcontext_dir ^/ "crashcontext.json") + in let trace_file_regexp = Str.regexp "\\(.*\\)\\.json" in - let pairs_for_stactrace_dir = match stacktraces_dir with - | None -> [] - | Some s -> begin - let dir = DB.filename_from_string s in + let pairs_for_stactrace_dir = + match stacktraces_dir with + | None + -> [] + | Some s + -> let dir = DB.filename_from_string s in let trace_file_matcher path = let path_str = DB.filename_to_string path in - Str.string_match trace_file_regexp path_str 0 in + Str.string_match trace_file_regexp path_str 0 + in let trace_fold stacktrace_file acc = let stacktrace_file_str = DB.filename_to_string stacktrace_file in - let out_file = - (Str.matched_group 1 stacktrace_file_str) ^ ".crashcontext.json" in - (stacktrace_file_str, out_file) :: acc in - try - DB.fold_paths_matching ~dir ~p:trace_file_matcher ~init:[] ~f:trace_fold + let out_file = Str.matched_group 1 stacktrace_file_str ^ ".crashcontext.json" in + (stacktrace_file_str, out_file) :: acc + in + try DB.fold_paths_matching ~dir ~p:trace_file_matcher ~init:[] ~f:trace_fold with - (* trace_fold runs immediately after trace_file_matcher in the + | (* trace_fold runs immediately after trace_file_matcher in the DB.fold_paths_matching statement below, so we don't need to call Str.string_match again. *) - | Not_found -> assert false - end in - let input_output_file_pairs = match pair_for_stacktrace_file with - | None -> pairs_for_stactrace_dir - | Some pair -> pair :: pairs_for_stactrace_dir in + Not_found + -> assert false + in + let input_output_file_pairs = + match pair_for_stacktrace_file with + | None + -> pairs_for_stactrace_dir + | Some pair + -> pair :: pairs_for_stactrace_dir + in let process_stacktrace (stacktrace_file, out_file) = - stitch_summaries stacktrace_file method_summaries out_file in + stitch_summaries stacktrace_file method_summaries out_file + in List.iter ~f:process_stacktrace input_output_file_pairs let crashcontext_epilogue ~in_buck_mode = @@ -120,15 +126,12 @@ let crashcontext_epilogue ~in_buck_mode = Important: Note that when running under buck, this is not the final infer-out/ directory, but instead it is buck-out/, which contains the infer output directories for every buck target. *) - let root_summaries_dir = if in_buck_mode then begin - let buck_out = match Config.buck_out with - | Some dir -> dir - | None -> "buck-out" in + let root_summaries_dir = + if in_buck_mode then + let buck_out = match Config.buck_out with Some dir -> dir | None -> "buck-out" in Config.project_root ^/ buck_out - end - else Config.results_dir in - collect_all_summaries - root_summaries_dir Config.stacktrace Config.stacktraces_dir + else Config.results_dir + in + collect_all_summaries root_summaries_dir Config.stacktrace Config.stacktraces_dir -let pp_stacktree fmt st = - Format.fprintf fmt "%s" (Stacktree_j.string_of_stacktree st) +let pp_stacktree fmt st = Format.fprintf fmt "%s" (Stacktree_j.string_of_stacktree st) diff --git a/infer/src/backend/crashcontext.mli b/infer/src/backend/crashcontext.mli index d10503139..d3abc6826 100644 --- a/infer/src/backend/crashcontext.mli +++ b/infer/src/backend/crashcontext.mli @@ -32,6 +32,7 @@ open! IStd infer/tests/endtoend/java/crashcontext/ *) +val crashcontext_epilogue : in_buck_mode:bool -> unit (** Runs crashcontext epilogue code, which takes the per-method summaries produced by crashcontext related analysis (future: analyses) and stitches @@ -41,6 +42,5 @@ open! IStd so this runs at the end of the parent buck infer process only. TODO: Similar integration with build systems other than buck. *) -val crashcontext_epilogue : in_buck_mode:bool -> unit val pp_stacktree : Format.formatter -> Stacktree_t.stacktree -> unit diff --git a/infer/src/backend/dom.ml b/infer/src/backend/dom.ml index c2fac47be..b4471cf6f 100644 --- a/infer/src/backend/dom.ml +++ b/infer/src/backend/dom.ml @@ -16,31 +16,33 @@ module Hashtbl = Caml.Hashtbl module L = Logging module F = Format -let (++) = IntLit.add -let (--) = IntLit.sub +let ( ++ ) = IntLit.add + +let ( -- ) = IntLit.sub (** {2 Utility functions for ids} *) -let can_rename id = - Ident.is_primed id || Ident.is_footprint id +let can_rename id = Ident.is_primed id || Ident.is_footprint id (** {2 Utility functions for sigma} *) let equal_sigma sigma1 sigma2 = let rec f sigma1_rest sigma2_rest = match (sigma1_rest, sigma2_rest) with - | [], [] -> () - | [], _:: _ | _:: _, [] -> - (L.d_strln "failure reason 1"; raise Sil.JoinFail) - | hpred1:: sigma1_rest', hpred2:: sigma2_rest' -> - if Sil.equal_hpred hpred1 hpred2 then f sigma1_rest' sigma2_rest' - else (L.d_strln "failure reason 2"; raise Sil.JoinFail) in + | [], [] + -> () + | [], _ :: _ | _ :: _, [] + -> L.d_strln "failure reason 1" ; raise Sil.JoinFail + | hpred1 :: sigma1_rest', hpred2 :: sigma2_rest' + -> if Sil.equal_hpred hpred1 hpred2 then f sigma1_rest' sigma2_rest' + else ( L.d_strln "failure reason 2" ; raise Sil.JoinFail ) + in let sigma1_sorted = List.sort ~cmp:Sil.compare_hpred sigma1 in let sigma2_sorted = List.sort ~cmp:Sil.compare_hpred sigma2 in f sigma1_sorted sigma2_sorted let sigma_get_start_lexps_sort sigma = - let exp_compare_neg e1 e2 = - (Exp.compare e1 e2) in + let exp_compare_neg e1 e2 = -Exp.compare e1 e2 in let filter e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in let lexps = Sil.hpred_list_get_lexps filter sigma in List.sort ~cmp:exp_compare_neg lexps @@ -49,342 +51,330 @@ let sigma_get_start_lexps_sort sigma = type side = Lhs | Rhs -let select side e1 e2 = - match side with - | Lhs -> e1 - | Rhs -> e2 +let select side e1 e2 = match side with Lhs -> e1 | Rhs -> e2 -let opposite side = - match side with - | Lhs -> Rhs - | Rhs -> Lhs +let opposite side = match side with Lhs -> Rhs | Rhs -> Lhs -let do_side side f e1 e2 = - match side with - | Lhs -> f e1 e2 - | Rhs -> f e2 e1 +let do_side side f e1 e2 = match side with Lhs -> f e1 e2 | Rhs -> f e2 e1 (** {2 Sets for expression pairs} *) -module EPset = Caml.Set.Make - (struct - type t = Exp.t * Exp.t [@@deriving compare] - end) +module EPset = Caml.Set.Make (struct + type t = Exp.t * Exp.t [@@deriving compare] +end) (** {2 Module for maintaining information about noninjectivity during join} *) module NonInj : sig - val init : unit -> unit + val final : unit -> unit + val add : side -> Exp.t -> Exp.t -> unit - val check : side -> Exp.t list -> bool + val check : side -> Exp.t list -> bool end = struct - (* mappings from primed or footprint var exps to primed or footprint var exps *) let equiv_tbl1 = Hashtbl.create 32 + let equiv_tbl2 = Hashtbl.create 32 (* mappings from primed or footprint var exps to normal var, lvar or const exps *) let const_tbl1 = Hashtbl.create 32 + let const_tbl2 = Hashtbl.create 32 let reset () = - Hashtbl.clear equiv_tbl1; - Hashtbl.clear equiv_tbl2; - Hashtbl.clear const_tbl1; + Hashtbl.clear equiv_tbl1 ; + Hashtbl.clear equiv_tbl2 ; + Hashtbl.clear const_tbl1 ; Hashtbl.clear const_tbl2 let init () = reset () + let final () = reset () let lookup' tbl e default = match e with - | Exp.Var _ -> - begin - try Hashtbl.find tbl e - with Not_found -> (Hashtbl.replace tbl e default; default) - end - | _ -> assert false - - let lookup_equiv' tbl e = - lookup' tbl e e - let lookup_const' tbl e = - lookup' tbl e Exp.Set.empty + | Exp.Var _ -> ( + try Hashtbl.find tbl e + with Not_found -> Hashtbl.replace tbl e default ; default ) + | _ + -> assert false + + let lookup_equiv' tbl e = lookup' tbl e e + + let lookup_const' tbl e = lookup' tbl e Exp.Set.empty let rec find' tbl e = let e' = lookup_equiv' tbl e in match e' with - | Exp.Var _ -> - if Exp.equal e e' then e + | Exp.Var _ + -> if Exp.equal e e' then e else - begin - let root = find' tbl e' in - Hashtbl.replace tbl e root; - root - end - | _ -> assert false + let root = find' tbl e' in + Hashtbl.replace tbl e root ; root + | _ + -> assert false let union' tbl const_tbl e1 e2 = let r1 = find' tbl e1 in let r2 = find' tbl e2 in - let new_r, old_r = - match (Exp.compare r1 r2) with - | i when i <= 0 -> r1, r2 - | _ -> r2, r1 in + let new_r, old_r = match Exp.compare r1 r2 with i when i <= 0 -> (r1, r2) | _ -> (r2, r1) in let new_c = lookup_const' const_tbl new_r in let old_c = lookup_const' const_tbl old_r in let res_c = Exp.Set.union new_c old_c in - if Exp.Set.cardinal res_c > 1 then (L.d_strln "failure reason 3"; raise Sil.JoinFail); - Hashtbl.replace tbl old_r new_r; + if Exp.Set.cardinal res_c > 1 then ( L.d_strln "failure reason 3" ; raise Sil.JoinFail ) ; + Hashtbl.replace tbl old_r new_r ; Hashtbl.replace const_tbl new_r res_c let replace_const' tbl const_tbl e c = let r = find' tbl e in let set = Exp.Set.add c (lookup_const' const_tbl r) in - if Exp.Set.cardinal set > 1 then (L.d_strln "failure reason 4"; raise Sil.JoinFail); + if Exp.Set.cardinal set > 1 then ( L.d_strln "failure reason 4" ; raise Sil.JoinFail ) ; Hashtbl.replace const_tbl r set let add side e e' = let tbl, const_tbl = - match side with - | Lhs -> equiv_tbl1, const_tbl1 - | Rhs -> equiv_tbl2, const_tbl2 + match side with Lhs -> (equiv_tbl1, const_tbl1) | Rhs -> (equiv_tbl2, const_tbl2) in - match e, e' with - | Exp.Var id, Exp.Var id' -> - begin - match can_rename id, can_rename id' with - | true, true -> union' tbl const_tbl e e' - | true, false -> replace_const' tbl const_tbl e e' - | false, true -> replace_const' tbl const_tbl e' e - | _ -> L.d_strln "failure reason 5"; raise Sil.JoinFail - end - | Exp.Var id, Exp.Const _ | Exp.Var id, Exp.Lvar _ -> - if (can_rename id) then replace_const' tbl const_tbl e e' - else (L.d_strln "failure reason 6"; raise Sil.JoinFail) - | Exp.Const _, Exp.Var id' | Exp.Lvar _, Exp.Var id' -> - if (can_rename id') then replace_const' tbl const_tbl e' e - else (L.d_strln "failure reason 7"; raise Sil.JoinFail) - | _ -> - if not (Exp.equal e e') then (L.d_strln "failure reason 8"; raise Sil.JoinFail) else () + match (e, e') with + | Exp.Var id, Exp.Var id' -> ( + match (can_rename id, can_rename id') with + | true, true + -> union' tbl const_tbl e e' + | true, false + -> replace_const' tbl const_tbl e e' + | false, true + -> replace_const' tbl const_tbl e' e + | _ + -> L.d_strln "failure reason 5" ; raise Sil.JoinFail ) + | Exp.Var id, Exp.Const _ | Exp.Var id, Exp.Lvar _ + -> if can_rename id then replace_const' tbl const_tbl e e' + else ( L.d_strln "failure reason 6" ; raise Sil.JoinFail ) + | Exp.Const _, Exp.Var id' | Exp.Lvar _, Exp.Var id' + -> if can_rename id' then replace_const' tbl const_tbl e' e + else ( L.d_strln "failure reason 7" ; raise Sil.JoinFail ) + | _ + -> if not (Exp.equal e e') then ( L.d_strln "failure reason 8" ; raise Sil.JoinFail ) else () let check side es = let f = function Exp.Var id -> can_rename id | _ -> false in let vars, nonvars = List.partition_tf ~f es in let tbl, const_tbl = - match side with - | Lhs -> equiv_tbl1, const_tbl1 - | Rhs -> equiv_tbl2, const_tbl2 + match side with Lhs -> (equiv_tbl1, const_tbl1) | Rhs -> (equiv_tbl2, const_tbl2) in - if (List.length nonvars > 1) then false + if List.length nonvars > 1 then false else - match vars, nonvars with - | [], _ | [_], [] -> true - | v:: vars', _ -> - let r = find' tbl v in + match (vars, nonvars) with + | [], _ | [_], [] + -> true + | v :: vars', _ + -> let r = find' tbl v in let set = lookup_const' const_tbl r in - (List.for_all ~f:(fun v' -> Exp.equal (find' tbl v') r) vars') && - (List.for_all ~f:(fun c -> Exp.Set.mem c set) nonvars) - + List.for_all ~f:(fun v' -> Exp.equal (find' tbl v') r) vars' + && List.for_all ~f:(fun c -> Exp.Set.mem c set) nonvars end (** {2 Modules for checking whether join or meet loses too much info} *) -module type InfoLossCheckerSig = -sig +module type InfoLossCheckerSig = sig val init : Prop.sigma -> Prop.sigma -> unit + val final : unit -> unit + val lost_little : side -> Exp.t -> Exp.t list -> bool + val add : side -> Exp.t -> Exp.t -> unit end module Dangling : sig - val init : Prop.sigma -> Prop.sigma -> unit + val final : unit -> unit - val check : side -> Exp.t -> bool + val check : side -> Exp.t -> bool end = struct - let lexps1 = ref Exp.Set.empty + let lexps2 = ref Exp.Set.empty let get_lexp_set' sigma = let lexp_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma in List.fold ~f:(fun set e -> Exp.Set.add e set) ~init:Exp.Set.empty lexp_lst + let init sigma1 sigma2 = - lexps1 := get_lexp_set' sigma1; + lexps1 := get_lexp_set' sigma1 ; lexps2 := get_lexp_set' sigma2 + let final () = - lexps1 := Exp.Set.empty; + lexps1 := Exp.Set.empty ; lexps2 := Exp.Set.empty (* conservatively checks whether e is dangling *) let check side e = - let lexps = - match side with - | Lhs -> !lexps1 - | Rhs -> !lexps2 - in + let lexps = match side with Lhs -> !lexps1 | Rhs -> !lexps2 in match e with - | Exp.Var id -> can_rename id && not (Exp.Set.mem e lexps) - | Exp.Const _ -> not (Exp.Set.mem e lexps) - | Exp.BinOp _ -> not (Exp.Set.mem e lexps) - | _ -> false + | Exp.Var id + -> can_rename id && not (Exp.Set.mem e lexps) + | Exp.Const _ + -> not (Exp.Set.mem e lexps) + | Exp.BinOp _ + -> not (Exp.Set.mem e lexps) + | _ + -> false end module CheckJoinPre : InfoLossCheckerSig = struct + let init sigma1 sigma2 = NonInj.init () ; Dangling.init sigma1 sigma2 - let init sigma1 sigma2 = - NonInj.init (); - Dangling.init sigma1 sigma2 - - let final () = - NonInj.final (); - Dangling.final () + let final () = NonInj.final () ; Dangling.final () let fail_case side e es = let side_op = opposite side in match e with - | Exp.Lvar _ -> false - | Exp.Var id when Ident.is_normal id -> List.length es >= 1 - | Exp.Var _ -> - if Int.equal Config.join_cond 0 then - List.exists ~f:(Exp.equal Exp.zero) es + | Exp.Lvar _ + -> false + | Exp.Var id when Ident.is_normal id + -> List.length es >= 1 + | Exp.Var _ + -> if Int.equal Config.join_cond 0 then List.exists ~f:(Exp.equal Exp.zero) es else if Dangling.check side e then - begin - let r = List.exists ~f:(fun e' -> not (Dangling.check side_op e')) es in - if r then begin - L.d_str ".... Dangling Check (dang e:"; Sil.d_exp e; - L.d_str ") (? es:"; Sil.d_exp_list es; L.d_strln ") ...."; - L.d_ln () - end; - r - end + let r = List.exists ~f:(fun e' -> not (Dangling.check side_op e')) es in + if r then ( + L.d_str ".... Dangling Check (dang e:" ; + Sil.d_exp e ; + L.d_str ") (? es:" ; + Sil.d_exp_list es ; + L.d_strln ") ...." ; + L.d_ln () ) ; + r else - begin - let r = List.exists ~f:(Dangling.check side_op) es in - if r then begin - L.d_str ".... Dangling Check (notdang e:"; Sil.d_exp e; - L.d_str ") (? es:"; Sil.d_exp_list es; L.d_strln ") ...."; - L.d_ln () - end; - r - end - | _ -> false + let r = List.exists ~f:(Dangling.check side_op) es in + if r then ( + L.d_str ".... Dangling Check (notdang e:" ; + Sil.d_exp e ; + L.d_str ") (? es:" ; + Sil.d_exp_list es ; + L.d_strln ") ...." ; + L.d_ln () ) ; + r + | _ + -> false let lost_little side e es = let side_op = opposite side in let es = match e with Exp.Const _ -> [] | _ -> es in - if (fail_case side e es) then false - else - match es with - | [] | [_] -> true - | _ -> (NonInj.check side_op es) + if fail_case side e es then false + else match es with [] | [_] -> true | _ -> NonInj.check side_op es let add = NonInj.add end module CheckJoinPost : InfoLossCheckerSig = struct + let init _ _ = NonInj.init () - let init _ _ = - NonInj.init () - - let final () = - NonInj.final () + let final () = NonInj.final () let fail_case _ e es = match e with - | Exp.Lvar _ -> false - | Exp.Var id when Ident.is_normal id -> List.length es >= 1 - | Exp.Var _ -> false - | _ -> false + | Exp.Lvar _ + -> false + | Exp.Var id when Ident.is_normal id + -> List.length es >= 1 + | Exp.Var _ + -> false + | _ + -> false let lost_little side e es = let side_op = opposite side in let es = match e with Exp.Const _ -> [] | _ -> es in - if (fail_case side e es) then false - else - match es with - | [] | [_] -> true - | _ -> NonInj.check side_op es + if fail_case side e es then false + else match es with [] | [_] -> true | _ -> NonInj.check side_op es let add = NonInj.add end module CheckJoin : sig - val init : JoinState.mode -> Prop.sigma -> Prop.sigma -> unit + val final : unit -> unit + val lost_little : side -> Exp.t -> Exp.t list -> bool - val add : side -> Exp.t -> Exp.t -> unit + val add : side -> Exp.t -> Exp.t -> unit end = struct - let mode_ref : JoinState.mode ref = ref JoinState.Post let init mode sigma1 sigma2 = - mode_ref := mode; + mode_ref := mode ; match mode with - | JoinState.Pre -> CheckJoinPre.init sigma1 sigma2 - | JoinState.Post -> CheckJoinPost.init sigma1 sigma2 + | JoinState.Pre + -> CheckJoinPre.init sigma1 sigma2 + | JoinState.Post + -> CheckJoinPost.init sigma1 sigma2 let final () = match !mode_ref with - | JoinState.Pre -> CheckJoinPre.final (); mode_ref := JoinState.Post - | JoinState.Post -> CheckJoinPost.final (); mode_ref := JoinState.Post + | JoinState.Pre + -> CheckJoinPre.final () ; + mode_ref := JoinState.Post + | JoinState.Post + -> CheckJoinPost.final () ; + mode_ref := JoinState.Post let lost_little side e es = match !mode_ref with - | JoinState.Pre -> CheckJoinPre.lost_little side e es - | JoinState.Post -> CheckJoinPost.lost_little side e es + | JoinState.Pre + -> CheckJoinPre.lost_little side e es + | JoinState.Post + -> CheckJoinPost.lost_little side e es let add side e1 e2 = match !mode_ref with - | JoinState.Pre -> CheckJoinPre.add side e1 e2 - | JoinState.Post -> CheckJoinPost.add side e1 e2 + | JoinState.Pre + -> CheckJoinPre.add side e1 e2 + | JoinState.Post + -> CheckJoinPost.add side e1 e2 end module CheckMeet : InfoLossCheckerSig = struct - let lexps1 = ref Exp.Set.empty + let lexps2 = ref Exp.Set.empty let init sigma1 sigma2 = let lexps1_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma1 in let lexps2_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma2 in - lexps1 := Sil.elist_to_eset lexps1_lst; + lexps1 := Sil.elist_to_eset lexps1_lst ; lexps2 := Sil.elist_to_eset lexps2_lst let final () = - lexps1 := Exp.Set.empty; + lexps1 := Exp.Set.empty ; lexps2 := Exp.Set.empty let lost_little side e es = - let lexps = match side with - | Lhs -> !lexps1 - | Rhs -> !lexps2 - in - match es, e with - | [], _ -> - true - | [Exp.Const _], Exp.Lvar _ -> - false - | [Exp.Const _], Exp.Var _ -> - not (Exp.Set.mem e lexps) - | [Exp.Const _], _ -> - assert false - | [_], Exp.Lvar _ | [_], Exp.Var _ -> - true - | [_], _ -> - assert false - | _, Exp.Lvar _ | _, Exp.Var _ -> - false - | _, Exp.Const _ -> - assert false - | _ -> assert false + let lexps = match side with Lhs -> !lexps1 | Rhs -> !lexps2 in + match (es, e) with + | [], _ + -> true + | [(Exp.Const _)], Exp.Lvar _ + -> false + | [(Exp.Const _)], Exp.Var _ + -> not (Exp.Set.mem e lexps) + | [(Exp.Const _)], _ + -> assert false + | [_], Exp.Lvar _ | [_], Exp.Var _ + -> true + | [_], _ + -> assert false + | _, Exp.Lvar _ | _, Exp.Var _ + -> false + | _, Exp.Const _ + -> assert false + | _ + -> assert false let add = NonInj.add end @@ -392,57 +382,72 @@ end (** {2 Module for worklist} *) module Todo : sig - exception Empty + type t + val init : unit -> unit + val final : unit -> unit + val reset : (Exp.t * Exp.t * Exp.t) list -> unit - val push : (Exp.t * Exp.t * Exp.t) -> unit - val pop : unit -> (Exp.t * Exp.t * Exp.t) + + val push : Exp.t * Exp.t * Exp.t -> unit + + val pop : unit -> Exp.t * Exp.t * Exp.t + val set : t -> unit - val take : unit -> t + val take : unit -> t end = struct - exception Empty + type t = (Exp.t * Exp.t * Exp.t) list let tbl = ref [] let init () = tbl := [] + let final () = tbl := [] + let reset todo = tbl := todo - let push e = - tbl := e :: !tbl + let push e = tbl := e :: !tbl + let pop () = match !tbl with - | h:: t -> tbl := t; h - | _ -> raise Empty + | h :: t + -> tbl := t ; + h + | _ + -> raise Empty let set todo = tbl := todo - let take () = let res = !tbl in tbl := []; res + let take () = + let res = !tbl in + tbl := [] ; + res end (** {2 Module for introducing fresh variables} *) module FreshVarExp : sig - val init : unit -> unit + val get_fresh_exp : Exp.t -> Exp.t -> Exp.t + val get_induced_pi : Tenv.t -> unit -> Prop.pi - val final : unit -> unit -(* + val final : unit -> unit + (* val lookup : side -> Exp.t -> (Exp.t * Exp.t) option *) end = struct - let t = ref [] let init () = t := [] + let final () = t := [] let entry_compare (e1, e2, _) (_, e2', _) = @@ -451,112 +456,128 @@ end = struct let get_fresh_exp e1 e2 = match - List.find ~f:(fun (e1', e2', _) -> Exp.equal e1 e1' && Exp.equal e2 e2') !t |> - Option.map ~f:trd3 + List.find ~f:(fun (e1', e2', _) -> Exp.equal e1 e1' && Exp.equal e2 e2') !t + |> Option.map ~f:trd3 with - | Some res -> - res - | None -> - let e = Exp.get_undefined (JoinState.get_footprint ()) in - t := (e1, e2, e)::!t; + | Some res + -> res + | None + -> let e = Exp.get_undefined (JoinState.get_footprint ()) in + t := (e1, e2, e) :: !t ; e let get_induced_atom tenv acc strict_lower upper e = - let ineq_lower = Prop.mk_inequality tenv (Exp.BinOp(Binop.Lt, strict_lower, e)) in - let ineq_upper = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, e, upper)) in - ineq_lower:: ineq_upper:: acc + let ineq_lower = Prop.mk_inequality tenv (Exp.BinOp (Binop.Lt, strict_lower, e)) in + let ineq_upper = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, e, upper)) in + ineq_lower :: ineq_upper :: acc let minus2_to_2 = List.map ~f:IntLit.of_int [-2; -1; 0; 1; 2] let get_induced_pi tenv () = let t_sorted = List.sort ~cmp:entry_compare !t in - let add_and_chk_eq e1 e1' n = - match e1, e1' with - | Exp.Const (Const.Cint n1), Exp.Const (Const.Cint n1') -> IntLit.eq (n1 ++ n) n1' - | _ -> false in + match (e1, e1') with + | Exp.Const Const.Cint n1, Exp.Const Const.Cint n1' + -> IntLit.eq (n1 ++ n) n1' + | _ + -> false + in let add_and_gen_eq e e' n = - let e_plus_n = Exp.BinOp(Binop.PlusA, e, Exp.int n) in - Prop.mk_eq tenv e_plus_n e' in - let rec f_eqs_entry ((e1, e2, e) as entry) eqs_acc t_seen = function - | [] -> eqs_acc, t_seen - | ((e1', e2', e') as entry'):: t_rest' -> - (match - List.find ~f:(fun n -> - add_and_chk_eq e1 e1' n && add_and_chk_eq e2 e2' n) minus2_to_2 |> - Option.map ~f:(fun n -> + let e_plus_n = Exp.BinOp (Binop.PlusA, e, Exp.int n) in + Prop.mk_eq tenv e_plus_n e' + in + let rec f_eqs_entry (e1, e2, e as entry) eqs_acc t_seen = function + | [] + -> (eqs_acc, t_seen) + | (e1', e2', e' as entry') :: t_rest' -> + match + List.find ~f:(fun n -> add_and_chk_eq e1 e1' n && add_and_chk_eq e2 e2' n) minus2_to_2 + |> Option.map ~f:(fun n -> let eq = add_and_gen_eq e e' n in - let eqs_acc' = eq:: eqs_acc in - f_eqs_entry entry eqs_acc' t_seen t_rest') - with - | Some res -> - res - | None -> - let t_seen' = entry':: t_seen in - f_eqs_entry entry eqs_acc t_seen' t_rest') in + let eqs_acc' = eq :: eqs_acc in + f_eqs_entry entry eqs_acc' t_seen t_rest' ) + with + | Some res + -> res + | None + -> let t_seen' = entry' :: t_seen in + f_eqs_entry entry eqs_acc t_seen' t_rest' + in let rec f_eqs eqs_acc t_acc = function - | [] -> (eqs_acc, t_acc) - | entry:: t_rest -> - let eqs_acc', t_rest' = f_eqs_entry entry eqs_acc [] t_rest in - let t_acc' = entry:: t_acc in - f_eqs eqs_acc' t_acc' t_rest' in + | [] + -> (eqs_acc, t_acc) + | entry :: t_rest + -> let eqs_acc', t_rest' = f_eqs_entry entry eqs_acc [] t_rest in + let t_acc' = entry :: t_acc in + f_eqs eqs_acc' t_acc' t_rest' + in let eqs, t_minimal = f_eqs [] [] t_sorted in - let f_ineqs acc (e1, e2, e) = - match e1, e2 with - | Exp.Const (Const.Cint n1), Exp.Const (Const.Cint n2) -> - let strict_lower1, upper1 = - if IntLit.leq n1 n2 then (n1 -- IntLit.one, n2) else (n2 -- IntLit.one, n1) in + match (e1, e2) with + | Exp.Const Const.Cint n1, Exp.Const Const.Cint n2 + -> let strict_lower1, upper1 = + if IntLit.leq n1 n2 then (n1 -- IntLit.one, n2) else (n2 -- IntLit.one, n1) + in let e_strict_lower1 = Exp.int strict_lower1 in let e_upper1 = Exp.int upper1 in get_induced_atom tenv acc e_strict_lower1 e_upper1 e - | _ -> acc in + | _ + -> acc + in List.fold ~f:f_ineqs ~init:eqs t_minimal - end (** {2 Modules for renaming} *) module Rename : sig - type data_opt = ExtFresh | ExtDefault of Exp.t val init : unit -> unit + val final : unit -> unit + val reset : unit -> (Exp.t * Exp.t * Exp.t) list val extend : Exp.t -> Exp.t -> data_opt -> Exp.t + val check : (side -> Exp.t -> Exp.t list -> bool) -> bool val get_others : side -> Exp.t -> (Exp.t * Exp.t) option + val get_other_atoms : Tenv.t -> side -> Sil.atom -> (Sil.atom * Sil.atom) option val lookup : side -> Exp.t -> Exp.t + val lookup_list : side -> Exp.t list -> Exp.t list + val lookup_list_todo : side -> Exp.t list -> Exp.t list val to_subst_proj : side -> Sil.fav -> Sil.exp_subst + val to_subst_emb : side -> Sil.exp_subst -(* + (* val get : Exp.t -> Exp.t -> Exp.t option val pp : printenv -> Format.formatter -> (Exp.t * Exp.t * Exp.t) list -> unit *) end = struct - type t = (Exp.t * Exp.t * Exp.t) list let tbl : t ref = ref [] let init () = tbl := [] + let final () = tbl := [] + let reset () = let f = function - | Exp.Var id, e, _ | e, Exp.Var id, _ -> - (Ident.is_footprint id) && - (Sil.fav_for_all (Sil.exp_fav e) (fun id -> not (Ident.is_primed id))) - | _ -> false in + | Exp.Var id, e, _ | e, Exp.Var id, _ + -> Ident.is_footprint id + && Sil.fav_for_all (Sil.exp_fav e) (fun id -> not (Ident.is_primed id)) + | _ + -> false + in let t' = List.filter ~f !tbl in - tbl := t'; + tbl := t' ; t' let push v = tbl := v :: !tbl @@ -566,19 +587,23 @@ end = struct let side_op = opposite side in let assoc_es = match e with - | Exp.Const _ -> [] - | Exp.Lvar _ | Exp.Var _ - | Exp.BinOp (Binop.PlusA, Exp.Var _, _) -> - let is_same_e (e1, e2, _) = Exp.equal e (select side e1 e2) in + | Exp.Const _ + -> [] + | Exp.Lvar _ | Exp.Var _ | Exp.BinOp (Binop.PlusA, Exp.Var _, _) + -> let is_same_e (e1, e2, _) = Exp.equal e (select side e1 e2) in let assoc = List.filter ~f:is_same_e !tbl in List.map ~f:(fun (e1, e2, _) -> select side_op e1 e2) assoc - | _ -> - L.d_str "no pattern match in check lost_little e: "; Sil.d_exp e; L.d_ln (); - raise Sil.JoinFail in - lost_little side e assoc_es in + | _ + -> L.d_str "no pattern match in check lost_little e: " ; + Sil.d_exp e ; + L.d_ln () ; + raise Sil.JoinFail + in + lost_little side e assoc_es + in let lhs_es = List.map ~f:(fun (e1, _, _) -> e1) !tbl in let rhs_es = List.map ~f:(fun (_, e2, _) -> e2) !tbl in - (List.for_all ~f:(f Rhs) rhs_es) && (List.for_all ~f:(f Lhs) lhs_es) + List.for_all ~f:(f Rhs) rhs_es && List.for_all ~f:(f Lhs) lhs_es let lookup_side' side e = let f (e1, e2, _) = Exp.equal e (select side e1 e2) in @@ -586,172 +611,187 @@ end = struct let lookup_side_induced' side e = let res = ref [] in - let f v = match v, side with - | (Exp.BinOp (Binop.PlusA, e1', Exp.Const (Const.Cint i)), e2, e'), Lhs - when Exp.equal e e1' -> - let c' = Exp.int (IntLit.neg i) in - let v' = (e1', Exp.BinOp(Binop.PlusA, e2, c'), Exp.BinOp (Binop.PlusA, e', c')) in - res := v'::!res - | (e1, Exp.BinOp (Binop.PlusA, e2', Exp.Const (Const.Cint i)), e'), Rhs - when Exp.equal e e2' -> - let c' = Exp.int (IntLit.neg i) in - let v' = (Exp.BinOp(Binop.PlusA, e1, c'), e2', Exp.BinOp (Binop.PlusA, e', c')) in - res := v'::!res - | _ -> () in - begin - List.iter ~f !tbl; - List.rev !res - end + let f v = + match (v, side) with + | (Exp.BinOp (Binop.PlusA, e1', Exp.Const Const.Cint i), e2, e'), Lhs when Exp.equal e e1' + -> let c' = Exp.int (IntLit.neg i) in + let v' = (e1', Exp.BinOp (Binop.PlusA, e2, c'), Exp.BinOp (Binop.PlusA, e', c')) in + res := v' :: !res + | (e1, Exp.BinOp (Binop.PlusA, e2', Exp.Const Const.Cint i), e'), Rhs when Exp.equal e e2' + -> let c' = Exp.int (IntLit.neg i) in + let v' = (Exp.BinOp (Binop.PlusA, e1, c'), e2', Exp.BinOp (Binop.PlusA, e', c')) in + res := v' :: !res + | _ + -> () + in + List.iter ~f !tbl ; + List.rev !res (* Return the triple whose side is [e], if it exists unique *) let lookup' todo side e : Exp.t = match e with - | Exp.Var id when can_rename id -> - begin - let r = lookup_side' side e in - match r with - | [(_, _, id) as t] -> if todo then Todo.push t; id - | _ -> L.d_strln "failure reason 9"; raise Sil.JoinFail - end - | Exp.Var _ | Exp.Const _ | Exp.Lvar _ -> if todo then Todo.push (e, e, e); e - | _ -> L.d_strln "failure reason 10"; raise Sil.JoinFail + | Exp.Var id when can_rename id + -> ( + let r = lookup_side' side e in + match r with + | [(_, _, id as t)] + -> if todo then Todo.push t ; + id + | _ + -> L.d_strln "failure reason 9" ; raise Sil.JoinFail ) + | Exp.Var _ | Exp.Const _ | Exp.Lvar _ + -> if todo then Todo.push (e, e, e) ; + e + | _ + -> L.d_strln "failure reason 10" ; raise Sil.JoinFail let lookup side e = lookup' false side e + let lookup_todo side e = lookup' true side e + let lookup_list side l = List.map ~f:(lookup side) l + let lookup_list_todo side l = List.map ~f:(lookup_todo side) l let to_subst_proj (side: side) vars = let renaming_restricted = - List.filter ~f:(function (_, _, Exp.Var i) -> Sil.fav_mem vars i | _ -> assert false) !tbl in + List.filter ~f:(function _, _, Exp.Var i -> Sil.fav_mem vars i | _ -> assert false) !tbl + in let sub_list_side = List.map - ~f:(function (e1, e2, Exp.Var i) -> (i, select side e1 e2) | _ -> assert false) - renaming_restricted in + ~f:(function e1, e2, Exp.Var i -> (i, select side e1 e2) | _ -> assert false) + renaming_restricted + in let sub_list_side_sorted = - List.sort ~cmp:(fun (_, e) (_, e') -> Exp.compare e e') sub_list_side in - let rec find_duplicates = - function - | (_, e):: ((_, e'):: _ as t) -> Exp.equal e e' || find_duplicates t - | _ -> false in - if find_duplicates sub_list_side_sorted then (L.d_strln "failure reason 11"; raise Sil.JoinFail) + List.sort ~cmp:(fun (_, e) (_, e') -> Exp.compare e e') sub_list_side + in + let rec find_duplicates = function + | (_, e) :: ((_, e') :: _ as t) + -> Exp.equal e e' || find_duplicates t + | _ + -> false + in + if find_duplicates sub_list_side_sorted then ( + L.d_strln "failure reason 11" ; raise Sil.JoinFail ) else Sil.exp_subst_of_list sub_list_side - let to_subst_emb (side : side) = + let to_subst_emb (side: side) = let renaming_restricted = let pick_id_case (e1, e2, _) = - match select side e1 e2 with - | Exp.Var i -> can_rename i - | _ -> false in - List.filter ~f:pick_id_case !tbl in + match select side e1 e2 with Exp.Var i -> can_rename i | _ -> false + in + List.filter ~f:pick_id_case !tbl + in let sub_list = let project (e1, e2, e) = - match select side e1 e2 with - | Exp.Var i -> (i, e) - | _ -> assert false in - List.map ~f:project renaming_restricted in + match select side e1 e2 with Exp.Var i -> (i, e) | _ -> assert false + in + List.map ~f:project renaming_restricted + in let sub_list_sorted = let compare (i, _) (i', _) = Ident.compare i i' in - List.sort ~cmp:compare sub_list in + List.sort ~cmp:compare sub_list + in let rec find_duplicates = function - | (i, _):: ((i', _):: _ as t) -> Ident.equal i i' || find_duplicates t - | _ -> false in - if find_duplicates sub_list_sorted then (L.d_strln "failure reason 12"; raise Sil.JoinFail) + | (i, _) :: ((i', _) :: _ as t) + -> Ident.equal i i' || find_duplicates t + | _ + -> false + in + if find_duplicates sub_list_sorted then ( L.d_strln "failure reason 12" ; raise Sil.JoinFail ) else Sil.exp_subst_of_list sub_list_sorted let get_others' f_lookup side e = let side_op = opposite side in let r = f_lookup side e in - match r with - | [] -> None - | [(e1, e2, e')] -> Some (e', select side_op e1 e2) - | _ -> None + match r with [] -> None | [(e1, e2, e')] -> Some (e', select side_op e1 e2) | _ -> None + let get_others = get_others' lookup_side' + let get_others_direct_or_induced side e = let others = get_others side e in - match others with - | None -> get_others' lookup_side_induced' side e - | Some _ -> others + match others with None -> get_others' lookup_side_induced' side e | Some _ -> others + let get_others_deep side = function - | Exp.BinOp(op, e, e') -> + | Exp.BinOp (op, e, e') + -> ( let others = get_others_direct_or_induced side e in let others' = get_others_direct_or_induced side e' in - (match others, others' with - | None, _ | _, None -> None - | Some (e_res, e_op), Some(e_res', e_op') -> - let e_res'' = Exp.BinOp(op, e_res, e_res') in - let e_op'' = Exp.BinOp(op, e_op, e_op') in - Some (e_res'', e_op'')) - | _ -> None + match (others, others') with + | None, _ | _, None + -> None + | Some (e_res, e_op), Some (e_res', e_op') + -> let e_res'' = Exp.BinOp (op, e_res, e_res') in + let e_op'' = Exp.BinOp (op, e_op, e_op') in + Some (e_res'', e_op'') ) + | _ + -> None let get_other_atoms tenv side atom_in = let build_other_atoms construct side e = - if Config.trace_join then (L.d_str "build_other_atoms: "; Sil.d_exp e; L.d_ln ()); + if Config.trace_join then ( L.d_str "build_other_atoms: " ; Sil.d_exp e ; L.d_ln () ) ; let others1 = get_others_direct_or_induced side e in let others2 = match others1 with None -> get_others_deep side e | Some _ -> others1 in match others2 with - | None -> None - | Some (e_res, e_op) -> - let a_res = construct e_res in + | None + -> None + | Some (e_res, e_op) + -> let a_res = construct e_res in let a_op = construct e_op in - if Config.trace_join then begin - L.d_str "build_other_atoms (successful) "; - Sil.d_atom a_res; L.d_str ", "; Sil.d_atom a_op; L.d_ln () - end; - Some (a_res, a_op) in + if Config.trace_join then ( + L.d_str "build_other_atoms (successful) " ; + Sil.d_atom a_res ; + L.d_str ", " ; + Sil.d_atom a_op ; + L.d_ln () ) ; + Some (a_res, a_op) + in let exp_contains_only_normal_ids e = let fav = Sil.exp_fav e in - Sil.fav_for_all fav Ident.is_normal in + Sil.fav_for_all fav Ident.is_normal + in let atom_contains_only_normal_ids a = let fav = Sil.atom_fav a in - Sil.fav_for_all fav Ident.is_normal in + Sil.fav_for_all fav Ident.is_normal + in let normal_ids_only = atom_contains_only_normal_ids atom_in in if normal_ids_only then Some (atom_in, atom_in) else - begin - match atom_in with - | Sil.Aneq((Exp.Var id as e), e') - when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> - (* e' cannot also be a normal id according to the guard so we can consider the two cases + match atom_in with + | Sil.Aneq ((Exp.Var id as e), e') + when exp_contains_only_normal_ids e' && not (Ident.is_normal id) + -> (* e' cannot also be a normal id according to the guard so we can consider the two cases separately (this case and the next) *) - build_other_atoms (fun e0 -> Prop.mk_neq tenv e0 e') side e - - | Sil.Aneq(e', (Exp.Var id as e)) - when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> - build_other_atoms (fun e0 -> Prop.mk_neq tenv e0 e') side e - - | Sil.Apred (a, (Var id as e) :: es) - when not (Ident.is_normal id) && List.for_all ~f:exp_contains_only_normal_ids es -> - build_other_atoms (fun e0 -> Prop.mk_pred tenv a (e0 :: es)) side e - - | Sil.Anpred (a, (Var id as e) :: es) - when not (Ident.is_normal id) && List.for_all ~f:exp_contains_only_normal_ids es -> - build_other_atoms (fun e0 -> Prop.mk_npred tenv a (e0 :: es)) side e - - | Sil.Aeq((Exp.Var id as e), e') - when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> - (* e' cannot also be a normal id according to the guard so we can consider the two cases + build_other_atoms (fun e0 -> Prop.mk_neq tenv e0 e') side e + | Sil.Aneq (e', (Exp.Var id as e)) + when exp_contains_only_normal_ids e' && not (Ident.is_normal id) + -> build_other_atoms (fun e0 -> Prop.mk_neq tenv e0 e') side e + | Sil.Apred (a, (Var id as e) :: es) + when not (Ident.is_normal id) && List.for_all ~f:exp_contains_only_normal_ids es + -> build_other_atoms (fun e0 -> Prop.mk_pred tenv a (e0 :: es)) side e + | Sil.Anpred (a, (Var id as e) :: es) + when not (Ident.is_normal id) && List.for_all ~f:exp_contains_only_normal_ids es + -> build_other_atoms (fun e0 -> Prop.mk_npred tenv a (e0 :: es)) side e + | Sil.Aeq ((Exp.Var id as e), e') + when exp_contains_only_normal_ids e' && not (Ident.is_normal id) + -> (* e' cannot also be a normal id according to the guard so we can consider the two cases separately (this case and the next) *) - build_other_atoms (fun e0 -> Prop.mk_eq tenv e0 e') side e - - | Sil.Aeq(e', (Exp.Var id as e)) - when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> - build_other_atoms (fun e0 -> Prop.mk_eq tenv e0 e') side e - - | Sil.Aeq(Exp.BinOp(Binop.Le, e, e'), Exp.Const (Const.Cint i)) - | Sil.Aeq(Exp.Const (Const.Cint i), Exp.BinOp(Binop.Le, e, e')) - when IntLit.isone i && (exp_contains_only_normal_ids e') -> - let construct e0 = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, e0, e')) in - build_other_atoms construct side e - - | Sil.Aeq(Exp.BinOp(Binop.Lt, e', e), Exp.Const (Const.Cint i)) - | Sil.Aeq(Exp.Const (Const.Cint i), Exp.BinOp(Binop.Lt, e', e)) - when IntLit.isone i && (exp_contains_only_normal_ids e') -> - let construct e0 = Prop.mk_inequality tenv (Exp.BinOp(Binop.Lt, e', e0)) in - build_other_atoms construct side e - - | Sil.Aeq _ | Aneq _ | Apred _ | Anpred _ -> None - end + build_other_atoms (fun e0 -> Prop.mk_eq tenv e0 e') side e + | Sil.Aeq (e', (Exp.Var id as e)) + when exp_contains_only_normal_ids e' && not (Ident.is_normal id) + -> build_other_atoms (fun e0 -> Prop.mk_eq tenv e0 e') side e + | Sil.Aeq (Exp.BinOp (Binop.Le, e, e'), Exp.Const Const.Cint i) + | Sil.Aeq (Exp.Const Const.Cint i, Exp.BinOp (Binop.Le, e, e')) + when IntLit.isone i && exp_contains_only_normal_ids e' + -> let construct e0 = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, e0, e')) in + build_other_atoms construct side e + | Sil.Aeq (Exp.BinOp (Binop.Lt, e', e), Exp.Const Const.Cint i) + | Sil.Aeq (Exp.Const Const.Cint i, Exp.BinOp (Binop.Lt, e', e)) + when IntLit.isone i && exp_contains_only_normal_ids e' + -> let construct e0 = Prop.mk_inequality tenv (Exp.BinOp (Binop.Lt, e', e0)) in + build_other_atoms construct side e + | Sil.Aeq _ | Aneq _ | Apred _ | Anpred _ + -> None type data_opt = ExtFresh | ExtDefault of Exp.t @@ -759,85 +799,86 @@ end = struct * should be a primed or footprint variable *) let extend e1 e2 default_op = match - List.find ~f:(fun (f1, f2, _) -> Exp.equal e1 f1 && Exp.equal e2 f2) !tbl |> - Option.map ~f:trd3 + List.find ~f:(fun (f1, f2, _) -> Exp.equal e1 f1 && Exp.equal e2 f2) !tbl + |> Option.map ~f:trd3 with - | Some res -> - res - | None -> - let fav1 = Sil.exp_fav e1 in + | Some res + -> res + | None + -> let fav1 = Sil.exp_fav e1 in let fav2 = Sil.exp_fav e2 in let no_ren1 = not (Sil.fav_exists fav1 can_rename) in let no_ren2 = not (Sil.fav_exists fav2 can_rename) in let some_primed () = - Sil.fav_exists fav1 Ident.is_primed || Sil.fav_exists fav2 Ident.is_primed in + Sil.fav_exists fav1 Ident.is_primed || Sil.fav_exists fav2 Ident.is_primed + in let e = - if (no_ren1 && no_ren2) then - if (Exp.equal e1 e2) then e1 else (L.d_strln "failure reason 13"; raise Sil.JoinFail) + if no_ren1 && no_ren2 then + if Exp.equal e1 e2 then e1 else ( L.d_strln "failure reason 13" ; raise Sil.JoinFail ) else match default_op with - | ExtDefault e -> e - | ExtFresh -> - let kind = - if JoinState.get_footprint () && not (some_primed ()) - then Ident.kfootprint - else Ident.kprimed in - Exp.Var (Ident.create_fresh kind) in - let entry = e1, e2, e in - push entry; - Todo.push entry; - e + | ExtDefault e + -> e + | ExtFresh + -> let kind = + if JoinState.get_footprint () && not (some_primed ()) then Ident.kfootprint + else Ident.kprimed + in + Exp.Var (Ident.create_fresh kind) + in + let entry = (e1, e2, e) in + push entry ; Todo.push entry ; e end (** {2 Functions for constructing fresh sil data types} *) let extend_side' kind side e = match Rename.get_others side e with - | None -> - let e_op = Exp.Var (Ident.create_fresh kind) in + | None + -> let e_op = Exp.Var (Ident.create_fresh kind) in let e_new = Exp.Var (Ident.create_fresh kind) in - let e1, e2 = - match side with - | Lhs -> e, e_op - | Rhs -> e_op, e in - Rename.extend e1 e2 (Rename.ExtDefault (e_new)) - | Some (e', _) -> e' + let e1, e2 = match side with Lhs -> (e, e_op) | Rhs -> (e_op, e) in + Rename.extend e1 e2 (Rename.ExtDefault e_new) + | Some (e', _) + -> e' let rec exp_construct_fresh side e = match e with - | Exp.Var id -> - if Ident.is_normal id then - (Todo.push (e, e, e); e) - else if Ident.is_footprint id then - extend_side' Ident.kfootprint side e - else - extend_side' Ident.kprimed side e - | Exp.Const _ -> e - | Exp.Cast (t, e1) -> - let e1' = exp_construct_fresh side e1 in + | Exp.Var id + -> if Ident.is_normal id then ( + Todo.push (e, e, e) ; + e ) + else if Ident.is_footprint id then extend_side' Ident.kfootprint side e + else extend_side' Ident.kprimed side e + | Exp.Const _ + -> e + | Exp.Cast (t, e1) + -> let e1' = exp_construct_fresh side e1 in Exp.Cast (t, e1') - | Exp.UnOp(unop, e1, topt) -> - let e1' = exp_construct_fresh side e1 in - Exp.UnOp(unop, e1', topt) - | Exp.BinOp(binop, e1, e2) -> - let e1' = exp_construct_fresh side e1 in + | Exp.UnOp (unop, e1, topt) + -> let e1' = exp_construct_fresh side e1 in + Exp.UnOp (unop, e1', topt) + | Exp.BinOp (binop, e1, e2) + -> let e1' = exp_construct_fresh side e1 in let e2' = exp_construct_fresh side e2 in - Exp.BinOp(binop, e1', e2') - | Exp.Exn _ -> e - | Exp.Closure _ -> e - | Exp.Lvar _ -> - e - | Exp.Lfield(e1, fld, typ) -> - let e1' = exp_construct_fresh side e1 in - Exp.Lfield(e1', fld, typ) - | Exp.Lindex(e1, e2) -> - let e1' = exp_construct_fresh side e1 in + Exp.BinOp (binop, e1', e2') + | Exp.Exn _ + -> e + | Exp.Closure _ + -> e + | Exp.Lvar _ + -> e + | Exp.Lfield (e1, fld, typ) + -> let e1' = exp_construct_fresh side e1 in + Exp.Lfield (e1', fld, typ) + | Exp.Lindex (e1, e2) + -> let e1' = exp_construct_fresh side e1 in let e2' = exp_construct_fresh side e2 in - Exp.Lindex(e1', e2') - | Exp.Sizeof {dynamic_length=None} -> - e - | Exp.Sizeof ({dynamic_length=Some len} as sizeof) -> - Exp.Sizeof {sizeof with dynamic_length=Some (exp_construct_fresh side len)} + Exp.Lindex (e1', e2') + | Exp.Sizeof {dynamic_length= None} + -> e + | Exp.Sizeof ({dynamic_length= Some len} as sizeof) + -> Exp.Sizeof {sizeof with dynamic_length= Some (exp_construct_fresh side len)} let strexp_construct_fresh side = let f (e, inst_opt) = (exp_construct_fresh side e, inst_opt) in @@ -850,151 +891,147 @@ let hpred_construct_fresh side = (** {2 Join and Meet for Ids} *) let ident_same_kind_primed_footprint id1 id2 = - (Ident.is_primed id1 && Ident.is_primed id2) || - (Ident.is_footprint id1 && Ident.is_footprint id2) + Ident.is_primed id1 && Ident.is_primed id2 || Ident.is_footprint id1 && Ident.is_footprint id2 let ident_partial_join (id1: Ident.t) (id2: Ident.t) = - match Ident.is_normal id1, Ident.is_normal id2 with - | true, true -> - if Ident.equal id1 id2 - then Exp.Var id1 - else (L.d_strln "failure reason 14"; raise Sil.JoinFail) - | true, _ | _, true -> - Rename.extend (Exp.Var id1) (Exp.Var id2) Rename.ExtFresh - | _ -> - begin - if not (ident_same_kind_primed_footprint id1 id2) then - (L.d_strln "failure reason 15"; raise Sil.JoinFail) - else - let e1 = Exp.Var id1 in - let e2 = Exp.Var id2 in - Rename.extend e1 e2 Rename.ExtFresh - end + match (Ident.is_normal id1, Ident.is_normal id2) with + | true, true + -> if Ident.equal id1 id2 then Exp.Var id1 + else ( L.d_strln "failure reason 14" ; raise Sil.JoinFail ) + | true, _ | _, true + -> Rename.extend (Exp.Var id1) (Exp.Var id2) Rename.ExtFresh + | _ + -> if not (ident_same_kind_primed_footprint id1 id2) then ( + L.d_strln "failure reason 15" ; raise Sil.JoinFail ) + else + let e1 = Exp.Var id1 in + let e2 = Exp.Var id2 in + Rename.extend e1 e2 Rename.ExtFresh let ident_partial_meet (id1: Ident.t) (id2: Ident.t) = - match Ident.is_normal id1, Ident.is_normal id2 with - | true, true -> - if Ident.equal id1 id2 then Exp.Var id1 - else (L.d_strln "failure reason 16"; raise Sil.JoinFail) - | true, _ -> - let e1, e2 = Exp.Var id1, Exp.Var id2 in - Rename.extend e1 e2 (Rename.ExtDefault(e1)) - | _, true -> - let e1, e2 = Exp.Var id1, Exp.Var id2 in - Rename.extend e1 e2 (Rename.ExtDefault(e2)) - | _ -> - if Ident.is_primed id1 && Ident.is_primed id2 then + match (Ident.is_normal id1, Ident.is_normal id2) with + | true, true + -> if Ident.equal id1 id2 then Exp.Var id1 + else ( L.d_strln "failure reason 16" ; raise Sil.JoinFail ) + | true, _ + -> let e1, e2 = (Exp.Var id1, Exp.Var id2) in + Rename.extend e1 e2 (Rename.ExtDefault e1) + | _, true + -> let e1, e2 = (Exp.Var id1, Exp.Var id2) in + Rename.extend e1 e2 (Rename.ExtDefault e2) + | _ + -> if Ident.is_primed id1 && Ident.is_primed id2 then Rename.extend (Exp.Var id1) (Exp.Var id2) Rename.ExtFresh else if Ident.is_footprint id1 && Ident.equal id1 id2 then - let e = Exp.Var id1 in Rename.extend e e (Rename.ExtDefault(e)) - else - (L.d_strln "failure reason 17"; raise Sil.JoinFail) + let e = Exp.Var id1 in + Rename.extend e e (Rename.ExtDefault e) + else ( L.d_strln "failure reason 17" ; raise Sil.JoinFail ) (** {2 Join and Meet for Exps} *) let option_partial_join partial_join o1 o2 = - match o1, o2 with - | None, _ -> o2 - | _, None -> o1 - | Some x1, Some x2 -> partial_join x1 x2 + match (o1, o2) with None, _ -> o2 | _, None -> o1 | Some x1, Some x2 -> partial_join x1 x2 let const_partial_join c1 c2 = let is_int = function Const.Cint _ -> true | _ -> false in if Const.equal c1 c2 then Exp.Const c1 - else if Const.kind_equal c1 c2 && not (is_int c1) then - (L.d_strln "failure reason 18"; raise Sil.JoinFail) - else if !Config.abs_val >= 2 then - FreshVarExp.get_fresh_exp (Exp.Const c1) (Exp.Const c2) - else (L.d_strln "failure reason 19"; raise Sil.JoinFail) + else if Const.kind_equal c1 c2 && not (is_int c1) then ( + L.d_strln "failure reason 18" ; raise Sil.JoinFail ) + else if !Config.abs_val >= 2 then FreshVarExp.get_fresh_exp (Exp.Const c1) (Exp.Const c2) + else ( L.d_strln "failure reason 19" ; raise Sil.JoinFail ) let rec exp_partial_join (e1: Exp.t) (e2: Exp.t) : Exp.t = (* L.d_str "exp_partial_join "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) - match e1, e2 with - | Exp.Var id1, Exp.Var id2 -> - ident_partial_join id1 id2 - - | Exp.Var id, Exp.Const _ - | Exp.Const _, Exp.Var id -> - if Ident.is_normal id then - (L.d_strln "failure reason 20"; raise Sil.JoinFail) - else - Rename.extend e1 e2 Rename.ExtFresh - | Exp.Const c1, Exp.Const c2 -> - const_partial_join c1 c2 - - | Exp.Var id, Exp.Lvar _ - | Exp.Lvar _, Exp.Var id -> - if Ident.is_normal id then (L.d_strln "failure reason 21"; raise Sil.JoinFail) + match (e1, e2) with + | Exp.Var id1, Exp.Var id2 + -> ident_partial_join id1 id2 + | Exp.Var id, Exp.Const _ | Exp.Const _, Exp.Var id + -> if Ident.is_normal id then ( L.d_strln "failure reason 20" ; raise Sil.JoinFail ) else Rename.extend e1 e2 Rename.ExtFresh - - | Exp.BinOp(Binop.PlusA, Exp.Var id1, Exp.Const _), Exp.Var id2 - | Exp.Var id1, Exp.BinOp(Binop.PlusA, Exp.Var id2, Exp.Const _) - when ident_same_kind_primed_footprint id1 id2 -> - Rename.extend e1 e2 Rename.ExtFresh - | Exp.BinOp(Binop.PlusA, Exp.Var id1, Exp.Const (Const.Cint c1)), Exp.Const (Const.Cint c2) - when can_rename id1 -> - let c2' = c2 -- c1 in + | Exp.Const c1, Exp.Const c2 + -> const_partial_join c1 c2 + | Exp.Var id, Exp.Lvar _ | Exp.Lvar _, Exp.Var id + -> if Ident.is_normal id then ( L.d_strln "failure reason 21" ; raise Sil.JoinFail ) + else Rename.extend e1 e2 Rename.ExtFresh + | Exp.BinOp (Binop.PlusA, Exp.Var id1, Exp.Const _), Exp.Var id2 + | Exp.Var id1, Exp.BinOp (Binop.PlusA, Exp.Var id2, Exp.Const _) + when ident_same_kind_primed_footprint id1 id2 + -> Rename.extend e1 e2 Rename.ExtFresh + | Exp.BinOp (Binop.PlusA, Exp.Var id1, Exp.Const Const.Cint c1), Exp.Const Const.Cint c2 + when can_rename id1 + -> let c2' = c2 -- c1 in let e_res = Rename.extend (Exp.Var id1) (Exp.int c2') Rename.ExtFresh in - Exp.BinOp(Binop.PlusA, e_res, Exp.int c1) - | Exp.Const (Const.Cint c1), Exp.BinOp(Binop.PlusA, Exp.Var id2, Exp.Const (Const.Cint c2)) - when can_rename id2 -> - let c1' = c1 -- c2 in + Exp.BinOp (Binop.PlusA, e_res, Exp.int c1) + | Exp.Const Const.Cint c1, Exp.BinOp (Binop.PlusA, Exp.Var id2, Exp.Const Const.Cint c2) + when can_rename id2 + -> let c1' = c1 -- c2 in let e_res = Rename.extend (Exp.int c1') (Exp.Var id2) Rename.ExtFresh in - Exp.BinOp(Binop.PlusA, e_res, Exp.int c2) - | Exp.Cast(t1, e1), Exp.Cast(t2, e2) -> - if not (Typ.equal t1 t2) then (L.d_strln "failure reason 22"; raise Sil.JoinFail) + Exp.BinOp (Binop.PlusA, e_res, Exp.int c2) + | Exp.Cast (t1, e1), Exp.Cast (t2, e2) + -> if not (Typ.equal t1 t2) then ( L.d_strln "failure reason 22" ; raise Sil.JoinFail ) else let e1'' = exp_partial_join e1 e2 in Exp.Cast (t1, e1'') - | Exp.UnOp(unop1, e1, topt1), Exp.UnOp(unop2, e2, _) -> - if not (Unop.equal unop1 unop2) then (L.d_strln "failure reason 23"; raise Sil.JoinFail) - else Exp.UnOp (unop1, exp_partial_join e1 e2, topt1) (* should be topt1 = topt2 *) - | Exp.BinOp(Binop.PlusPI, e1, e1'), Exp.BinOp(Binop.PlusPI, e2, e2') -> - let e1'' = exp_partial_join e1 e2 in - let e2'' = match e1', e2' with - | Exp.Const _, Exp.Const _ -> exp_partial_join e1' e2' - | _ -> FreshVarExp.get_fresh_exp e1 e2 in - Exp.BinOp(Binop.PlusPI, e1'', e2'') - | Exp.BinOp(binop1, e1, e1'), Exp.BinOp(binop2, e2, e2') -> - if not (Binop.equal binop1 binop2) then (L.d_strln "failure reason 24"; raise Sil.JoinFail) + | Exp.UnOp (unop1, e1, topt1), Exp.UnOp (unop2, e2, _) + -> if not (Unop.equal unop1 unop2) then ( L.d_strln "failure reason 23" ; raise Sil.JoinFail ) + else Exp.UnOp (unop1, exp_partial_join e1 e2, topt1) + (* should be topt1 = topt2 *) + | Exp.BinOp (Binop.PlusPI, e1, e1'), Exp.BinOp (Binop.PlusPI, e2, e2') + -> let e1'' = exp_partial_join e1 e2 in + let e2'' = + match (e1', e2') with + | Exp.Const _, Exp.Const _ + -> exp_partial_join e1' e2' + | _ + -> FreshVarExp.get_fresh_exp e1 e2 + in + Exp.BinOp (Binop.PlusPI, e1'', e2'') + | Exp.BinOp (binop1, e1, e1'), Exp.BinOp (binop2, e2, e2') + -> if not (Binop.equal binop1 binop2) then ( L.d_strln "failure reason 24" ; raise Sil.JoinFail ) else let e1'' = exp_partial_join e1 e2 in let e2'' = exp_partial_join e1' e2' in - Exp.BinOp(binop1, e1'', e2'') - | Exp.Lvar(pvar1), Exp.Lvar(pvar2) -> - if not (Pvar.equal pvar1 pvar2) then (L.d_strln "failure reason 25"; raise Sil.JoinFail) + Exp.BinOp (binop1, e1'', e2'') + | Exp.Lvar pvar1, Exp.Lvar pvar2 + -> if not (Pvar.equal pvar1 pvar2) then ( L.d_strln "failure reason 25" ; raise Sil.JoinFail ) else e1 - | Exp.Lfield(e1, f1, t1), Exp.Lfield(e2, f2, _) -> - if not (Typ.Fieldname.equal f1 f2) then (L.d_strln "failure reason 26"; raise Sil.JoinFail) - else Exp.Lfield(exp_partial_join e1 e2, f1, t1) (* should be t1 = t2 *) - | Exp.Lindex(e1, e1'), Exp.Lindex(e2, e2') -> - let e1'' = exp_partial_join e1 e2 in + | Exp.Lfield (e1, f1, t1), Exp.Lfield (e2, f2, _) + -> if not (Typ.Fieldname.equal f1 f2) then ( L.d_strln "failure reason 26" ; raise Sil.JoinFail ) + else Exp.Lfield (exp_partial_join e1 e2, f1, t1) + (* should be t1 = t2 *) + | Exp.Lindex (e1, e1'), Exp.Lindex (e2, e2') + -> let e1'' = exp_partial_join e1 e2 in let e2'' = exp_partial_join e1' e2' in - Exp.Lindex(e1'', e2'') - | Exp.Sizeof {typ=t1; nbytes=nbytes1; dynamic_length=len1; subtype=st1}, - Exp.Sizeof {typ=t2; nbytes=nbytes2; dynamic_length=len2; subtype=st2} -> - (* forget the static sizes if they differ *) + Exp.Lindex (e1'', e2'') + | ( Exp.Sizeof {typ= t1; nbytes= nbytes1; dynamic_length= len1; subtype= st1} + , Exp.Sizeof {typ= t2; nbytes= nbytes2; dynamic_length= len2; subtype= st2} ) + -> (* forget the static sizes if they differ *) let nbytes_join i1 i2 = if Int.equal i1 i2 then Some i1 else None in - Exp.Sizeof { - typ=typ_partial_join t1 t2; - nbytes=option_partial_join nbytes_join nbytes1 nbytes2; - dynamic_length=dynamic_length_partial_join len1 len2; - subtype=Subtype.join st1 st2; - } - | _ -> - L.d_str "exp_partial_join no match "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); + Exp.Sizeof + { typ= typ_partial_join t1 t2 + ; nbytes= option_partial_join nbytes_join nbytes1 nbytes2 + ; dynamic_length= dynamic_length_partial_join len1 len2 + ; subtype= Subtype.join st1 st2 } + | _ + -> L.d_str "exp_partial_join no match " ; + Sil.d_exp e1 ; + L.d_str " " ; + Sil.d_exp e2 ; + L.d_ln () ; raise Sil.JoinFail -and length_partial_join len1 len2 = match len1, len2 with - | Exp.BinOp(Binop.PlusA, e1, Exp.Const c1), Exp.BinOp(Binop.PlusA, e2, Exp.Const c2) -> - let e' = exp_partial_join e1 e2 in +and length_partial_join len1 len2 = + match (len1, len2) with + | Exp.BinOp (Binop.PlusA, e1, Exp.Const c1), Exp.BinOp (Binop.PlusA, e2, Exp.Const c2) + -> let e' = exp_partial_join e1 e2 in let c' = exp_partial_join (Exp.Const c1) (Exp.Const c2) in Exp.BinOp (Binop.PlusA, e', c') - | Exp.BinOp(Binop.PlusA, _, _), Exp.BinOp(Binop.PlusA, _, _) -> - Rename.extend len1 len2 Rename.ExtFresh - | Exp.Var id1, Exp.Var id2 when Ident.equal id1 id2 -> - len1 - | _ -> exp_partial_join len1 len2 + | Exp.BinOp (Binop.PlusA, _, _), Exp.BinOp (Binop.PlusA, _, _) + -> Rename.extend len1 len2 Rename.ExtFresh + | Exp.Var id1, Exp.Var id2 when Ident.equal id1 id2 + -> len1 + | _ + -> exp_partial_join len1 len2 and static_length_partial_join l1 l2 = option_partial_join (fun len1 len2 -> if IntLit.eq len1 len2 then Some len1 else None) l1 l2 @@ -1002,322 +1039,328 @@ and static_length_partial_join l1 l2 = and dynamic_length_partial_join l1 l2 = option_partial_join (fun len1 len2 -> Some (length_partial_join len1 len2)) l1 l2 -and typ_partial_join (t1 : Typ.t) (t2 : Typ.t) = match t1.desc, t2.desc with +and typ_partial_join (t1: Typ.t) (t2: Typ.t) = + match (t1.desc, t2.desc) with | Typ.Tptr (t1, pk1), Typ.Tptr (t2, pk2) - when Typ.equal_ptr_kind pk1 pk2 && Typ.equal_quals t1.quals t2.quals -> - Typ.mk ~default:t1 (Tptr (typ_partial_join t1 t2, pk1)) (* quals are the same for t1 and t2 *) - | Typ.Tarray (typ1, len1, stride1), - Typ.Tarray (typ2, len2, stride2) when Typ.equal_quals typ1.quals typ2.quals -> - let t = typ_partial_join typ1 typ2 in + when Typ.equal_ptr_kind pk1 pk2 && Typ.equal_quals t1.quals t2.quals + -> Typ.mk ~default:t1 (Tptr (typ_partial_join t1 t2, pk1)) + (* quals are the same for t1 and t2 *) + | Typ.Tarray (typ1, len1, stride1), Typ.Tarray (typ2, len2, stride2) + when Typ.equal_quals typ1.quals typ2.quals + -> let t = typ_partial_join typ1 typ2 in let len = static_length_partial_join len1 len2 in let stride = static_length_partial_join stride1 stride2 in - Typ.mk ~default:t1 (Tarray (t, len, stride)) (* quals are the same for t1 and t2 *) - | _ when Typ.equal t1 t2 -> t1 (* common case *) - | _ -> - L.d_str "typ_partial_join no match "; - Typ.d_full t1; L.d_str " "; Typ.d_full t2; L.d_ln (); + Typ.mk ~default:t1 (Tarray (t, len, stride)) + (* quals are the same for t1 and t2 *) + | _ when Typ.equal t1 t2 + -> t1 (* common case *) + | _ + -> L.d_str "typ_partial_join no match " ; + Typ.d_full t1 ; + L.d_str " " ; + Typ.d_full t2 ; + L.d_ln () ; raise Sil.JoinFail let rec exp_partial_meet (e1: Exp.t) (e2: Exp.t) : Exp.t = - match e1, e2 with - | Exp.Var id1, Exp.Var id2 -> - ident_partial_meet id1 id2 - | Exp.Var id, Exp.Const _ -> - if not (Ident.is_normal id) then - Rename.extend e1 e2 (Rename.ExtDefault(e2)) - else (L.d_strln "failure reason 27"; raise Sil.JoinFail) - | Exp.Const _, Exp.Var id -> - if not (Ident.is_normal id) then - Rename.extend e1 e2 (Rename.ExtDefault(e1)) - else (L.d_strln "failure reason 28"; raise Sil.JoinFail) - | Exp.Const c1, Exp.Const c2 -> - if (Const.equal c1 c2) then e1 else (L.d_strln "failure reason 29"; raise Sil.JoinFail) - | Exp.Cast(t1, e1), Exp.Cast(t2, e2) -> - if not (Typ.equal t1 t2) then (L.d_strln "failure reason 30"; raise Sil.JoinFail) + match (e1, e2) with + | Exp.Var id1, Exp.Var id2 + -> ident_partial_meet id1 id2 + | Exp.Var id, Exp.Const _ + -> if not (Ident.is_normal id) then Rename.extend e1 e2 (Rename.ExtDefault e2) + else ( L.d_strln "failure reason 27" ; raise Sil.JoinFail ) + | Exp.Const _, Exp.Var id + -> if not (Ident.is_normal id) then Rename.extend e1 e2 (Rename.ExtDefault e1) + else ( L.d_strln "failure reason 28" ; raise Sil.JoinFail ) + | Exp.Const c1, Exp.Const c2 + -> if Const.equal c1 c2 then e1 else ( L.d_strln "failure reason 29" ; raise Sil.JoinFail ) + | Exp.Cast (t1, e1), Exp.Cast (t2, e2) + -> if not (Typ.equal t1 t2) then ( L.d_strln "failure reason 30" ; raise Sil.JoinFail ) else let e1'' = exp_partial_meet e1 e2 in Exp.Cast (t1, e1'') - | Exp.UnOp(unop1, e1, topt1), Exp.UnOp(unop2, e2, _) -> - if not (Unop.equal unop1 unop2) then (L.d_strln "failure reason 31"; raise Sil.JoinFail) - else Exp.UnOp (unop1, exp_partial_meet e1 e2, topt1) (* should be topt1 = topt2 *) - | Exp.BinOp(binop1, e1, e1'), Exp.BinOp(binop2, e2, e2') -> - if not (Binop.equal binop1 binop2) then (L.d_strln "failure reason 32"; raise Sil.JoinFail) + | Exp.UnOp (unop1, e1, topt1), Exp.UnOp (unop2, e2, _) + -> if not (Unop.equal unop1 unop2) then ( L.d_strln "failure reason 31" ; raise Sil.JoinFail ) + else Exp.UnOp (unop1, exp_partial_meet e1 e2, topt1) + (* should be topt1 = topt2 *) + | Exp.BinOp (binop1, e1, e1'), Exp.BinOp (binop2, e2, e2') + -> if not (Binop.equal binop1 binop2) then ( L.d_strln "failure reason 32" ; raise Sil.JoinFail ) else let e1'' = exp_partial_meet e1 e2 in let e2'' = exp_partial_meet e1' e2' in - Exp.BinOp(binop1, e1'', e2'') - | Exp.Var id, Exp.Lvar _ -> - if not (Ident.is_normal id) then - Rename.extend e1 e2 (Rename.ExtDefault(e2)) - else (L.d_strln "failure reason 33"; raise Sil.JoinFail) - | Exp.Lvar _, Exp.Var id -> - if not (Ident.is_normal id) then - Rename.extend e1 e2 (Rename.ExtDefault(e1)) - else (L.d_strln "failure reason 34"; raise Sil.JoinFail) - | Exp.Lvar(pvar1), Exp.Lvar(pvar2) -> - if not (Pvar.equal pvar1 pvar2) then (L.d_strln "failure reason 35"; raise Sil.JoinFail) + Exp.BinOp (binop1, e1'', e2'') + | Exp.Var id, Exp.Lvar _ + -> if not (Ident.is_normal id) then Rename.extend e1 e2 (Rename.ExtDefault e2) + else ( L.d_strln "failure reason 33" ; raise Sil.JoinFail ) + | Exp.Lvar _, Exp.Var id + -> if not (Ident.is_normal id) then Rename.extend e1 e2 (Rename.ExtDefault e1) + else ( L.d_strln "failure reason 34" ; raise Sil.JoinFail ) + | Exp.Lvar pvar1, Exp.Lvar pvar2 + -> if not (Pvar.equal pvar1 pvar2) then ( L.d_strln "failure reason 35" ; raise Sil.JoinFail ) else e1 - | Exp.Lfield(e1, f1, t1), Exp.Lfield(e2, f2, _) -> - if not (Typ.Fieldname.equal f1 f2) then (L.d_strln "failure reason 36"; raise Sil.JoinFail) - else Exp.Lfield(exp_partial_meet e1 e2, f1, t1) (* should be t1 = t2 *) - | Exp.Lindex(e1, e1'), Exp.Lindex(e2, e2') -> - let e1'' = exp_partial_meet e1 e2 in + | Exp.Lfield (e1, f1, t1), Exp.Lfield (e2, f2, _) + -> if not (Typ.Fieldname.equal f1 f2) then ( L.d_strln "failure reason 36" ; raise Sil.JoinFail ) + else Exp.Lfield (exp_partial_meet e1 e2, f1, t1) + (* should be t1 = t2 *) + | Exp.Lindex (e1, e1'), Exp.Lindex (e2, e2') + -> let e1'' = exp_partial_meet e1 e2 in let e2'' = exp_partial_meet e1' e2' in - Exp.Lindex(e1'', e2'') - | _ -> (L.d_strln "failure reason 37"; raise Sil.JoinFail) + Exp.Lindex (e1'', e2'') + | _ + -> L.d_strln "failure reason 37" ; raise Sil.JoinFail let exp_list_partial_join = List.map2_exn ~f:exp_partial_join let exp_list_partial_meet = List.map2_exn ~f:exp_partial_meet - (** {2 Join and Meet for Strexp} *) let rec strexp_partial_join mode (strexp1: Sil.strexp) (strexp2: Sil.strexp) : Sil.strexp = - let rec f_fld_se_list inst mode acc fld_se_list1 fld_se_list2 = - match fld_se_list1, fld_se_list2 with - | [], [] -> Sil.Estruct (List.rev acc, inst) - | [], _ | _, [] -> - begin - match mode with - | JoinState.Pre -> (L.d_strln "failure reason 42"; raise Sil.JoinFail) - | JoinState.Post -> Sil.Estruct (List.rev acc, inst) - end - | (fld1, se1):: fld_se_list1', (fld2, se2):: fld_se_list2' -> - let comparison = Typ.Fieldname.compare fld1 fld2 in + match (fld_se_list1, fld_se_list2) with + | [], [] + -> Sil.Estruct (List.rev acc, inst) + | [], _ | _, [] -> ( + match mode with + | JoinState.Pre + -> L.d_strln "failure reason 42" ; raise Sil.JoinFail + | JoinState.Post + -> Sil.Estruct (List.rev acc, inst) ) + | (fld1, se1) :: fld_se_list1', (fld2, se2) :: fld_se_list2' + -> let comparison = Typ.Fieldname.compare fld1 fld2 in if Int.equal comparison 0 then let strexp' = strexp_partial_join mode se1 se2 in let fld_se_list_new = (fld1, strexp') :: acc in f_fld_se_list inst mode fld_se_list_new fld_se_list1' fld_se_list2' - else begin + else match mode with - | JoinState.Pre -> - (L.d_strln "failure reason 43"; raise Sil.JoinFail) - | JoinState.Post -> - if comparison < 0 then begin - f_fld_se_list inst mode acc fld_se_list1' fld_se_list2 - end - else if comparison > 0 then begin - f_fld_se_list inst mode acc fld_se_list1 fld_se_list2' - end - else - assert false (* This case should not happen. *) - end in - + | JoinState.Pre + -> L.d_strln "failure reason 43" ; raise Sil.JoinFail + | JoinState.Post + -> if comparison < 0 then f_fld_se_list inst mode acc fld_se_list1' fld_se_list2 + else if comparison > 0 then f_fld_se_list inst mode acc fld_se_list1 fld_se_list2' + else assert false + (* This case should not happen. *) + in let rec f_idx_se_list inst len idx_se_list_acc idx_se_list1 idx_se_list2 = - match idx_se_list1, idx_se_list2 with - | [], [] -> Sil.Earray (len, List.rev idx_se_list_acc, inst) - | [], _ | _, [] -> - begin - match mode with - | JoinState.Pre -> (L.d_strln "failure reason 44"; raise Sil.JoinFail) - | JoinState.Post -> - Sil.Earray (len, List.rev idx_se_list_acc, inst) - end - | (idx1, se1):: idx_se_list1', (idx2, se2):: idx_se_list2' -> - let idx = exp_partial_join idx1 idx2 in + match (idx_se_list1, idx_se_list2) with + | [], [] + -> Sil.Earray (len, List.rev idx_se_list_acc, inst) + | [], _ | _, [] -> ( + match mode with + | JoinState.Pre + -> L.d_strln "failure reason 44" ; raise Sil.JoinFail + | JoinState.Post + -> Sil.Earray (len, List.rev idx_se_list_acc, inst) ) + | (idx1, se1) :: idx_se_list1', (idx2, se2) :: idx_se_list2' + -> let idx = exp_partial_join idx1 idx2 in let strexp' = strexp_partial_join mode se1 se2 in let idx_se_list_new = (idx, strexp') :: idx_se_list_acc in - f_idx_se_list inst len idx_se_list_new idx_se_list1' idx_se_list2' in - - match strexp1, strexp2 with - | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> - Sil.Eexp (exp_partial_join e1 e2, Sil.inst_partial_join inst1 inst2) - | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) -> - let inst = Sil.inst_partial_join inst1 inst2 in + f_idx_se_list inst len idx_se_list_new idx_se_list1' idx_se_list2' + in + match (strexp1, strexp2) with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) + -> Sil.Eexp (exp_partial_join e1 e2, Sil.inst_partial_join inst1 inst2) + | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) + -> let inst = Sil.inst_partial_join inst1 inst2 in f_fld_se_list inst mode [] fld_se_list1 fld_se_list2 - | Sil.Earray (len1, idx_se_list1, inst1), Sil.Earray (len2, idx_se_list2, inst2) -> - let len = length_partial_join len1 len2 in + | Sil.Earray (len1, idx_se_list1, inst1), Sil.Earray (len2, idx_se_list2, inst2) + -> let len = length_partial_join len1 len2 in let inst = Sil.inst_partial_join inst1 inst2 in f_idx_se_list inst len [] idx_se_list1 idx_se_list2 - | _ -> L.d_strln "no match in strexp_partial_join"; raise Sil.JoinFail + | _ + -> L.d_strln "no match in strexp_partial_join" ; raise Sil.JoinFail let rec strexp_partial_meet (strexp1: Sil.strexp) (strexp2: Sil.strexp) : Sil.strexp = - let construct side rev_list ref_list = let construct_offset_se (off, se) = (off, strexp_construct_fresh side se) in let acc = List.map ~f:construct_offset_se ref_list in - List.rev_append rev_list acc in - + List.rev_append rev_list acc + in let rec f_fld_se_list inst acc fld_se_list1 fld_se_list2 = - match fld_se_list1, fld_se_list2 with - | [], [] -> - Sil.Estruct (List.rev acc, inst) - | [], _ -> - Sil.Estruct (construct Rhs acc fld_se_list2, inst) - | _, [] -> - Sil.Estruct (construct Lhs acc fld_se_list1, inst) - | (fld1, se1):: fld_se_list1', (fld2, se2):: fld_se_list2' -> - let comparison = Typ.Fieldname.compare fld1 fld2 in + match (fld_se_list1, fld_se_list2) with + | [], [] + -> Sil.Estruct (List.rev acc, inst) + | [], _ + -> Sil.Estruct (construct Rhs acc fld_se_list2, inst) + | _, [] + -> Sil.Estruct (construct Lhs acc fld_se_list1, inst) + | (fld1, se1) :: fld_se_list1', (fld2, se2) :: fld_se_list2' + -> let comparison = Typ.Fieldname.compare fld1 fld2 in if comparison < 0 then let se' = strexp_construct_fresh Lhs se1 in - let acc_new = (fld1, se'):: acc in + let acc_new = (fld1, se') :: acc in f_fld_se_list inst acc_new fld_se_list1' fld_se_list2 else if comparison > 0 then let se' = strexp_construct_fresh Rhs se2 in - let acc_new = (fld2, se'):: acc in + let acc_new = (fld2, se') :: acc in f_fld_se_list inst acc_new fld_se_list1 fld_se_list2' else let strexp' = strexp_partial_meet se1 se2 in let acc_new = (fld1, strexp') :: acc in - f_fld_se_list inst acc_new fld_se_list1' fld_se_list2' in - + f_fld_se_list inst acc_new fld_se_list1' fld_se_list2' + in let rec f_idx_se_list inst len acc idx_se_list1 idx_se_list2 = - match idx_se_list1, idx_se_list2 with - | [],[] -> - Sil.Earray (len, List.rev acc, inst) - | [], _ -> - Sil.Earray (len, construct Rhs acc idx_se_list2, inst) - | _, [] -> - Sil.Earray (len, construct Lhs acc idx_se_list1, inst) - | (idx1, se1):: idx_se_list1', (idx2, se2):: idx_se_list2' -> - let idx = exp_partial_meet idx1 idx2 in + match (idx_se_list1, idx_se_list2) with + | [], [] + -> Sil.Earray (len, List.rev acc, inst) + | [], _ + -> Sil.Earray (len, construct Rhs acc idx_se_list2, inst) + | _, [] + -> Sil.Earray (len, construct Lhs acc idx_se_list1, inst) + | (idx1, se1) :: idx_se_list1', (idx2, se2) :: idx_se_list2' + -> let idx = exp_partial_meet idx1 idx2 in let se' = strexp_partial_meet se1 se2 in let acc_new = (idx, se') :: acc in - f_idx_se_list inst len acc_new idx_se_list1' idx_se_list2' in - - match strexp1, strexp2 with - | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> - Sil.Eexp (exp_partial_meet e1 e2, Sil.inst_partial_meet inst1 inst2) - | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) -> - let inst = Sil.inst_partial_meet inst1 inst2 in + f_idx_se_list inst len acc_new idx_se_list1' idx_se_list2' + in + match (strexp1, strexp2) with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) + -> Sil.Eexp (exp_partial_meet e1 e2, Sil.inst_partial_meet inst1 inst2) + | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) + -> let inst = Sil.inst_partial_meet inst1 inst2 in f_fld_se_list inst [] fld_se_list1 fld_se_list2 | Sil.Earray (len1, idx_se_list1, inst1), Sil.Earray (len2, idx_se_list2, inst2) - when Exp.equal len1 len2 -> - let inst = Sil.inst_partial_meet inst1 inst2 in + when Exp.equal len1 len2 + -> let inst = Sil.inst_partial_meet inst1 inst2 in f_idx_se_list inst len1 [] idx_se_list1 idx_se_list2 - | _ -> (L.d_strln "failure reason 52"; raise Sil.JoinFail) + | _ + -> L.d_strln "failure reason 52" ; raise Sil.JoinFail (** {2 Join and Meet for kind, hpara, hpara_dll} *) -let kind_join k1 k2 = match k1, k2 with - | Sil.Lseg_PE, _ -> Sil.Lseg_PE - | _, Sil.Lseg_PE -> Sil.Lseg_PE - | Sil.Lseg_NE, Sil.Lseg_NE -> Sil.Lseg_NE - -let kind_meet k1 k2 = match k1, k2 with - | Sil.Lseg_NE, _ -> Sil.Lseg_NE - | _, Sil.Lseg_NE -> Sil.Lseg_NE - | Sil.Lseg_PE, Sil.Lseg_PE -> Sil.Lseg_PE +let kind_join k1 k2 = + match (k1, k2) with + | Sil.Lseg_PE, _ + -> Sil.Lseg_PE + | _, Sil.Lseg_PE + -> Sil.Lseg_PE + | Sil.Lseg_NE, Sil.Lseg_NE + -> Sil.Lseg_NE + +let kind_meet k1 k2 = + match (k1, k2) with + | Sil.Lseg_NE, _ + -> Sil.Lseg_NE + | _, Sil.Lseg_NE + -> Sil.Lseg_NE + | Sil.Lseg_PE, Sil.Lseg_PE + -> Sil.Lseg_PE let hpara_partial_join tenv (hpara1: Sil.hpara) (hpara2: Sil.hpara) : Sil.hpara = - if Match.hpara_match_with_impl tenv true hpara2 hpara1 then - hpara1 - else if Match.hpara_match_with_impl tenv true hpara1 hpara2 then - hpara2 - else - (L.d_strln "failure reason 53"; raise Sil.JoinFail) + if Match.hpara_match_with_impl tenv true hpara2 hpara1 then hpara1 + else if Match.hpara_match_with_impl tenv true hpara1 hpara2 then hpara2 + else ( L.d_strln "failure reason 53" ; raise Sil.JoinFail ) let hpara_partial_meet tenv (hpara1: Sil.hpara) (hpara2: Sil.hpara) : Sil.hpara = - if Match.hpara_match_with_impl tenv true hpara2 hpara1 then - hpara2 - else if Match.hpara_match_with_impl tenv true hpara1 hpara2 then - hpara1 - else - (L.d_strln "failure reason 54"; raise Sil.JoinFail) + if Match.hpara_match_with_impl tenv true hpara2 hpara1 then hpara2 + else if Match.hpara_match_with_impl tenv true hpara1 hpara2 then hpara1 + else ( L.d_strln "failure reason 54" ; raise Sil.JoinFail ) let hpara_dll_partial_join tenv (hpara1: Sil.hpara_dll) (hpara2: Sil.hpara_dll) : Sil.hpara_dll = - if Match.hpara_dll_match_with_impl tenv true hpara2 hpara1 then - hpara1 - else if Match.hpara_dll_match_with_impl tenv true hpara1 hpara2 then - hpara2 - else - (L.d_strln "failure reason 55"; raise Sil.JoinFail) + if Match.hpara_dll_match_with_impl tenv true hpara2 hpara1 then hpara1 + else if Match.hpara_dll_match_with_impl tenv true hpara1 hpara2 then hpara2 + else ( L.d_strln "failure reason 55" ; raise Sil.JoinFail ) let hpara_dll_partial_meet tenv (hpara1: Sil.hpara_dll) (hpara2: Sil.hpara_dll) : Sil.hpara_dll = - if Match.hpara_dll_match_with_impl tenv true hpara2 hpara1 then - hpara2 - else if Match.hpara_dll_match_with_impl tenv true hpara1 hpara2 then - hpara1 - else - (L.d_strln "failure reason 56"; raise Sil.JoinFail) + if Match.hpara_dll_match_with_impl tenv true hpara2 hpara1 then hpara2 + else if Match.hpara_dll_match_with_impl tenv true hpara1 hpara2 then hpara1 + else ( L.d_strln "failure reason 56" ; raise Sil.JoinFail ) (** {2 Join and Meet for hpred} *) -let hpred_partial_join tenv mode (todo: Exp.t * Exp.t * Exp.t) (hpred1: Sil.hpred) (hpred2: Sil.hpred) - : Sil.hpred = +let hpred_partial_join tenv mode (todo: Exp.t * Exp.t * Exp.t) (hpred1: Sil.hpred) + (hpred2: Sil.hpred) : Sil.hpred = let e1, e2, e = todo in - match hpred1, hpred2 with - | Sil.Hpointsto (_, se1, te1), Sil.Hpointsto (_, se2, te2) -> - let te = exp_partial_join te1 te2 in + match (hpred1, hpred2) with + | Sil.Hpointsto (_, se1, te1), Sil.Hpointsto (_, se2, te2) + -> let te = exp_partial_join te1 te2 in Prop.mk_ptsto tenv e (strexp_partial_join mode se1 se2) te - | Sil.Hlseg (k1, hpara1, _, next1, shared1), Sil.Hlseg (k2, hpara2, _, next2, shared2) -> - let hpara' = hpara_partial_join tenv hpara1 hpara2 in + | Sil.Hlseg (k1, hpara1, _, next1, shared1), Sil.Hlseg (k2, hpara2, _, next2, shared2) + -> let hpara' = hpara_partial_join tenv hpara1 hpara2 in let next' = exp_partial_join next1 next2 in let shared' = exp_list_partial_join shared1 shared2 in Prop.mk_lseg tenv (kind_join k1 k2) hpara' e next' shared' - | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1), - Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) -> - let fwd1 = Exp.equal e1 iF1 in + | ( Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1) + , Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) ) + -> let fwd1 = Exp.equal e1 iF1 in let fwd2 = Exp.equal e2 iF2 in let hpara' = hpara_dll_partial_join tenv para1 para2 in let iF', iB' = - if (fwd1 && fwd2) then (e, exp_partial_join iB1 iB2) - else if (not fwd1 && not fwd2) then (exp_partial_join iF1 iF2, e) - else (L.d_strln "failure reason 57"; raise Sil.JoinFail) in + if fwd1 && fwd2 then (e, exp_partial_join iB1 iB2) + else if not fwd1 && not fwd2 then (exp_partial_join iF1 iF2, e) + else ( L.d_strln "failure reason 57" ; raise Sil.JoinFail ) + in let oF' = exp_partial_join oF1 oF2 in let oB' = exp_partial_join oB1 oB2 in let shared' = exp_list_partial_join shared1 shared2 in Prop.mk_dllseg tenv (kind_join k1 k2) hpara' iF' oB' oF' iB' shared' - | _ -> - assert false + | _ + -> assert false let hpred_partial_meet tenv (todo: Exp.t * Exp.t * Exp.t) (hpred1: Sil.hpred) (hpred2: Sil.hpred) - : Sil.hpred = + : Sil.hpred = let e1, e2, e = todo in - match hpred1, hpred2 with - | Sil.Hpointsto (_, se1, te1), Sil.Hpointsto (_, se2, te2) when Exp.equal te1 te2 -> - Prop.mk_ptsto tenv e (strexp_partial_meet se1 se2) te1 - | Sil.Hpointsto _, _ | _, Sil.Hpointsto _ -> - (L.d_strln "failure reason 58"; raise Sil.JoinFail) - | Sil.Hlseg (k1, hpara1, _, next1, shared1), Sil.Hlseg (k2, hpara2, _, next2, shared2) -> - let hpara' = hpara_partial_meet tenv hpara1 hpara2 in + match (hpred1, hpred2) with + | Sil.Hpointsto (_, se1, te1), Sil.Hpointsto (_, se2, te2) when Exp.equal te1 te2 + -> Prop.mk_ptsto tenv e (strexp_partial_meet se1 se2) te1 + | Sil.Hpointsto _, _ | _, Sil.Hpointsto _ + -> L.d_strln "failure reason 58" ; raise Sil.JoinFail + | Sil.Hlseg (k1, hpara1, _, next1, shared1), Sil.Hlseg (k2, hpara2, _, next2, shared2) + -> let hpara' = hpara_partial_meet tenv hpara1 hpara2 in let next' = exp_partial_meet next1 next2 in let shared' = exp_list_partial_meet shared1 shared2 in Prop.mk_lseg tenv (kind_meet k1 k2) hpara' e next' shared' - | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1), - Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) -> - let fwd1 = Exp.equal e1 iF1 in + | ( Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1) + , Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) ) + -> let fwd1 = Exp.equal e1 iF1 in let fwd2 = Exp.equal e2 iF2 in let hpara' = hpara_dll_partial_meet tenv para1 para2 in let iF', iB' = - if (fwd1 && fwd2) then (e, exp_partial_meet iB1 iB2) - else if (not fwd1 && not fwd2) then (exp_partial_meet iF1 iF2, e) - else (L.d_strln "failure reason 59"; raise Sil.JoinFail) in + if fwd1 && fwd2 then (e, exp_partial_meet iB1 iB2) + else if not fwd1 && not fwd2 then (exp_partial_meet iF1 iF2, e) + else ( L.d_strln "failure reason 59" ; raise Sil.JoinFail ) + in let oF' = exp_partial_meet oF1 oF2 in let oB' = exp_partial_meet oB1 oB2 in let shared' = exp_list_partial_meet shared1 shared2 in Prop.mk_dllseg tenv (kind_meet k1 k2) hpara' iF' oB' oF' iB' shared' - | _ -> - assert false + | _ + -> assert false (** {2 Join and Meet for Sigma} *) let find_hpred_by_address tenv (e: Exp.t) (sigma: Prop.sigma) : Sil.hpred option * Prop.sigma = let is_root_for_e e' = - match (Prover.is_root tenv Prop.prop_emp e' e) with - | None -> false - | Some _ -> true in + match Prover.is_root tenv Prop.prop_emp e' e with None -> false | Some _ -> true + in let contains_e = function - | Sil.Hpointsto (e', _, _) -> is_root_for_e e' - | Sil.Hlseg (_, _, e', _, _) -> is_root_for_e e' - | Sil.Hdllseg (_, _, iF, _, _, iB, _) -> is_root_for_e iF || is_root_for_e iB in + | Sil.Hpointsto (e', _, _) + -> is_root_for_e e' + | Sil.Hlseg (_, _, e', _, _) + -> is_root_for_e e' + | Sil.Hdllseg (_, _, iF, _, _, iB, _) + -> is_root_for_e iF || is_root_for_e iB + in let rec f sigma_acc = function - | [] -> None, sigma - | hpred:: sigma -> - if contains_e hpred then - Some hpred, List.rev_append sigma_acc sigma - else - f (hpred:: sigma_acc) sigma in + | [] + -> (None, sigma) + | hpred :: sigma + -> if contains_e hpred then (Some hpred, List.rev_append sigma_acc sigma) + else f (hpred :: sigma_acc) sigma + in f [] sigma let same_pred (hpred1: Sil.hpred) (hpred2: Sil.hpred) : bool = - match hpred1, hpred2 with - | Sil.Hpointsto _, Sil.Hpointsto _ -> true - | Sil.Hlseg _, Sil.Hlseg _ -> true - | Sil.Hdllseg _, Sil.Hdllseg _ -> true - | _ -> false + match (hpred1, hpred2) with + | Sil.Hpointsto _, Sil.Hpointsto _ + -> true + | Sil.Hlseg _, Sil.Hlseg _ + -> true + | Sil.Hdllseg _, Sil.Hdllseg _ + -> true + | _ + -> false (* check that applying renaming to the lhs / rhs of [sigma_new] * gives [sigma] and that the renaming is injective *) @@ -1331,247 +1374,245 @@ let sigma_renaming_check (lhs: side) (sigma: Prop.sigma) (sigma_new: Prop.sigma) equal_sigma sigma sigma' let sigma_renaming_check_lhs = sigma_renaming_check Lhs -let sigma_renaming_check_rhs = sigma_renaming_check Rhs -let rec sigma_partial_join' tenv mode (sigma_acc: Prop.sigma) - (sigma1_in: Prop.sigma) (sigma2_in: Prop.sigma) : (Prop.sigma * Prop.sigma * Prop.sigma) = +let sigma_renaming_check_rhs = sigma_renaming_check Rhs +let rec sigma_partial_join' tenv mode (sigma_acc: Prop.sigma) (sigma1_in: Prop.sigma) + (sigma2_in: Prop.sigma) : Prop.sigma * Prop.sigma * Prop.sigma = let lookup_and_expand side e e' = match (Rename.get_others side e, side) with - | None, _ -> (L.d_strln "failure reason 60"; raise Sil.JoinFail) - | Some(e_res, e_op), Lhs -> (e_res, exp_partial_join e' e_op) - | Some(e_res, e_op), Rhs -> (e_res, exp_partial_join e_op e') in - + | None, _ + -> L.d_strln "failure reason 60" ; raise Sil.JoinFail + | Some (e_res, e_op), Lhs + -> (e_res, exp_partial_join e' e_op) + | Some (e_res, e_op), Rhs + -> (e_res, exp_partial_join e_op e') + in let join_list_and_non side root' hlseg e opposite = match hlseg with - | Sil.Hlseg (_, hpara, root, next, shared) -> - let next' = do_side side exp_partial_join next opposite in + | Sil.Hlseg (_, hpara, root, next, shared) + -> let next' = do_side side exp_partial_join next opposite in let shared' = Rename.lookup_list side shared in - CheckJoin.add side root next; - Sil.Hlseg (Sil.Lseg_PE, hpara, root', next', shared') - - | Sil.Hdllseg (_, hpara, iF, oB, oF, iB, shared) - when Exp.equal iF e -> - let oF' = do_side side exp_partial_join oF opposite in + CheckJoin.add side root next ; Sil.Hlseg (Sil.Lseg_PE, hpara, root', next', shared') + | Sil.Hdllseg (_, hpara, iF, oB, oF, iB, shared) when Exp.equal iF e + -> let oF' = do_side side exp_partial_join oF opposite in let shared' = Rename.lookup_list side shared in let oB', iB' = lookup_and_expand side oB iB in (* let oB' = Rename.lookup side oB in let iB' = Rename.lookup side iB in *) - CheckJoin.add side iF oF; - CheckJoin.add side oB iB; + CheckJoin.add side iF oF ; + CheckJoin.add side oB iB ; Sil.Hdllseg (Sil.Lseg_PE, hpara, root', oB', oF', iB', shared') - - | Sil.Hdllseg (_, hpara, iF, oB, oF, iB, shared) - when Exp.equal iB e -> - let oB' = do_side side exp_partial_join oB opposite in + | Sil.Hdllseg (_, hpara, iF, oB, oF, iB, shared) when Exp.equal iB e + -> let oB' = do_side side exp_partial_join oB opposite in let shared' = Rename.lookup_list side shared in let oF', iF' = lookup_and_expand side oF iF in (* let oF' = Rename.lookup side oF in let iF' = Rename.lookup side iF in *) - CheckJoin.add side iF oF; - CheckJoin.add side oB iB; + CheckJoin.add side iF oF ; + CheckJoin.add side oB iB ; Sil.Hdllseg (Sil.Lseg_PE, hpara, iF', oB', oF', root', shared') - - | _ -> assert false in - + | _ + -> assert false + in let update_list side lseg root' = match lseg with - | Sil.Hlseg (k, hpara, _, next, shared) -> - let next' = Rename.lookup side next - and shared' = Rename.lookup_list_todo side shared in + | Sil.Hlseg (k, hpara, _, next, shared) + -> let next' = Rename.lookup side next and shared' = Rename.lookup_list_todo side shared in Sil.Hlseg (k, hpara, root', next', shared') - | _ -> assert false in - + | _ + -> assert false + in let update_dllseg side dllseg iF iB = match dllseg with - | Sil.Hdllseg (k, hpara, _, oB, oF, _, shared) -> - let oB' = Rename.lookup side oB + | Sil.Hdllseg (k, hpara, _, oB, oF, _, shared) + -> let oB' = Rename.lookup side oB and oF' = Rename.lookup side oF and shared' = Rename.lookup_list_todo side shared in Sil.Hdllseg (k, hpara, iF, oB', oF', iB, shared') - | _ -> assert false in - + | _ + -> assert false + in (* Drop the part of 'other' sigma corresponding to 'target' sigma if possible. 'side' describes that target is Lhs or Rhs. 'todo' describes the start point. *) - let cut_sigma side todo (target: Prop.sigma) (other: Prop.sigma) = - let list_is_empty l = if l <> [] then (L.d_strln "failure reason 61"; raise Sil.JoinFail) in + let list_is_empty l = if l <> [] then ( L.d_strln "failure reason 61" ; raise Sil.JoinFail ) in let x = Todo.take () in - Todo.push todo; + Todo.push todo ; let res = match side with - | Lhs -> - let res, target', other' = sigma_partial_join' tenv mode [] target other in - list_is_empty target'; - sigma_renaming_check_lhs target res; - other' - | Rhs -> - let res, other', target' = sigma_partial_join' tenv mode [] other target in - list_is_empty target'; - sigma_renaming_check_rhs target res; - other' in - Todo.set x; - res in - + | Lhs + -> let res, target', other' = sigma_partial_join' tenv mode [] target other in + list_is_empty target' ; sigma_renaming_check_lhs target res ; other' + | Rhs + -> let res, other', target' = sigma_partial_join' tenv mode [] other target in + list_is_empty target' ; sigma_renaming_check_rhs target res ; other' + in + Todo.set x ; res + in let cut_lseg side todo lseg sigma = match lseg with - | Sil.Hlseg (_, hpara, root, next, shared) -> - let _, sigma_lseg = Sil.hpara_instantiate hpara root next shared in + | Sil.Hlseg (_, hpara, root, next, shared) + -> let _, sigma_lseg = Sil.hpara_instantiate hpara root next shared in cut_sigma side todo sigma_lseg sigma - | _ -> assert false in - + | _ + -> assert false + in let cut_dllseg side todo root lseg sigma = match lseg with - | Sil.Hdllseg (_, hpara, _, oB, oF, _, shared) -> - let _, sigma_dllseg = Sil.hpara_dll_instantiate hpara root oB oF shared in + | Sil.Hdllseg (_, hpara, _, oB, oF, _, shared) + -> let _, sigma_dllseg = Sil.hpara_dll_instantiate hpara root oB oF shared in cut_sigma side todo sigma_dllseg sigma - | _ -> assert false in - + | _ + -> assert false + in try let todo_curr = Todo.pop () in let e1, e2, e = todo_curr in - if Config.trace_join then begin - L.d_strln ".... sigma_partial_join' ...."; - L.d_str "TODO: "; Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; L.d_str ","; Sil.d_exp e; L.d_ln (); - L.d_strln "SIGMA1 ="; Prop.d_sigma sigma1_in; L.d_ln (); - L.d_strln "SIGMA2 ="; Prop.d_sigma sigma2_in; L.d_ln (); - L.d_ln () - end; + if Config.trace_join then ( + L.d_strln ".... sigma_partial_join' ...." ; + L.d_str "TODO: " ; + Sil.d_exp e1 ; + L.d_str "," ; + Sil.d_exp e2 ; + L.d_str "," ; + Sil.d_exp e ; + L.d_ln () ; + L.d_strln "SIGMA1 =" ; + Prop.d_sigma sigma1_in ; + L.d_ln () ; + L.d_strln "SIGMA2 =" ; + Prop.d_sigma sigma2_in ; + L.d_ln () ; + L.d_ln () ) ; let hpred_opt1, sigma1 = find_hpred_by_address tenv e1 sigma1_in in let hpred_opt2, sigma2 = find_hpred_by_address tenv e2 sigma2_in in - match hpred_opt1, hpred_opt2 with - | None, None -> - sigma_partial_join' tenv mode sigma_acc sigma1 sigma2 - + match (hpred_opt1, hpred_opt2) with + | None, None + -> sigma_partial_join' tenv mode sigma_acc sigma1 sigma2 | Some (Sil.Hlseg (k, _, _, _, _) as lseg), None - | Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg), None -> - if (not Config.nelseg) || (Sil.equal_lseg_kind k Sil.Lseg_PE) then + | Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg), None + -> if not Config.nelseg || Sil.equal_lseg_kind k Sil.Lseg_PE then let sigma_acc' = join_list_and_non Lhs e lseg e1 e2 :: sigma_acc in sigma_partial_join' tenv mode sigma_acc' sigma1 sigma2 - else - (L.d_strln "failure reason 62"; raise Sil.JoinFail) - + else ( L.d_strln "failure reason 62" ; raise Sil.JoinFail ) | None, Some (Sil.Hlseg (k, _, _, _, _) as lseg) - | None, Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg) -> - if (not Config.nelseg) || (Sil.equal_lseg_kind k Sil.Lseg_PE) then + | None, Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg) + -> if not Config.nelseg || Sil.equal_lseg_kind k Sil.Lseg_PE then let sigma_acc' = join_list_and_non Rhs e lseg e2 e1 :: sigma_acc in sigma_partial_join' tenv mode sigma_acc' sigma1 sigma2 - else - (L.d_strln "failure reason 63"; raise Sil.JoinFail) - - | None, _ | _, None -> (L.d_strln "failure reason 64"; raise Sil.JoinFail) - - | Some (hpred1), Some (hpred2) when same_pred hpred1 hpred2 -> - let hpred_res1 = hpred_partial_join tenv mode todo_curr hpred1 hpred2 in - sigma_partial_join' tenv mode (hpred_res1:: sigma_acc) sigma1 sigma2 - - | Some (Sil.Hlseg _ as lseg), Some (hpred2) -> - let sigma2' = cut_lseg Lhs todo_curr lseg (hpred2:: sigma2) in + else ( L.d_strln "failure reason 63" ; raise Sil.JoinFail ) + | None, _ | _, None + -> L.d_strln "failure reason 64" ; raise Sil.JoinFail + | Some hpred1, Some hpred2 when same_pred hpred1 hpred2 + -> let hpred_res1 = hpred_partial_join tenv mode todo_curr hpred1 hpred2 in + sigma_partial_join' tenv mode (hpred_res1 :: sigma_acc) sigma1 sigma2 + | Some (Sil.Hlseg _ as lseg), Some hpred2 + -> let sigma2' = cut_lseg Lhs todo_curr lseg (hpred2 :: sigma2) in let sigma_acc' = update_list Lhs lseg e :: sigma_acc in sigma_partial_join' tenv mode sigma_acc' sigma1 sigma2' - - | Some (hpred1), Some (Sil.Hlseg _ as lseg) -> - let sigma1' = cut_lseg Rhs todo_curr lseg (hpred1:: sigma1) in + | Some hpred1, Some (Sil.Hlseg _ as lseg) + -> let sigma1' = cut_lseg Rhs todo_curr lseg (hpred1 :: sigma1) in let sigma_acc' = update_list Rhs lseg e :: sigma_acc in sigma_partial_join' tenv mode sigma_acc' sigma1' sigma2 - - | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some (hpred2) - when Exp.equal e1 iF1 -> - let iB_res = exp_partial_join iB1 e2 in - let sigma2' = cut_dllseg Lhs todo_curr iF1 dllseg (hpred2:: sigma2) in + | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some hpred2 when Exp.equal e1 iF1 + -> let iB_res = exp_partial_join iB1 e2 in + let sigma2' = cut_dllseg Lhs todo_curr iF1 dllseg (hpred2 :: sigma2) in let sigma_acc' = update_dllseg Lhs dllseg e iB_res :: sigma_acc in - CheckJoin.add Lhs iF1 iB1; (* add equality iF1=iB1 *) + CheckJoin.add Lhs iF1 iB1 ; + (* add equality iF1=iB1 *) sigma_partial_join' tenv mode sigma_acc' sigma1 sigma2' - - | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some (hpred2) - (* when Exp.equal e1 iB1 *) -> - let iF_res = exp_partial_join iF1 e2 in - let sigma2' = cut_dllseg Lhs todo_curr iB1 dllseg (hpred2:: sigma2) in + | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some hpred2 + (* when Exp.equal e1 iB1 *) + -> let iF_res = exp_partial_join iF1 e2 in + let sigma2' = cut_dllseg Lhs todo_curr iB1 dllseg (hpred2 :: sigma2) in let sigma_acc' = update_dllseg Lhs dllseg iF_res e :: sigma_acc in - CheckJoin.add Lhs iF1 iB1; (* add equality iF1=iB1 *) + CheckJoin.add Lhs iF1 iB1 ; + (* add equality iF1=iB1 *) sigma_partial_join' tenv mode sigma_acc' sigma1 sigma2' - - | Some (hpred1), Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) - when Exp.equal e2 iF2 -> - let iB_res = exp_partial_join e1 iB2 in - let sigma1' = cut_dllseg Rhs todo_curr iF2 dllseg (hpred1:: sigma1) in + | Some hpred1, Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) when Exp.equal e2 iF2 + -> let iB_res = exp_partial_join e1 iB2 in + let sigma1' = cut_dllseg Rhs todo_curr iF2 dllseg (hpred1 :: sigma1) in let sigma_acc' = update_dllseg Rhs dllseg e iB_res :: sigma_acc in - CheckJoin.add Rhs iF2 iB2; (* add equality iF2=iB2 *) + CheckJoin.add Rhs iF2 iB2 ; + (* add equality iF2=iB2 *) sigma_partial_join' tenv mode sigma_acc' sigma1' sigma2 - - | Some (hpred1), Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) -> - let iF_res = exp_partial_join e1 iF2 in - let sigma1' = cut_dllseg Rhs todo_curr iB2 dllseg (hpred1:: sigma1) in + | Some hpred1, Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) + -> let iF_res = exp_partial_join e1 iF2 in + let sigma1' = cut_dllseg Rhs todo_curr iB2 dllseg (hpred1 :: sigma1) in let sigma_acc' = update_dllseg Rhs dllseg iF_res e :: sigma_acc in - CheckJoin.add Rhs iF2 iB2; (* add equality iF2=iB2 *) + CheckJoin.add Rhs iF2 iB2 ; + (* add equality iF2=iB2 *) sigma_partial_join' tenv mode sigma_acc' sigma1' sigma2 - - | Some (Sil.Hpointsto _), Some (Sil.Hpointsto _) -> - assert false (* Should be handled by a guarded case *) - + | Some Sil.Hpointsto _, Some Sil.Hpointsto _ + -> assert false + (* Should be handled by a guarded case *) with Todo.Empty -> - match sigma1_in, sigma2_in with - | _:: _, _:: _ -> L.d_strln "todo is empty, but the sigmas are not"; raise Sil.JoinFail - | _ -> sigma_acc, sigma1_in, sigma2_in + match (sigma1_in, sigma2_in) with + | _ :: _, _ :: _ + -> L.d_strln "todo is empty, but the sigmas are not" ; raise Sil.JoinFail + | _ + -> (sigma_acc, sigma1_in, sigma2_in) let sigma_partial_join tenv mode (sigma1: Prop.sigma) (sigma2: Prop.sigma) - : (Prop.sigma * Prop.sigma * Prop.sigma) = - CheckJoin.init mode sigma1 sigma2; + : Prop.sigma * Prop.sigma * Prop.sigma = + CheckJoin.init mode sigma1 sigma2 ; let lost_little = CheckJoin.lost_little in let s1, s2, s3 = sigma_partial_join' tenv mode [] sigma1 sigma2 in try - if Rename.check lost_little then - (CheckJoin.final (); (s1, s2, s3)) - else begin - L.d_strln "failed Rename.check"; - CheckJoin.final (); - raise Sil.JoinFail - end - with - | exn -> (CheckJoin.final (); raise exn) + if Rename.check lost_little then ( CheckJoin.final () ; (s1, s2, s3) ) + else ( L.d_strln "failed Rename.check" ; CheckJoin.final () ; raise Sil.JoinFail ) + with exn -> CheckJoin.final () ; raise exn -let rec sigma_partial_meet' tenv (sigma_acc: Prop.sigma) (sigma1_in: Prop.sigma) (sigma2_in: Prop.sigma) - : Prop.sigma = +let rec sigma_partial_meet' tenv (sigma_acc: Prop.sigma) (sigma1_in: Prop.sigma) + (sigma2_in: Prop.sigma) : Prop.sigma = try let todo_curr = Todo.pop () in let e1, e2, e = todo_curr in - L.d_strln ".... sigma_partial_meet' ...."; - L.d_str "TODO: "; Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; L.d_str ","; Sil.d_exp e; L.d_ln (); - L.d_str "PROP1="; Prop.d_sigma sigma1_in; L.d_ln (); - L.d_str "PROP2="; Prop.d_sigma sigma2_in; L.d_ln (); - L.d_ln (); + L.d_strln ".... sigma_partial_meet' ...." ; + L.d_str "TODO: " ; + Sil.d_exp e1 ; + L.d_str "," ; + Sil.d_exp e2 ; + L.d_str "," ; + Sil.d_exp e ; + L.d_ln () ; + L.d_str "PROP1=" ; + Prop.d_sigma sigma1_in ; + L.d_ln () ; + L.d_str "PROP2=" ; + Prop.d_sigma sigma2_in ; + L.d_ln () ; + L.d_ln () ; let hpred_opt1, sigma1 = find_hpred_by_address tenv e1 sigma1_in in let hpred_opt2, sigma2 = find_hpred_by_address tenv e2 sigma2_in in - match hpred_opt1, hpred_opt2 with - | None, None -> - sigma_partial_meet' tenv sigma_acc sigma1 sigma2 - - | Some hpred, None -> - let hpred' = hpred_construct_fresh Lhs hpred in + match (hpred_opt1, hpred_opt2) with + | None, None + -> sigma_partial_meet' tenv sigma_acc sigma1 sigma2 + | Some hpred, None + -> let hpred' = hpred_construct_fresh Lhs hpred in let sigma_acc' = hpred' :: sigma_acc in sigma_partial_meet' tenv sigma_acc' sigma1 sigma2 - - | None, Some hpred -> - let hpred' = hpred_construct_fresh Rhs hpred in + | None, Some hpred + -> let hpred' = hpred_construct_fresh Rhs hpred in let sigma_acc' = hpred' :: sigma_acc in sigma_partial_meet' tenv sigma_acc' sigma1 sigma2 - - | Some (hpred1), Some (hpred2) when same_pred hpred1 hpred2 -> - let hpred' = hpred_partial_meet tenv todo_curr hpred1 hpred2 in - sigma_partial_meet' tenv (hpred':: sigma_acc) sigma1 sigma2 - - | Some _, Some _ -> - (L.d_strln "failure reason 65"; raise Sil.JoinFail) - + | Some hpred1, Some hpred2 when same_pred hpred1 hpred2 + -> let hpred' = hpred_partial_meet tenv todo_curr hpred1 hpred2 in + sigma_partial_meet' tenv (hpred' :: sigma_acc) sigma1 sigma2 + | Some _, Some _ + -> L.d_strln "failure reason 65" ; raise Sil.JoinFail with Todo.Empty -> - match sigma1_in, sigma2_in with - | [], [] -> sigma_acc - | _, _ -> L.d_strln "todo is empty, but the sigmas are not"; raise Sil.JoinFail + match (sigma1_in, sigma2_in) with + | [], [] + -> sigma_acc + | _, _ + -> L.d_strln "todo is empty, but the sigmas are not" ; raise Sil.JoinFail let sigma_partial_meet tenv (sigma1: Prop.sigma) (sigma2: Prop.sigma) : Prop.sigma = sigma_partial_meet' tenv [] sigma1 sigma2 @@ -1579,179 +1620,179 @@ let sigma_partial_meet tenv (sigma1: Prop.sigma) (sigma2: Prop.sigma) : Prop.sig let widening_top = (* nearly max_int but not so close to overflow *) IntLit.of_int64 Int64.max_value -- IntLit.of_int 1000 + let widening_bottom = (* nearly min_int but not so close to underflow *) IntLit.of_int64 Int64.min_value ++ IntLit.of_int 1000 (** {2 Join and Meet for Pi} *) -let pi_partial_join tenv mode - (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) - (pi1: Prop.pi) (pi2: Prop.pi) : Prop.pi - = +let pi_partial_join tenv mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) (pi1: Prop.pi) + (pi2: Prop.pi) : Prop.pi = let get_array_len prop = (* find some array length in the prop, to be used as heuritic for upper bound in widening *) let len_list = ref [] in let do_hpred = function - | Sil.Hpointsto (_, Sil.Earray (Exp.Const (Const.Cint n), _, _), _) -> - (if IntLit.geq n IntLit.one then len_list := n :: !len_list) - | _ -> () in - List.iter ~f:do_hpred prop.Prop.sigma; - !len_list in + | Sil.Hpointsto (_, Sil.Earray (Exp.Const Const.Cint n, _, _), _) + -> if IntLit.geq n IntLit.one then len_list := n :: !len_list + | _ + -> () + in + List.iter ~f:do_hpred prop.Prop.sigma ; !len_list + in let bounds = let bounds1 = get_array_len ep1 in let bounds2 = get_array_len ep2 in - let bounds_sorted = List.sort ~cmp:IntLit.compare_value (bounds1@bounds2) in - List.rev (List.remove_consecutive_duplicates ~equal:IntLit.eq bounds_sorted) in + let bounds_sorted = List.sort ~cmp:IntLit.compare_value (bounds1 @ bounds2) in + List.rev (List.remove_consecutive_duplicates ~equal:IntLit.eq bounds_sorted) + in let widening_atom a = (* widening heuristic for upper bound: take the length of some array, -2 and -1 *) - match Prop.atom_exp_le_const a, bounds with - | Some (e, n), len :: _ -> - let first_try = IntLit.sub len IntLit.one in + match (Prop.atom_exp_le_const a, bounds) with + | Some (e, n), len :: _ + -> let first_try = IntLit.sub len IntLit.one in let second_try = IntLit.sub len IntLit.two in let bound = - if IntLit.leq n first_try then - if IntLit.leq n second_try then second_try else first_try - else widening_top in - let a' = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, e, Exp.int bound)) in + if IntLit.leq n first_try then if IntLit.leq n second_try then second_try else first_try + else widening_top + in + let a' = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, e, Exp.int bound)) in Some a' - | Some (e, _), [] -> - let bound = widening_top in - let a' = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, e, Exp.int bound)) in + | Some (e, _), [] + -> let bound = widening_top in + let a' = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, e, Exp.int bound)) in Some a' | _ -> - begin - match Prop.atom_const_lt_exp a with - | None -> None - | Some (n, e) -> - let bound = - if IntLit.leq IntLit.minus_one n then IntLit.minus_one else widening_bottom in - let a' = Prop.mk_inequality tenv (Exp.BinOp(Binop.Lt, Exp.int bound, e)) in - Some a' - end in + match Prop.atom_const_lt_exp a with + | None + -> None + | Some (n, e) + -> let bound = + if IntLit.leq IntLit.minus_one n then IntLit.minus_one else widening_bottom + in + let a' = Prop.mk_inequality tenv (Exp.BinOp (Binop.Lt, Exp.int bound, e)) in + Some a' + in let is_stronger_le e n a = match Prop.atom_exp_le_const a with - | None -> false - | Some (e', n') -> Exp.equal e e' && IntLit.lt n' n in + | None + -> false + | Some (e', n') + -> Exp.equal e e' && IntLit.lt n' n + in let is_stronger_lt n e a = match Prop.atom_const_lt_exp a with - | None -> false - | Some (n', e') -> Exp.equal e e' && IntLit.lt n n' in + | None + -> false + | Some (n', e') + -> Exp.equal e e' && IntLit.lt n n' + in let join_atom_check_pre p a = (* check for atoms in pre mode: fail if the negation is implied by the other side *) let not_a = Prover.atom_negate tenv a in - if (Prover.check_atom tenv p not_a) then - (L.d_str "join_atom_check failed on "; Sil.d_atom a; L.d_ln (); raise Sil.JoinFail) in + if Prover.check_atom tenv p not_a then ( + L.d_str "join_atom_check failed on " ; Sil.d_atom a ; L.d_ln () ; raise Sil.JoinFail ) + in let join_atom_check_attribute p a = (* check for attribute: fail if the attribute is not in the other side *) - if not (Prover.check_atom tenv p a) then - (L.d_str "join_atom_check_attribute failed on "; - Sil.d_atom a; L.d_ln (); - raise Sil.JoinFail) in + if not (Prover.check_atom tenv p a) then ( + L.d_str "join_atom_check_attribute failed on " ; + Sil.d_atom a ; + L.d_ln () ; + raise Sil.JoinFail ) + in let join_atom side p_op pi_op a = (* try to find the atom corresponding to a on the other side, and check if it is implied *) match Rename.get_other_atoms tenv side a with - | None -> None - | Some (a_res, a_op) -> - if JoinState.equal_mode mode JoinState.Pre then join_atom_check_pre p_op a_op; - if Attribute.is_pred a then join_atom_check_attribute p_op a_op; + | None + -> None + | Some (a_res, a_op) + -> if JoinState.equal_mode mode JoinState.Pre then join_atom_check_pre p_op a_op ; + if Attribute.is_pred a then join_atom_check_attribute p_op a_op ; if not (Prover.check_atom tenv p_op a_op) then None - else begin + else match Prop.atom_exp_le_const a_op with - | None -> - begin - match Prop.atom_const_lt_exp a_op with - | None -> - Some a_res - | Some (n, e) -> - if List.exists ~f:(is_stronger_lt n e) pi_op - then (widening_atom a_res) - else Some a_res - end - | Some (e, n) -> - if List.exists ~f:(is_stronger_le e n) pi_op - then (widening_atom a_res) - else Some a_res - end in + | None -> ( + match Prop.atom_const_lt_exp a_op with + | None + -> Some a_res + | Some (n, e) + -> if List.exists ~f:(is_stronger_lt n e) pi_op then widening_atom a_res + else Some a_res ) + | Some (e, n) + -> if List.exists ~f:(is_stronger_le e n) pi_op then widening_atom a_res else Some a_res + in let handle_atom_with_widening len p_op pi_op atom_list a = (* find a join for the atom, if it fails apply widening heuristing and try again *) match join_atom len p_op pi_op a with - | None -> - (match widening_atom a with - | None -> atom_list - | Some a' -> - (match join_atom len p_op pi_op a' with - | None -> atom_list - | Some a' -> a' :: atom_list)) - | Some a' -> a' :: atom_list in - begin - if Config.trace_join then begin - L.d_str "pi1: "; Prop.d_pi pi1; L.d_ln (); - L.d_str "pi2: "; Prop.d_pi pi2; L.d_ln () - end; - let atom_list1 = - let p2 = Prop.normalize tenv ep2 in - List.fold ~f:(handle_atom_with_widening Lhs p2 pi2) ~init:[] pi1 in - if Config.trace_join then (L.d_str "atom_list1: "; Prop.d_pi atom_list1; L.d_ln ()); - let atom_list2 = - let p1 = Prop.normalize tenv ep1 in - List.fold ~f:(handle_atom_with_widening Rhs p1 pi1) ~init:[] pi2 in - if Config.trace_join then - (L.d_str "atom_list2: "; Prop.d_pi atom_list2; L.d_ln ()); - let atom_list_combined = IList.inter Sil.compare_atom atom_list1 atom_list2 in - if Config.trace_join then - (L.d_str "atom_list_combined: "; Prop.d_pi atom_list_combined; L.d_ln ()); - atom_list_combined - end - -let pi_partial_meet tenv (p: Prop.normal Prop.t) (ep1: 'a Prop.t) (ep2: 'b Prop.t) : Prop.normal Prop.t = + | None -> ( + match widening_atom a with + | None + -> atom_list + | Some a' -> + match join_atom len p_op pi_op a' with None -> atom_list | Some a' -> a' :: atom_list ) + | Some a' + -> a' :: atom_list + in + if Config.trace_join then ( + L.d_str "pi1: " ; Prop.d_pi pi1 ; L.d_ln () ; L.d_str "pi2: " ; Prop.d_pi pi2 ; L.d_ln () ) ; + let atom_list1 = + let p2 = Prop.normalize tenv ep2 in + List.fold ~f:(handle_atom_with_widening Lhs p2 pi2) ~init:[] pi1 + in + if Config.trace_join then ( L.d_str "atom_list1: " ; Prop.d_pi atom_list1 ; L.d_ln () ) ; + let atom_list2 = + let p1 = Prop.normalize tenv ep1 in + List.fold ~f:(handle_atom_with_widening Rhs p1 pi1) ~init:[] pi2 + in + if Config.trace_join then ( L.d_str "atom_list2: " ; Prop.d_pi atom_list2 ; L.d_ln () ) ; + let atom_list_combined = IList.inter Sil.compare_atom atom_list1 atom_list2 in + if Config.trace_join then ( + L.d_str "atom_list_combined: " ; Prop.d_pi atom_list_combined ; L.d_ln () ) ; + atom_list_combined + +let pi_partial_meet tenv (p: Prop.normal Prop.t) (ep1: 'a Prop.t) (ep2: 'b Prop.t) + : Prop.normal Prop.t = let sub1 = Rename.to_subst_emb Lhs in let sub2 = Rename.to_subst_emb Rhs in - let dom1 = Ident.idlist_to_idset (Sil.sub_domain sub1) in let dom2 = Ident.idlist_to_idset (Sil.sub_domain sub2) in - let handle_atom sub dom atom = let fav_list = Sil.fav_to_list (Sil.atom_fav atom) in if List.for_all ~f:(fun id -> Ident.IdentSet.mem id dom) fav_list then Sil.atom_sub (`Exp sub) atom - else (L.d_str "handle_atom failed on "; Sil.d_atom atom; L.d_ln (); raise Sil.JoinFail) in - let f1 p' atom = - Prop.prop_atom_and tenv p' (handle_atom sub1 dom1 atom) in - let f2 p' atom = - Prop.prop_atom_and tenv p' (handle_atom sub2 dom2 atom) in - + else ( L.d_str "handle_atom failed on " ; Sil.d_atom atom ; L.d_ln () ; raise Sil.JoinFail ) + in + let f1 p' atom = Prop.prop_atom_and tenv p' (handle_atom sub1 dom1 atom) in + let f2 p' atom = Prop.prop_atom_and tenv p' (handle_atom sub2 dom2 atom) in let pi1 = ep1.Prop.pi in let pi2 = ep2.Prop.pi in - let p_pi1 = List.fold ~f:f1 ~init:p pi1 in let p_pi2 = List.fold ~f:f2 ~init:p_pi1 pi2 in - if (Prover.check_inconsistency_base tenv p_pi2) - then (L.d_strln "check_inconsistency_base failed"; raise Sil.JoinFail) + if Prover.check_inconsistency_base tenv p_pi2 then ( + L.d_strln "check_inconsistency_base failed" ; raise Sil.JoinFail ) else p_pi2 (** {2 Join and Meet for Prop} *) let eprop_partial_meet tenv (ep1: 'a Prop.t) (ep2: 'b Prop.t) : 'c Prop.t = - SymOp.pay(); (* pay one symop *) + SymOp.pay () ; + (* pay one symop *) let sigma1 = ep1.Prop.sigma in let sigma2 = ep2.Prop.sigma in - let es1 = sigma_get_start_lexps_sort sigma1 in let es2 = sigma_get_start_lexps_sort sigma2 in let es = IList.merge_sorted_nodup Exp.compare [] es1 es2 in - let sub_check _ = let sub1 = ep1.Prop.sub in let sub2 = ep2.Prop.sub in let range1 = Sil.sub_range sub1 in let f e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in - Sil.equal_exp_subst sub1 sub2 && List.for_all ~f range1 in - - if not (sub_check ()) then - (L.d_strln "sub_check() failed"; raise Sil.JoinFail) - else begin + Sil.equal_exp_subst sub1 sub2 && List.for_all ~f range1 + in + if not (sub_check ()) then ( L.d_strln "sub_check() failed" ; raise Sil.JoinFail ) + else let todos = List.map ~f:(fun x -> (x, x, x)) es in - List.iter ~f:Todo.push todos; + List.iter ~f:Todo.push todos ; let sigma_new = sigma_partial_meet tenv sigma1 sigma2 in let ep = Prop.set ep1 ~sigma:sigma_new in let ep' = Prop.set ep ~pi:[] in @@ -1759,172 +1800,197 @@ let eprop_partial_meet tenv (ep1: 'a Prop.t) (ep2: 'b Prop.t) : 'c Prop.t = let p'' = pi_partial_meet tenv p' ep1 ep2 in let res = Prop.prop_rename_primed_footprint_vars tenv p'' in res - end let prop_partial_meet tenv p1 p2 = - Rename.init (); FreshVarExp.init (); Todo.init (); + Rename.init () ; + FreshVarExp.init () ; + Todo.init () ; try let res = eprop_partial_meet tenv p1 p2 in - Rename.final (); FreshVarExp.final (); Todo.final (); - Some res + Rename.final () ; FreshVarExp.final () ; Todo.final () ; Some res with exn -> - begin - Rename.final (); FreshVarExp.final (); Todo.final (); - match exn with - | Sil.JoinFail -> None - | _ -> raise exn - end - -let eprop_partial_join' tenv mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) : Prop.normal Prop.t = - SymOp.pay(); (* pay one symop *) + Rename.final () ; + FreshVarExp.final () ; + Todo.final () ; + match exn with Sil.JoinFail -> None | _ -> raise exn + +let eprop_partial_join' tenv mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) + : Prop.normal Prop.t = + SymOp.pay () ; + (* pay one symop *) let sigma1 = ep1.Prop.sigma in let sigma2 = ep2.Prop.sigma in let es1 = sigma_get_start_lexps_sort sigma1 in let es2 = sigma_get_start_lexps_sort sigma2 in - let simple_check = Int.equal (List.length es1) (List.length es2) in let rec expensive_check es1' es2' = match (es1', es2') with - | [], [] -> true - | [], _:: _ | _:: _, [] -> false - | e1:: es1'', e2:: es2'' -> - Exp.equal e1 e2 && expensive_check es1'' es2'' in + | [], [] + -> true + | [], _ :: _ | _ :: _, [] + -> false + | e1 :: es1'', e2 :: es2'' + -> Exp.equal e1 e2 && expensive_check es1'' es2'' + in let sub_common, eqs_from_sub1, eqs_from_sub2 = let sub1 = ep1.Prop.sub in let sub2 = ep2.Prop.sub in let sub_common, sub1_only, sub2_only = Sil.sub_symmetric_difference sub1 sub2 in let sub_common_normal, sub_common_other = let f e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in - Sil.sub_range_partition f sub_common in + Sil.sub_range_partition f sub_common + in let eqs1, eqs2 = let sub_to_eqs sub = - List.map ~f:(fun (id, e) -> Sil.Aeq (Exp.Var id, e)) (Sil.sub_to_list sub) in + List.map ~f:(fun (id, e) -> Sil.Aeq (Exp.Var id, e)) (Sil.sub_to_list sub) + in let eqs1 = sub_to_eqs sub1_only @ sub_to_eqs sub_common_other in let eqs2 = sub_to_eqs sub2_only in - (eqs1, eqs2) in - (sub_common_normal, eqs1, eqs2) in - - if not (simple_check && expensive_check es1 es2) then - begin - if not simple_check then L.d_strln "simple_check failed" - else L.d_strln "expensive_check failed"; - raise Sil.JoinFail - end; + (eqs1, eqs2) + in + (sub_common_normal, eqs1, eqs2) + in + if not (simple_check && expensive_check es1 es2) then ( + if not simple_check then L.d_strln "simple_check failed" + else L.d_strln "expensive_check failed" ; + raise Sil.JoinFail ) ; let todos = List.map ~f:(fun x -> (x, x, x)) es1 in - List.iter ~f:Todo.push todos; + List.iter ~f:Todo.push todos ; match sigma_partial_join tenv mode sigma1 sigma2 with - | sigma_new, [], [] -> - L.d_strln "sigma_partial_join succeeded"; + | sigma_new, [], [] + -> L.d_strln "sigma_partial_join succeeded" ; let ep_sub = let ep = Prop.set ep1 ~pi:[] in - Prop.set ep ~sub:sub_common in - let p_sub_sigma = - Prop.normalize tenv (Prop.set ep_sub ~sigma:sigma_new) in + Prop.set ep ~sub:sub_common + in + let p_sub_sigma = Prop.normalize tenv (Prop.set ep_sub ~sigma:sigma_new) in let p_sub_sigma_pi = let pi1 = ep1.Prop.pi @ eqs_from_sub1 in let pi2 = ep2.Prop.pi @ eqs_from_sub2 in let pi' = pi_partial_join tenv mode ep1 ep2 pi1 pi2 in - L.d_strln "pi_partial_join succeeded"; + L.d_strln "pi_partial_join succeeded" ; let pi_from_fresh_vars = FreshVarExp.get_induced_pi tenv () in let pi_all = pi' @ pi_from_fresh_vars in - List.fold ~f:(Prop.prop_atom_and tenv) ~init:p_sub_sigma pi_all in + List.fold ~f:(Prop.prop_atom_and tenv) ~init:p_sub_sigma pi_all + in p_sub_sigma_pi - | _ -> - L.d_strln "leftovers not empty"; raise Sil.JoinFail + | _ + -> L.d_strln "leftovers not empty" ; raise Sil.JoinFail -let footprint_partial_join' tenv (p1: Prop.normal Prop.t) (p2: Prop.normal Prop.t) : Prop.normal Prop.t * Prop.normal Prop.t = - if not !Config.footprint then p1, p2 - else begin +let footprint_partial_join' tenv (p1: Prop.normal Prop.t) (p2: Prop.normal Prop.t) + : Prop.normal Prop.t * Prop.normal Prop.t = + if not !Config.footprint then (p1, p2) + else let fp1 = Prop.extract_footprint p1 in let fp2 = Prop.extract_footprint p2 in let efp = eprop_partial_join' tenv JoinState.Pre fp1 fp2 in let pi_fp = let pi_fp0 = Prop.get_pure efp in let f a = Sil.fav_for_all (Sil.atom_fav a) Ident.is_footprint in - List.filter ~f pi_fp0 in + List.filter ~f pi_fp0 + in let sigma_fp = let sigma_fp0 = efp.Prop.sigma in let f a = Sil.fav_exists (Sil.hpred_fav a) (fun a -> not (Ident.is_footprint a)) in - if List.exists ~f sigma_fp0 then (L.d_strln "failure reason 66"; raise Sil.JoinFail); - sigma_fp0 in + if List.exists ~f sigma_fp0 then ( L.d_strln "failure reason 66" ; raise Sil.JoinFail ) ; + sigma_fp0 + in let ep1' = Prop.set p1 ~pi_fp ~sigma_fp in let ep2' = Prop.set p2 ~pi_fp ~sigma_fp in - Prop.normalize tenv ep1', Prop.normalize tenv ep2' - end + (Prop.normalize tenv ep1', Prop.normalize tenv ep2') let prop_partial_join pname tenv mode p1 p2 = let res_by_implication_only = if !Config.footprint then None else if Prover.check_implication pname tenv p1 (Prop.expose p2) then Some p2 else if Prover.check_implication pname tenv p2 (Prop.expose p1) then Some p1 - else None in + else None + in match res_by_implication_only with - | None -> - begin - (if !Config.footprint then JoinState.set_footprint true); - Rename.init (); FreshVarExp.init (); Todo.init (); - try - let p1', p2' = footprint_partial_join' tenv p1 p2 in - let rename_footprint = Rename.reset () in - Todo.reset rename_footprint; - let res = Some (eprop_partial_join' tenv mode (Prop.expose p1') (Prop.expose p2')) in - (if !Config.footprint then JoinState.set_footprint false); - Rename.final (); FreshVarExp.final (); Todo.final (); - res - with exn -> - begin - Rename.final (); FreshVarExp.final (); Todo.final (); - (if !Config.footprint then JoinState.set_footprint false); - (match exn with Sil.JoinFail -> None | _ -> raise exn) - end - end - | Some _ -> res_by_implication_only - -let eprop_partial_join tenv mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) : Prop.normal Prop.t = - Rename.init (); FreshVarExp.init (); Todo.init (); + | None + -> ( + if !Config.footprint then JoinState.set_footprint true ; + Rename.init () ; + FreshVarExp.init () ; + Todo.init () ; + try + let p1', p2' = footprint_partial_join' tenv p1 p2 in + let rename_footprint = Rename.reset () in + Todo.reset rename_footprint ; + let res = Some (eprop_partial_join' tenv mode (Prop.expose p1') (Prop.expose p2')) in + if !Config.footprint then JoinState.set_footprint false ; + Rename.final () ; + FreshVarExp.final () ; + Todo.final () ; + res + with exn -> + Rename.final () ; + FreshVarExp.final () ; + Todo.final () ; + if !Config.footprint then JoinState.set_footprint false ; + match exn with Sil.JoinFail -> None | _ -> raise exn ) + | Some _ + -> res_by_implication_only + +let eprop_partial_join tenv mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) + : Prop.normal Prop.t = + Rename.init () ; + FreshVarExp.init () ; + Todo.init () ; try let res = eprop_partial_join' tenv mode ep1 ep2 in - Rename.final (); FreshVarExp.final (); Todo.final (); - res - with exn -> (Rename.final (); FreshVarExp.final (); Todo.final (); raise exn) + Rename.final () ; FreshVarExp.final () ; Todo.final () ; res + with exn -> Rename.final () ; FreshVarExp.final () ; Todo.final () ; raise exn (** {2 Join and Meet for Propset} *) let list_reduce name dd f list = let rec element_list_reduce acc (x, p1) = function - | [] -> ((x, p1), List.rev acc) - | (y, p2):: ys -> begin - L.d_strln ("COMBINE[" ^ name ^ "] ...."); - L.d_str "ENTRY1: "; L.d_ln (); dd x; L.d_ln (); - L.d_str "ENTRY2: "; L.d_ln (); dd y; L.d_ln (); - L.d_ln (); + | [] + -> ((x, p1), List.rev acc) + | (y, p2) :: ys + -> L.d_strln ("COMBINE[" ^ name ^ "] ....") ; + L.d_str "ENTRY1: " ; + L.d_ln () ; + dd x ; + L.d_ln () ; + L.d_str "ENTRY2: " ; + L.d_ln () ; + dd y ; + L.d_ln () ; + L.d_ln () ; match f x y with - | None -> - L.d_strln_color Red (".... COMBINE[" ^ name ^ "] FAILED ..."); - element_list_reduce ((y, p2):: acc) (x, p1) ys - | Some x' -> - L.d_strln_color Green (".... COMBINE[" ^ name ^ "] SUCCEEDED ...."); - L.d_strln "RESULT:"; dd x'; L.d_ln (); + | None + -> L.d_strln_color Red (".... COMBINE[" ^ name ^ "] FAILED ...") ; + element_list_reduce ((y, p2) :: acc) (x, p1) ys + | Some x' + -> L.d_strln_color Green (".... COMBINE[" ^ name ^ "] SUCCEEDED ....") ; + L.d_strln "RESULT:" ; + dd x' ; + L.d_ln () ; element_list_reduce acc (x', p1) ys - end in + in let rec reduce acc = function - | [] -> List.rev acc - | x:: xs -> - let (x', xs') = element_list_reduce [] x xs in - reduce (x':: acc) xs' in + | [] + -> List.rev acc + | x :: xs + -> let x', xs' = element_list_reduce [] x xs in + reduce (x' :: acc) xs' + in reduce [] list let pathset_collapse_impl pname tenv pset = let f x y = if Prover.check_implication pname tenv x (Prop.expose y) then Some y else if Prover.check_implication pname tenv y (Prop.expose x) then Some x - else None in + else None + in let plist = Paths.PathSet.elements pset in let plist' = list_reduce "JOIN_IMPL" Prop.d_prop f plist in Paths.PathSet.from_renamed_list plist' let jprop_partial_join tenv mode jp1 jp2 = - let p1, p2 = Prop.expose (Specs.Jprop.to_prop jp1), Prop.expose (Specs.Jprop.to_prop jp2) in + let p1, p2 = (Prop.expose (Specs.Jprop.to_prop jp1), Prop.expose (Specs.Jprop.to_prop jp2)) in try let p = eprop_partial_join tenv mode p1 p2 in let p_renamed = Prop.prop_rename_primed_footprint_vars tenv p in @@ -1935,17 +2001,17 @@ let jplist_collapse tenv mode jplist = let f = jprop_partial_join tenv mode in list_reduce "JOIN" Specs.Jprop.d_shallow f jplist - (** Add identifiers to a list of jprops *) let jprop_list_add_ids jplist = let seq_number = ref 0 in let rec do_jprop = function - | Specs.Jprop.Prop (_, p) -> incr seq_number; Specs.Jprop.Prop (!seq_number, p) - | Specs.Jprop.Joined (_, p, jp1, jp2) -> - let jp1' = do_jprop jp1 in + | Specs.Jprop.Prop (_, p) + -> incr seq_number ; Specs.Jprop.Prop (!seq_number, p) + | Specs.Jprop.Joined (_, p, jp1, jp2) + -> let jp1' = do_jprop jp1 in let jp2' = do_jprop jp2 in - incr seq_number; - Specs.Jprop.Joined (!seq_number, p, jp1', jp2') in + incr seq_number ; Specs.Jprop.Joined (!seq_number, p, jp1', jp2') + in List.map ~f:(fun (p, path) -> (do_jprop p, path)) jplist let proplist_collapse tenv mode plist = @@ -1965,43 +2031,57 @@ let pathset_collapse tenv pset = let join_time = ref 0.0 -let pathset_join - pname tenv (pset1: Paths.PathSet.t) (pset2: Paths.PathSet.t) - : Paths.PathSet.t * Paths.PathSet.t = +let pathset_join pname tenv (pset1: Paths.PathSet.t) (pset2: Paths.PathSet.t) + : Paths.PathSet.t * Paths.PathSet.t = let mode = JoinState.Post in let initial_time = Unix.gettimeofday () in let pset_to_plist pset = let f_list p pa acc = (p, pa) :: acc in - Paths.PathSet.fold f_list pset [] in + Paths.PathSet.fold f_list pset [] + in let ppalist1 = pset_to_plist pset1 in let ppalist2 = pset_to_plist pset2 in - let rec join_proppath_plist ppalist2_acc ((p2, pa2) as ppa2) = function - | [] -> (ppa2, List.rev ppalist2_acc) - | ((p2', pa2') as ppa2') :: ppalist2_rest -> begin - L.d_strln ".... JOIN ...."; - L.d_strln "JOIN SYM HEAP1: "; Prop.d_prop p2; L.d_ln (); - L.d_strln "JOIN SYM HEAP2: "; Prop.d_prop p2'; L.d_ln (); L.d_ln (); + let rec join_proppath_plist ppalist2_acc (p2, pa2 as ppa2) = function + | [] + -> (ppa2, List.rev ppalist2_acc) + | (p2', pa2' as ppa2') :: ppalist2_rest + -> L.d_strln ".... JOIN ...." ; + L.d_strln "JOIN SYM HEAP1: " ; + Prop.d_prop p2 ; + L.d_ln () ; + L.d_strln "JOIN SYM HEAP2: " ; + Prop.d_prop p2' ; + L.d_ln () ; + L.d_ln () ; match prop_partial_join pname tenv mode p2 p2' with - | None -> - L.d_strln_color Red ".... JOIN FAILED ...."; L.d_ln (); - join_proppath_plist (ppa2':: ppalist2_acc) ppa2 ppalist2_rest - | Some p2'' -> - L.d_strln_color Green ".... JOIN SUCCEEDED ...."; - L.d_strln "RESULT SYM HEAP:"; Prop.d_prop p2''; L.d_ln (); L.d_ln (); + | None + -> L.d_strln_color Red ".... JOIN FAILED ...." ; + L.d_ln () ; + join_proppath_plist (ppa2' :: ppalist2_acc) ppa2 ppalist2_rest + | Some p2'' + -> L.d_strln_color Green ".... JOIN SUCCEEDED ...." ; + L.d_strln "RESULT SYM HEAP:" ; + Prop.d_prop p2'' ; + L.d_ln () ; + L.d_ln () ; join_proppath_plist ppalist2_acc (p2'', Paths.Path.join pa2 pa2') ppalist2_rest - end in + in let rec join ppalist1_cur ppalist2_acc = function - | [] -> (ppalist1_cur, ppalist2_acc) - | ppa2:: ppalist2_rest -> - let (ppa2', ppalist2_acc') = join_proppath_plist [] ppa2 ppalist2_acc in - let (ppa2'', ppalist2_rest') = join_proppath_plist [] ppa2' ppalist2_rest in - let (ppa2_new, ppalist1_cur') = join_proppath_plist [] ppa2'' ppalist1_cur in - join ppalist1_cur' (ppa2_new:: ppalist2_acc') ppalist2_rest' in + | [] + -> (ppalist1_cur, ppalist2_acc) + | ppa2 :: ppalist2_rest + -> let ppa2', ppalist2_acc' = join_proppath_plist [] ppa2 ppalist2_acc in + let ppa2'', ppalist2_rest' = join_proppath_plist [] ppa2' ppalist2_rest in + let ppa2_new, ppalist1_cur' = join_proppath_plist [] ppa2'' ppalist1_cur in + join ppalist1_cur' (ppa2_new :: ppalist2_acc') ppalist2_rest' + in let _ppalist1_res, _ppalist2_res = join ppalist1 [] ppalist2 in let ren l = List.map ~f:(fun (p, x) -> (Prop.prop_rename_primed_footprint_vars tenv p, x)) l in - let ppalist1_res, ppalist2_res = ren _ppalist1_res, ren _ppalist2_res in - let res = (Paths.PathSet.from_renamed_list ppalist1_res, Paths.PathSet.from_renamed_list ppalist2_res) in - join_time := !join_time +. (Unix.gettimeofday () -. initial_time); + let ppalist1_res, ppalist2_res = (ren _ppalist1_res, ren _ppalist2_res) in + let res = + (Paths.PathSet.from_renamed_list ppalist1_res, Paths.PathSet.from_renamed_list ppalist2_res) + in + join_time := !join_time +. (Unix.gettimeofday () -. initial_time) ; res (** @@ -2017,31 +2097,40 @@ let pathset_join let proplist_meet_generate tenv plist = let props_done = ref Propset.empty in let combine p (porig, pcombined) = - SymOp.pay (); (* pay one symop *) - L.d_strln ".... MEET ...."; - L.d_strln "MEET SYM HEAP1: "; Prop.d_prop p; L.d_ln (); - L.d_strln "MEET SYM HEAP2: "; Prop.d_prop pcombined; L.d_ln (); + SymOp.pay () ; + (* pay one symop *) + L.d_strln ".... MEET ...." ; + L.d_strln "MEET SYM HEAP1: " ; + Prop.d_prop p ; + L.d_ln () ; + L.d_strln "MEET SYM HEAP2: " ; + Prop.d_prop pcombined ; + L.d_ln () ; match prop_partial_meet tenv p pcombined with - | None -> - L.d_strln_color Red ".... MEET FAILED ...."; L.d_ln (); - (porig, pcombined) - | Some pcombined' -> - L.d_strln_color Green ".... MEET SUCCEEDED ...."; - L.d_strln "RESULT SYM HEAP:"; Prop.d_prop pcombined'; L.d_ln (); L.d_ln (); - (porig, pcombined') in + | None + -> L.d_strln_color Red ".... MEET FAILED ...." ; L.d_ln () ; (porig, pcombined) + | Some pcombined' + -> L.d_strln_color Green ".... MEET SUCCEEDED ...." ; + L.d_strln "RESULT SYM HEAP:" ; + Prop.d_prop pcombined' ; + L.d_ln () ; + L.d_ln () ; + (porig, pcombined') + in let rec proplist_meet = function - | [] -> () - | (porig, pcombined) :: pplist -> - (* use porig instead of pcombined because it might be combinable with more othe props *) + | [] + -> () + | (porig, pcombined) :: pplist + -> (* use porig instead of pcombined because it might be combinable with more othe props *) (* e.g. porig might contain a global var to add to the ture branch of a conditional *) (* but pcombined might have been combined with the false branch already *) let pplist' = List.map ~f:(combine porig) pplist in - props_done := Propset.add tenv pcombined !props_done; - proplist_meet pplist' in - proplist_meet (List.map ~f:(fun p -> (p, p)) plist); + props_done := Propset.add tenv pcombined !props_done ; + proplist_meet pplist' + in + proplist_meet (List.map ~f:(fun p -> (p, p)) plist) ; !props_done - let propset_meet_generate_pre tenv pset = let plist = Propset.to_proplist pset in if Int.equal Config.meet_level 0 then plist diff --git a/infer/src/backend/dom.mli b/infer/src/backend/dom.mli index 61921c7f2..d5fc08db9 100644 --- a/infer/src/backend/dom.mli +++ b/infer/src/backend/dom.mli @@ -14,9 +14,10 @@ open! IStd (** {2 Join Operators} *) -(** Join two pathsets *) val pathset_join : - Typ.Procname.t -> Tenv.t -> Paths.PathSet.t -> Paths.PathSet.t -> Paths.PathSet.t * Paths.PathSet.t + Typ.Procname.t -> Tenv.t -> Paths.PathSet.t -> Paths.PathSet.t + -> Paths.PathSet.t * Paths.PathSet.t +(** Join two pathsets *) val join_time : float ref @@ -24,13 +25,13 @@ val proplist_collapse_pre : Tenv.t -> Prop.normal Prop.t list -> Prop.normal Spe val pathset_collapse : Tenv.t -> Paths.PathSet.t -> Paths.PathSet.t -(** reduce the pathset only based on implication checking. *) val pathset_collapse_impl : Typ.Procname.t -> Tenv.t -> Paths.PathSet.t -> Paths.PathSet.t +(** reduce the pathset only based on implication checking. *) (** {2 Meet Operators} *) +val propset_meet_generate_pre : Tenv.t -> Propset.t -> Prop.normal Prop.t list (** [propset_meet_generate_pre] generates new symbolic heaps (i.e., props) by applying the partial meet operator, adds the generated heaps to the argument propset, and returns the resulting propset. This function is tuned for combining preconditions. *) -val propset_meet_generate_pre : Tenv.t -> Propset.t -> Prop.normal Prop.t list diff --git a/infer/src/backend/dotty.ml b/infer/src/backend/dotty.ml index 6bc23bba8..468ece06e 100644 --- a/infer/src/backend/dotty.ml +++ b/infer/src/backend/dotty.ml @@ -9,7 +9,6 @@ *) open! IStd - module L = Logging module F = Format @@ -22,7 +21,7 @@ let print_full_prop = ref false type kind_of_dotty_prop = | Generic_proposition | Spec_precondition - | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) + | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) | Lambda_pred of int * int * bool (* the kind of links between different kinds of nodes*) @@ -37,30 +36,24 @@ type kind_of_links = | LinkToSSL | LinkToDLL | LinkRetainCycle -[@@deriving compare] + [@@deriving compare] (* coordinate identifies a node using two dimension: id is an numerical identifier of the node,*) (* lambda identifies in which hpred parameter id lays in*) -type coordinate = { - id: int; - lambda: int; -} [@@deriving compare] +type coordinate = {id: int; lambda: int} [@@deriving compare] (* define a link between two nodes. src_fld/trg_fld define the label of the src/trg field. It is*) (* useful for having nodes from within a struct and/or to inside a struct *) -type link = { - kind: kind_of_links; - src: coordinate; - src_fld: string; - trg: coordinate; - trg_fld: string; -} [@@deriving compare] +type link = + {kind: kind_of_links; src: coordinate; src_fld: string; trg: coordinate; trg_fld: string} + [@@deriving compare] let equal_link = [%compare.equal : link] (* type of the visualized boxes/nodes in the graph*) type dotty_node = - | Dotnil of coordinate (* nil box *) + | Dotnil of coordinate + (* nil box *) (* Dotdangling(coo,e,c): dangling box for expression e at coordinate coo and color c *) | Dotdangling of coordinate * Exp.t * string (* Dotpointsto(coo,e,c): basic memory cell box for expression e at coordinate coo and color c *) @@ -76,9 +69,9 @@ type dotty_node = | Dotdllseg of coordinate * Exp.t * Exp.t * Exp.t * Exp.t * Sil.lseg_kind * Sil.hpred list * string -let mk_coordinate i l = { id = i; lambda = l } +let mk_coordinate i l = {id= i; lambda= l} -let mk_link k s sf t tf = { kind = k; src = s; src_fld = sf; trg = t; trg_fld = tf } +let mk_link k s sf t tf = {kind= k; src= s; src_fld= sf; trg= t; trg_fld= tf} (* list of dangling boxes*) let dangling_dotboxes = ref [] @@ -90,6 +83,7 @@ let exps_neq_zero = ref [] (* list of fields in the structs *) let fields_structs = ref [] + let struct_exp_nodes = ref [] (* general unique counter to assign a different number to boxex, *) @@ -97,12 +91,19 @@ let struct_exp_nodes = ref [] let dotty_state_count = ref 0 let spec_counter = ref 0 + let post_counter = ref 0 + let lambda_counter = ref 0 + let proposition_counter = ref 0 + let target_invisible_arrow_pre = ref 0 + let current_pre = ref 0 + let spec_id = ref 0 + let invisible_arrows = ref false let print_stack_info = ref false @@ -111,14 +112,14 @@ let print_stack_info = ref false (* a dollar sign i a label*) let strip_special_chars b = let replace st c c' = - if String.contains st c then begin + if String.contains st c then let idx = String.index_exn st c in - try - String.set st idx c'; - st + try st.[idx] <- c' ; st with Invalid_argument _ -> - L.internal_error "@\n@\nstrip_special_chars: Invalid argument!@\n@."; assert false - end else st in + L.internal_error "@\n@\nstrip_special_chars: Invalid argument!@\n@." ; + assert false + else st + in let s0 = replace b '(' 'B' in let s1 = replace s0 '$' 'D' in let s2 = replace s1 '#' 'H' in @@ -131,71 +132,74 @@ let strip_special_chars b = let rec strexp_to_string pe coo f se = match se with - | Sil.Eexp (Exp.Lvar pvar, _) -> F.fprintf f "%a" (Pvar.pp pe) pvar - | Sil.Eexp (Exp.Var id, _) -> - if !print_full_prop then - F.fprintf f "%a" (Ident.pp pe) id - else () - | Sil.Eexp (e, _) -> - if !print_full_prop then - F.fprintf f "%a" (Sil.pp_exp_printenv pe) e - else F.fprintf f "_" - | Sil.Estruct (ls, _) -> F.fprintf f " STRUCT | { %a } " (struct_to_dotty_str pe coo) ls - | Sil.Earray(e, idx, _) -> F.fprintf f " ARRAY[%a] | { %a } " (Sil.pp_exp_printenv pe) e (get_contents pe coo) idx + | Sil.Eexp (Exp.Lvar pvar, _) + -> F.fprintf f "%a" (Pvar.pp pe) pvar + | Sil.Eexp (Exp.Var id, _) + -> if !print_full_prop then F.fprintf f "%a" (Ident.pp pe) id else () + | Sil.Eexp (e, _) + -> if !print_full_prop then F.fprintf f "%a" (Sil.pp_exp_printenv pe) e else F.fprintf f "_" + | Sil.Estruct (ls, _) + -> F.fprintf f " STRUCT | { %a } " (struct_to_dotty_str pe coo) ls + | Sil.Earray (e, idx, _) + -> F.fprintf f " ARRAY[%a] | { %a } " (Sil.pp_exp_printenv pe) e (get_contents pe coo) idx and struct_to_dotty_str pe coo f ls : unit = match ls with - | [] -> () - | (fn, se)::[]-> F.fprintf f "{ <%s%iL%i> %s: %a } " (Typ.Fieldname.to_string fn) coo.id coo.lambda (Typ.Fieldname.to_string fn) (strexp_to_string pe coo) se - | (fn, se):: ls'-> F.fprintf f " { <%s%iL%i> %s: %a } | %a" (Typ.Fieldname.to_string fn) coo.id coo.lambda (Typ.Fieldname.to_string fn) (strexp_to_string pe coo) se (struct_to_dotty_str pe coo) ls' + | [] + -> () + | [(fn, se)] + -> F.fprintf f "{ <%s%iL%i> %s: %a } " (Typ.Fieldname.to_string fn) coo.id coo.lambda + (Typ.Fieldname.to_string fn) (strexp_to_string pe coo) se + | (fn, se) :: ls' + -> F.fprintf f " { <%s%iL%i> %s: %a } | %a" (Typ.Fieldname.to_string fn) coo.id coo.lambda + (Typ.Fieldname.to_string fn) (strexp_to_string pe coo) se (struct_to_dotty_str pe coo) ls' and get_contents_sexp pe coo f se = match se with - | Sil.Eexp (e', _) -> - F.fprintf f "%a" (Sil.pp_exp_printenv pe) e' - | Sil.Estruct (se', _) -> - F.fprintf f "| { %a }" (struct_to_dotty_str pe coo) se' - | Sil.Earray(e', [], _) -> - F.fprintf f "(ARRAY Size: %a) | { }" (Sil.pp_exp_printenv pe) e' - | Sil.Earray(e', ((idx, a):: linner), _) -> - F.fprintf f "(ARRAY Size: %a) | { %a: %a | %a }" (Sil.pp_exp_printenv pe) e' (Sil.pp_exp_printenv pe) idx - (strexp_to_string pe coo) a (get_contents pe coo) linner + | Sil.Eexp (e', _) + -> F.fprintf f "%a" (Sil.pp_exp_printenv pe) e' + | Sil.Estruct (se', _) + -> F.fprintf f "| { %a }" (struct_to_dotty_str pe coo) se' + | Sil.Earray (e', [], _) + -> F.fprintf f "(ARRAY Size: %a) | { }" (Sil.pp_exp_printenv pe) e' + | Sil.Earray (e', (idx, a) :: linner, _) + -> F.fprintf f "(ARRAY Size: %a) | { %a: %a | %a }" (Sil.pp_exp_printenv pe) e' + (Sil.pp_exp_printenv pe) idx (strexp_to_string pe coo) a (get_contents pe coo) linner and get_contents_single pe coo f (e, se) = let e_no_special_char = strip_special_chars (Exp.to_string e) in - F.fprintf f "{ <%s> %a : %a }" - e_no_special_char (Sil.pp_exp_printenv pe) e (get_contents_sexp pe coo) se + F.fprintf f "{ <%s> %a : %a }" e_no_special_char (Sil.pp_exp_printenv pe) e + (get_contents_sexp pe coo) se and get_contents pe coo f = function - | [] -> () - | [idx_se] -> - F.fprintf f "%a" (get_contents_single pe coo) idx_se - | idx_se:: l -> - F.fprintf f "%a | %a" (get_contents_single pe coo) idx_se (get_contents pe coo) l + | [] + -> () + | [idx_se] + -> F.fprintf f "%a" (get_contents_single pe coo) idx_se + | idx_se :: l + -> F.fprintf f "%a | %a" (get_contents_single pe coo) idx_se (get_contents pe coo) l (* true if node is the sorce node of the expression e*) let is_source_node_of_exp e node = - match node with - | Dotpointsto (_, e', _) -> Exp.equal e e' - | _ -> false + match node with Dotpointsto (_, e', _) -> Exp.equal e e' | _ -> false (* given a node returns its coordinates and the expression. Return -1 in case the expression doesn't*) (* make sense for that case *) let get_coordinate_and_exp dotnode = match dotnode with - | Dotnil(coo) -> (coo, Exp.minus_one) - | Dotarray (coo, _, _, _, _, _) -> (coo, Exp.minus_one) + | Dotnil coo + -> (coo, Exp.minus_one) + | Dotarray (coo, _, _, _, _, _) + -> (coo, Exp.minus_one) | Dotpointsto (coo, b, _) | Dotlseg (coo, b, _, _, _, _) | Dotdllseg (coo, b, _, _, _, _, _, _) | Dotstruct (coo, b, _, _, _) - | Dotdangling(coo, b, _) -> (coo, b) + | Dotdangling (coo, b, _) + -> (coo, b) (* true if a node is of a Dotstruct *) -let is_not_struct node = - match node with - | Dotstruct _ -> false - | _ -> true +let is_not_struct node = match node with Dotstruct _ -> false | _ -> true (* returns the id field of the coordinate of node *) let get_coordinate_id node = @@ -204,423 +208,502 @@ let get_coordinate_id node = let rec look_up_for_back_pointer e dotnodes lambda = match dotnodes with - | [] -> [] - | Dotdllseg(coo, _, _, _, e4, _, _, _):: dotnodes' -> - if Exp.equal e e4 && Int.equal lambda coo.lambda then [coo.id + 1] + | [] + -> [] + | (Dotdllseg (coo, _, _, _, e4, _, _, _)) :: dotnodes' + -> if Exp.equal e e4 && Int.equal lambda coo.lambda then [coo.id + 1] else look_up_for_back_pointer e dotnodes' lambda - | _:: dotnodes' -> look_up_for_back_pointer e dotnodes' lambda + | _ :: dotnodes' + -> look_up_for_back_pointer e dotnodes' lambda (* get the nodes corresponding to an expression and a lambda*) let rec select_nodes_exp_lambda dotnodes e lambda = match dotnodes with - | [] -> [] - | node:: l' -> - let (coo, e') = get_coordinate_and_exp node in - if (Exp.equal e e') && Int.equal lambda coo.lambda - then node:: select_nodes_exp_lambda l' e lambda + | [] + -> [] + | node :: l' + -> let coo, e' = get_coordinate_and_exp node in + if Exp.equal e e' && Int.equal lambda coo.lambda then node + :: select_nodes_exp_lambda l' e lambda else select_nodes_exp_lambda l' e lambda (* look-up the coordinate id in the list of dotnodes those nodes which correspond to expression e*) (* this is written in this strange way for legacy reason. It should be changed a bit*) let look_up dotnodes e lambda = let r = select_nodes_exp_lambda dotnodes e lambda in - let r'= List.map ~f:get_coordinate_id r in + let r' = List.map ~f:get_coordinate_id r in r' @ look_up_for_back_pointer e dotnodes lambda -let reset_proposition_counter () = proposition_counter:= 0 +let reset_proposition_counter () = proposition_counter := 0 -let reset_dotty_spec_counter () = spec_counter:= 0 +let reset_dotty_spec_counter () = spec_counter := 0 -let color_to_str (c : Pp.color) = +let color_to_str (c: Pp.color) = match c with - | Black -> "black" - | Blue -> "blue" - | Green -> "green" - | Orange -> "orange" - | Red -> "red" + | Black + -> "black" + | Blue + -> "blue" + | Green + -> "green" + | Orange + -> "orange" + | Red + -> "red" let make_dangling_boxes pe allocated_nodes (sigma_lambda: (Sil.hpred * int) list) = - let exp_color hpred (exp : Exp.t) = + let exp_color hpred (exp: Exp.t) = if Pp.equal_color (pe.Pp.cmap_norm (Obj.repr hpred)) Pp.Red then Pp.Red - else pe.Pp.cmap_norm (Obj.repr exp) in + else pe.Pp.cmap_norm (Obj.repr exp) + in let get_rhs_predicate (hpred, lambda) = let n = !dotty_state_count in - incr dotty_state_count; + incr dotty_state_count ; let coo = mk_coordinate n lambda in - (match hpred with - | Sil.Hpointsto (_, Sil.Eexp (e, _), _) - when not (Exp.equal e Exp.zero) && !print_full_prop -> - let e_color_str = color_to_str (exp_color hpred e) in - [Dotdangling(coo, e, e_color_str)] - | Sil.Hlseg (_, _, _, e2, _) when not (Exp.equal e2 Exp.zero) -> - let e2_color_str = color_to_str (exp_color hpred e2) in - [Dotdangling(coo, e2, e2_color_str)] - | Sil.Hdllseg (_, _, _, e2, e3, _, _) -> - let e2_color_str = color_to_str (exp_color hpred e2) in - let e3_color_str = color_to_str (exp_color hpred e3) in - let ll = if not (Exp.equal e2 Exp.zero) then - [Dotdangling(coo, e2, e2_color_str)] - else [] in - if not (Exp.equal e3 Exp.zero) then Dotdangling(coo, e3, e3_color_str):: ll - else ll - | Sil.Hpointsto (_, _, _) - | _ -> [] (* arrays and struct do not give danglings*) - ) in + match hpred with + | Sil.Hpointsto (_, Sil.Eexp (e, _), _) when not (Exp.equal e Exp.zero) && !print_full_prop + -> let e_color_str = color_to_str (exp_color hpred e) in + [Dotdangling (coo, e, e_color_str)] + | Sil.Hlseg (_, _, _, e2, _) when not (Exp.equal e2 Exp.zero) + -> let e2_color_str = color_to_str (exp_color hpred e2) in + [Dotdangling (coo, e2, e2_color_str)] + | Sil.Hdllseg (_, _, _, e2, e3, _, _) + -> let e2_color_str = color_to_str (exp_color hpred e2) in + let e3_color_str = color_to_str (exp_color hpred e3) in + let ll = + if not (Exp.equal e2 Exp.zero) then [Dotdangling (coo, e2, e2_color_str)] else [] + in + if not (Exp.equal e3 Exp.zero) then Dotdangling (coo, e3, e3_color_str) :: ll else ll + | Sil.Hpointsto (_, _, _) | _ + -> [] + (* arrays and struct do not give danglings*) + in let is_allocated d = match d with - | Dotdangling(_, e, _) -> - List.exists ~f:(fun a -> match a with - | Dotpointsto(_, e', _) - | Dotarray(_, _, e', _, _, _) - | Dotlseg(_, e', _, _, _, _) - | Dotdllseg(_, e', _, _, _, _, _, _) -> Exp.equal e e' - | _ -> false - ) allocated_nodes - | _ -> false (*this should never happen since d must be a dangling node *) in + | Dotdangling (_, e, _) + -> List.exists + ~f:(fun a -> + match a with + | Dotpointsto (_, e', _) + | Dotarray (_, _, e', _, _, _) + | Dotlseg (_, e', _, _, _, _) + | Dotdllseg (_, e', _, _, _, _, _, _) + -> Exp.equal e e' + | _ + -> false) + allocated_nodes + | _ + -> false + (*this should never happen since d must be a dangling node *) + in let rec filter_duplicate l seen_exp = match l with - | [] -> [] - | Dotdangling(coo, e, color):: l' -> - if (List.exists ~f:(Exp.equal e) seen_exp) then filter_duplicate l' seen_exp - else Dotdangling(coo, e, color):: filter_duplicate l' (e:: seen_exp) - | box:: l' -> box:: filter_duplicate l' seen_exp (* this case cannot happen*) in + | [] + -> [] + | (Dotdangling (coo, e, color)) :: l' + -> if List.exists ~f:(Exp.equal e) seen_exp then filter_duplicate l' seen_exp + else Dotdangling (coo, e, color) :: filter_duplicate l' (e :: seen_exp) + | box :: l' + -> box :: filter_duplicate l' seen_exp + (* this case cannot happen*) + in let rec subtract_allocated candidate_dangling = match candidate_dangling with - | [] -> [] - | d:: candidates -> - if (is_allocated d) then subtract_allocated candidates - else d:: subtract_allocated candidates in + | [] + -> [] + | d :: candidates + -> if is_allocated d then subtract_allocated candidates + else d :: subtract_allocated candidates + in let candidate_dangling = List.concat_map ~f:get_rhs_predicate sigma_lambda in let candidate_dangling = filter_duplicate candidate_dangling [] in let dangling = subtract_allocated candidate_dangling in - dangling_dotboxes:= dangling + dangling_dotboxes := dangling let rec dotty_mk_node pe sigma = let n = !dotty_state_count in - incr dotty_state_count; + incr dotty_state_count ; let do_hpred_lambda exp_color = function - | (Sil.Hpointsto (e, Sil.Earray (e', l, _), Exp.Sizeof {typ={Typ.desc=Tarray (t, _, _)}}), - lambda) -> - incr dotty_state_count; (* increment once more n+1 is the box for the array *) + | ( Sil.Hpointsto (e, Sil.Earray (e', l, _), Exp.Sizeof {typ= {Typ.desc= Tarray (t, _, _)}}) + , lambda ) + -> incr dotty_state_count ; + (* increment once more n+1 is the box for the array *) let e_color_str = color_to_str (exp_color e) in - let e_color_str'= color_to_str (exp_color e') in - [Dotpointsto((mk_coordinate n lambda), e, e_color_str); - Dotarray((mk_coordinate (n + 1) lambda), e, e', l, t, e_color_str')] - | (Sil.Hpointsto (e, Sil.Estruct (l, _), te), lambda) -> - incr dotty_state_count; (* increment once more n+1 is the box for the struct *) + let e_color_str' = color_to_str (exp_color e') in + [ Dotpointsto (mk_coordinate n lambda, e, e_color_str) + ; Dotarray (mk_coordinate (n + 1) lambda, e, e', l, t, e_color_str') ] + | Sil.Hpointsto (e, Sil.Estruct (l, _), te), lambda + -> incr dotty_state_count ; + (* increment once more n+1 is the box for the struct *) let e_color_str = color_to_str (exp_color e) in (* [Dotpointsto((mk_coordinate n lambda), e, l, true, e_color_str)] *) - [Dotpointsto((mk_coordinate n lambda), e, e_color_str); - Dotstruct((mk_coordinate (n + 1) lambda), e, l, e_color_str, te);] - | (Sil.Hpointsto (e, _, _), lambda) -> - let e_color_str = color_to_str (exp_color e) in - if List.mem ~equal:Exp.equal !struct_exp_nodes e then [] else - [Dotpointsto((mk_coordinate n lambda), e, e_color_str)] - | (Sil.Hlseg (k, hpara, e1, e2, _), lambda) -> - incr dotty_state_count; (* increment once more n+1 is the box for last element of the list *) + [ Dotpointsto (mk_coordinate n lambda, e, e_color_str) + ; Dotstruct (mk_coordinate (n + 1) lambda, e, l, e_color_str, te) ] + | Sil.Hpointsto (e, _, _), lambda + -> let e_color_str = color_to_str (exp_color e) in + if List.mem ~equal:Exp.equal !struct_exp_nodes e then [] + else [Dotpointsto (mk_coordinate n lambda, e, e_color_str)] + | Sil.Hlseg (k, hpara, e1, e2, _), lambda + -> incr dotty_state_count ; + (* increment once more n+1 is the box for last element of the list *) let eq_color_str = color_to_str (exp_color e1) in - [Dotlseg((mk_coordinate n lambda), e1, e2, k, hpara.Sil.body, eq_color_str)] - | (Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, _), lambda) -> - let e1_color_str = color_to_str (exp_color e1) in - incr dotty_state_count; (* increment once more n+1 is the box for e4 *) - [Dotdllseg((mk_coordinate n lambda), e1, e2, e3, e4, k, hpara_dll.Sil.body_dll, e1_color_str)] in + [Dotlseg (mk_coordinate n lambda, e1, e2, k, hpara.Sil.body, eq_color_str)] + | Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, _), lambda + -> let e1_color_str = color_to_str (exp_color e1) in + incr dotty_state_count ; + (* increment once more n+1 is the box for e4 *) + [ Dotdllseg + (mk_coordinate n lambda, e1, e2, e3, e4, k, hpara_dll.Sil.body_dll, e1_color_str) ] + in match sigma with - | [] -> [] - | (hpred, lambda) :: sigma' -> - let exp_color (exp : Exp.t) = + | [] + -> [] + | (hpred, lambda) :: sigma' + -> let exp_color (exp: Exp.t) = if Pp.equal_color (pe.Pp.cmap_norm (Obj.repr hpred)) Pp.Red then Pp.Red - else pe.Pp.cmap_norm (Obj.repr exp) in + else pe.Pp.cmap_norm (Obj.repr exp) + in do_hpred_lambda exp_color (hpred, lambda) @ dotty_mk_node pe sigma' let set_exps_neq_zero pi = let f = function - | Sil.Aneq (e, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - exps_neq_zero := e :: !exps_neq_zero - | _ -> () in - exps_neq_zero := []; + | Sil.Aneq (e, Exp.Const Const.Cint i) when IntLit.iszero i + -> exps_neq_zero := e :: !exps_neq_zero + | _ + -> () + in + exps_neq_zero := [] ; List.iter ~f pi let box_dangling e = - let entry_e = List.filter ~f:(fun b -> match b with - | Dotdangling(_, e', _) -> Exp.equal e e' | _ -> false ) !dangling_dotboxes in - match entry_e with - |[] -> None - | Dotdangling(coo, _, _):: _ -> Some coo.id - | _ -> None (* NOTE: this cannot be possible since entry_e can be composed only by Dotdangling, see def of entry_e*) + let entry_e = + List.filter + ~f:(fun b -> match b with Dotdangling (_, e', _) -> Exp.equal e e' | _ -> false) + !dangling_dotboxes + in + match entry_e with [] -> None | (Dotdangling (coo, _, _)) :: _ -> Some coo.id | _ -> None +(* NOTE: this cannot be possible since entry_e can be composed only by Dotdangling, see def of entry_e*) (* construct a Dotnil and returns it's id *) let make_nil_node lambda = let n = !dotty_state_count in - incr dotty_state_count; - nil_dotboxes:= Dotnil(mk_coordinate n lambda)::!nil_dotboxes; + incr dotty_state_count ; + nil_dotboxes := Dotnil (mk_coordinate n lambda) :: !nil_dotboxes ; n let compute_fields_struct sigma = - fields_structs:=[]; + fields_structs := [] ; let rec do_strexp se in_struct = match se with - | Sil.Eexp (e, _) -> if in_struct then fields_structs:= e ::!fields_structs else () - | Sil.Estruct (l, _) -> List.iter ~f:(fun e -> do_strexp e true) (snd (List.unzip l)) - | Sil.Earray (_, l, _) -> List.iter ~f:(fun e -> do_strexp e false) (snd (List.unzip l)) in + | Sil.Eexp (e, _) + -> if in_struct then fields_structs := e :: !fields_structs else () + | Sil.Estruct (l, _) + -> List.iter ~f:(fun e -> do_strexp e true) (snd (List.unzip l)) + | Sil.Earray (_, l, _) + -> List.iter ~f:(fun e -> do_strexp e false) (snd (List.unzip l)) + in let rec fs s = match s with - | [] -> () - | Sil.Hpointsto(_, se, _):: s' -> do_strexp se false; fs s' - | _:: s' -> fs s' in + | [] + -> () + | (Sil.Hpointsto (_, se, _)) :: s' + -> do_strexp se false ; fs s' + | _ :: s' + -> fs s' + in fs sigma let compute_struct_exp_nodes sigma = - struct_exp_nodes:=[]; + struct_exp_nodes := [] ; let rec sen s = match s with - | [] -> () - | Sil.Hpointsto(e, Sil.Estruct _, _):: s' -> struct_exp_nodes:= e::!struct_exp_nodes; sen s' - | _:: s' -> sen s' in + | [] + -> () + | (Sil.Hpointsto (e, Sil.Estruct _, _)) :: s' + -> struct_exp_nodes := e :: !struct_exp_nodes ; + sen s' + | _ :: s' + -> sen s' + in sen sigma (* returns the expression of a node*) let get_node_exp n = snd (get_coordinate_and_exp n) -let is_nil e prop = - (Exp.equal e Exp.zero) || (Prover.check_equal (Tenv.create ()) prop e Exp.zero) +let is_nil e prop = Exp.equal e Exp.zero || Prover.check_equal (Tenv.create ()) prop e Exp.zero (* an edge is in cycle *) let in_cycle cycle edge = match cycle with - | Some cycle' -> - let (fn, se) = edge in + | Some cycle' + -> let fn, se = edge in List.exists - ~f:(fun (_,fn',se') -> Typ.Fieldname.equal fn fn' && Sil.equal_strexp se se') + ~f:(fun (_, fn', se') -> Typ.Fieldname.equal fn fn' && Sil.equal_strexp se se') cycle' - | _ -> false + | _ + -> false let node_in_cycle cycle node = - match cycle, node with - | Some _, Dotstruct(_, _, l, _,_) -> (* only struct nodes can be in cycle *) + match (cycle, node) with + | Some _, Dotstruct (_, _, l, _, _) + -> (* only struct nodes can be in cycle *) List.exists ~f:(in_cycle cycle) l - | _ -> false - + | _ + -> false (* compute a list of (kind of link, field name, coo.id target, name_target) *) let rec compute_target_struct_fields dotnodes list_fld p f lambda cycle = let find_target_one_fld (fn, se) = match se with - | Sil.Eexp (e, _) -> - if is_nil e p then begin - let n'= make_nil_node lambda in - if !print_full_prop then - [(LinkStructToExp, Typ.Fieldname.to_string fn, n',"")] - else [] - end else + | Sil.Eexp (e, _) + -> ( + if is_nil e p then + let n' = make_nil_node lambda in + if !print_full_prop then [(LinkStructToExp, Typ.Fieldname.to_string fn, n', "")] else [] + else let nodes_e = select_nodes_exp_lambda dotnodes e lambda in - (match nodes_e with - | [] -> - (match box_dangling e with - | None -> [] - | Some n' -> [(LinkStructToExp, Typ.Fieldname.to_string fn, n',"")] - ) - | [node] | [Dotpointsto _ ; node] | [node; Dotpointsto _] -> - let n = get_coordinate_id node in - if List.mem ~equal:Exp.equal !struct_exp_nodes e then begin - let e_no_special_char = strip_special_chars (Exp.to_string e) in - let link_kind = if (in_cycle cycle (fn, se)) && (not !print_full_prop) then - LinkRetainCycle - else LinkStructToStruct in - [(link_kind, Typ.Fieldname.to_string fn, n, e_no_special_char)] - end else - [(LinkStructToExp, Typ.Fieldname.to_string fn, n,"")] - | _ -> (* by construction there must be at most 2 nodes for an expression*) - L.internal_error "@\n Too many nodes! Error! @\n@."; assert false) - | Sil.Estruct (_, _) -> [] (* inner struct are printed by print_struc function *) - | Sil.Earray _ -> [] (* inner arrays are printed by print_array function *) in + match nodes_e with + | [] -> ( + match box_dangling e with + | None + -> [] + | Some n' + -> [(LinkStructToExp, Typ.Fieldname.to_string fn, n', "")] ) + | [node] | [(Dotpointsto _); node] | [node; (Dotpointsto _)] + -> let n = get_coordinate_id node in + if List.mem ~equal:Exp.equal !struct_exp_nodes e then + let e_no_special_char = strip_special_chars (Exp.to_string e) in + let link_kind = + if in_cycle cycle (fn, se) && not !print_full_prop then LinkRetainCycle + else LinkStructToStruct + in + [(link_kind, Typ.Fieldname.to_string fn, n, e_no_special_char)] + else [(LinkStructToExp, Typ.Fieldname.to_string fn, n, "")] + | _ + -> (* by construction there must be at most 2 nodes for an expression*) + L.internal_error "@\n Too many nodes! Error! @\n@." ; + assert false ) + | Sil.Estruct (_, _) + -> [] (* inner struct are printed by print_struc function *) + | Sil.Earray _ + -> [] + (* inner arrays are printed by print_array function *) + in match list_fld with - | [] -> [] - | a:: list_fld' -> - let targets_a = find_target_one_fld a in + | [] + -> [] + | a :: list_fld' + -> let targets_a = find_target_one_fld a in targets_a @ compute_target_struct_fields dotnodes list_fld' p f lambda cycle (* compute a list of (kind of link, field name, coo.id target, name_target) *) let rec compute_target_array_elements dotnodes list_elements p f lambda = let find_target_one_element (idx, se) = match se with - | Sil.Eexp (e, _) -> - if is_nil e p then begin - let n'= make_nil_node lambda in - [(LinkArrayToExp, Exp.to_string idx, n',"")] - end else + | Sil.Eexp (e, _) + -> ( + if is_nil e p then + let n' = make_nil_node lambda in + [(LinkArrayToExp, Exp.to_string idx, n', "")] + else let nodes_e = select_nodes_exp_lambda dotnodes e lambda in - (match nodes_e with - | [] -> - (match box_dangling e with - | None -> [] - | Some n' -> [(LinkArrayToExp, Exp.to_string idx, n',"")] - ) - | [node] | [Dotpointsto _ ; node] | [node; Dotpointsto _] -> - let n = get_coordinate_id node in - if List.mem ~equal:Exp.equal !struct_exp_nodes e then begin - let e_no_special_char = strip_special_chars (Exp.to_string e) in - [(LinkArrayToStruct, Exp.to_string idx, n, e_no_special_char)] - end else - [(LinkArrayToExp, Exp.to_string idx, n,"")] - | _ -> (* by construction there must be at most 2 nodes for an expression*) - L.internal_error "@\nToo many nodes! Error!@\n@."; assert false - ) - | Sil.Estruct (_, _) -> [] (* inner struct are printed by print_struc function *) - | Sil.Earray _ ->[] (* inner arrays are printed by print_array function *) + match nodes_e with + | [] -> ( + match box_dangling e with + | None + -> [] + | Some n' + -> [(LinkArrayToExp, Exp.to_string idx, n', "")] ) + | [node] | [(Dotpointsto _); node] | [node; (Dotpointsto _)] + -> let n = get_coordinate_id node in + if List.mem ~equal:Exp.equal !struct_exp_nodes e then + let e_no_special_char = strip_special_chars (Exp.to_string e) in + [(LinkArrayToStruct, Exp.to_string idx, n, e_no_special_char)] + else [(LinkArrayToExp, Exp.to_string idx, n, "")] + | _ + -> (* by construction there must be at most 2 nodes for an expression*) + L.internal_error "@\nToo many nodes! Error!@\n@." ; + assert false ) + | Sil.Estruct (_, _) + -> [] (* inner struct are printed by print_struc function *) + | Sil.Earray _ + -> [] + (* inner arrays are printed by print_array function *) in match list_elements with - | [] -> [] - | a:: list_ele' -> - let targets_a = find_target_one_element a in + | [] + -> [] + | a :: list_ele' + -> let targets_a = find_target_one_element a in targets_a @ compute_target_array_elements dotnodes list_ele' p f lambda let compute_target_from_eexp dotnodes e p lambda = if is_nil e p then - let n'= make_nil_node lambda in + let n' = make_nil_node lambda in [(LinkExpToExp, n', "")] else let nodes_e = select_nodes_exp_lambda dotnodes e lambda in let nodes_e_no_struct = List.filter ~f:is_not_struct nodes_e in let trg = List.map ~f:get_coordinate_id nodes_e_no_struct in - (match trg with - | [] -> - (match box_dangling e with - | None -> [] - | Some n -> [(LinkExpToExp, n, "")] - ) - | _ -> List.map ~f:(fun n -> (LinkExpToExp, n, "")) trg - ) + match trg with + | [] -> ( + match box_dangling e with None -> [] | Some n -> [(LinkExpToExp, n, "")] ) + | _ + -> List.map ~f:(fun n -> (LinkExpToExp, n, "")) trg (* build the set of edges between nodes *) let rec dotty_mk_set_links dotnodes sigma p f cycle = - let make_links_for_arrays e lie lambda sigma' = (* used for both Earray and ENarray*) + let make_links_for_arrays e lie lambda sigma' = + (* used for both Earray and ENarray*) let src = look_up dotnodes e lambda in match src with - | [] -> assert false - | n:: nl -> - let target_list = compute_target_array_elements dotnodes lie p f lambda in + | [] + -> assert false + | n :: nl + -> let target_list = compute_target_array_elements dotnodes lie p f lambda in (* below it's n+1 because n is the address, n+1 is the actual array node*) let ff n = List.map ~f:(fun (k, lab_src, m, lab_trg) -> - mk_link - k - (mk_coordinate (n + 1) lambda) - (strip_special_chars lab_src) - (mk_coordinate m lambda) - (strip_special_chars lab_trg)) - target_list in - let links_from_elements = List.concat_map ~f:ff (n:: nl) in - + mk_link k + (mk_coordinate (n + 1) lambda) + (strip_special_chars lab_src) (mk_coordinate m lambda) + (strip_special_chars lab_trg)) + target_list + in + let links_from_elements = List.concat_map ~f:ff (n :: nl) in let trg_label = strip_special_chars (Exp.to_string e) in - let lnk = mk_link (LinkToArray) (mk_coordinate n lambda) "" (mk_coordinate (n + 1) lambda) trg_label in - lnk :: links_from_elements @ dotty_mk_set_links dotnodes sigma' p f cycle in + let lnk = + mk_link LinkToArray (mk_coordinate n lambda) "" (mk_coordinate (n + 1) lambda) trg_label + in + lnk :: links_from_elements @ dotty_mk_set_links dotnodes sigma' p f cycle + in match sigma with - | [] -> [] - | (Sil.Hpointsto (e, Sil.Earray(_, lie, _), _), lambda):: sigma' -> - make_links_for_arrays e lie lambda sigma' - | (Sil.Hpointsto (e, Sil.Estruct (lfld, _), _), lambda):: sigma' -> + | [] + -> [] + | (Sil.Hpointsto (e, Sil.Earray (_, lie, _), _), lambda) :: sigma' + -> make_links_for_arrays e lie lambda sigma' + | (Sil.Hpointsto (e, Sil.Estruct (lfld, _), _), lambda) :: sigma' + -> ( let src = look_up dotnodes e lambda in - (match src with - | [] -> assert false - | nl -> - (* L.out "@\n@\n List of nl= "; List.iter ~f:(L.out " %i ") nl; L.out "@.@.@."; *) - let target_list = compute_target_struct_fields dotnodes lfld p f lambda cycle in - let ff n = List.map ~f:(fun (k, lab_src, m, lab_trg) -> - mk_link k (mk_coordinate n lambda) lab_src (mk_coordinate m lambda) lab_trg - ) target_list in - let nodes_e = select_nodes_exp_lambda dotnodes e lambda in - let address_struct_id = - try get_coordinate_id (List.hd_exn (List.filter ~f:(is_source_node_of_exp e) nodes_e)) - with exn when SymOp.exn_not_failure exn -> assert false in - (* we need to exclude the address node from the sorce of fields. no fields should start from there*) - let nl'= List.filter ~f:(fun id -> address_struct_id <> id) nl in - let links_from_fields = List.concat_map ~f:ff nl' in - let lnk_from_address_struct = if !print_full_prop then - let trg_label = strip_special_chars (Exp.to_string e) in - [mk_link (LinkExpToStruct) (mk_coordinate address_struct_id lambda) "" - (mk_coordinate (address_struct_id + 1) lambda) trg_label] - else [] in - lnk_from_address_struct @ links_from_fields @ - dotty_mk_set_links dotnodes sigma' p f cycle) - | (Sil.Hpointsto (e, Sil.Eexp (e', _), _), lambda):: sigma' -> + match src with + | [] + -> assert false + | nl + -> (* L.out "@\n@\n List of nl= "; List.iter ~f:(L.out " %i ") nl; L.out "@.@.@."; *) + let target_list = compute_target_struct_fields dotnodes lfld p f lambda cycle in + let ff n = + List.map + ~f:(fun (k, lab_src, m, lab_trg) -> + mk_link k (mk_coordinate n lambda) lab_src (mk_coordinate m lambda) lab_trg) + target_list + in + let nodes_e = select_nodes_exp_lambda dotnodes e lambda in + let address_struct_id = + try get_coordinate_id (List.hd_exn (List.filter ~f:(is_source_node_of_exp e) nodes_e)) + with exn when SymOp.exn_not_failure exn -> assert false + in + (* we need to exclude the address node from the sorce of fields. no fields should start from there*) + let nl' = List.filter ~f:(fun id -> address_struct_id <> id) nl in + let links_from_fields = List.concat_map ~f:ff nl' in + let lnk_from_address_struct = + if !print_full_prop then + let trg_label = strip_special_chars (Exp.to_string e) in + [ mk_link LinkExpToStruct (mk_coordinate address_struct_id lambda) "" + (mk_coordinate (address_struct_id + 1) lambda) + trg_label ] + else [] + in + lnk_from_address_struct @ links_from_fields + @ dotty_mk_set_links dotnodes sigma' p f cycle ) + | (Sil.Hpointsto (e, Sil.Eexp (e', _), _), lambda) :: sigma' + -> ( let src = look_up dotnodes e lambda in - (match src with - | [] -> assert false - | nl -> if !print_full_prop then - let target_list = compute_target_from_eexp dotnodes e' p lambda in - let ff n = List.map ~f:(fun (k, m, lab_target) -> - mk_link k (mk_coordinate n lambda) "" - (mk_coordinate m lambda) (strip_special_chars lab_target) - ) target_list in - let ll = List.concat_map ~f:ff nl in - ll @ dotty_mk_set_links dotnodes sigma' p f cycle - else dotty_mk_set_links dotnodes sigma' p f cycle) - - | (Sil.Hlseg (_, _, e1, e2, _), lambda):: sigma' -> - let src = look_up dotnodes e1 lambda in - (match src with - | [] -> assert false - | n:: _ -> - let (_, m, lab) = List.hd_exn (compute_target_from_eexp dotnodes e2 p lambda) in - let lnk = mk_link LinkToSSL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) lab in - lnk:: dotty_mk_set_links dotnodes sigma' p f cycle - ) - | (Sil.Hdllseg (_, _, e1, e2, e3, _, _), lambda):: sigma' -> + match src with + | [] + -> assert false + | nl + -> if !print_full_prop then + let target_list = compute_target_from_eexp dotnodes e' p lambda in + let ff n = + List.map + ~f:(fun (k, m, lab_target) -> + mk_link k (mk_coordinate n lambda) "" (mk_coordinate m lambda) + (strip_special_chars lab_target)) + target_list + in + let ll = List.concat_map ~f:ff nl in + ll @ dotty_mk_set_links dotnodes sigma' p f cycle + else dotty_mk_set_links dotnodes sigma' p f cycle ) + | (Sil.Hlseg (_, _, e1, e2, _), lambda) :: sigma' + -> ( let src = look_up dotnodes e1 lambda in - (match src with - | [] -> assert false - | n:: _ -> (* n is e1's box and n+1 is e4's box *) - let targetF = look_up dotnodes e3 lambda in - let target_Flink = (match targetF with - | [] -> [] - | m:: _ -> [mk_link LinkToDLL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) ""] - ) in - let targetB = look_up dotnodes e2 lambda in - let target_Blink = (match targetB with - | [] -> [] - | m:: _ -> [mk_link LinkToDLL (mk_coordinate n lambda) "" (mk_coordinate m lambda) ""] - ) in - target_Blink @ target_Flink @ dotty_mk_set_links dotnodes sigma' p f cycle - ) + match src with + | [] + -> assert false + | n :: _ + -> let _, m, lab = List.hd_exn (compute_target_from_eexp dotnodes e2 p lambda) in + let lnk = + mk_link LinkToSSL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) lab + in + lnk :: dotty_mk_set_links dotnodes sigma' p f cycle ) + | (Sil.Hdllseg (_, _, e1, e2, e3, _, _), lambda) :: sigma' + -> let src = look_up dotnodes e1 lambda in + match src with + | [] + -> assert false + | n :: _ + -> (* n is e1's box and n+1 is e4's box *) + let targetF = look_up dotnodes e3 lambda in + let target_Flink = + match targetF with + | [] + -> [] + | m :: _ + -> [mk_link LinkToDLL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) ""] + in + let targetB = look_up dotnodes e2 lambda in + let target_Blink = + match targetB with + | [] + -> [] + | m :: _ + -> [mk_link LinkToDLL (mk_coordinate n lambda) "" (mk_coordinate m lambda) ""] + in + target_Blink @ target_Flink @ dotty_mk_set_links dotnodes sigma' p f cycle let print_kind f kind = - incr dotty_state_count; + incr dotty_state_count ; match kind with - | Spec_precondition -> - incr dotty_state_count; - current_pre:=!dotty_state_count; + | Spec_precondition + -> incr dotty_state_count ; + current_pre := !dotty_state_count ; F.fprintf f "@\n PRE%iL0 [label=\"PRE %i \", style=filled, color= yellow]@\n" - !dotty_state_count !spec_counter; - print_stack_info:= true; - | Spec_postcondition _ -> - F.fprintf f "@\n POST%iL0 [label=\"POST %i \", style=filled, color= yellow]@\n" - !dotty_state_count !post_counter; - print_stack_info:= true; - | Generic_proposition -> - if !print_full_prop then + !dotty_state_count !spec_counter ; + print_stack_info := true + | Spec_postcondition _ + -> F.fprintf f "@\n POST%iL0 [label=\"POST %i \", style=filled, color= yellow]@\n" + !dotty_state_count !post_counter ; + print_stack_info := true + | Generic_proposition + -> if !print_full_prop then F.fprintf f "@\n HEAP%iL0 [label=\"HEAP %i \", style=filled, color= yellow]@\n" - !dotty_state_count - !proposition_counter + !dotty_state_count !proposition_counter | Lambda_pred (no, lev, array) -> - match array with - | false -> - F.fprintf f - "%s @\n state%iL%i [label=\"INTERNAL STRUCTURE %i \", %s]@\n" - "style=dashed; color=blue" - !dotty_state_count !lambda_counter !lambda_counter - "style=filled, color= lightblue"; - F.fprintf f "state%iL%i -> state%iL%i [color=\"lightblue \" arrowhead=none] @\n" - !dotty_state_count !lambda_counter no lev; - | true -> - F.fprintf f - "%s @\n state%iL%i [label=\"INTERNAL STRUCTURE %i \", %s]@\n" - "style=dashed; color=blue" - !dotty_state_count !lambda_counter !lambda_counter - "style=filled, color= lightblue" ; - (* F.fprintf f "state%iL%i -> struct%iL%i:%s [color=\"lightblue \" arrowhead=none] @\n" + match array with + | false + -> F.fprintf f "%s @\n state%iL%i [label=\"INTERNAL STRUCTURE %i \", %s]@\n" + "style=dashed; color=blue" !dotty_state_count !lambda_counter !lambda_counter + "style=filled, color= lightblue" ; + F.fprintf f "state%iL%i -> state%iL%i [color=\"lightblue \" arrowhead=none] @\n" + !dotty_state_count !lambda_counter no lev + | true + -> F.fprintf f "%s @\n state%iL%i [label=\"INTERNAL STRUCTURE %i \", %s]@\n" + "style=dashed; color=blue" !dotty_state_count !lambda_counter !lambda_counter + "style=filled, color= lightblue" ; + (* F.fprintf f "state%iL%i -> struct%iL%i:%s [color=\"lightblue \" arrowhead=none] @\n" !dotty_state_count !lambda_counter no lev lab;*) - incr dotty_state_count + incr dotty_state_count (* print a link between two nodes in the graph *) let dotty_pp_link f link = @@ -630,30 +713,31 @@ let dotty_pp_link f link = let lambda2 = link.trg.lambda in let src_fld = link.src_fld in let trg_fld = link.trg_fld in - match n2, link.kind with - | 0, _ when !print_full_prop -> - F.fprintf f "state%iL%i -> state%iL%i[label=\"%s DANG\", color= red];@\n" - n1 lambda1 n2 lambda2 src_fld - | _, LinkToArray when !print_full_prop -> - F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" - n1 lambda1 n2 lambda2 trg_fld n2 lambda2 - | _, LinkExpToStruct when !print_full_prop -> - F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" - n1 lambda1 n2 lambda2 trg_fld n2 lambda2 - | _, LinkStructToExp when !print_full_prop -> - F.fprintf f "struct%iL%i:%s%iL%i -> state%iL%i[label=\"\"]@\n" - n1 lambda1 src_fld n1 lambda1 n2 lambda2 - | _, LinkRetainCycle -> - F.fprintf f "struct%iL%i:%s%iL%i -> struct%iL%i:%s%iL%i[label=\"\", color= red]@\n" - n1 lambda1 src_fld n1 lambda1 n2 lambda2 trg_fld n2 lambda2 - | _, LinkStructToStruct when !print_full_prop -> - F.fprintf f "struct%iL%i:%s%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" - n1 lambda1 src_fld n1 lambda1 n2 lambda2 trg_fld n2 lambda2 - | _, LinkArrayToExp when !print_full_prop -> - F.fprintf f "struct%iL%i:%s -> state%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n2 lambda2 - | _, LinkArrayToStruct when !print_full_prop -> - F.fprintf f "struct%iL%i:%s -> struct%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n2 lambda2 - | _, _ -> if !print_full_prop then + match (n2, link.kind) with + | 0, _ when !print_full_prop + -> F.fprintf f "state%iL%i -> state%iL%i[label=\"%s DANG\", color= red];@\n" n1 lambda1 n2 + lambda2 src_fld + | _, LinkToArray when !print_full_prop + -> F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" n1 lambda1 n2 lambda2 trg_fld + n2 lambda2 + | _, LinkExpToStruct when !print_full_prop + -> F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" n1 lambda1 n2 lambda2 trg_fld + n2 lambda2 + | _, LinkStructToExp when !print_full_prop + -> F.fprintf f "struct%iL%i:%s%iL%i -> state%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n1 lambda1 + n2 lambda2 + | _, LinkRetainCycle + -> F.fprintf f "struct%iL%i:%s%iL%i -> struct%iL%i:%s%iL%i[label=\"\", color= red]@\n" n1 + lambda1 src_fld n1 lambda1 n2 lambda2 trg_fld n2 lambda2 + | _, LinkStructToStruct when !print_full_prop + -> F.fprintf f "struct%iL%i:%s%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n1 + lambda1 n2 lambda2 trg_fld n2 lambda2 + | _, LinkArrayToExp when !print_full_prop + -> F.fprintf f "struct%iL%i:%s -> state%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n2 lambda2 + | _, LinkArrayToStruct when !print_full_prop + -> F.fprintf f "struct%iL%i:%s -> struct%iL%i[label=\"\"]@\n" n1 lambda1 src_fld n2 lambda2 + | _, _ + -> if !print_full_prop then F.fprintf f "state%iL%i -> state%iL%i[label=\"%s\"];@\n" n1 lambda1 n2 lambda2 src_fld else () @@ -662,60 +746,72 @@ let filter_useless_spec_dollar_box (nodes: dotty_node list) (links: link list) = let tmp_nodes = ref nodes in let tmp_links = ref links in let remove_links_from ln = - List.filter - ~f:(fun n' -> not (List.mem ~equal:equal_link ln n')) - !tmp_links in + List.filter ~f:(fun n' -> not (List.mem ~equal:equal_link ln n')) !tmp_links + in let remove_node n ns = - List.filter ~f:(fun n' -> match n' with - | Dotpointsto _ -> (get_coordinate_id n') <> (get_coordinate_id n) - | _ -> true - ) ns in + List.filter + ~f:(fun n' -> + match n' with Dotpointsto _ -> get_coordinate_id n' <> get_coordinate_id n | _ -> true) + ns + in let rec boxes_pointed_by n lns = match lns with - | [] -> [] - | l:: ln' -> let n_id = get_coordinate_id n in - if Int.equal l.src.id n_id && String.equal l.src_fld "" then ( + | [] + -> [] + | l :: ln' + -> let n_id = get_coordinate_id n in + if Int.equal l.src.id n_id && String.equal l.src_fld "" then (*L.out "@\n Found link (%i,%i)" l.src.id l.trg.id;*) - l:: boxes_pointed_by n ln' - ) - else boxes_pointed_by n ln' in + l :: boxes_pointed_by n ln' + else boxes_pointed_by n ln' + in let rec boxes_pointing_at n lns = match lns with - | [] -> [] - | l:: ln' -> let n_id = get_coordinate_id n in - if Int.equal l.trg.id n_id && String.equal l.trg_fld "" then ( + | [] + -> [] + | l :: ln' + -> let n_id = get_coordinate_id n in + if Int.equal l.trg.id n_id && String.equal l.trg_fld "" then (*L.out "@\n Found link (%i,%i)" l.src.id l.trg.id;*) - l:: boxes_pointing_at n ln' ) - else boxes_pointing_at n ln' in + l :: boxes_pointing_at n ln' + else boxes_pointing_at n ln' + in let is_spec_variable = function - | Exp.Var id -> - Ident.is_normal id && Ident.equal_name (Ident.get_name id) Ident.name_spec - | _ -> false in + | Exp.Var id + -> Ident.is_normal id && Ident.equal_name (Ident.get_name id) Ident.name_spec + | _ + -> false + in let handle_one_node node = match node with - | Dotpointsto _ -> - let e = get_node_exp node in - if is_spec_variable e then begin + | Dotpointsto _ + -> let e = get_node_exp node in + if is_spec_variable e then let links_from_node = boxes_pointed_by node links in let links_to_node = boxes_pointing_at node links in - if List.is_empty links_to_node then begin - tmp_links:= remove_links_from links_from_node ; - tmp_nodes:= remove_node node !tmp_nodes; - end - end - | _ -> () in - List.iter ~f:handle_one_node nodes; - (!tmp_nodes,!tmp_links) + if List.is_empty links_to_node then ( + tmp_links := remove_links_from links_from_node ; + tmp_nodes := remove_node node !tmp_nodes ) + | _ + -> () + in + List.iter ~f:handle_one_node nodes ; (!tmp_nodes, !tmp_links) (* print a struct node *) let rec print_struct f pe e te l coo c = - let print_type = match te with - | Exp.Sizeof {typ} -> + let print_type = + match te with + | Exp.Sizeof {typ} + -> ( let str_t = Typ.to_string typ in - (match Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) str_t with - | [_; _] -> "BLOCK object" - | _ -> str_t) - | _ -> Exp.to_string te in + match Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) str_t with + | [_; _] + -> "BLOCK object" + | _ + -> str_t ) + | _ + -> Exp.to_string te + in let n = coo.id in let lambda = coo.lambda in let e_no_special_char = strip_special_chars (Exp.to_string e) in @@ -723,14 +819,13 @@ let rec print_struct f pe e te l coo c = if !print_full_prop then F.fprintf f " node [%s]; @\n struct%iL%i [label=\"{<%s%iL%i> STRUCT: %a } | %a\" ] fontcolor=%s@\n" - "shape=record" - n lambda - e_no_special_char n lambda (Sil.pp_exp_printenv pe) e (struct_to_dotty_str pe coo) l c + "shape=record" n lambda e_no_special_char n lambda (Sil.pp_exp_printenv pe) e + (struct_to_dotty_str pe coo) l c else F.fprintf f " node [%s]; @\n struct%iL%i [label=\"{<%s%iL%i> OBJECT: %s } | %a\" ] fontcolor=%s@\n" - "shape=record" - n lambda e_no_special_char n lambda print_type (struct_to_dotty_str pe coo) l c; + "shape=record" n lambda e_no_special_char n lambda print_type (struct_to_dotty_str pe coo) l + c ; F.fprintf f "}@\n" and print_array f pe e1 e2 l coo c = @@ -740,224 +835,227 @@ and print_array f pe e1 e2 l coo c = F.fprintf f "subgraph structs_%iL%i {@\n" n lambda ; F.fprintf f " node [%s]; @\n struct%iL%i [label=\"{<%s%iL%i> ARRAY| SIZE: %a } | %a\" ] fontcolor=%s@\n" - "shape=record" - n lambda e_no_special_char n lambda (Sil.pp_exp_printenv pe) e2 (get_contents pe coo) l c; + "shape=record" n lambda e_no_special_char n lambda (Sil.pp_exp_printenv pe) e2 + (get_contents pe coo) l c ; F.fprintf f "}@\n" and print_sll f pe nesting k e1 coo = let n = coo.id in let lambda = coo.lambda in let n' = !dotty_state_count in - incr dotty_state_count; - begin - match k with - | Sil.Lseg_NE -> - F.fprintf f - "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"list NE\";" - n' lambda "style=filled; color=lightgrey;" - | Sil.Lseg_PE -> - F.fprintf f - "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"list PE\";" - n' lambda "style=filled; color=lightgrey;" - end; - F.fprintf f "state%iL%i [label=\"%a\"]@\n" n lambda (Sil.pp_exp_printenv pe) e1; + incr dotty_state_count ; + ( match k with + | Sil.Lseg_NE + -> F.fprintf f + "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"list NE\";" n' + lambda "style=filled; color=lightgrey;" + | Sil.Lseg_PE + -> F.fprintf f + "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"list PE\";" n' + lambda "style=filled; color=lightgrey;" ) ; + F.fprintf f "state%iL%i [label=\"%a\"]@\n" n lambda (Sil.pp_exp_printenv pe) e1 ; let n' = !dotty_state_count in - incr dotty_state_count; + incr dotty_state_count ; F.fprintf f "state%iL%i [label=\"... \" style=filled color=lightgrey] @\n" n' lambda ; F.fprintf f "state%iL%i -> state%iL%i [label=\" \"] @\n" n lambda n' lambda ; F.fprintf f "state%iL%i [label=\" \"] @\n" (n + 1) lambda ; F.fprintf f "state%iL%i -> state%iL%i [label=\" \"] }" n' lambda (n + 1) lambda ; - incr lambda_counter; - pp_dotty f (Lambda_pred(n + 1, lambda, false)) - (Prop.normalize (Tenv.create ()) (Prop.from_sigma nesting)) None + incr lambda_counter ; + pp_dotty f (Lambda_pred (n + 1, lambda, false)) + (Prop.normalize (Tenv.create ()) (Prop.from_sigma nesting)) + None and print_dll f pe nesting k e1 e4 coo = let n = coo.id in let lambda = coo.lambda in let n' = !dotty_state_count in - incr dotty_state_count; - begin - match k with - | Sil.Lseg_NE -> - F.fprintf f - "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"%s\";" - n' lambda "style=filled; color=lightgrey;" "doubly-linked list NE" - | Sil.Lseg_PE -> - F.fprintf f - "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"%s\";" - n' lambda "style=filled; color=lightgrey;" "doubly-linked list PE" - end; - F.fprintf f "state%iL%i [label=\"%a\"]@\n" n lambda (Sil.pp_exp_printenv pe) e1; + incr dotty_state_count ; + ( match k with + | Sil.Lseg_NE + -> F.fprintf f "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"%s\";" n' + lambda "style=filled; color=lightgrey;" "doubly-linked list NE" + | Sil.Lseg_PE + -> F.fprintf f "subgraph cluster_%iL%i { %s node [style=filled,color=white]; label=\"%s\";" n' + lambda "style=filled; color=lightgrey;" "doubly-linked list PE" ) ; + F.fprintf f "state%iL%i [label=\"%a\"]@\n" n lambda (Sil.pp_exp_printenv pe) e1 ; let n' = !dotty_state_count in - incr dotty_state_count; - F.fprintf f "state%iL%i [label=\"... \" style=filled color=lightgrey] @\n" n' lambda; - F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" n lambda n' lambda; - F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" n' lambda n lambda; - F.fprintf f "state%iL%i [label=\"%a\"]@\n" (n + 1) lambda (Sil.pp_exp_printenv pe) e4; - F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" (n + 1) lambda n' lambda; + incr dotty_state_count ; + F.fprintf f "state%iL%i [label=\"... \" style=filled color=lightgrey] @\n" n' lambda ; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" n lambda n' lambda ; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" n' lambda n lambda ; + F.fprintf f "state%iL%i [label=\"%a\"]@\n" (n + 1) lambda (Sil.pp_exp_printenv pe) e4 ; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]@\n" (n + 1) lambda n' lambda ; F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]}@\n" n' lambda (n + 1) lambda ; - incr lambda_counter; - pp_dotty f (Lambda_pred(n', lambda, false)) - (Prop.normalize (Tenv.create ()) (Prop.from_sigma nesting)) None + incr lambda_counter ; + pp_dotty f (Lambda_pred (n', lambda, false)) + (Prop.normalize (Tenv.create ()) (Prop.from_sigma nesting)) + None and dotty_pp_state f pe cycle dotnode = let dotty_exp coo e c is_dangling = let n = coo.id in let lambda = coo.lambda in if is_dangling then - F.fprintf f "state%iL%i [label=\"%a \", color=red, style=dashed, fontcolor=%s]@\n" - n lambda (Sil.pp_exp_printenv pe) e c + F.fprintf f "state%iL%i [label=\"%a \", color=red, style=dashed, fontcolor=%s]@\n" n lambda + (Sil.pp_exp_printenv pe) e c else - F.fprintf f "state%iL%i [label=\"%a\" fontcolor=%s]@\n" - n lambda (Sil.pp_exp_printenv pe) e c in + F.fprintf f "state%iL%i [label=\"%a\" fontcolor=%s]@\n" n lambda (Sil.pp_exp_printenv pe) e c + in match dotnode with - | Dotnil coo when !print_full_prop -> - F.fprintf f "state%iL%i [label=\"NIL \", color=green, style=filled]@\n" coo.id coo.lambda - | Dotdangling(coo, e, c) when !print_full_prop -> dotty_exp coo e c true - | Dotpointsto(coo, e1, c) when !print_full_prop -> dotty_exp coo e1 c false - | Dotstruct(coo, e1, l, c,te) -> - let l' = if !print_full_prop then l - else List.filter ~f:(fun edge -> in_cycle cycle edge) l in + | Dotnil coo when !print_full_prop + -> F.fprintf f "state%iL%i [label=\"NIL \", color=green, style=filled]@\n" coo.id coo.lambda + | Dotdangling (coo, e, c) when !print_full_prop + -> dotty_exp coo e c true + | Dotpointsto (coo, e1, c) when !print_full_prop + -> dotty_exp coo e1 c false + | Dotstruct (coo, e1, l, c, te) + -> let l' = + if !print_full_prop then l else List.filter ~f:(fun edge -> in_cycle cycle edge) l + in print_struct f pe e1 te l' coo c - | Dotarray(coo, e1, e2, l, _, c) when !print_full_prop -> print_array f pe e1 e2 l coo c - | Dotlseg(coo, e1, _, Sil.Lseg_NE, nesting, _) when !print_full_prop -> - print_sll f pe nesting Sil.Lseg_NE e1 coo - | Dotlseg(coo, e1, _, Sil.Lseg_PE, nesting, _) when !print_full_prop -> - print_sll f pe nesting Sil.Lseg_PE e1 coo - | Dotdllseg(coo, e1, _, _, e4, Sil.Lseg_NE, nesting, _) when !print_full_prop -> - print_dll f pe nesting Sil.Lseg_NE e1 e4 coo - | Dotdllseg(coo, e1, _, _, e4, Sil.Lseg_PE, nesting, _) when !print_full_prop -> - print_dll f pe nesting Sil.Lseg_PE e1 e4 coo - | _ -> () + | Dotarray (coo, e1, e2, l, _, c) when !print_full_prop + -> print_array f pe e1 e2 l coo c + | Dotlseg (coo, e1, _, Sil.Lseg_NE, nesting, _) when !print_full_prop + -> print_sll f pe nesting Sil.Lseg_NE e1 coo + | Dotlseg (coo, e1, _, Sil.Lseg_PE, nesting, _) when !print_full_prop + -> print_sll f pe nesting Sil.Lseg_PE e1 coo + | Dotdllseg (coo, e1, _, _, e4, Sil.Lseg_NE, nesting, _) when !print_full_prop + -> print_dll f pe nesting Sil.Lseg_NE e1 e4 coo + | Dotdllseg (coo, e1, _, _, e4, Sil.Lseg_PE, nesting, _) when !print_full_prop + -> print_dll f pe nesting Sil.Lseg_PE e1 e4 coo + | _ + -> () (* Build the graph data structure to be printed *) and build_visual_graph f pe p cycle = let sigma = p.Prop.sigma in - compute_fields_struct sigma; - compute_struct_exp_nodes sigma; + compute_fields_struct sigma ; + compute_struct_exp_nodes sigma ; (* L.out "@\n@\n Computed fields structs: "; List.iter ~f:(fun e -> L.out " %a " (Sil.pp_exp_printenv pe) e) !fields_structs; L.out "@\n@."; L.out "@\n@\n Computed exp structs nodes: "; List.iter ~f:(fun e -> L.out " %a " (Sil.pp_exp_printenv pe) e) !struct_exp_nodes; L.out "@\n@."; *) - let sigma_lambda = List.map ~f:(fun hp -> (hp,!lambda_counter)) sigma in - let nodes = (dotty_mk_node pe) sigma_lambda in - if !print_full_prop then make_dangling_boxes pe nodes sigma_lambda; + let sigma_lambda = List.map ~f:(fun hp -> (hp, !lambda_counter)) sigma in + let nodes = dotty_mk_node pe sigma_lambda in + if !print_full_prop then make_dangling_boxes pe nodes sigma_lambda ; let links = dotty_mk_set_links nodes sigma_lambda p f cycle in filter_useless_spec_dollar_box nodes links and display_pure_info f pe prop = let print_invisible_objects () = for j = 1 to 4 do - F.fprintf f " inv_%i%i [style=invis]@\n" !spec_counter j; - F.fprintf f " inv_%i%i%i [style=invis]@\n" !spec_counter j j; - F.fprintf f " inv_%i%i%i%i [style=invis]@\n" !spec_counter j j j; - done; + F.fprintf f " inv_%i%i [style=invis]@\n" !spec_counter j ; + F.fprintf f " inv_%i%i%i [style=invis]@\n" !spec_counter j j ; + F.fprintf f " inv_%i%i%i%i [style=invis]@\n" !spec_counter j j j + done ; for j = 1 to 4 do - F.fprintf f " state_pi_%i -> inv_%i%i [style=invis]@\n" !proposition_counter !spec_counter j; - F.fprintf f " inv_%i%i -> inv_%i%i%i [style=invis]@\n" !spec_counter j !spec_counter j j; - F.fprintf f " inv_%i%i%i -> inv_%i%i%i%i [style=invis]@\n" - !spec_counter j j !spec_counter j j j; - done in + F.fprintf f " state_pi_%i -> inv_%i%i [style=invis]@\n" !proposition_counter !spec_counter j ; + F.fprintf f " inv_%i%i -> inv_%i%i%i [style=invis]@\n" !spec_counter j !spec_counter j j ; + F.fprintf f " inv_%i%i%i -> inv_%i%i%i%i [style=invis]@\n" !spec_counter j j !spec_counter j + j j + done + in let pure = Prop.get_pure prop in - F.fprintf f "subgraph {@\n"; + F.fprintf f "subgraph {@\n" ; F.fprintf f " node [shape=box]; @\n state_pi_%i [label=\"STACK \\n\\n %a\" color=orange style=filled]@\n" - !proposition_counter (Prop.pp_pi pe) pure; - if !invisible_arrows then print_invisible_objects (); + !proposition_counter (Prop.pp_pi pe) pure ; + if !invisible_arrows then print_invisible_objects () ; F.fprintf f "}@\n" (** Pretty print a proposition in dotty format. *) and pp_dotty f kind (_prop: Prop.normal Prop.t) cycle = - incr proposition_counter; - let pe, prop = match kind with - | Spec_postcondition pre -> - target_invisible_arrow_pre:=!proposition_counter; - let diff = Propgraph.compute_diff Black (Propgraph.from_prop pre) (Propgraph.from_prop _prop) in + incr proposition_counter ; + let pe, prop = + match kind with + | Spec_postcondition pre + -> target_invisible_arrow_pre := !proposition_counter ; + let diff = + Propgraph.compute_diff Black (Propgraph.from_prop pre) (Propgraph.from_prop _prop) + in let cmap_norm = Propgraph.diff_get_colormap false diff in let cmap_foot = Propgraph.diff_get_colormap true diff in - let pe = { (Prop.prop_update_obj_sub Pp.text pre) with cmap_norm; cmap_foot } in + let pe = {(Prop.prop_update_obj_sub Pp.text pre) with cmap_norm; cmap_foot} in (* add stack vars from pre *) let pre_stack = fst (Prop.sigma_get_stack_nonstack true pre.Prop.sigma) in let prop = Prop.set _prop ~sigma:(pre_stack @ _prop.Prop.sigma) in - pe, Prop.normalize (Tenv.create ()) prop - | _ -> - let pe = Prop.prop_update_obj_sub Pp.text _prop in - pe, _prop in - dangling_dotboxes := []; - nil_dotboxes :=[]; - set_exps_neq_zero prop.Prop.pi; - incr dotty_state_count; - F.fprintf f "@\n subgraph cluster_prop_%i { color=black @\n" !proposition_counter; - print_kind f kind; - if !print_stack_info then begin - display_pure_info f pe prop; - print_stack_info:= false - end; + (pe, Prop.normalize (Tenv.create ()) prop) + | _ + -> let pe = Prop.prop_update_obj_sub Pp.text _prop in + (pe, _prop) + in + dangling_dotboxes := [] ; + nil_dotboxes := [] ; + set_exps_neq_zero prop.Prop.pi ; + incr dotty_state_count ; + F.fprintf f "@\n subgraph cluster_prop_%i { color=black @\n" !proposition_counter ; + print_kind f kind ; + if !print_stack_info then ( + display_pure_info f pe prop ; + print_stack_info := false ) ; (* F.fprintf f "@\n subgraph cluster_%i { color=black @\n" !dotty_state_count; *) - let (nodes, links) = build_visual_graph f pe prop cycle in - let all_nodes = (nodes @ !dangling_dotboxes @ !nil_dotboxes) in - if !print_full_prop then - List.iter ~f:((dotty_pp_state f pe) cycle) all_nodes + let nodes, links = build_visual_graph f pe prop cycle in + let all_nodes = nodes @ !dangling_dotboxes @ !nil_dotboxes in + if !print_full_prop then List.iter ~f:(dotty_pp_state f pe cycle) all_nodes else - List.iter ~f:(fun node -> - if node_in_cycle cycle node then (dotty_pp_state f pe) cycle node) all_nodes; - List.iter ~f:(dotty_pp_link f) links; + List.iter + ~f:(fun node -> if node_in_cycle cycle node then dotty_pp_state f pe cycle node) + all_nodes ; + List.iter ~f:(dotty_pp_link f) links ; (* F.fprintf f "@\n } @\n"; *) F.fprintf f "@\n } @\n" let pp_dotty_one_spec f pre posts = - post_counter := 0; - incr spec_counter; - incr proposition_counter; - incr dotty_state_count; - F.fprintf f "@\n subgraph cluster_%i { color=blue @\n" !dotty_state_count; - incr dotty_state_count; + post_counter := 0 ; + incr spec_counter ; + incr proposition_counter ; + incr dotty_state_count ; + F.fprintf f "@\n subgraph cluster_%i { color=blue @\n" !dotty_state_count ; + incr dotty_state_count ; F.fprintf f "@\n state%iL0 [label=\"SPEC %i \", style=filled, color= lightblue]@\n" - !dotty_state_count !spec_counter; - spec_id:=!dotty_state_count; - invisible_arrows:= true; - pp_dotty f Spec_precondition pre None; - invisible_arrows:= false; - List.iter ~f:(fun (po, _) -> incr post_counter ; pp_dotty f (Spec_postcondition pre) po None; - for j = 1 to 4 do - F.fprintf f " inv_%i%i%i%i -> state_pi_%i [style=invis]@\n" - !spec_counter - j - j - j - !target_invisible_arrow_pre; - done - ) posts; + !dotty_state_count !spec_counter ; + spec_id := !dotty_state_count ; + invisible_arrows := true ; + pp_dotty f Spec_precondition pre None ; + invisible_arrows := false ; + List.iter + ~f:(fun (po, _) -> + incr post_counter ; + pp_dotty f (Spec_postcondition pre) po None ; + for j = 1 to 4 do + F.fprintf f " inv_%i%i%i%i -> state_pi_%i [style=invis]@\n" !spec_counter j j j + !target_invisible_arrow_pre + done) + posts ; F.fprintf f "@\n } @\n" (* this is used to print a list of proposition when considered in a path of nodes *) let pp_dotty_prop_list_in_path f plist prev_n curr_n = try - incr proposition_counter; - incr dotty_state_count; - F.fprintf f "@\n subgraph cluster_%i { color=blue @\n" !dotty_state_count; - incr dotty_state_count; - F.fprintf f "@\n state%iN [label=\"NODE %i \", style=filled, color= lightblue]@\n" - curr_n curr_n; - List.iter ~f:(fun po -> incr proposition_counter ; - pp_dotty f Generic_proposition po None) plist; - if prev_n <> - 1 then F.fprintf f "@\n state%iN ->state%iN@\n" prev_n curr_n; + incr proposition_counter ; + incr dotty_state_count ; + F.fprintf f "@\n subgraph cluster_%i { color=blue @\n" !dotty_state_count ; + incr dotty_state_count ; + F.fprintf f "@\n state%iN [label=\"NODE %i \", style=filled, color= lightblue]@\n" curr_n + curr_n ; + List.iter + ~f:(fun po -> incr proposition_counter ; pp_dotty f Generic_proposition po None) + plist ; + if prev_n <> -1 then F.fprintf f "@\n state%iN ->state%iN@\n" prev_n curr_n ; F.fprintf f "@\n } @\n" - with exn when SymOp.exn_not_failure exn -> - () + with exn when SymOp.exn_not_failure exn -> () let pp_dotty_prop fmt (prop, cycle) = - reset_proposition_counter (); - Format.fprintf fmt "@\n@\n@\ndigraph main { @\nnode [shape=box]; @\n"; - Format.fprintf fmt "@\n compound = true; rankdir =LR; @\n"; - pp_dotty fmt Generic_proposition prop (Some cycle); + reset_proposition_counter () ; + Format.fprintf fmt "@\n@\n@\ndigraph main { @\nnode [shape=box]; @\n" ; + Format.fprintf fmt "@\n compound = true; rankdir =LR; @\n" ; + pp_dotty fmt Generic_proposition prop (Some cycle) ; Format.fprintf fmt "@\n}" let dotty_prop_to_str prop cycle = - try - Some (F.asprintf "%a" (pp_dotty_prop) (prop, cycle)) + try Some (F.asprintf "%a" pp_dotty_prop (prop, cycle)) with exn when SymOp.exn_not_failure exn -> None (* create a dotty file with a single proposition *) @@ -965,104 +1063,107 @@ let dotty_prop_to_dotty_file fname prop cycle = try let out_dot = Out_channel.create fname in let fmt_dot = Format.formatter_of_out_channel out_dot in - pp_dotty_prop fmt_dot (prop, cycle); + pp_dotty_prop fmt_dot (prop, cycle) ; Out_channel.close out_dot - with exn when SymOp.exn_not_failure exn -> - () + with exn when SymOp.exn_not_failure exn -> () (* This is used only to print a list of prop parsed with the external parser. Basically deprecated.*) let pp_proplist_parsed2dotty_file filename plist = try let pp_list f plist = - reset_proposition_counter (); - F.fprintf f "@\n@\n@\ndigraph main { @\nnode [shape=box];@\n"; - F.fprintf f "@\n compound = true; @\n"; - F.fprintf f "@\n /* size=\"12,7\"; ratio=fill;*/ @\n"; - ignore (List.map ~f:(pp_dotty f Generic_proposition) plist); - F.fprintf f "@\n}" in + reset_proposition_counter () ; + F.fprintf f "@\n@\n@\ndigraph main { @\nnode [shape=box];@\n" ; + F.fprintf f "@\n compound = true; @\n" ; + F.fprintf f "@\n /* size=\"12,7\"; ratio=fill;*/ @\n" ; + ignore (List.map ~f:(pp_dotty f Generic_proposition) plist) ; + F.fprintf f "@\n}" + in let outc = Out_channel.create filename in let fmt = F.formatter_of_out_channel outc in - F.fprintf fmt "#### Dotty version: ####@.%a@.@." pp_list plist; - Out_channel.close outc - with exn when SymOp.exn_not_failure exn -> - () + F.fprintf fmt "#### Dotty version: ####@.%a@.@." pp_list plist ; Out_channel.close outc + with exn when SymOp.exn_not_failure exn -> () (********** START of Print interprocedural cfgs in dotty format *) (********** Print control flow graph (in dot form) for fundec to *) (* channel. You have to compute an interprocedural cfg first *) -let pp_cfgnodename pname fmt (n : Procdesc.Node.t) = +let pp_cfgnodename pname fmt (n: Procdesc.Node.t) = F.fprintf fmt "\"%s_%d\"" (Typ.Procname.to_filename pname) (Procdesc.Node.get_id n :> int) let pp_etlist fmt etl = - List.iter ~f:(fun (id, ty) -> - Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) etl + List.iter + ~f:(fun (id, ty) -> Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) + etl let pp_local_list fmt etl = - List.iter ~f:(fun (id, ty) -> - Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) etl + List.iter + ~f:(fun (id, ty) -> Format.fprintf fmt " %a:%a" Mangled.pp id (Typ.pp_full Pp.text) ty) + etl -let pp_cfgnodelabel pdesc fmt (n : Procdesc.Node.t) = +let pp_cfgnodelabel pdesc fmt (n: Procdesc.Node.t) = let pp_label fmt n = match Procdesc.Node.get_kind n with - | Procdesc.Node.Start_node pname -> - let pname_string = Typ.Procname.to_string pname in - Format.fprintf fmt "Start %s\\nFormals: %a\\nLocals: %a" - pname_string - pp_etlist (Procdesc.get_formals pdesc) - pp_local_list (Procdesc.get_locals pdesc); + | Procdesc.Node.Start_node pname + -> let pname_string = Typ.Procname.to_string pname in + Format.fprintf fmt "Start %s\\nFormals: %a\\nLocals: %a" pname_string pp_etlist + (Procdesc.get_formals pdesc) pp_local_list (Procdesc.get_locals pdesc) ; if List.length (Procdesc.get_captured pdesc) <> 0 then - Format.fprintf fmt "\\nCaptured: %a" - pp_local_list (Procdesc.get_captured pdesc); + Format.fprintf fmt "\\nCaptured: %a" pp_local_list (Procdesc.get_captured pdesc) ; let attributes = Procdesc.get_attributes pdesc in let method_annotation = attributes.ProcAttributes.method_annotation in if not (Annot.Method.is_empty method_annotation) then Format.fprintf fmt "\\nAnnotation: %a" (Annot.Method.pp pname_string) method_annotation - | Procdesc.Node.Exit_node pname -> - Format.fprintf fmt "Exit %s" (Typ.Procname.to_string pname) - | Procdesc.Node.Join_node -> - Format.fprintf fmt "+" - | Procdesc.Node.Prune_node (is_true_branch, _, _) -> - Format.fprintf fmt "Prune (%b branch)" is_true_branch - | Procdesc.Node.Stmt_node s -> Format.fprintf fmt " %s" s - | Procdesc.Node.Skip_node s -> Format.fprintf fmt "Skip %s" s in + | Procdesc.Node.Exit_node pname + -> Format.fprintf fmt "Exit %s" (Typ.Procname.to_string pname) + | Procdesc.Node.Join_node + -> Format.fprintf fmt "+" + | Procdesc.Node.Prune_node (is_true_branch, _, _) + -> Format.fprintf fmt "Prune (%b branch)" is_true_branch + | Procdesc.Node.Stmt_node s + -> Format.fprintf fmt " %s" s + | Procdesc.Node.Skip_node s + -> Format.fprintf fmt "Skip %s" s + in let instr_string i = let pp f = Sil.pp_instr Pp.text f i in let str = F.asprintf "%t" pp in - Escape.escape_dotty str in + Escape.escape_dotty str + in let pp_instrs fmt instrs = - List.iter ~f:(fun i -> F.fprintf fmt " %s\\n " (instr_string i)) instrs in + List.iter ~f:(fun i -> F.fprintf fmt " %s\\n " (instr_string i)) instrs + in let instrs = Procdesc.Node.get_instrs n in F.fprintf fmt "%d: %a \\n %a" (Procdesc.Node.get_id n :> int) pp_label n pp_instrs instrs let pp_cfgnodeshape fmt (n: Procdesc.Node.t) = match Procdesc.Node.get_kind n with - | Procdesc.Node.Start_node _ - | Procdesc.Node.Exit_node _ -> F.fprintf fmt "color=yellow style=filled" - | Procdesc.Node.Prune_node _ -> F.fprintf fmt "shape=\"invhouse\"" - | Procdesc.Node.Skip_node _ -> F.fprintf fmt "color=\"gray\"" - | Procdesc.Node.Stmt_node _ -> F.fprintf fmt "shape=\"box\"" - | _ -> F.fprintf fmt "" + | Procdesc.Node.Start_node _ | Procdesc.Node.Exit_node _ + -> F.fprintf fmt "color=yellow style=filled" + | Procdesc.Node.Prune_node _ + -> F.fprintf fmt "shape=\"invhouse\"" + | Procdesc.Node.Skip_node _ + -> F.fprintf fmt "color=\"gray\"" + | Procdesc.Node.Stmt_node _ + -> F.fprintf fmt "shape=\"box\"" + | _ + -> F.fprintf fmt "" let pp_cfgnode pdesc fmt (n: Procdesc.Node.t) = let pname = Procdesc.get_proc_name pdesc in - F.fprintf fmt "%a [label=\"%a\" %a]@\n\t@\n" - (pp_cfgnodename pname) n - (pp_cfgnodelabel pdesc) n - pp_cfgnodeshape n; + F.fprintf fmt "%a [label=\"%a\" %a]@\n\t@\n" (pp_cfgnodename pname) n (pp_cfgnodelabel pdesc) n + pp_cfgnodeshape n ; let print_edge n1 n2 is_exn = let color = if is_exn then "[color=\"red\" ]" else "" in match Procdesc.Node.get_kind n2 with - | Procdesc.Node.Exit_node _ - when is_exn -> (* don't print exception edges to the exit node *) + | Procdesc.Node.Exit_node _ when is_exn + -> (* don't print exception edges to the exit node *) () - | _ -> - F.fprintf fmt "@\n\t %a -> %a %s;" - (pp_cfgnodename pname) n1 - (pp_cfgnodename pname) n2 - color in - List.iter ~f:(fun n' -> print_edge n n' false) (Procdesc.Node.get_succs n); + | _ + -> F.fprintf fmt "@\n\t %a -> %a %s;" (pp_cfgnodename pname) n1 (pp_cfgnodename pname) n2 + color + in + List.iter ~f:(fun n' -> print_edge n n' false) (Procdesc.Node.get_succs n) ; List.iter ~f:(fun n' -> print_edge n n' true) (Procdesc.Node.get_exn n) (* * print control flow graph (in dot form) for fundec to channel let *) @@ -1072,36 +1173,37 @@ let pp_cfgnode pdesc fmt (n: Procdesc.Node.t) = (* print_cfg_filename (filename : string) (fd : fundec) = let chan = *) (* open_out filename in begin print_cfg_channel chan fd; close_out chan; *) (* end *) - (* Print the extra information related to the inteprocedural aspect, ie., *) (* special node, and call / return edges *) let print_icfg source fmt cfg = let print_node pdesc node = let loc = Procdesc.Node.get_loc node in - if (Config.dotty_cfg_libs || SourceFile.equal loc.Location.file source) then - F.fprintf fmt "%a@\n" (pp_cfgnode pdesc) node in + if Config.dotty_cfg_libs || SourceFile.equal loc.Location.file source then + F.fprintf fmt "%a@\n" (pp_cfgnode pdesc) node + in Cfg.iter_all_nodes ~sorted:true print_node cfg let write_icfg_dotty_to_file source cfg fname = let chan = Out_channel.create fname in let fmt = Format.formatter_of_out_channel chan in (* avoid phabricator thinking this file was generated by substituting substring with %s *) - F.fprintf fmt "/* %@%s */@\ndigraph iCFG {@\n" "generated"; - print_icfg source fmt cfg; - F.fprintf fmt "}@\n"; + F.fprintf fmt "/* %@%s */@\ndigraph iCFG {@\n" "generated" ; + print_icfg source fmt cfg ; + F.fprintf fmt "}@\n" ; Out_channel.close chan let print_icfg_dotty source cfg = let fname = match Config.icfg_dotty_outfile with - | Some file -> file - | None when Config.frontend_tests -> - (SourceFile.to_abs_path source) ^ ".test.dot" - | None -> - DB.filename_to_string - (DB.Results_dir.path_to_filename - (DB.Results_dir.Abs_source_dir source) - [Config.dotty_output]) in + | Some file + -> file + | None when Config.frontend_tests + -> SourceFile.to_abs_path source ^ ".test.dot" + | None + -> DB.filename_to_string + (DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) + [Config.dotty_output]) + in write_icfg_dotty_to_file source cfg fname (********** END of Printing dotty files ***********) @@ -1109,52 +1211,48 @@ let print_icfg_dotty source cfg = (** Dotty printing for specs *) let pp_speclist_dotty f (splist: Prop.normal Specs.spec list) = let pp_simple_saved = !Config.pp_simple in - Config.pp_simple := true; - reset_proposition_counter (); - reset_dotty_spec_counter (); - F.fprintf f "@\n@\n@\ndigraph main { @\nnode [shape=box]; @\n"; - F.fprintf f "@\n compound = true; @\n"; + Config.pp_simple := true ; + reset_proposition_counter () ; + reset_dotty_spec_counter () ; + F.fprintf f "@\n@\n@\ndigraph main { @\nnode [shape=box]; @\n" ; + F.fprintf f "@\n compound = true; @\n" ; (* F.fprintf f "@\n size=\"12,7\"; ratio=fill; @\n"; *) List.iter ~f:(fun s -> pp_dotty_one_spec f (Specs.Jprop.to_prop s.Specs.pre) s.Specs.posts) - splist; - F.fprintf f "@\n}"; + splist ; + F.fprintf f "@\n}" ; Config.pp_simple := pp_simple_saved -let pp_speclist_to_file (filename : DB.filename) spec_list = +let pp_speclist_to_file (filename: DB.filename) spec_list = let pp_simple_saved = !Config.pp_simple in - Config.pp_simple := true; + Config.pp_simple := true ; let outc = Out_channel.create (DB.filename_to_string (DB.filename_add_suffix filename ".dot")) in let fmt = F.formatter_of_out_channel outc in - let () = F.fprintf fmt "#### Dotty version: ####@\n%a@\n@\n" (pp_speclist_dotty) spec_list in - Out_channel.close outc; + let () = F.fprintf fmt "#### Dotty version: ####@\n%a@\n@\n" pp_speclist_dotty spec_list in + Out_channel.close outc ; Config.pp_simple := pp_simple_saved -let pp_speclist_dotty_file (filename : DB.filename) spec_list = +let pp_speclist_dotty_file (filename: DB.filename) spec_list = try pp_speclist_to_file filename spec_list - with exn when SymOp.exn_not_failure exn -> - () + with exn when SymOp.exn_not_failure exn -> () (**********************************************************************) (* Code prodicing a xml version of a graph *) (**********************************************************************) - (* each node has an unique integer identifier *) type visual_heap_node = | VH_dangling of int * Exp.t - | VH_pointsto of int * Exp.t * Sil.strexp * Exp.t (* VH_pointsto(id,address,content,type) *) - | VH_lseg of int * Exp.t * Exp.t * Sil.lseg_kind (*VH_lseg(id,address,content last cell, kind) *) + | VH_pointsto of int * Exp.t * Sil.strexp * Exp.t + (* VH_pointsto(id,address,content,type) *) + | VH_lseg of int * Exp.t * Exp.t * Sil.lseg_kind + (*VH_lseg(id,address,content last cell, kind) *) (*VH_dllseg(id, address, content first cell, content last cell, address last cell, kind) *) | VH_dllseg of int * Exp.t * Exp.t * Exp.t * Exp.t * Sil.lseg_kind (* an edge is a pair of node identifiers*) -type visual_heap_edge = { - src: int; - trg: int; - lab: string -} +type visual_heap_edge = {src: int; trg: int; lab: string} -let mk_visual_heap_edge s t l = { src = s; trg = t; lab = l } +let mk_visual_heap_edge s t l = {src= s; trg= t; lab= l} (* used to generate unique identifier for all the nodes in the set of visual graphs used to *) (* represent a proposition*) @@ -1166,47 +1264,55 @@ let set_dangling_nodes = ref [] (* convert an exp into a string which is xml friendly, ie. special character are replaced by*) (* the proper xml way to visualize them*) -let exp_to_xml_string e = - F.asprintf "%a" (Sil.pp_exp_printenv (Pp.html Black)) e +let exp_to_xml_string e = F.asprintf "%a" (Sil.pp_exp_printenv (Pp.html Black)) e (* convert an atom into an xml-friendly string without special characters *) -let atom_to_xml_string a = - F.asprintf "%a" (Sil.pp_atom (Pp.html Black)) a +let atom_to_xml_string a = F.asprintf "%a" (Sil.pp_atom (Pp.html Black)) a (* return the dangling node corresponding to an expression it exists or None *) let exp_dangling_node e = - let entry_e = List.filter ~f:(fun b -> match b with - | VH_dangling(_, e') -> Exp.equal e e' | _ -> false ) !set_dangling_nodes in + let entry_e = + List.filter + ~f:(fun b -> match b with VH_dangling (_, e') -> Exp.equal e e' | _ -> false) + !set_dangling_nodes + in match entry_e with - |[] -> None - | VH_dangling(n, e') :: _ -> Some (VH_dangling(n, e')) - | _ -> None (* NOTE: this cannot be possible since entry_e can be composed only by VH_dangling, see def of entry_e*) - + | [] + -> None + | (VH_dangling (n, e')) :: _ + -> Some (VH_dangling (n, e')) + | _ + -> None + +(* NOTE: this cannot be possible since entry_e can be composed only by VH_dangling, see def of entry_e*) (* make nodes and when it finds a list records in the working list *) (* to do (n, prop) where n is the integer identifier of the list node. *) (* This allow to keep the connection between the list node and the graph *) (* that displays its contents. *) let rec make_visual_heap_nodes sigma = let n = !global_node_counter in - incr global_node_counter; + incr global_node_counter ; match sigma with - | [] -> [] - | Sil.Hpointsto (e, se, t):: sigma' -> - VH_pointsto(n, e, se, t):: make_visual_heap_nodes sigma' - | Sil.Hlseg (k, hpara, e1, e2, _):: sigma' -> - working_list:= (n, hpara.Sil.body)::!working_list; - VH_lseg(n, e1, e2, k):: make_visual_heap_nodes sigma' - | Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, _):: sigma'-> - working_list:= (n, hpara_dll.Sil.body_dll)::!working_list; - VH_dllseg(n, e1, e2, e3, e4, k):: make_visual_heap_nodes sigma' + | [] + -> [] + | (Sil.Hpointsto (e, se, t)) :: sigma' + -> VH_pointsto (n, e, se, t) :: make_visual_heap_nodes sigma' + | (Sil.Hlseg (k, hpara, e1, e2, _)) :: sigma' + -> working_list := (n, hpara.Sil.body) :: !working_list ; + VH_lseg (n, e1, e2, k) :: make_visual_heap_nodes sigma' + | (Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, _)) :: sigma' + -> working_list := (n, hpara_dll.Sil.body_dll) :: !working_list ; + VH_dllseg (n, e1, e2, e3, e4, k) :: make_visual_heap_nodes sigma' (* given a node returns its id and address*) let get_node_id_and_addr node = - match node with - | VH_dangling(n, e) - | VH_pointsto(n, e, _, _) - | VH_lseg(n, e, _ , _) - | VH_dllseg(n, e, _, _, _, _) -> (n, e) + match node + with + | VH_dangling (n, e) + | VH_pointsto (n, e, _, _) + | VH_lseg (n, e, _, _) + | VH_dllseg (n, e, _, _, _, _) + -> (n, e) (* return node's id*) let get_node_id node = fst (get_node_id_and_addr node) @@ -1217,47 +1323,54 @@ let get_node_addr node = snd (get_node_id_and_addr node) (* return the nodes corresponding to an address given by an expression *) let rec select_node_at_address nodes e = match nodes with - | [] -> None - | n:: l' -> - let e' = get_node_addr n in - if (Exp.equal e e') then Some n - else select_node_at_address l' e + | [] + -> None + | n :: l' + -> let e' = get_node_addr n in + if Exp.equal e e' then Some n else select_node_at_address l' e (* look-up the ids in the list of nodes corresponding to expression e*) (* let look_up_nodes_ids nodes e = List.map ~f:get_node_id (select_nodes_exp nodes e) *) - (* create a list of dangling nodes *) let make_set_dangling_nodes allocated_nodes (sigma: Sil.hpred list) = let make_new_dangling e = let n = !global_node_counter in - incr global_node_counter; - VH_dangling(n, e) in + incr global_node_counter ; VH_dangling (n, e) + in let get_rhs_predicate hpred = - (match hpred with - | Sil.Hpointsto (_, Sil.Eexp (e, _), _) when not (Exp.equal e Exp.zero) -> [e] - | Sil.Hlseg (_, _, _, e2, _) when not (Exp.equal e2 Exp.zero) -> [e2] - | Sil.Hdllseg (_, _, _, e2, e3, _, _) -> - if (Exp.equal e2 Exp.zero) then - if (Exp.equal e3 Exp.zero) then [] - else [e3] - else [e2; e3] - | Sil.Hpointsto (_, _, _) - | _ -> [] (* arrays and struct do not give danglings. CHECK THIS!*) - ) in + match hpred with + | Sil.Hpointsto (_, Sil.Eexp (e, _), _) when not (Exp.equal e Exp.zero) + -> [e] + | Sil.Hlseg (_, _, _, e2, _) when not (Exp.equal e2 Exp.zero) + -> [e2] + | Sil.Hdllseg (_, _, _, e2, e3, _, _) + -> if Exp.equal e2 Exp.zero then if Exp.equal e3 Exp.zero then [] else [e3] else [e2; e3] + | Sil.Hpointsto (_, _, _) | _ + -> [] + (* arrays and struct do not give danglings. CHECK THIS!*) + in let is_not_allocated e = - let allocated = List.exists ~f:(fun a -> match a with - | VH_pointsto(_, e', _, _) - | VH_lseg(_, e', _ , _) - | VH_dllseg(_, e', _, _, _, _) -> Exp.equal e e' - | _ -> false ) allocated_nodes in - not allocated in + let allocated = + List.exists + ~f:(fun a -> + match a with + | VH_pointsto (_, e', _, _) | VH_lseg (_, e', _, _) | VH_dllseg (_, e', _, _, _, _) + -> Exp.equal e e' + | _ + -> false) + allocated_nodes + in + not allocated + in let rec filter_duplicate l seen_exp = match l with - | [] -> [] - | e:: l' -> - if (List.exists ~f:(Exp.equal e) seen_exp) then filter_duplicate l' seen_exp - else e:: filter_duplicate l' (e:: seen_exp) in + | [] + -> [] + | e :: l' + -> if List.exists ~f:(Exp.equal e) seen_exp then filter_duplicate l' seen_exp + else e :: filter_duplicate l' (e :: seen_exp) + in let rhs_exp_list = List.concat_map ~f:get_rhs_predicate sigma in let candidate_dangling_exps = filter_duplicate rhs_exp_list [] in (* get rid of allocated ones*) @@ -1269,115 +1382,130 @@ let make_set_dangling_nodes allocated_nodes (sigma: Sil.hpred list) = (* field_lab is the name of the field which points to n (if any)*) let rec compute_target_nodes_from_sexp nodes se prop field_lab = match se with - | Sil.Eexp (e, _) when is_nil e prop -> - (* Nil is not represented by a node, it's just a value which should be printed*) + | Sil.Eexp (e, _) when is_nil e prop + -> (* Nil is not represented by a node, it's just a value which should be printed*) [] - | Sil.Eexp (e, _) -> + | Sil.Eexp (e, _) + -> ( let e_node = select_node_at_address nodes e in - (match e_node with - | None -> - (match exp_dangling_node e with - | None -> [] - | Some dang_node -> [(dang_node, field_lab)] - ) - | Some n -> [(n, field_lab)] - ) - | Sil.Estruct (lfld, inst) -> - (match lfld with - | [] -> [] - | (fn, se2):: l' -> - compute_target_nodes_from_sexp nodes se2 prop (Typ.Fieldname.to_string fn) @ - compute_target_nodes_from_sexp nodes (Sil.Estruct (l', inst)) prop "" - ) + match e_node with + | None -> ( + match exp_dangling_node e with None -> [] | Some dang_node -> [(dang_node, field_lab)] ) + | Some n + -> [(n, field_lab)] ) + | Sil.Estruct (lfld, inst) -> ( + match lfld with + | [] + -> [] + | (fn, se2) :: l' + -> compute_target_nodes_from_sexp nodes se2 prop (Typ.Fieldname.to_string fn) + @ compute_target_nodes_from_sexp nodes (Sil.Estruct (l', inst)) prop "" ) | Sil.Earray (len, lie, inst) -> - (match lie with - | [] -> [] - | (idx, se2):: l' -> - let lab ="["^exp_to_xml_string idx^"]" in - compute_target_nodes_from_sexp nodes se2 prop lab @ - compute_target_nodes_from_sexp nodes (Sil.Earray (len, l', inst)) prop "" - ) - + match lie with + | [] + -> [] + | (idx, se2) :: l' + -> let lab = "[" ^ exp_to_xml_string idx ^ "]" in + compute_target_nodes_from_sexp nodes se2 prop lab + @ compute_target_nodes_from_sexp nodes (Sil.Earray (len, l', inst)) prop "" (* build the set of edges between nodes *) let rec make_visual_heap_edges nodes sigma prop = let combine_source_target_label n (m, lab) = - mk_visual_heap_edge (get_node_id n) (get_node_id m) lab in + mk_visual_heap_edge (get_node_id n) (get_node_id m) lab + in match sigma with - | [] -> [] - | Sil.Hpointsto (e, se, _):: sigma' -> + | [] + -> [] + | (Sil.Hpointsto (e, se, _)) :: sigma' + -> ( let e_node = select_node_at_address nodes e in - (match e_node with - | None -> assert false - | Some n -> - let target_nodes = compute_target_nodes_from_sexp nodes se prop "" in - let ll = List.map ~f:(combine_source_target_label n) target_nodes in - ll @ make_visual_heap_edges nodes sigma' prop - ) - | Sil.Hlseg (_, _, e1, e2, _):: sigma' -> - let e1_node = select_node_at_address nodes e1 in - (match e1_node with - | None -> assert false - | Some n -> - let target_nodes = compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" in - let ll = List.map ~f:(combine_source_target_label n) target_nodes in - ll @ make_visual_heap_edges nodes sigma' prop - ) - - | Sil.Hdllseg (_, _, e1, e2, e3, _, _):: sigma' -> + match e_node with + | None + -> assert false + | Some n + -> let target_nodes = compute_target_nodes_from_sexp nodes se prop "" in + let ll = List.map ~f:(combine_source_target_label n) target_nodes in + ll @ make_visual_heap_edges nodes sigma' prop ) + | (Sil.Hlseg (_, _, e1, e2, _)) :: sigma' + -> ( let e1_node = select_node_at_address nodes e1 in - (match e1_node with - | None -> assert false - | Some n -> - let target_nodesF = compute_target_nodes_from_sexp nodes (Sil.Eexp (e3, Sil.inst_none)) prop "" in - let target_nodesB = compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" in - let llF = List.map ~f:(combine_source_target_label n) target_nodesF in - let llB = List.map ~f:(combine_source_target_label n) target_nodesB in - llF @ llB @ make_visual_heap_edges nodes sigma' prop - ) + match e1_node with + | None + -> assert false + | Some n + -> let target_nodes = + compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" + in + let ll = List.map ~f:(combine_source_target_label n) target_nodes in + ll @ make_visual_heap_edges nodes sigma' prop ) + | (Sil.Hdllseg (_, _, e1, e2, e3, _, _)) :: sigma' + -> let e1_node = select_node_at_address nodes e1 in + match e1_node with + | None + -> assert false + | Some n + -> let target_nodesF = + compute_target_nodes_from_sexp nodes (Sil.Eexp (e3, Sil.inst_none)) prop "" + in + let target_nodesB = + compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" + in + let llF = List.map ~f:(combine_source_target_label n) target_nodesF in + let llB = List.map ~f:(combine_source_target_label n) target_nodesB in + llF @ llB @ make_visual_heap_edges nodes sigma' prop (* from a prop generate and return visual proposition *) let prop_to_set_of_visual_heaps prop = let result = ref [] in - working_list := [(!global_node_counter, prop.Prop.sigma)]; - incr global_node_counter; - while (!working_list <> []) do - set_dangling_nodes:=[]; - let (n, h) = List.hd_exn !working_list in - working_list:= List.tl_exn !working_list; + working_list := [(!global_node_counter, prop.Prop.sigma)] ; + incr global_node_counter ; + while !working_list <> [] do + set_dangling_nodes := [] ; + let n, h = List.hd_exn !working_list in + working_list := List.tl_exn !working_list ; let nodes = make_visual_heap_nodes h in - set_dangling_nodes:= make_set_dangling_nodes nodes h; + set_dangling_nodes := make_set_dangling_nodes nodes h ; let edges = make_visual_heap_edges nodes h prop in - result:= !result @ [(n, nodes @ !set_dangling_nodes, edges)]; - done; + result := !result @ [(n, nodes @ !set_dangling_nodes, edges)] + done ; !result let rec pointsto_contents_to_xml (co: Sil.strexp) : Io_infer.Xml.node = match co with - | Sil.Eexp (e, _) -> - Io_infer.Xml.create_tree "cell" [("content-value", exp_to_xml_string e)] [] - | Sil.Estruct (fel, _) -> - let f (fld, exp) = Io_infer.Xml.create_tree "struct-field" [("id", Typ.Fieldname.to_string fld)] [(pointsto_contents_to_xml exp)] in + | Sil.Eexp (e, _) + -> Io_infer.Xml.create_tree "cell" [("content-value", exp_to_xml_string e)] [] + | Sil.Estruct (fel, _) + -> let f (fld, exp) = + Io_infer.Xml.create_tree "struct-field" [("id", Typ.Fieldname.to_string fld)] + [pointsto_contents_to_xml exp] + in Io_infer.Xml.create_tree "struct" [] (List.map ~f fel) - | Sil.Earray (len, nel, _) -> - let f (e, se) = Io_infer.Xml.create_tree "array-element" [("index", exp_to_xml_string e)] [pointsto_contents_to_xml se] in + | Sil.Earray (len, nel, _) + -> let f (e, se) = + Io_infer.Xml.create_tree "array-element" [("index", exp_to_xml_string e)] + [pointsto_contents_to_xml se] + in Io_infer.Xml.create_tree "array" [("size", exp_to_xml_string len)] (List.map ~f nel) (* Convert an atom to xml in a light version. Namely, the expressions are not fully blown-up into *) (* xml tree but visualized as strings *) let atom_to_xml_light (a: Sil.atom) : Io_infer.Xml.node = - let kind_info = match a with - | Sil.Aeq _ when Prop.atom_is_inequality a -> - "inequality" - | Sil.Aeq _ -> - "equality" - | Sil.Aneq _ -> - "disequality" - | Sil.Apred _ -> - "pred" - | Sil.Anpred _ -> - "npred" in - Io_infer.Xml.create_tree "stack-variable" [("type", kind_info); ("instance", atom_to_xml_string a)] [] + let kind_info = + match a with + | Sil.Aeq _ when Prop.atom_is_inequality a + -> "inequality" + | Sil.Aeq _ + -> "equality" + | Sil.Aneq _ + -> "disequality" + | Sil.Apred _ + -> "pred" + | Sil.Anpred _ + -> "npred" + in + Io_infer.Xml.create_tree "stack-variable" + [("type", kind_info); ("instance", atom_to_xml_string a)] [] let xml_pure_info prop = let pure = Prop.get_pure prop in @@ -1386,43 +1514,71 @@ let xml_pure_info prop = (** Return a string describing the kind of a pointsto address *) let pointsto_addr_kind = function - | Exp.Lvar pv -> - if Pvar.is_global pv - then "global" - else if Pvar.is_local pv && Mangled.equal (Pvar.get_name pv) Ident.name_return - then "return" - else if Pvar.is_local pv - then "parameter" + | Exp.Lvar pv + -> if Pvar.is_global pv then "global" + else if Pvar.is_local pv && Mangled.equal (Pvar.get_name pv) Ident.name_return then "return" + else if Pvar.is_local pv then "parameter" else "other" - | _ -> "other" + | _ + -> "other" let heap_node_to_xml node = match node with - | VH_dangling(id, addr) -> - let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","dangling"); ("memory-type", pointsto_addr_kind addr)] in + | VH_dangling (id, addr) + -> let atts = + [ ("id", string_of_int id) + ; ("address", exp_to_xml_string addr) + ; ("node-type", "dangling") + ; ("memory-type", pointsto_addr_kind addr) ] + in Io_infer.Xml.create_tree "node" atts [] - | VH_pointsto(id, addr, cont, _) -> - let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","allocated"); ("memory-type", pointsto_addr_kind addr)] in + | VH_pointsto (id, addr, cont, _) + -> let atts = + [ ("id", string_of_int id) + ; ("address", exp_to_xml_string addr) + ; ("node-type", "allocated") + ; ("memory-type", pointsto_addr_kind addr) ] + in let contents = pointsto_contents_to_xml cont in Io_infer.Xml.create_tree "node" atts [contents] - | VH_lseg(id, addr, _, Sil.Lseg_NE) -> - let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","single linked list"); ("list-type","non-empty"); ("memory-type", "other")] in + | VH_lseg (id, addr, _, Sil.Lseg_NE) + -> let atts = + [ ("id", string_of_int id) + ; ("address", exp_to_xml_string addr) + ; ("node-type", "single linked list") + ; ("list-type", "non-empty") + ; ("memory-type", "other") ] + in Io_infer.Xml.create_tree "node" atts [] - | VH_lseg(id, addr, _, Sil.Lseg_PE) -> - let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","single linked list"); ("list-type","possibly empty"); ("memory-type", "other")] in + | VH_lseg (id, addr, _, Sil.Lseg_PE) + -> let atts = + [ ("id", string_of_int id) + ; ("address", exp_to_xml_string addr) + ; ("node-type", "single linked list") + ; ("list-type", "possibly empty") + ; ("memory-type", "other") ] + in Io_infer.Xml.create_tree "node" atts [] - | VH_dllseg(id, addr1, cont1, cont2, addr2, _) -> - let contents1 = pointsto_contents_to_xml (Sil.Eexp (cont1, Sil.inst_none)) in + | VH_dllseg (id, addr1, cont1, cont2, addr2, _) + -> let contents1 = pointsto_contents_to_xml (Sil.Eexp (cont1, Sil.inst_none)) in let contents2 = pointsto_contents_to_xml (Sil.Eexp (cont2, Sil.inst_none)) in - let atts =[("id", string_of_int id); ("addr-first", exp_to_xml_string addr1); ("addr-last", exp_to_xml_string addr2); ("node-type","double linked list"); ("memory-type", "other") ] in - Io_infer.Xml.create_tree "node" atts [contents1 ; contents2] + let atts = + [ ("id", string_of_int id) + ; ("addr-first", exp_to_xml_string addr1) + ; ("addr-last", exp_to_xml_string addr2) + ; ("node-type", "double linked list") + ; ("memory-type", "other") ] + in + Io_infer.Xml.create_tree "node" atts [contents1; contents2] let heap_edge_to_xml edge = - let atts =[("source", string_of_int edge.src); ("target", string_of_int edge.trg); ("label", edge.lab) ] in + let atts = + [("source", string_of_int edge.src); ("target", string_of_int edge.trg); ("label", edge.lab)] + in Io_infer.Xml.create_tree "edge" atts [] let visual_heap_to_xml heap = - let (n, nodes, edges) = heap in + let n, nodes, edges = heap in let xml_heap_nodes = List.map ~f:heap_node_to_xml nodes in let xml_heap_edges = List.map ~f:heap_edge_to_xml edges in Io_infer.Xml.create_tree "heap" [("id", string_of_int n)] (xml_heap_nodes @ xml_heap_edges) @@ -1432,40 +1588,49 @@ let prop_to_xml prop tag_name id = let visual_heaps = prop_to_set_of_visual_heaps prop in let xml_visual_heaps = List.map ~f:visual_heap_to_xml visual_heaps in let xml_pure_part = xml_pure_info prop in - let xml_graph = Io_infer.Xml.create_tree tag_name [("id", string_of_int id)] (xml_visual_heaps @ [xml_pure_part]) in + let xml_graph = + Io_infer.Xml.create_tree tag_name [("id", string_of_int id)] + (xml_visual_heaps @ [xml_pure_part]) + in xml_graph (** reset the counter used for node and heap identifiers *) -let reset_node_counter () = - global_node_counter := 0 +let reset_node_counter () = global_node_counter := 0 let print_specs_xml signature specs loc fmt = - reset_node_counter (); + reset_node_counter () ; let do_one_spec pre posts n = let add_stack_to_prop _prop = (* add stack vars from pre *) let pre_stack = fst (Prop.sigma_get_stack_nonstack true pre.Prop.sigma) in let _prop' = Prop.set _prop ~sigma:(pre_stack @ _prop.Prop.sigma) in - Prop.normalize (Tenv.create ()) _prop' in + Prop.normalize (Tenv.create ()) _prop' + in let jj = ref 0 in let xml_pre = prop_to_xml pre "precondition" !jj in let xml_spec = - xml_pre :: - (List.map ~f:(fun (po, _) -> - jj := !jj + 1; prop_to_xml (add_stack_to_prop po) "postcondition" !jj - ) posts) in - Io_infer.Xml.create_tree "specification" [("id", string_of_int n)] xml_spec in + xml_pre + :: List.map + ~f:(fun (po, _) -> + jj := !jj + 1 ; + prop_to_xml (add_stack_to_prop po) "postcondition" !jj) + posts + in + Io_infer.Xml.create_tree "specification" [("id", string_of_int n)] xml_spec + in let j = ref 0 in let list_of_specs_xml = List.map ~f:(fun s -> - j:=!j + 1; - do_one_spec (Specs.Jprop.to_prop s.Specs.pre) s.Specs.posts !j) - specs in + j := !j + 1 ; + do_one_spec (Specs.Jprop.to_prop s.Specs.pre) s.Specs.posts !j) + specs + in let xml_specifications = Io_infer.Xml.create_tree "specifications" [] list_of_specs_xml in let xml_signature = Io_infer.Xml.create_tree "signature" [("name", signature)] [] in - let proc_summary = Io_infer.Xml.create_tree "procedure" - [("file", SourceFile.to_string loc.Location.file); - ("line", string_of_int loc.Location.line)] - [xml_signature; xml_specifications] in + let proc_summary = + Io_infer.Xml.create_tree "procedure" + [("file", SourceFile.to_string loc.Location.file); ("line", string_of_int loc.Location.line)] + [xml_signature; xml_specifications] + in Io_infer.Xml.pp_document true fmt proc_summary diff --git a/infer/src/backend/dotty.mli b/infer/src/backend/dotty.mli index 27b6ce31a..4cb250c86 100644 --- a/infer/src/backend/dotty.mli +++ b/infer/src/backend/dotty.mli @@ -17,44 +17,47 @@ open! IStd type kind_of_dotty_prop = | Generic_proposition | Spec_precondition - | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) + | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) | Lambda_pred of int * int * bool val reset_proposition_counter : unit -> unit -val pp_dotty : Format.formatter -> kind_of_dotty_prop -> Prop.normal Prop.t -> - ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list option -> unit +val pp_dotty : + Format.formatter -> kind_of_dotty_prop -> Prop.normal Prop.t + -> ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list option -> unit (** {2 Sets and lists of propositions} *) -val pp_dotty_prop_list_in_path: Format.formatter -> Prop.normal Prop.t list -> int -> int -> unit +val pp_dotty_prop_list_in_path : Format.formatter -> Prop.normal Prop.t list -> int -> int -> unit val pp_proplist_parsed2dotty_file : string -> Prop.normal Prop.t list -> unit (** {2 Contol-Flow Graph} *) -(** Print the cfg *) val print_icfg_dotty : SourceFile.t -> Cfg.cfg -> unit +(** Print the cfg *) -(** {2 Specs} *) val reset_dotty_spec_counter : unit -> unit +(** {2 Specs} *) -(** Dotty printing for specs *) val pp_speclist_dotty_file : DB.filename -> Prop.normal Specs.spec list -> unit +(** Dotty printing for specs *) (* create a dotty file with a single proposition *) -val dotty_prop_to_dotty_file : string -> Prop.normal Prop.t -> - ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> unit -val dotty_prop_to_str : Prop.normal Prop.t -> - ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> string option +val dotty_prop_to_dotty_file : + string -> Prop.normal Prop.t -> ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list + -> unit + +val dotty_prop_to_str : + Prop.normal Prop.t -> ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> string option -(** reset the counter used for node and heap identifiers *) val reset_node_counter : unit -> unit +(** reset the counter used for node and heap identifiers *) -(** convert a proposition to xml with the given tag and id *) val prop_to_xml : Prop.normal Prop.t -> string -> int -> Io_infer.Xml.node +(** convert a proposition to xml with the given tag and id *) -(** Print a list of specs in XML format *) val print_specs_xml : string -> Prop.normal Specs.spec list -> Location.t -> Format.formatter -> unit +(** Print a list of specs in XML format *) diff --git a/infer/src/backend/errdesc.ml b/infer/src/backend/errdesc.ml index 59bc003e5..00d96c4ff 100644 --- a/infer/src/backend/errdesc.ml +++ b/infer/src/backend/errdesc.ml @@ -17,45 +17,47 @@ module F = Format module DExp = DecompiledExp let vector_matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["std::vector"] -let mutex_matcher = QualifiedCppName.Match.of_fuzzy_qual_names [ - "std::__infer_mutex_model"; - "std::mutex"; - "std::timed_mutex"; - ] -let is_one_of_classes = QualifiedCppName.Match.match_qualifiers +let mutex_matcher = + QualifiedCppName.Match.of_fuzzy_qual_names + ["std::__infer_mutex_model"; "std::mutex"; "std::timed_mutex"] +let is_one_of_classes = QualifiedCppName.Match.match_qualifiers let is_method_of_objc_cpp_class pname matcher = match pname with - | Typ.Procname.ObjC_Cpp objc_cpp -> - let class_qual_opt = Typ.Procname.objc_cpp_get_class_qualifiers objc_cpp in + | Typ.Procname.ObjC_Cpp objc_cpp + -> let class_qual_opt = Typ.Procname.objc_cpp_get_class_qualifiers objc_cpp in is_one_of_classes matcher class_qual_opt - | _ -> false + | _ + -> false -let is_mutex_method pname = - is_method_of_objc_cpp_class pname mutex_matcher +let is_mutex_method pname = is_method_of_objc_cpp_class pname mutex_matcher -let is_vector_method pname = - is_method_of_objc_cpp_class pname vector_matcher +let is_vector_method pname = is_method_of_objc_cpp_class pname vector_matcher let is_special_field matcher field_name_opt field = let field_name = Typ.Fieldname.to_flat_string field in let class_qual_opt = Typ.Fieldname.clang_get_qual_class field in let field_ok = match field_name_opt with - | Some field_name' -> String.equal field_name' field_name - | None -> true in + | Some field_name' + -> String.equal field_name' field_name + | None + -> true + in field_ok && Option.value_map ~f:(is_one_of_classes matcher) ~default:false class_qual_opt (** Check whether the hpred is a |-> representing a resource in the Racquire state *) let hpred_is_open_resource tenv prop = function - | Sil.Hpointsto(e, _, _) -> - (match Attribute.get_resource tenv prop e with - | Some (Apred (Aresource { ra_kind = Racquire; ra_res = res }, _)) -> Some res - | _ -> None) - | _ -> - None + | Sil.Hpointsto (e, _, _) -> ( + match Attribute.get_resource tenv prop e with + | Some Apred (Aresource {ra_kind= Racquire; ra_res= res}, _) + -> Some res + | _ + -> None ) + | _ + -> None (** Produce a description of a persistent reference to an Android Context *) let explain_context_leak pname context_typ fieldname error_path = @@ -69,9 +71,9 @@ let explain_deallocate_stack_var pvar ra = (** Explain a deallocate constant string error *) let explain_deallocate_constant_string s ra = let const_str = - let pp fmt = - Exp.pp fmt (Exp.Const (Const.Cstr s)) in - F.asprintf "%t" pp in + let pp fmt = Exp.pp fmt (Exp.Const (Const.Cstr s)) in + F.asprintf "%t" pp + in Localise.desc_deallocate_static_memory const_str ra.PredSymb.ra_pname ra.PredSymb.ra_loc let verbose = Config.trace_error @@ -80,21 +82,26 @@ let find_in_node_or_preds start_node f_node_instr = let visited = ref Procdesc.NodeSet.empty in let rec find node = if Procdesc.NodeSet.mem node !visited then None - else - begin - visited := Procdesc.NodeSet.add node !visited; - let instrs = Procdesc.Node.get_instrs node in - match List.find_map ~f:(f_node_instr node) (List.rev instrs) with - | Some res -> Some res - | None -> List.find_map ~f:find (Procdesc.Node.get_preds node) - end in + else ( + visited := Procdesc.NodeSet.add node !visited ; + let instrs = Procdesc.Node.get_instrs node in + match List.find_map ~f:(f_node_instr node) (List.rev instrs) with + | Some res + -> Some res + | None + -> List.find_map ~f:find (Procdesc.Node.get_preds node) ) + in find start_node (** Find the Set instruction used to assign [id] to a program variable, if any *) let find_variable_assigment node id : Sil.instr option = - let find_set _ instr = match instr with - | Sil.Store (Exp.Lvar _, _, e, _) when Exp.equal (Exp.Var id) e -> Some instr - | _ -> None in + let find_set _ instr = + match instr with + | Sil.Store (Exp.Lvar _, _, e, _) when Exp.equal (Exp.Var id) e + -> Some instr + | _ + -> None + in find_in_node_or_preds node find_set (** Check if a nullify instruction exists for the program variable after the given instruction *) @@ -102,70 +109,77 @@ let find_nullify_after_instr node instr pvar : bool = let node_instrs = Procdesc.Node.get_instrs node in let found_instr = ref false in let find_nullify = function - | Sil.Nullify (pv, _) when !found_instr -> Pvar.equal pv pvar - | instr_ -> - if Sil.equal_instr instr instr_ then found_instr := true; - false in + | Sil.Nullify (pv, _) when !found_instr + -> Pvar.equal pv pvar + | instr_ + -> if Sil.equal_instr instr instr_ then found_instr := true ; + false + in List.exists ~f:find_nullify node_instrs (** Find the other prune node of a conditional (e.g. the false branch given the true branch of a conditional) *) let find_other_prune_node node = match Procdesc.Node.get_preds node with - | [n_pre] -> - (match Procdesc.Node.get_succs n_pre with - | [n1; n2] -> - if Procdesc.Node.equal n1 node then Some n2 else Some n1 - | _ -> None) - | _ -> None + | [n_pre] -> ( + match Procdesc.Node.get_succs n_pre with + | [n1; n2] + -> if Procdesc.Node.equal n1 node then Some n2 else Some n1 + | _ + -> None ) + | _ + -> None (** Return true if [id] is assigned to a program variable which is then nullified *) let id_is_assigned_then_dead node id = match find_variable_assigment node id with | Some (Sil.Store (Exp.Lvar pvar, _, _, _) as instr) - when Pvar.is_local pvar || Pvar.is_callee pvar -> - let is_prune = match Procdesc.Node.get_kind node with - | Procdesc.Node.Prune_node _ -> true - | _ -> false in + when Pvar.is_local pvar || Pvar.is_callee pvar + -> let is_prune = + match Procdesc.Node.get_kind node with Procdesc.Node.Prune_node _ -> true | _ -> false + in let prune_check = function (* if prune node, check that it's also nullified in the other branch *) - | Some node' -> - (match Procdesc.Node.get_instrs node' with - | instr':: _ -> find_nullify_after_instr node' instr' pvar - | _ -> false) - | _ -> false in + | Some node' -> ( + match Procdesc.Node.get_instrs node' with + | instr' :: _ + -> find_nullify_after_instr node' instr' pvar + | _ + -> false ) + | _ + -> false + in find_nullify_after_instr node instr pvar && (not is_prune || prune_check (find_other_prune_node node)) - | _ -> false + | _ + -> false (** Find the function call instruction used to initialize normal variable [id], and return the function name and arguments *) -let find_normal_variable_funcall - (node: Procdesc.Node.t) - (id: Ident.t): (Exp.t * (Exp.t list) * Location.t * CallFlags.t) option = +let find_normal_variable_funcall (node: Procdesc.Node.t) (id: Ident.t) + : (Exp.t * Exp.t list * Location.t * CallFlags.t) option = let find_declaration _ = function - | Sil.Call (Some (id0, _), fun_exp, args, loc, call_flags) when Ident.equal id id0 -> - Some (fun_exp, List.map ~f:fst args, loc, call_flags) - | _ -> None in + | Sil.Call (Some (id0, _), fun_exp, args, loc, call_flags) when Ident.equal id id0 + -> Some (fun_exp, List.map ~f:fst args, loc, call_flags) + | _ + -> None + in let res = find_in_node_or_preds node find_declaration in - if verbose && is_none res - then - (L.d_str - ("find_normal_variable_funcall could not find " ^ - Ident.to_string id ^ - " in node " ^ - string_of_int (Procdesc.Node.get_id node :> int)); - L.d_ln ()); + if verbose && is_none res then ( + L.d_str + ( "find_normal_variable_funcall could not find " ^ Ident.to_string id ^ " in node " + ^ string_of_int (Procdesc.Node.get_id node :> int) ) ; + L.d_ln () ) ; res (** Find a program variable assignment in the current node or predecessors. *) let find_program_variable_assignment node pvar : (Procdesc.Node.t * Ident.t) option = let find_instr node = function - | Sil.Store (Exp.Lvar _pvar, _, Exp.Var id, _) - when Pvar.equal pvar _pvar && Ident.is_normal id -> - Some (node, id) - | _ -> - None in + | Sil.Store (Exp.Lvar _pvar, _, Exp.Var id, _) when Pvar.equal pvar _pvar && Ident.is_normal id + -> Some (node, id) + | _ + -> None + in find_in_node_or_preds node find_instr (** Special case for C++, where we translate code like @@ -173,27 +187,28 @@ let find_program_variable_assignment node pvar : (Procdesc.Node.t * Ident.t) opt `void getX(struct X * frontend_generated_pvar)`. This lets us recognize that X was returned from getX *) let find_struct_by_value_assignment node pvar = - if Pvar.is_frontend_tmp pvar - then + if Pvar.is_frontend_tmp pvar then let find_instr node = function - | Sil.Call (_, Const (Cfun pname), args, loc, cf) -> - begin - match List.last args with - | Some (Exp.Lvar last_arg, _) when Pvar.equal pvar last_arg -> - Some (node, pname, loc, cf) - | _ -> - None - end - | _ -> - None in + | Sil.Call (_, Const Cfun pname, args, loc, cf) -> ( + match List.last args with + | Some (Exp.Lvar last_arg, _) when Pvar.equal pvar last_arg + -> Some (node, pname, loc, cf) + | _ + -> None ) + | _ + -> None + in find_in_node_or_preds node find_instr else None (** Find a program variable assignment to id in the current node or predecessors. *) let find_ident_assignment node id : (Procdesc.Node.t * Exp.t) option = let find_instr node = function - | Sil.Load (_id, e, _, _) when Ident.equal _id id -> Some (node, e) - | _ -> None in + | Sil.Load (_id, e, _, _) when Ident.equal _id id + -> Some (node, e) + | _ + -> None + in find_in_node_or_preds node find_instr (** Find a boolean assignment to a temporary variable holding a boolean condition. @@ -201,224 +216,223 @@ let find_ident_assignment node id : (Procdesc.Node.t * Exp.t) option = let rec find_boolean_assignment node pvar true_branch : Procdesc.Node.t option = let find_instr n = let filter = function - | Sil.Store (Exp.Lvar _pvar, _, Exp.Const (Const.Cint i), _) when Pvar.equal pvar _pvar -> - IntLit.iszero i <> true_branch - | _ -> false in - List.exists ~f:filter (Procdesc.Node.get_instrs n) in + | Sil.Store (Exp.Lvar _pvar, _, Exp.Const Const.Cint i, _) when Pvar.equal pvar _pvar + -> IntLit.iszero i <> true_branch + | _ + -> false + in + List.exists ~f:filter (Procdesc.Node.get_instrs n) + in match Procdesc.Node.get_preds node with - | [pred_node] -> find_boolean_assignment pred_node pvar true_branch - | [n1; n2] -> - if find_instr n1 then (Some n1) - else if find_instr n2 then (Some n2) - else None - | _ -> None + | [pred_node] + -> find_boolean_assignment pred_node pvar true_branch + | [n1; n2] + -> if find_instr n1 then Some n1 else if find_instr n2 then Some n2 else None + | _ + -> None (** Find the Load instruction used to declare normal variable [id], and return the expression dereferenced to initialize [id] *) -let rec _find_normal_variable_load tenv (seen : Exp.Set.t) node id : DExp.t option = +let rec _find_normal_variable_load tenv (seen: Exp.Set.t) node id : DExp.t option = let find_declaration node = function - | Sil.Load (id0, e, _, _) when Ident.equal id id0 -> - if verbose - then - (L.d_str "find_normal_variable_load defining "; - Sil.d_exp e; L.d_ln ()); + | Sil.Load (id0, e, _, _) when Ident.equal id id0 + -> if verbose then ( L.d_str "find_normal_variable_load defining " ; Sil.d_exp e ; L.d_ln () ) ; _exp_lv_dexp tenv seen node e - | Sil.Call (Some (id0, _), Exp.Const (Const.Cfun pn), (e, _):: _, _, _) - when Ident.equal id id0 && Typ.Procname.equal pn (Typ.Procname.from_string_c_fun "__cast") -> - if verbose - then - (L.d_str "find_normal_variable_load cast on "; - Sil.d_exp e; L.d_ln ()); + | Sil.Call (Some (id0, _), Exp.Const Const.Cfun pn, (e, _) :: _, _, _) + when Ident.equal id id0 && Typ.Procname.equal pn (Typ.Procname.from_string_c_fun "__cast") + -> if verbose then ( L.d_str "find_normal_variable_load cast on " ; Sil.d_exp e ; L.d_ln () ) ; _exp_rv_dexp tenv seen node e - | Sil.Call (Some (id0, _), (Exp.Const (Const.Cfun pname) as fun_exp), args, loc, call_flags) - when Ident.equal id id0 -> - if verbose - then - (L.d_str "find_normal_variable_load function call "; - Sil.d_exp fun_exp; L.d_ln ()); - + | Sil.Call (Some (id0, _), (Exp.Const Const.Cfun pname as fun_exp), args, loc, call_flags) + when Ident.equal id id0 + -> if verbose then ( + L.d_str "find_normal_variable_load function call " ; Sil.d_exp fun_exp ; L.d_ln () ) ; let fun_dexp = DExp.Dconst (Const.Cfun pname) in let args_dexp = let args_dexpo = List.map ~f:(fun (e, _) -> _exp_rv_dexp tenv seen node e) args in - if List.exists ~f:is_none args_dexpo - then [] + if List.exists ~f:is_none args_dexpo then [] else let unNone = function Some x -> x | None -> assert false in - List.map ~f:unNone args_dexpo in + List.map ~f:unNone args_dexpo + in Some (DExp.Dretcall (fun_dexp, args_dexp, loc, call_flags)) | Sil.Store (Exp.Lvar pvar, _, Exp.Var id0, _) - when Config.biabduction && Ident.equal id id0 && not (Pvar.is_frontend_tmp pvar) -> - (* this case is a hack to make bucketing continue to work in the presence of copy + when Config.biabduction && Ident.equal id id0 && not (Pvar.is_frontend_tmp pvar) + -> (* this case is a hack to make bucketing continue to work in the presence of copy propagation. previously, we would have code like: n1 = foo(); x = n1; n2 = x; n2.toString(), but copy-propagation will optimize this to: n1 = foo(); x = n1; n1.toString(). This case allows us to recognize the association between n1 and x. Eradicate/checkers don't use copy-prop, so they don't need this. *) Some (DExp.Dpvar pvar) - | _ -> None in + | _ + -> None + in let res = find_in_node_or_preds node find_declaration in - if verbose && is_none res - then - (L.d_str - ("find_normal_variable_load could not find " ^ - Ident.to_string id ^ - " in node " ^ - string_of_int (Procdesc.Node.get_id node :> int)); - L.d_ln ()); + if verbose && is_none res then ( + L.d_str + ( "find_normal_variable_load could not find " ^ Ident.to_string id ^ " in node " + ^ string_of_int (Procdesc.Node.get_id node :> int) ) ; + L.d_ln () ) ; res (** describe lvalue [e] as a dexp *) -and _exp_lv_dexp tenv (_seen : Exp.Set.t) node e : DExp.t option = - if Exp.Set.mem e _seen then - (L.d_str "exp_lv_dexp: cycle detected"; Sil.d_exp e; L.d_ln (); None) +and _exp_lv_dexp tenv (_seen: Exp.Set.t) node e : DExp.t option = + if Exp.Set.mem e _seen then ( + L.d_str "exp_lv_dexp: cycle detected" ; Sil.d_exp e ; L.d_ln () ; None ) else let seen = Exp.Set.add e _seen in match Prop.exp_normalize_noabs tenv Sil.sub_empty e with - | Exp.Const c -> - if verbose then (L.d_str "exp_lv_dexp: constant "; Sil.d_exp e; L.d_ln ()); + | Exp.Const c + -> if verbose then ( L.d_str "exp_lv_dexp: constant " ; Sil.d_exp e ; L.d_ln () ) ; Some (DExp.Dderef (DExp.Dconst c)) - | Exp.BinOp(Binop.PlusPI, e1, e2) -> - if verbose then (L.d_str "exp_lv_dexp: (e1 +PI e2) "; Sil.d_exp e; L.d_ln ()); - (match _exp_lv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2 with - | Some de1, Some de2 -> Some (DExp.Dbinop(Binop.PlusPI, de1, de2)) - | _ -> None) - | Exp.Var id when Ident.is_normal id -> - if verbose then (L.d_str "exp_lv_dexp: normal var "; Sil.d_exp e; L.d_ln ()); - (match _find_normal_variable_load tenv seen node id with - | None -> None - | Some de -> Some (DExp.Dderef de)) - | Exp.Lvar pvar -> - if verbose then (L.d_str "exp_lv_dexp: program var "; Sil.d_exp e; L.d_ln ()); + | Exp.BinOp (Binop.PlusPI, e1, e2) + -> ( + if verbose then ( L.d_str "exp_lv_dexp: (e1 +PI e2) " ; Sil.d_exp e ; L.d_ln () ) ; + match (_exp_lv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2) with + | Some de1, Some de2 + -> Some (DExp.Dbinop (Binop.PlusPI, de1, de2)) + | _ + -> None ) + | Exp.Var id when Ident.is_normal id + -> ( + if verbose then ( L.d_str "exp_lv_dexp: normal var " ; Sil.d_exp e ; L.d_ln () ) ; + match _find_normal_variable_load tenv seen node id with + | None + -> None + | Some de + -> Some (DExp.Dderef de) ) + | Exp.Lvar pvar + -> if verbose then ( L.d_str "exp_lv_dexp: program var " ; Sil.d_exp e ; L.d_ln () ) ; if Pvar.is_frontend_tmp pvar then - begin - match find_program_variable_assignment node pvar with - | None -> - begin - match find_struct_by_value_assignment node pvar with - | Some (_, pname, loc, call_flags) -> - Some (DExp.Dfcall (DExp.Dconst (Cfun pname), [], loc, call_flags)) - | None -> - None - end - | Some (node', id) -> - begin - match find_normal_variable_funcall node' id with - | Some (fun_exp, eargs, loc, call_flags) -> - let fun_dexpo = _exp_rv_dexp tenv seen node' fun_exp in - let blame_args = List.map ~f:(_exp_rv_dexp tenv seen node') eargs in - if List.exists ~f:is_none (fun_dexpo:: blame_args) then None - else - let unNone = function Some x -> x | None -> assert false in - let args = List.map ~f:unNone blame_args in - Some (DExp.Dfcall (unNone fun_dexpo, args, loc, call_flags)) - | None -> - _exp_rv_dexp tenv seen node' (Exp.Var id) - end - end + match find_program_variable_assignment node pvar with + | None -> ( + match find_struct_by_value_assignment node pvar with + | Some (_, pname, loc, call_flags) + -> Some (DExp.Dfcall (DExp.Dconst (Cfun pname), [], loc, call_flags)) + | None + -> None ) + | Some (node', id) -> + match find_normal_variable_funcall node' id with + | Some (fun_exp, eargs, loc, call_flags) + -> let fun_dexpo = _exp_rv_dexp tenv seen node' fun_exp in + let blame_args = List.map ~f:(_exp_rv_dexp tenv seen node') eargs in + if List.exists ~f:is_none (fun_dexpo :: blame_args) then None + else + let unNone = function Some x -> x | None -> assert false in + let args = List.map ~f:unNone blame_args in + Some (DExp.Dfcall (unNone fun_dexpo, args, loc, call_flags)) + | None + -> _exp_rv_dexp tenv seen node' (Exp.Var id) else Some (DExp.Dpvar pvar) - | Exp.Lfield (Exp.Var id, f, _) when Ident.is_normal id -> - if verbose then - begin - L.d_str "exp_lv_dexp: Lfield with var "; - Sil.d_exp (Exp.Var id); - L.d_str (" " ^ Typ.Fieldname.to_string f); - L.d_ln () - end; - (match _find_normal_variable_load tenv seen node id with - | None -> None - | Some de -> Some (DExp.Darrow (de, f))) - | Exp.Lfield (e1, f, _) -> - if verbose then - begin - L.d_str "exp_lv_dexp: Lfield "; - Sil.d_exp e1; - L.d_str (" " ^ Typ.Fieldname.to_string f); - L.d_ln () - end; - (match _exp_lv_dexp tenv seen node e1 with - | None -> None - | Some de -> Some (DExp.Ddot (de, f))) - | Exp.Lindex (e1, e2) -> - if verbose then - begin - L.d_str "exp_lv_dexp: Lindex "; - Sil.d_exp e1; - L.d_str " "; - Sil.d_exp e2; - L.d_ln () - end; - (match _exp_lv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2 with - | None, _ -> None - | Some de1, None -> - (* even if the index is unknown, the array info is useful for bound errors *) - Some (DExp.Darray (de1, DExp.Dunknown)) - | Some de1, Some de2 -> Some (DExp.Darray (de1, de2))) - | _ -> - if verbose then (L.d_str "exp_lv_dexp: no match for "; Sil.d_exp e; L.d_ln ()); + | Exp.Lfield (Exp.Var id, f, _) when Ident.is_normal id + -> ( + if verbose then ( + L.d_str "exp_lv_dexp: Lfield with var " ; + Sil.d_exp (Exp.Var id) ; + L.d_str (" " ^ Typ.Fieldname.to_string f) ; + L.d_ln () ) ; + match _find_normal_variable_load tenv seen node id with + | None + -> None + | Some de + -> Some (DExp.Darrow (de, f)) ) + | Exp.Lfield (e1, f, _) + -> ( + if verbose then ( + L.d_str "exp_lv_dexp: Lfield " ; + Sil.d_exp e1 ; + L.d_str (" " ^ Typ.Fieldname.to_string f) ; + L.d_ln () ) ; + match _exp_lv_dexp tenv seen node e1 with + | None + -> None + | Some de + -> Some (DExp.Ddot (de, f)) ) + | Exp.Lindex (e1, e2) + -> ( + if verbose then ( + L.d_str "exp_lv_dexp: Lindex " ; Sil.d_exp e1 ; L.d_str " " ; Sil.d_exp e2 ; L.d_ln () ) ; + match (_exp_lv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2) with + | None, _ + -> None + | Some de1, None + -> (* even if the index is unknown, the array info is useful for bound errors *) + Some (DExp.Darray (de1, DExp.Dunknown)) + | Some de1, Some de2 + -> Some (DExp.Darray (de1, de2)) ) + | _ + -> if verbose then ( L.d_str "exp_lv_dexp: no match for " ; Sil.d_exp e ; L.d_ln () ) ; None (** describe rvalue [e] as a dexp *) -and _exp_rv_dexp tenv (_seen : Exp.Set.t) node e : DExp.t option = - if Exp.Set.mem e _seen then - (L.d_str "exp_rv_dexp: cycle detected"; Sil.d_exp e; L.d_ln (); None) +and _exp_rv_dexp tenv (_seen: Exp.Set.t) node e : DExp.t option = + if Exp.Set.mem e _seen then ( + L.d_str "exp_rv_dexp: cycle detected" ; Sil.d_exp e ; L.d_ln () ; None ) else let seen = Exp.Set.add e _seen in match e with - | Exp.Const c -> - if verbose then (L.d_str "exp_rv_dexp: constant "; Sil.d_exp e; L.d_ln ()); + | Exp.Const c + -> if verbose then ( L.d_str "exp_rv_dexp: constant " ; Sil.d_exp e ; L.d_ln () ) ; Some (DExp.Dconst c) - | Exp.Lvar pv -> - if verbose then (L.d_str "exp_rv_dexp: program var "; Sil.d_exp e; L.d_ln ()); - if Pvar.is_frontend_tmp pv - then _exp_lv_dexp tenv _seen (* avoid spurious cycle detection *) node e + | Exp.Lvar pv + -> if verbose then ( L.d_str "exp_rv_dexp: program var " ; Sil.d_exp e ; L.d_ln () ) ; + if Pvar.is_frontend_tmp pv then + _exp_lv_dexp tenv _seen (* avoid spurious cycle detection *) node e else Some (DExp.Dpvaraddr pv) - | Exp.Var id when Ident.is_normal id -> - if verbose then (L.d_str "exp_rv_dexp: normal var "; Sil.d_exp e; L.d_ln ()); + | Exp.Var id when Ident.is_normal id + -> if verbose then ( L.d_str "exp_rv_dexp: normal var " ; Sil.d_exp e ; L.d_ln () ) ; _find_normal_variable_load tenv seen node id - | Exp.Lfield (e1, f, _) -> - if verbose then - begin - L.d_str "exp_rv_dexp: Lfield "; - Sil.d_exp e1; - L.d_str (" " ^ Typ.Fieldname.to_string f); - L.d_ln () - end; - (match _exp_rv_dexp tenv seen node e1 with - | None -> None - | Some de -> Some (DExp.Ddot(de, f))) - | Exp.Lindex (e1, e2) -> - if verbose then - begin - L.d_str "exp_rv_dexp: Lindex "; - Sil.d_exp e1; - L.d_str " "; - Sil.d_exp e2; - L.d_ln () - end; - (match _exp_rv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2 with - | None, _ | _, None -> None - | Some de1, Some de2 -> Some (DExp.Darray(de1, de2))) - | Exp.BinOp (op, e1, e2) -> - if verbose then (L.d_str "exp_rv_dexp: BinOp "; Sil.d_exp e; L.d_ln ()); - (match _exp_rv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2 with - | None, _ | _, None -> None - | Some de1, Some de2 -> Some (DExp.Dbinop (op, de1, de2))) - | Exp.UnOp (op, e1, _) -> - if verbose then (L.d_str "exp_rv_dexp: UnOp "; Sil.d_exp e; L.d_ln ()); - (match _exp_rv_dexp tenv seen node e1 with - | None -> None - | Some de1 -> Some (DExp.Dunop (op, de1))) - | Exp.Cast (_, e1) -> - if verbose then (L.d_str "exp_rv_dexp: Cast "; Sil.d_exp e; L.d_ln ()); + | Exp.Lfield (e1, f, _) + -> ( + if verbose then ( + L.d_str "exp_rv_dexp: Lfield " ; + Sil.d_exp e1 ; + L.d_str (" " ^ Typ.Fieldname.to_string f) ; + L.d_ln () ) ; + match _exp_rv_dexp tenv seen node e1 with + | None + -> None + | Some de + -> Some (DExp.Ddot (de, f)) ) + | Exp.Lindex (e1, e2) + -> ( + if verbose then ( + L.d_str "exp_rv_dexp: Lindex " ; Sil.d_exp e1 ; L.d_str " " ; Sil.d_exp e2 ; L.d_ln () ) ; + match (_exp_rv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2) with + | None, _ | _, None + -> None + | Some de1, Some de2 + -> Some (DExp.Darray (de1, de2)) ) + | Exp.BinOp (op, e1, e2) + -> ( + if verbose then ( L.d_str "exp_rv_dexp: BinOp " ; Sil.d_exp e ; L.d_ln () ) ; + match (_exp_rv_dexp tenv seen node e1, _exp_rv_dexp tenv seen node e2) with + | None, _ | _, None + -> None + | Some de1, Some de2 + -> Some (DExp.Dbinop (op, de1, de2)) ) + | Exp.UnOp (op, e1, _) + -> ( + if verbose then ( L.d_str "exp_rv_dexp: UnOp " ; Sil.d_exp e ; L.d_ln () ) ; + match _exp_rv_dexp tenv seen node e1 with + | None + -> None + | Some de1 + -> Some (DExp.Dunop (op, de1)) ) + | Exp.Cast (_, e1) + -> if verbose then ( L.d_str "exp_rv_dexp: Cast " ; Sil.d_exp e ; L.d_ln () ) ; _exp_rv_dexp tenv seen node e1 - | Exp.Sizeof {typ; dynamic_length; subtype} -> - if verbose then (L.d_str "exp_rv_dexp: type "; Sil.d_exp e; L.d_ln ()); - Some (DExp.Dsizeof (typ, Option.bind dynamic_length ~f:(_exp_rv_dexp tenv seen node), subtype)) - | _ -> - if verbose then (L.d_str "exp_rv_dexp: no match for "; Sil.d_exp e; L.d_ln ()); + | Exp.Sizeof {typ; dynamic_length; subtype} + -> if verbose then ( L.d_str "exp_rv_dexp: type " ; Sil.d_exp e ; L.d_ln () ) ; + Some + (DExp.Dsizeof (typ, Option.bind dynamic_length ~f:(_exp_rv_dexp tenv seen node), subtype)) + | _ + -> if verbose then ( L.d_str "exp_rv_dexp: no match for " ; Sil.d_exp e ; L.d_ln () ) ; None let find_normal_variable_load tenv = _find_normal_variable_load tenv Exp.Set.empty + let exp_lv_dexp tenv = _exp_lv_dexp tenv Exp.Set.empty + let exp_rv_dexp tenv = _exp_rv_dexp tenv Exp.Set.empty (** Produce a description of a mismatch between an allocation function @@ -427,59 +441,63 @@ let explain_allocation_mismatch ra_alloc ra_dealloc = let get_primitive_called is_alloc ra = (* primitive alloc/dealloc function ultimately used, and function actually called *) (* e.g. malloc and my_malloc *) - let primitive = match ra.PredSymb.ra_res with - | PredSymb.Rmemory mk_alloc -> - (if is_alloc then PredSymb.mem_alloc_pname else PredSymb.mem_dealloc_pname) mk_alloc - | _ -> ra_alloc.PredSymb.ra_pname in + let primitive = + match ra.PredSymb.ra_res with + | PredSymb.Rmemory mk_alloc + -> (if is_alloc then PredSymb.mem_alloc_pname else PredSymb.mem_dealloc_pname) mk_alloc + | _ + -> ra_alloc.PredSymb.ra_pname + in let called = ra.PredSymb.ra_pname in - (primitive, called, ra.PredSymb.ra_loc) in - Localise.desc_allocation_mismatch - (get_primitive_called true ra_alloc) (get_primitive_called false ra_dealloc) + (primitive, called, ra.PredSymb.ra_loc) + in + Localise.desc_allocation_mismatch (get_primitive_called true ra_alloc) + (get_primitive_called false ra_dealloc) (** check whether the type of leaked [hpred] appears as a predicate in an inductive predicate in [prop] *) let leak_from_list_abstraction hpred prop = let hpred_type = function - | Sil.Hpointsto (_, _, texp) -> - Some texp - | Sil.Hlseg (_, { Sil.body =[Sil.Hpointsto (_, _, texp)]}, _, _, _) -> - Some texp - | Sil.Hdllseg (_, { Sil.body_dll =[Sil.Hpointsto (_, _, texp)]}, _, _, _, _, _) -> - Some texp - | _ -> None in + | Sil.Hpointsto (_, _, texp) + -> Some texp + | Sil.Hlseg (_, {Sil.body= [(Sil.Hpointsto (_, _, texp))]}, _, _, _) + -> Some texp + | Sil.Hdllseg (_, {Sil.body_dll= [(Sil.Hpointsto (_, _, texp))]}, _, _, _, _, _) + -> Some texp + | _ + -> None + in let found = ref false in - let check_hpred texp hp = match hpred_type hp with - | Some texp' when Exp.equal texp texp' -> found := true - | _ -> () in - let check_hpara texp _ hpara = - List.iter ~f:(check_hpred texp) hpara.Sil.body in - let check_hpara_dll texp _ hpara = - List.iter ~f:(check_hpred texp) hpara.Sil.body_dll in + let check_hpred texp hp = + match hpred_type hp with Some texp' when Exp.equal texp texp' -> found := true | _ -> () + in + let check_hpara texp _ hpara = List.iter ~f:(check_hpred texp) hpara.Sil.body in + let check_hpara_dll texp _ hpara = List.iter ~f:(check_hpred texp) hpara.Sil.body_dll in match hpred_type hpred with - | Some texp -> - let env = Prop.prop_pred_env prop in - Sil.Predicates.iter env (check_hpara texp) (check_hpara_dll texp); - if !found - then - (L.d_str "leak_from_list_abstraction of predicate of type "; - Sil.d_texp_full texp; L.d_ln()); + | Some texp + -> let env = Prop.prop_pred_env prop in + Sil.Predicates.iter env (check_hpara texp) (check_hpara_dll texp) ; + if !found then ( + L.d_str "leak_from_list_abstraction of predicate of type " ; + Sil.d_texp_full texp ; + L.d_ln () ) ; !found - | None -> false + | None + -> false (** find the type of hpred, if any *) -let find_hpred_typ hpred = match hpred with - | Sil.Hpointsto (_, _, texp) -> Some texp - | _ -> None +let find_hpred_typ hpred = match hpred with Sil.Hpointsto (_, _, texp) -> Some texp | _ -> None (** find the type of pvar and remove the pointer, if any *) let find_typ_without_ptr prop pvar = let res = ref None in let do_hpred = function - | Sil.Hpointsto (e, _, te) when Exp.equal e (Exp.Lvar pvar) -> - res := Some te - | _ -> () in - List.iter ~f:do_hpred prop.Prop.sigma; - !res + | Sil.Hpointsto (e, _, te) when Exp.equal e (Exp.Lvar pvar) + -> res := Some te + | _ + -> () + in + List.iter ~f:do_hpred prop.Prop.sigma ; !res (** Produce a description of a leak by looking at the current state. If the current instruction is a variable nullify, blame the variable. @@ -493,481 +511,512 @@ let explain_leak tenv hpred prop alloc_att_opt bucket = let hpred_typ_opt = find_hpred_typ hpred in let value_str_from_pvars_vpath pvars vpath = if pvars <> [] then - begin - let pp = Pp.seq (Pvar.pp_value Pp.text) in - let desc_string = F.asprintf "%a" pp pvars in - Some desc_string - end - else match vpath with - | Some de when not (DExp.has_tmp_var de) -> - Some (DExp.to_string de) - | _ -> None in - let res_action_opt, resource_opt, vpath = match alloc_att_opt with - | Some (PredSymb.Aresource ({ ra_kind = Racquire } as ra)) -> - Some ra, Some ra.ra_res, ra.ra_vpath - | _ -> (None, None, None) in - let is_file = match resource_opt with - | Some PredSymb.Rfile -> true - | _ -> false in + let pp = Pp.seq (Pvar.pp_value Pp.text) in + let desc_string = F.asprintf "%a" pp pvars in + Some desc_string + else + match vpath with + | Some de when not (DExp.has_tmp_var de) + -> Some (DExp.to_string de) + | _ + -> None + in + let res_action_opt, resource_opt, vpath = + match alloc_att_opt with + | Some PredSymb.Aresource ({ra_kind= Racquire} as ra) + -> (Some ra, Some ra.ra_res, ra.ra_vpath) + | _ + -> (None, None, None) + in + let is_file = match resource_opt with Some PredSymb.Rfile -> true | _ -> false in let check_pvar pvar = (* check that pvar is local or global and has the same type as the leaked hpred *) - (Pvar.is_local pvar || Pvar.is_global pvar) && - not (Pvar.is_frontend_tmp pvar) && - match hpred_typ_opt, find_typ_without_ptr prop pvar with - | Some (Exp.Sizeof {typ=t1}), Some (Exp.Sizeof {typ={Typ.desc=Tptr (t2, _)}}) -> - Typ.equal t1 t2 - | Some (Exp.Sizeof {typ={Typ.desc=Tint _}}), Some (Exp.Sizeof {typ={Typ.desc=Tint _}}) - when is_file -> (* must be a file opened with "open" *) + (Pvar.is_local pvar || Pvar.is_global pvar) && not (Pvar.is_frontend_tmp pvar) + && + match (hpred_typ_opt, find_typ_without_ptr prop pvar) with + | Some Exp.Sizeof {typ= t1}, Some Exp.Sizeof {typ= {Typ.desc= Tptr (t2, _)}} + -> Typ.equal t1 t2 + | Some Exp.Sizeof {typ= {Typ.desc= Tint _}}, Some Exp.Sizeof {typ= {Typ.desc= Tint _}} + when is_file + -> (* must be a file opened with "open" *) true - | _ -> false in - let value_str = match instro with - | None -> - if verbose then (L.d_str "explain_leak: no current instruction"; L.d_ln ()); + | _ + -> false + in + let value_str = + match instro with + | None + -> if verbose then ( L.d_str "explain_leak: no current instruction" ; L.d_ln () ) ; value_str_from_pvars_vpath [] vpath - | Some (Sil.Nullify (pvar, _)) when check_pvar pvar -> - if verbose - then - (L.d_str "explain_leak: current instruction is Nullify for pvar "; - Pvar.d pvar; L.d_ln ()); - (match exp_lv_dexp tenv (State.get_node ()) (Exp.Lvar pvar) with - | Some de when not (DExp.has_tmp_var de)-> Some (DExp.to_string de) - | _ -> None) - | Some (Sil.Abstract _) -> - if verbose then (L.d_str "explain_leak: current instruction is Abstract"; L.d_ln ()); + | Some Sil.Nullify (pvar, _) when check_pvar pvar + -> ( + if verbose then ( + L.d_str "explain_leak: current instruction is Nullify for pvar " ; + Pvar.d pvar ; + L.d_ln () ) ; + match exp_lv_dexp tenv (State.get_node ()) (Exp.Lvar pvar) with + | Some de when not (DExp.has_tmp_var de) + -> Some (DExp.to_string de) + | _ + -> None ) + | Some Sil.Abstract _ + -> if verbose then ( L.d_str "explain_leak: current instruction is Abstract" ; L.d_ln () ) ; let get_nullify = function - | Sil.Nullify (pvar, _) when check_pvar pvar -> - if verbose - then - (L.d_str "explain_leak: found nullify before Abstract for pvar "; - Pvar.d pvar; L.d_ln ()); + | Sil.Nullify (pvar, _) when check_pvar pvar + -> if verbose then ( + L.d_str "explain_leak: found nullify before Abstract for pvar " ; + Pvar.d pvar ; + L.d_ln () ) ; [pvar] - | _ -> [] in + | _ + -> [] + in let nullify_pvars = List.concat_map ~f:get_nullify node_instrs in let nullify_pvars_notmp = - List.filter ~f:(fun pvar -> not (Pvar.is_frontend_tmp pvar)) nullify_pvars in + List.filter ~f:(fun pvar -> not (Pvar.is_frontend_tmp pvar)) nullify_pvars + in value_str_from_pvars_vpath nullify_pvars_notmp vpath - | Some (Sil.Store (lexp, _, _, _)) when is_none vpath -> - if verbose - then - (L.d_str "explain_leak: current instruction Set for "; - Sil.d_exp lexp; L.d_ln ()); - (match exp_lv_dexp tenv node lexp with - | Some dexp when not (DExp.has_tmp_var dexp) -> Some (DExp.to_string dexp) - | _ -> None) - | Some instr -> - if verbose - then - (L.d_str "explain_leak: case not matched in instr "; - Sil.d_instr instr; L.d_ln()); - value_str_from_pvars_vpath [] vpath in - let exn_cat, bucket = (* decide whether Exn_user or Exn_developer *) + | Some Sil.Store (lexp, _, _, _) when is_none vpath + -> ( + if verbose then ( + L.d_str "explain_leak: current instruction Set for " ; Sil.d_exp lexp ; L.d_ln () ) ; + match exp_lv_dexp tenv node lexp with + | Some dexp when not (DExp.has_tmp_var dexp) + -> Some (DExp.to_string dexp) + | _ + -> None ) + | Some instr + -> if verbose then ( + L.d_str "explain_leak: case not matched in instr " ; Sil.d_instr instr ; L.d_ln () ) ; + value_str_from_pvars_vpath [] vpath + in + let exn_cat, bucket = + (* decide whether Exn_user or Exn_developer *) match resource_opt with - | Some _ -> (* we know it has been allocated *) - Exceptions.Exn_user, bucket - | None -> - if leak_from_list_abstraction hpred prop && value_str <> None - then + | Some _ + -> (* we know it has been allocated *) + (Exceptions.Exn_user, bucket) + | None + -> if leak_from_list_abstraction hpred prop && value_str <> None then (* we don't know it's been allocated, but it's coming from list abstraction and we have a name *) - Exceptions.Exn_user, bucket - else Exceptions.Exn_developer, Some Mleak_buckets.ml_bucket_unknown_origin in - exn_cat, Localise.desc_leak hpred_typ_opt value_str resource_opt res_action_opt loc bucket + (Exceptions.Exn_user, bucket) + else (Exceptions.Exn_developer, Some Mleak_buckets.ml_bucket_unknown_origin) + in + (exn_cat, Localise.desc_leak hpred_typ_opt value_str resource_opt res_action_opt loc bucket) (** find the dexp, if any, where the given value is stored also return the type of the value if found *) let vpath_find tenv prop _exp : DExp.t option * Typ.t option = - if verbose then (L.d_str "in vpath_find exp:"; Sil.d_exp _exp; L.d_ln ()); + if verbose then ( L.d_str "in vpath_find exp:" ; Sil.d_exp _exp ; L.d_ln () ) ; let rec find sigma_acc sigma_todo exp = - let do_fse res sigma_acc' sigma_todo' lexp texp (f, se) = match se with - | Sil.Eexp (e, _) when Exp.equal exp e -> - let sigma' = (List.rev_append sigma_acc' sigma_todo') in - (match lexp with - | Exp.Lvar pv -> - let typo = match texp with - | Exp.Sizeof {typ={Typ.desc=Tstruct name}} -> ( - match Tenv.lookup tenv name with - | Some {fields} -> - List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' f) fields |> - Option.map ~f:snd3 - | _ -> - None - ) - | _ -> None in - res := Some (DExp.Ddot (DExp.Dpvar pv, f)), typo - | Exp.Var id -> - (match find [] sigma' (Exp.Var id) with - | None, _ -> () - | Some de, typo -> res := Some (DExp.Darrow (de, f)), typo) - | lexp -> - if verbose - then - (L.d_str "vpath_find do_fse: no match on Eexp "; - Sil.d_exp lexp; L.d_ln ())) - | _ -> () in - let do_sexp sigma_acc' sigma_todo' lexp sexp texp = match sexp with - | Sil.Eexp (e, _) when Exp.equal exp e -> - let sigma' = (List.rev_append sigma_acc' sigma_todo') in - (match lexp with - | Exp.Lvar pv when not (Pvar.is_frontend_tmp pv) -> - let typo = match texp with - | Exp.Sizeof {typ} -> Some typ - | _ -> None in - Some (DExp.Dpvar pv), typo - | Exp.Var id -> - (match find [] sigma' (Exp.Var id) with - | None, typo -> None, typo - | Some de, typo -> Some (DExp.Dderef de), typo) - | lexp -> - if verbose - then - (L.d_str "vpath_find do_sexp: no match on Eexp "; - Sil.d_exp lexp; L.d_ln ()); - None, None) - | Sil.Estruct (fsel, _) -> - let res = ref (None, None) in - List.iter ~f:(do_fse res sigma_acc' sigma_todo' lexp texp) fsel; + let do_fse res sigma_acc' sigma_todo' lexp texp (f, se) = + match se with + | Sil.Eexp (e, _) when Exp.equal exp e + -> ( + let sigma' = List.rev_append sigma_acc' sigma_todo' in + match lexp with + | Exp.Lvar pv + -> let typo = + match texp with + | Exp.Sizeof {typ= {Typ.desc= Tstruct name}} -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f' f) fields + |> Option.map ~f:snd3 + | _ + -> None ) + | _ + -> None + in + res := (Some (DExp.Ddot (DExp.Dpvar pv, f)), typo) + | Exp.Var id -> ( + match find [] sigma' (Exp.Var id) with + | None, _ + -> () + | Some de, typo + -> res := (Some (DExp.Darrow (de, f)), typo) ) + | lexp + -> if verbose then ( + L.d_str "vpath_find do_fse: no match on Eexp " ; Sil.d_exp lexp ; L.d_ln () ) ) + | _ + -> () + in + let do_sexp sigma_acc' sigma_todo' lexp sexp texp = + match sexp with + | Sil.Eexp (e, _) when Exp.equal exp e + -> ( + let sigma' = List.rev_append sigma_acc' sigma_todo' in + match lexp with + | Exp.Lvar pv when not (Pvar.is_frontend_tmp pv) + -> let typo = match texp with Exp.Sizeof {typ} -> Some typ | _ -> None in + (Some (DExp.Dpvar pv), typo) + | Exp.Var id -> ( + match find [] sigma' (Exp.Var id) with + | None, typo + -> (None, typo) + | Some de, typo + -> (Some (DExp.Dderef de), typo) ) + | lexp + -> if verbose then ( + L.d_str "vpath_find do_sexp: no match on Eexp " ; Sil.d_exp lexp ; L.d_ln () ) ; + (None, None) ) + | Sil.Estruct (fsel, _) + -> let res = ref (None, None) in + List.iter ~f:(do_fse res sigma_acc' sigma_todo' lexp texp) fsel ; !res - | _ -> - None, None in + | _ + -> (None, None) + in let do_hpred sigma_acc' sigma_todo' = let substituted_from_normal id = let filter = function - | (ni, Exp.Var id') -> Ident.is_normal ni && Ident.equal id' id - | _ -> false in - List.exists ~f:filter (Sil.sub_to_list prop.Prop.sub) in + | ni, Exp.Var id' + -> Ident.is_normal ni && Ident.equal id' id + | _ + -> false + in + List.exists ~f:filter (Sil.sub_to_list prop.Prop.sub) + in function - | Sil.Hpointsto (Exp.Lvar pv, sexp, texp) - when (Pvar.is_local pv || Pvar.is_global pv || Pvar.is_seed pv) -> - do_sexp sigma_acc' sigma_todo' (Exp.Lvar pv) sexp texp - | Sil.Hpointsto (Exp.Var id, sexp, texp) - when Ident.is_normal id || (Ident.is_footprint id && substituted_from_normal id) -> - do_sexp sigma_acc' sigma_todo' (Exp.Var id) sexp texp - | _ -> - None, None in + | Sil.Hpointsto (Exp.Lvar pv, sexp, texp) + when Pvar.is_local pv || Pvar.is_global pv || Pvar.is_seed pv + -> do_sexp sigma_acc' sigma_todo' (Exp.Lvar pv) sexp texp + | Sil.Hpointsto (Exp.Var id, sexp, texp) + when Ident.is_normal id || Ident.is_footprint id && substituted_from_normal id + -> do_sexp sigma_acc' sigma_todo' (Exp.Var id) sexp texp + | _ + -> (None, None) + in match sigma_todo with - | [] -> None, None - | hpred:: sigma_todo' -> - (match do_hpred sigma_acc sigma_todo' hpred with - | Some de, typo -> Some de, typo - | None, _ -> find (hpred:: sigma_acc) sigma_todo' exp) in + | [] + -> (None, None) + | hpred :: sigma_todo' -> + match do_hpred sigma_acc sigma_todo' hpred with + | Some de, typo + -> (Some de, typo) + | None, _ + -> find (hpred :: sigma_acc) sigma_todo' exp + in let res = find [] prop.Prop.sigma _exp in - if verbose then begin - match res with - | None, _ -> L.d_str "vpath_find: cannot find "; Sil.d_exp _exp; L.d_ln () - | Some de, typo -> - L.d_str "vpath_find: found "; L.d_str (DExp.to_string de); L.d_str " : "; - match typo with - | None -> L.d_str " No type" - | Some typ -> Typ.d_full typ; - L.d_ln () - end; + ( if verbose then + match res with + | None, _ + -> L.d_str "vpath_find: cannot find " ; Sil.d_exp _exp ; L.d_ln () + | Some de, typo + -> L.d_str "vpath_find: found " ; + L.d_str (DExp.to_string de) ; + L.d_str " : " ; + match typo with None -> L.d_str " No type" | Some typ -> Typ.d_full typ ; L.d_ln () ) ; res (** produce a description of the access from the instrumentation at position [dexp] in [prop] *) let explain_dexp_access prop dexp is_nullable = let sigma = prop.Prop.sigma in let sexpo_to_inst = function - | None -> None - | Some (Sil.Eexp (_, inst)) -> Some inst - | Some se -> - if verbose then (L.d_str "sexpo_to_inst: can't find inst "; Sil.d_sexp se; L.d_ln()); - None in - let find_ptsto (e : Exp.t) : Sil.strexp option = + | None + -> None + | Some Sil.Eexp (_, inst) + -> Some inst + | Some se + -> if verbose then ( L.d_str "sexpo_to_inst: can't find inst " ; Sil.d_sexp se ; L.d_ln () ) ; + None + in + let find_ptsto (e: Exp.t) : Sil.strexp option = let res = ref None in let do_hpred = function - | Sil.Hpointsto (e', se, _) when Exp.equal e e' -> - res := Some se - | _ -> () in - List.iter ~f:do_hpred sigma; - !res in - let rec lookup_fld fsel f = match fsel with - | [] -> - if verbose - then - (L.d_strln ("lookup_fld: can't find field " ^ Typ.Fieldname.to_string f)); + | Sil.Hpointsto (e', se, _) when Exp.equal e e' + -> res := Some se + | _ + -> () + in + List.iter ~f:do_hpred sigma ; !res + in + let rec lookup_fld fsel f = + match fsel with + | [] + -> if verbose then L.d_strln ("lookup_fld: can't find field " ^ Typ.Fieldname.to_string f) ; None - | (f1, se):: fsel' -> - if Typ.Fieldname.equal f1 f then Some se - else lookup_fld fsel' f in - let rec lookup_esel esel e = match esel with - | [] -> - if verbose then (L.d_str "lookup_esel: can't find index "; Sil.d_exp e; L.d_ln ()); + | (f1, se) :: fsel' + -> if Typ.Fieldname.equal f1 f then Some se else lookup_fld fsel' f + in + let rec lookup_esel esel e = + match esel with + | [] + -> if verbose then ( L.d_str "lookup_esel: can't find index " ; Sil.d_exp e ; L.d_ln () ) ; None - | (e1, se):: esel' -> - if Exp.equal e1 e then Some se - else lookup_esel esel' e in + | (e1, se) :: esel' + -> if Exp.equal e1 e then Some se else lookup_esel esel' e + in let rec lookup : DExp.t -> Sil.strexp option = function - | DExp.Dconst c -> - Some (Sil.Eexp (Exp.Const c, Sil.inst_none)) - | DExp.Darray (de1, de2) -> - (match lookup de1, lookup de2 with - | None, _ | _, None -> None - | Some Sil.Earray (_, esel, _), Some Sil.Eexp (e, _) -> - lookup_esel esel e - | Some se1, Some se2 -> - if verbose - then - (L.d_str "lookup: case not matched on Darray "; - Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_ln()); - None) - | DExp.Darrow ((DExp.Dpvaraddr pvar), f) -> - (match lookup (DExp.Dpvaraddr pvar) with - | None -> None - | Some Sil.Estruct (fsel, _) -> - lookup_fld fsel f - | Some _ -> - if verbose then (L.d_str "lookup: case not matched on Darrow "; L.d_ln ()); - None) - | DExp.Darrow (de1, f) -> - (match lookup (DExp.Dderef de1) with - | None -> None - | Some Sil.Estruct (fsel, _) -> - lookup_fld fsel f - | Some _ -> - if verbose then (L.d_str "lookup: case not matched on Darrow "; L.d_ln ()); - None) - | DExp.Ddot (de1, f) -> - (match lookup de1 with - | None -> None - | Some Sil.Estruct (fsel, _) -> - lookup_fld fsel f - | Some ((Sil.Eexp (Const (Cfun _), _)) as fun_strexp) -> - Some fun_strexp - | Some _ -> - if verbose then (L.d_str "lookup: case not matched on Ddot "; L.d_ln ()); - None) - | DExp.Dpvar pvar -> - if verbose then (L.d_str "lookup: found Dpvar "; L.d_ln ()); - (find_ptsto (Exp.Lvar pvar)) - | DExp.Dderef de -> - (match lookup de with - | None -> None - | Some (Sil.Eexp (e, _)) -> find_ptsto e - | Some _ -> None) - | (DExp.Dbinop(Binop.PlusPI, DExp.Dpvar _, DExp.Dconst _) as de) -> - if verbose then (L.d_strln ("lookup: case )pvar + constant) " ^ DExp.to_string de)); + | DExp.Dconst c + -> Some (Sil.Eexp (Exp.Const c, Sil.inst_none)) + | DExp.Darray (de1, de2) -> ( + match (lookup de1, lookup de2) with + | None, _ | _, None + -> None + | Some Sil.Earray (_, esel, _), Some Sil.Eexp (e, _) + -> lookup_esel esel e + | Some se1, Some se2 + -> if verbose then ( + L.d_str "lookup: case not matched on Darray " ; + Sil.d_sexp se1 ; + L.d_str " " ; + Sil.d_sexp se2 ; + L.d_ln () ) ; + None ) + | DExp.Darrow (DExp.Dpvaraddr pvar, f) -> ( + match lookup (DExp.Dpvaraddr pvar) with + | None + -> None + | Some Sil.Estruct (fsel, _) + -> lookup_fld fsel f + | Some _ + -> if verbose then ( L.d_str "lookup: case not matched on Darrow " ; L.d_ln () ) ; + None ) + | DExp.Darrow (de1, f) -> ( + match lookup (DExp.Dderef de1) with + | None + -> None + | Some Sil.Estruct (fsel, _) + -> lookup_fld fsel f + | Some _ + -> if verbose then ( L.d_str "lookup: case not matched on Darrow " ; L.d_ln () ) ; + None ) + | DExp.Ddot (de1, f) -> ( + match lookup de1 with + | None + -> None + | Some Sil.Estruct (fsel, _) + -> lookup_fld fsel f + | Some (Sil.Eexp (Const Cfun _, _) as fun_strexp) + -> Some fun_strexp + | Some _ + -> if verbose then ( L.d_str "lookup: case not matched on Ddot " ; L.d_ln () ) ; + None ) + | DExp.Dpvar pvar + -> if verbose then ( L.d_str "lookup: found Dpvar " ; L.d_ln () ) ; + find_ptsto (Exp.Lvar pvar) + | DExp.Dderef de -> ( + match lookup de with None -> None | Some Sil.Eexp (e, _) -> find_ptsto e | Some _ -> None ) + | DExp.Dbinop (Binop.PlusPI, DExp.Dpvar _, DExp.Dconst _) as de + -> if verbose then L.d_strln ("lookup: case )pvar + constant) " ^ DExp.to_string de) ; None - | DExp.Dfcall (DExp.Dconst c, _, loc, _) -> - if verbose then (L.d_strln "lookup: found Dfcall "); - (match c with - | Const.Cfun _ -> (* Treat function as an update *) - Some (Sil.Eexp (Exp.Const c, Sil.Ireturn_from_call loc.Location.line)) - | _ -> None) - | DExp.Dpvaraddr pvar -> - (L.d_strln ("lookup: found Dvaraddr " ^ DExp.to_string (DExp.Dpvaraddr pvar))); + | DExp.Dfcall (DExp.Dconst c, _, loc, _) + -> ( + if verbose then L.d_strln "lookup: found Dfcall " ; + match c with + | Const.Cfun _ + -> (* Treat function as an update *) + Some (Sil.Eexp (Exp.Const c, Sil.Ireturn_from_call loc.Location.line)) + | _ + -> None ) + | DExp.Dpvaraddr pvar + -> L.d_strln ("lookup: found Dvaraddr " ^ DExp.to_string (DExp.Dpvaraddr pvar)) ; find_ptsto (Exp.Lvar pvar) - | de -> - if verbose then (L.d_strln ("lookup: unknown case not matched " ^ DExp.to_string de)); - None in - let access_opt = match sexpo_to_inst (lookup dexp) with - | None -> - if verbose - then - (L.d_strln ("explain_dexp_access: cannot find inst of " ^ DExp.to_string dexp)); + | de + -> if verbose then L.d_strln ("lookup: unknown case not matched " ^ DExp.to_string de) ; + None + in + let access_opt = + match sexpo_to_inst (lookup dexp) with + | None + -> if verbose then + L.d_strln ("explain_dexp_access: cannot find inst of " ^ DExp.to_string dexp) ; + None + | Some Sil.Iupdate (_, ncf, n, _) + -> Some (Localise.Last_assigned (n, ncf)) + | Some Sil.Irearrange (_, _, n, _) + -> Some (Localise.Last_accessed (n, is_nullable)) + | Some Sil.Ireturn_from_call n + -> Some (Localise.Returned_from_call n) + | Some Sil.Ialloc when Config.curr_language_is Config.Java + -> Some Localise.Initialized_automatically + | Some inst + -> if verbose then + L.d_strln ("explain_dexp_access: inst is not an update " ^ Sil.inst_to_string inst) ; None - | Some (Sil.Iupdate (_, ncf, n, _)) -> - Some (Localise.Last_assigned (n, ncf)) - | Some (Sil.Irearrange (_, _, n, _)) -> - Some (Localise.Last_accessed (n, is_nullable)) - | Some (Sil.Ireturn_from_call n) -> - Some (Localise.Returned_from_call n) - | Some Sil.Ialloc when Config.curr_language_is Config.Java -> - Some Localise.Initialized_automatically - | Some inst -> - if verbose - then - (L.d_strln - ("explain_dexp_access: inst is not an update " ^ - Sil.inst_to_string inst)); - None in + in access_opt let explain_dereference_access outermost_array is_nullable _de_opt prop = let de_opt = - let rec remove_outermost_array_access = function (* remove outermost array access from [de] *) - | DExp.Dbinop(Binop.PlusPI, de1, _) -> - (* remove pointer arithmetic before array access *) + let rec remove_outermost_array_access = function + (* remove outermost array access from [de] *) + | DExp.Dbinop (Binop.PlusPI, de1, _) + -> (* remove pointer arithmetic before array access *) remove_outermost_array_access de1 - | DExp.Darray(DExp.Dderef de1, _) -> - (* array access is a deref already: remove both *) + | DExp.Darray (DExp.Dderef de1, _) + -> (* array access is a deref already: remove both *) de1 - | DExp.Darray(de1, _) -> (* remove array access *) + | DExp.Darray (de1, _) + -> (* remove array access *) de1 - | DExp.Dderef de -> (* remove implicit array access *) + | DExp.Dderef de + -> (* remove implicit array access *) de - | DExp.Ddot (de, _) -> (* remove field access before array access *) + | DExp.Ddot (de, _) + -> (* remove field access before array access *) remove_outermost_array_access de - | de -> de in + | de + -> de + in match _de_opt with - | None -> None - | Some de -> - Some (if outermost_array then remove_outermost_array_access de else de) in - let value_str = match de_opt with - | Some de -> - DExp.to_string de - | None -> "" in - let access_opt = match de_opt with - | Some de -> explain_dexp_access prop de is_nullable - | None -> None in + | None + -> None + | Some de + -> Some (if outermost_array then remove_outermost_array_access de else de) + in + let value_str = match de_opt with Some de -> DExp.to_string de | None -> "" in + let access_opt = + match de_opt with Some de -> explain_dexp_access prop de is_nullable | None -> None + in (value_str, access_opt) (** Create a description of a dereference operation *) -let create_dereference_desc tenv - ?use_buckets: (use_buckets = false) - ?outermost_array: (outermost_array = false) - ?is_nullable: (is_nullable = false) - ?is_premature_nil: (is_premature_nil = false) - de_opt deref_str prop loc = - let value_str, access_opt = - explain_dereference_access outermost_array is_nullable de_opt prop in - let access_opt' = match access_opt with - | Some (Localise.Last_accessed _) - when outermost_array -> None (* don't report last accessed for arrays *) - | _ -> access_opt in +let create_dereference_desc tenv ?(use_buckets= false) ?(outermost_array= false) + ?(is_nullable= false) ?(is_premature_nil= false) de_opt deref_str prop loc = + let value_str, access_opt = explain_dereference_access outermost_array is_nullable de_opt prop in + let access_opt' = + match access_opt with + | Some Localise.Last_accessed _ when outermost_array + -> None (* don't report last accessed for arrays *) + | _ + -> access_opt + in let desc = Localise.dereference_string deref_str value_str access_opt' loc in let desc = if Config.curr_language_is Config.Clang && not is_premature_nil then match de_opt with - | Some (DExp.Dpvar pvar) - | Some (DExp.Dpvaraddr pvar) -> - (match Attribute.get_objc_null tenv prop (Exp.Lvar pvar) with - | Some (Apred (Aobjc_null, [_; vfs])) -> - Localise.parameter_field_not_null_checked_desc desc vfs - | _ -> - desc) - | Some (DExp.Dretcall (Dconst (Cfun pname), this_dexp :: _, loc, _ )) -> - if is_mutex_method pname then + | Some DExp.Dpvar pvar | Some DExp.Dpvaraddr pvar -> ( + match Attribute.get_objc_null tenv prop (Exp.Lvar pvar) with + | Some Apred (Aobjc_null, [_; vfs]) + -> Localise.parameter_field_not_null_checked_desc desc vfs + | _ + -> desc ) + | Some DExp.Dretcall (Dconst Cfun pname, this_dexp :: _, loc, _) + -> if is_mutex_method pname then Localise.desc_double_lock (Some pname) (DExp.to_string this_dexp) loc else if is_vector_method pname then Localise.desc_empty_vector_access (Some pname) (DExp.to_string this_dexp) loc - else - desc - | Some (DExp.Darrow (dexp, fieldname)) - | Some (DExp.Ddot (dexp, fieldname)) -> - if is_special_field mutex_matcher (Some "null_if_locked") fieldname then + else desc + | Some DExp.Darrow (dexp, fieldname) | Some DExp.Ddot (dexp, fieldname) + -> if is_special_field mutex_matcher (Some "null_if_locked") fieldname then Localise.desc_double_lock None (DExp.to_string dexp) loc else if is_special_field vector_matcher (Some "beginPtr") fieldname - || is_special_field vector_matcher (Some "endPtr") fieldname then - Localise.desc_empty_vector_access None (DExp.to_string dexp) loc - else - desc - | _ -> desc - else desc in - if use_buckets then Buckets.classify_access desc access_opt' de_opt is_nullable - else desc + || is_special_field vector_matcher (Some "endPtr") fieldname + then Localise.desc_empty_vector_access None (DExp.to_string dexp) loc + else desc + | _ + -> desc + else desc + in + if use_buckets then Buckets.classify_access desc access_opt' de_opt is_nullable else desc (** explain memory access performed by the current instruction if outermost_array is true, the outermost array access is removed if outermost_dereference is true, stop at the outermost dereference (skipping e.g. outermost field access) *) -let _explain_access tenv - ?(use_buckets = false) - ?(outermost_array = false) - ?(outermost_dereference = false) - ?(is_nullable = false) - ?(is_premature_nil = false) - deref_str prop loc = - let rec find_outermost_dereference node e = match e with - | Exp.Const _ -> - if verbose then (L.d_str "find_outermost_dereference: constant "; Sil.d_exp e; L.d_ln ()); +let _explain_access tenv ?(use_buckets= false) ?(outermost_array= false) + ?(outermost_dereference= false) ?(is_nullable= false) ?(is_premature_nil= false) deref_str prop + loc = + let rec find_outermost_dereference node e = + match e with + | Exp.Const _ + -> if verbose then ( L.d_str "find_outermost_dereference: constant " ; Sil.d_exp e ; L.d_ln () ) ; exp_lv_dexp tenv node e - | Exp.Var id when Ident.is_normal id -> (* look up the normal variable declaration *) - if verbose - then - (L.d_str "find_outermost_dereference: normal var "; - Sil.d_exp e; L.d_ln ()); + | Exp.Var id when Ident.is_normal id + -> (* look up the normal variable declaration *) + if verbose then ( + L.d_str "find_outermost_dereference: normal var " ; Sil.d_exp e ; L.d_ln () ) ; find_normal_variable_load tenv node id - | Exp.Lfield (e', _, _) -> - if verbose then (L.d_str "find_outermost_dereference: Lfield "; Sil.d_exp e; L.d_ln ()); + | Exp.Lfield (e', _, _) + -> if verbose then ( L.d_str "find_outermost_dereference: Lfield " ; Sil.d_exp e ; L.d_ln () ) ; find_outermost_dereference node e' - | Exp.Lindex(e', _) -> - if verbose then (L.d_str "find_outermost_dereference: Lindex "; Sil.d_exp e; L.d_ln ()); + | Exp.Lindex (e', _) + -> if verbose then ( L.d_str "find_outermost_dereference: Lindex " ; Sil.d_exp e ; L.d_ln () ) ; find_outermost_dereference node e' - | Exp.Lvar _ -> - if verbose then (L.d_str "find_outermost_dereference: Lvar "; Sil.d_exp e; L.d_ln ()); + | Exp.Lvar _ + -> if verbose then ( L.d_str "find_outermost_dereference: Lvar " ; Sil.d_exp e ; L.d_ln () ) ; exp_lv_dexp tenv node e - | Exp.BinOp(Binop.PlusPI, Exp.Lvar _, _) -> - if verbose - then - (L.d_str "find_outermost_dereference: Lvar+index "; - Sil.d_exp e; L.d_ln ()); + | Exp.BinOp (Binop.PlusPI, Exp.Lvar _, _) + -> if verbose then ( + L.d_str "find_outermost_dereference: Lvar+index " ; Sil.d_exp e ; L.d_ln () ) ; exp_lv_dexp tenv node e - | Exp.Cast (_, e') -> - if verbose then (L.d_str "find_outermost_dereference: cast "; Sil.d_exp e; L.d_ln ()); + | Exp.Cast (_, e') + -> if verbose then ( L.d_str "find_outermost_dereference: cast " ; Sil.d_exp e ; L.d_ln () ) ; find_outermost_dereference node e' - | _ -> - if verbose - then - (L.d_str "find_outermost_dereference: no match for "; - Sil.d_exp e; L.d_ln ()); - None in - let find_exp_dereferenced () = match State.get_instr () with - | Some Sil.Store (e, _, _, _) -> - if verbose then (L.d_str "explain_dereference Sil.Store "; Sil.d_exp e; L.d_ln ()); + | _ + -> if verbose then ( + L.d_str "find_outermost_dereference: no match for " ; Sil.d_exp e ; L.d_ln () ) ; + None + in + let find_exp_dereferenced () = + match State.get_instr () with + | Some Sil.Store (e, _, _, _) + -> if verbose then ( L.d_str "explain_dereference Sil.Store " ; Sil.d_exp e ; L.d_ln () ) ; Some e - | Some Sil.Load (_, e, _, _) -> - if verbose then (L.d_str "explain_dereference Binop.Leteref "; Sil.d_exp e; L.d_ln ()); + | Some Sil.Load (_, e, _, _) + -> if verbose then ( L.d_str "explain_dereference Binop.Leteref " ; Sil.d_exp e ; L.d_ln () ) ; Some e - | Some Sil.Call (_, Exp.Const (Const.Cfun fn), [(e, _)], _, _) + | Some Sil.Call (_, Exp.Const Const.Cfun fn, [(e, _)], _, _) when List.exists ~f:(Typ.Procname.equal fn) - [BuiltinDecl.free; BuiltinDecl.__delete; BuiltinDecl.__delete_array] -> - if verbose then (L.d_str "explain_dereference Sil.Call "; Sil.d_exp e; L.d_ln ()); + [BuiltinDecl.free; BuiltinDecl.__delete; BuiltinDecl.__delete_array] + -> if verbose then ( L.d_str "explain_dereference Sil.Call " ; Sil.d_exp e ; L.d_ln () ) ; Some e - | Some Sil.Call (_, (Exp.Var _ as e), _, _, _) -> - if verbose then (L.d_str "explain_dereference Sil.Call "; Sil.d_exp e; L.d_ln ()); + | Some Sil.Call (_, (Exp.Var _ as e), _, _, _) + -> if verbose then ( L.d_str "explain_dereference Sil.Call " ; Sil.d_exp e ; L.d_ln () ) ; Some e - | _ -> None in + | _ + -> None + in let node = State.get_node () in match find_exp_dereferenced () with - | None -> - if verbose then L.d_strln "_explain_access: find_exp_dereferenced returned None"; + | None + -> if verbose then L.d_strln "_explain_access: find_exp_dereferenced returned None" ; Localise.no_desc - | Some e -> - L.d_strln "Finding deref'd exp"; + | Some e + -> L.d_strln "Finding deref'd exp" ; let de_opt = if outermost_dereference then find_outermost_dereference node e - else exp_lv_dexp tenv node e in - create_dereference_desc tenv - ~use_buckets ~outermost_array ~is_nullable ~is_premature_nil + else exp_lv_dexp tenv node e + in + create_dereference_desc tenv ~use_buckets ~outermost_array ~is_nullable ~is_premature_nil de_opt deref_str prop loc (** Produce a description of which expression is dereferenced in the current instruction, if any. The subexpression to focus on is obtained by removing field and index accesses. *) -let explain_dereference tenv - ?(use_buckets = false) - ?(is_nullable = false) - ?(is_premature_nil = false) +let explain_dereference tenv ?(use_buckets= false) ?(is_nullable= false) ?(is_premature_nil= false) deref_str prop loc = - _explain_access tenv - ~use_buckets ~outermost_array: false ~outermost_dereference: true ~is_nullable ~is_premature_nil - deref_str prop loc + _explain_access tenv ~use_buckets ~outermost_array:false ~outermost_dereference:true ~is_nullable + ~is_premature_nil deref_str prop loc (** Produce a description of the array access performed in the current instruction, if any. The subexpression to focus on is obtained by removing the outermost array access. *) let explain_array_access tenv deref_str prop loc = - _explain_access tenv ~outermost_array: true deref_str prop loc + _explain_access tenv ~outermost_array:true deref_str prop loc (** Produce a description of the memory access performed in the current instruction, if any. *) -let explain_memory_access tenv deref_str prop loc = - _explain_access tenv deref_str prop loc +let explain_memory_access tenv deref_str prop loc = _explain_access tenv deref_str prop loc (* offset of an expression found following a program variable *) type pvar_off = (* value of a pvar *) | Fpvar - (* value obtained by dereferencing the pvar and following a sequence of fields *) | Fstruct of Typ.Fieldname.t list let dexp_apply_pvar_off dexp pvar_off = - let rec add_ddot de = function - | [] -> de - | f:: fl -> - add_ddot (DExp.Ddot (de, f)) fl in + let rec add_ddot de = function [] -> de | f :: fl -> add_ddot (DExp.Ddot (de, f)) fl in match pvar_off with - | Fpvar -> dexp - | Fstruct (f:: fl) -> add_ddot (DExp.Darrow (dexp, f)) fl - | Fstruct [] -> dexp (* case should not happen *) + | Fpvar + -> dexp + | Fstruct (f :: fl) + -> add_ddot (DExp.Darrow (dexp, f)) fl + | Fstruct [] + -> dexp + +(* case should not happen *) (** Produce a description of the nth parameter of the function call, if the current instruction is a function call with that parameter *) @@ -975,185 +1024,191 @@ let explain_nth_function_parameter tenv use_buckets deref_str prop n pvar_off = let node = State.get_node () in let loc = State.get_loc () in match State.get_instr () with - | Some Sil.Call (_, _, args, _, _) -> - (try - let arg = fst (List.nth_exn args (n - 1)) in - let dexp_opt = exp_rv_dexp tenv node arg in - let dexp_opt' = match dexp_opt with - | Some de -> - Some (dexp_apply_pvar_off de pvar_off) - | None -> None in - create_dereference_desc tenv ~use_buckets dexp_opt' deref_str prop loc - with exn when SymOp.exn_not_failure exn -> Localise.no_desc) - | _ -> Localise.no_desc + | Some Sil.Call (_, _, args, _, _) -> ( + try + let arg = fst (List.nth_exn args (n - 1)) in + let dexp_opt = exp_rv_dexp tenv node arg in + let dexp_opt' = + match dexp_opt with Some de -> Some (dexp_apply_pvar_off de pvar_off) | None -> None + in + create_dereference_desc tenv ~use_buckets dexp_opt' deref_str prop loc + with exn when SymOp.exn_not_failure exn -> Localise.no_desc ) + | _ + -> Localise.no_desc (** Find a program variable whose value is [exp] or pointing to a struct containing [exp] *) let find_with_exp prop exp = let res = ref None in let found_in_pvar pv = - if not (Pvar.is_abduced pv) && not (Pvar.is_this pv) then - res := Some (pv, Fpvar) in - let found_in_struct pv fld_lst = (* found_in_pvar has priority *) - if is_none !res then res := Some (pv, Fstruct (List.rev fld_lst)) in + if not (Pvar.is_abduced pv) && not (Pvar.is_this pv) then res := Some (pv, Fpvar) + in + let found_in_struct pv fld_lst = + (* found_in_pvar has priority *) + if is_none !res then res := Some (pv, Fstruct (List.rev fld_lst)) + in let rec search_struct pv fld_lst = function - | Sil.Eexp (e, _) -> - if Exp.equal e exp then found_in_struct pv fld_lst - | Sil.Estruct (fsel, _) -> - List.iter ~f:(fun (f, se) -> search_struct pv (f:: fld_lst) se) fsel - | _ -> () in + | Sil.Eexp (e, _) + -> if Exp.equal e exp then found_in_struct pv fld_lst + | Sil.Estruct (fsel, _) + -> List.iter ~f:(fun (f, se) -> search_struct pv (f :: fld_lst) se) fsel + | _ + -> () + in let do_hpred_pointed_by_pvar pv e = function - | Sil.Hpointsto(e1, se, _) -> - if Exp.equal e e1 then search_struct pv [] se - | _ -> () in + | Sil.Hpointsto (e1, se, _) + -> if Exp.equal e e1 then search_struct pv [] se + | _ + -> () + in let do_hpred = function - | Sil.Hpointsto(Exp.Lvar pv, Sil.Eexp (e, _), _) -> - if Exp.equal e exp then found_in_pvar pv + | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (e, _), _) + -> if Exp.equal e exp then found_in_pvar pv else List.iter ~f:(do_hpred_pointed_by_pvar pv e) prop.Prop.sigma - | _ -> () in - List.iter ~f:do_hpred prop.Prop.sigma; - !res + | _ + -> () + in + List.iter ~f:do_hpred prop.Prop.sigma ; !res (** return a description explaining value [exp] in [prop] in terms of a source expression using the formal parameters of the call *) -let explain_dereference_as_caller_expression tenv - ?use_buckets: (use_buckets = false) - deref_str actual_pre spec_pre exp node loc formal_params = +let explain_dereference_as_caller_expression tenv ?(use_buckets= false) deref_str actual_pre + spec_pre exp node loc formal_params = let find_formal_param_number name = let rec find n = function - | [] -> 0 - | v :: pars -> - if Mangled.equal (Pvar.get_name v) name then n - else find (n + 1) pars in - find 1 formal_params in + | [] + -> 0 + | v :: pars + -> if Mangled.equal (Pvar.get_name v) name then n else find (n + 1) pars + in + find 1 formal_params + in match find_with_exp spec_pre exp with - | Some (pv, pvar_off) -> - if verbose then L.d_strln ("pvar: " ^ (Pvar.to_string pv)); + | Some (pv, pvar_off) + -> if verbose then L.d_strln ("pvar: " ^ Pvar.to_string pv) ; let pv_name = Pvar.get_name pv in - if Pvar.is_global pv - then + if Pvar.is_global pv then let dexp = exp_lv_dexp tenv node (Exp.Lvar pv) in create_dereference_desc tenv ~use_buckets dexp deref_str actual_pre loc else if Pvar.is_callee pv then let position = find_formal_param_number pv_name in - if verbose then L.d_strln ("parameter number: " ^ string_of_int position); + if verbose then L.d_strln ("parameter number: " ^ string_of_int position) ; explain_nth_function_parameter tenv use_buckets deref_str actual_pre position pvar_off - else - if Attribute.has_dangling_uninit tenv spec_pre exp then + else if Attribute.has_dangling_uninit tenv spec_pre exp then Localise.desc_uninitialized_dangling_pointer_deref deref_str (Pvar.to_string pv) loc else Localise.no_desc - | None -> - if verbose - then (L.d_str "explain_dereference_as_caller_expression "; - Sil.d_exp exp; L.d_str ": cannot explain None "; L.d_ln ()); + | None + -> if verbose then ( + L.d_str "explain_dereference_as_caller_expression " ; + Sil.d_exp exp ; + L.d_str ": cannot explain None " ; + L.d_ln () ) ; Localise.no_desc (** explain a class cast exception *) let explain_class_cast_exception tenv pname_opt typ1 typ2 exp node loc = - let exp_str_opt = match exp_rv_dexp tenv node exp with - | Some dexp -> Some (DExp.to_string dexp) - | None -> None in - match exp_rv_dexp tenv node typ1, exp_rv_dexp tenv node typ2 with - | Some de1, Some de2 -> - let typ_str1 = DExp.to_string de1 in + let exp_str_opt = + match exp_rv_dexp tenv node exp with Some dexp -> Some (DExp.to_string dexp) | None -> None + in + match (exp_rv_dexp tenv node typ1, exp_rv_dexp tenv node typ2) with + | Some de1, Some de2 + -> let typ_str1 = DExp.to_string de1 in let typ_str2 = DExp.to_string de2 in Localise.desc_class_cast_exception pname_opt typ_str1 typ_str2 exp_str_opt loc - | _ -> Localise.no_desc + | _ + -> Localise.no_desc (** explain a division by zero *) let explain_divide_by_zero tenv exp node loc = match exp_rv_dexp tenv node exp with - | Some de -> - let exp_str = DExp.to_string de in + | Some de + -> let exp_str = DExp.to_string de in Localise.desc_divide_by_zero exp_str loc - | None -> Localise.no_desc + | None + -> Localise.no_desc (** explain a return expression required *) let explain_return_expression_required loc typ = let typ_str = let pp fmt = Typ.pp_full Pp.text fmt typ in - F.asprintf "%t" pp in + F.asprintf "%t" pp + in Localise.desc_return_expression_required typ_str loc (** Explain retain cycle value error *) -let explain_retain_cycle cycle loc dotty_str = - Localise.desc_retain_cycle cycle loc dotty_str +let explain_retain_cycle cycle loc dotty_str = Localise.desc_retain_cycle cycle loc dotty_str (** Explain a tainted value error *) -let explain_tainted_value_reaching_sensitive_function - prop e { PredSymb.taint_source; taint_kind } sensitive_fun loc = +let explain_tainted_value_reaching_sensitive_function prop e {PredSymb.taint_source; taint_kind} + sensitive_fun loc = let var_desc = match e with - | Exp.Lvar pv -> Pvar.to_string pv + | Exp.Lvar pv + -> Pvar.to_string pv | _ -> - begin - match find_with_exp prop e with - | Some (pvar, pvar_off) -> - let dexp = dexp_apply_pvar_off (DExp.Dpvar pvar) pvar_off in - DExp.to_string dexp - | None -> Exp.to_string e - end in - Localise.desc_tainted_value_reaching_sensitive_function - taint_kind - var_desc - taint_source - sensitive_fun - loc + match find_with_exp prop e with + | Some (pvar, pvar_off) + -> let dexp = dexp_apply_pvar_off (DExp.Dpvar pvar) pvar_off in + DExp.to_string dexp + | None + -> Exp.to_string e + in + Localise.desc_tainted_value_reaching_sensitive_function taint_kind var_desc taint_source + sensitive_fun loc (** explain a return statement missing *) -let explain_return_statement_missing loc = - Localise.desc_return_statement_missing loc +let explain_return_statement_missing loc = Localise.desc_return_statement_missing loc (** explain a fronend warning *) -let explain_frontend_warning loc = - Localise.desc_frontend_warning loc +let explain_frontend_warning loc = Localise.desc_frontend_warning loc (** explain a comparing floats for equality *) -let explain_comparing_floats_for_equality loc = - Localise.desc_comparing_floats_for_equality loc +let explain_comparing_floats_for_equality loc = Localise.desc_comparing_floats_for_equality loc (** explain a condition is an assignment *) -let explain_condition_is_assignment loc = - Localise.desc_condition_is_assignment loc +let explain_condition_is_assignment loc = Localise.desc_condition_is_assignment loc (** explain a condition which is always true or false *) let explain_condition_always_true_false tenv i cond node loc = - let cond_str_opt = match exp_rv_dexp tenv node cond with - | Some de -> - Some (DExp.to_string de) - | None -> None in + let cond_str_opt = + match exp_rv_dexp tenv node cond with Some de -> Some (DExp.to_string de) | None -> None + in Localise.desc_condition_always_true_false i cond_str_opt loc -let explain_unreachable_code_after loc = - Localise.desc_unreachable_code_after loc +let explain_unreachable_code_after loc = Localise.desc_unreachable_code_after loc (** explain the escape of a stack variable address from its scope *) let explain_stack_variable_address_escape loc pvar addr_dexp_opt = - let addr_dexp_str = match addr_dexp_opt with - | Some (DExp.Dpvar pv) - when Pvar.is_local pv && - Mangled.equal (Pvar.get_name pv) Ident.name_return -> - Some "the caller via a return" - | Some dexp -> Some (DExp.to_string dexp) - | None -> None in + let addr_dexp_str = + match addr_dexp_opt with + | Some DExp.Dpvar pv + when Pvar.is_local pv && Mangled.equal (Pvar.get_name pv) Ident.name_return + -> Some "the caller via a return" + | Some dexp + -> Some (DExp.to_string dexp) + | None + -> None + in Localise.desc_stack_variable_address_escape (Pvar.to_string pvar) addr_dexp_str loc (** explain unary minus applied to unsigned expression *) let explain_unary_minus_applied_to_unsigned_expression tenv exp typ node loc = - let exp_str_opt = match exp_rv_dexp tenv node exp with - | Some de -> Some (DExp.to_string de) - | None -> None in + let exp_str_opt = + match exp_rv_dexp tenv node exp with Some de -> Some (DExp.to_string de) | None -> None + in let typ_str = let pp fmt = Typ.pp_full Pp.text fmt typ in - F.asprintf "%t" pp in + F.asprintf "%t" pp + in Localise.desc_unary_minus_applied_to_unsigned_expression exp_str_opt typ_str loc (** explain a test for NULL of a dereferenced pointer *) let explain_null_test_after_dereference tenv exp node line loc = match exp_rv_dexp tenv node exp with - | Some de -> - let expr_str = DExp.to_string de in + | Some de + -> let expr_str = DExp.to_string de in Localise.desc_null_test_after_dereference expr_str line loc - | None -> Localise.no_desc + | None + -> Localise.no_desc let warning_err loc fmt_string = L.(debug Analysis Medium) ("%a: Warning: " ^^ fmt_string) Location.pp loc diff --git a/infer/src/backend/errdesc.mli b/infer/src/backend/errdesc.mli index 40517b6c3..88c0dcd88 100644 --- a/infer/src/backend/errdesc.mli +++ b/infer/src/backend/errdesc.mli @@ -12,133 +12,137 @@ open! IStd (** Create descriptions of analysis errors *) +val vpath_find : Tenv.t -> 'a Prop.t -> Exp.t -> DecompiledExp.vpath * Typ.t option (** find the dexp, if any, where the given value is stored also return the type of the value if found *) -val vpath_find : Tenv.t -> 'a Prop.t -> Exp.t -> DecompiledExp.vpath * Typ.t option -(** Return true if [id] is assigned to a program variable which is then nullified *) val id_is_assigned_then_dead : Procdesc.Node.t -> Ident.t -> bool +(** Return true if [id] is assigned to a program variable which is then nullified *) -(** Check whether the hpred is a |-> representing a resource in the Racquire state *) val hpred_is_open_resource : Tenv.t -> 'a Prop.t -> Sil.hpred -> PredSymb.resource option +(** Check whether the hpred is a |-> representing a resource in the Racquire state *) +val find_normal_variable_funcall : + Procdesc.Node.t -> Ident.t -> (Exp.t * Exp.t list * Location.t * CallFlags.t) option (** Find the function call instruction used to initialize normal variable [id], and return the function name and arguments *) -val find_normal_variable_funcall : - Procdesc.Node.t -> Ident.t -> (Exp.t * (Exp.t list) * Location.t * CallFlags.t) option -(** Find a program variable assignment in the current node or straightline predecessor. *) val find_program_variable_assignment : Procdesc.Node.t -> Pvar.t -> (Procdesc.Node.t * Ident.t) option +(** Find a program variable assignment in the current node or straightline predecessor. *) -(** Find a program variable assignment to id in the current node or predecessors. *) val find_ident_assignment : Procdesc.Node.t -> Ident.t -> (Procdesc.Node.t * Exp.t) option +(** Find a program variable assignment to id in the current node or predecessors. *) +val find_boolean_assignment : Procdesc.Node.t -> Pvar.t -> bool -> Procdesc.Node.t option (** Find a boolean assignment to a temporary variable holding a boolean condition. The boolean parameter indicates whether the true or false branch is required. *) -val find_boolean_assignment : Procdesc.Node.t -> Pvar.t -> bool -> Procdesc.Node.t option -(** describe rvalue [e] as a dexp *) val exp_rv_dexp : Tenv.t -> Procdesc.Node.t -> Exp.t -> DecompiledExp.t option +(** describe rvalue [e] as a dexp *) +val explain_context_leak : + Typ.Procname.t -> Typ.t -> Typ.Fieldname.t -> (Typ.Fieldname.t option * Typ.t) list + -> Localise.error_desc (** Produce a description of a persistent reference to an Android Context *) -val explain_context_leak : Typ.Procname.t -> Typ.t -> Typ.Fieldname.t -> - (Typ.Fieldname.t option * Typ.t) list -> Localise.error_desc +val explain_allocation_mismatch : PredSymb.res_action -> PredSymb.res_action -> Localise.error_desc (** Produce a description of a mismatch between an allocation function and a deallocation function *) -val explain_allocation_mismatch : - PredSymb.res_action -> PredSymb.res_action -> Localise.error_desc +val explain_array_access : + Tenv.t -> Localise.deref_str -> 'a Prop.t -> Location.t -> Localise.error_desc (** Produce a description of the array access performed in the current instruction, if any. *) -val explain_array_access : Tenv.t -> Localise.deref_str -> 'a Prop.t -> Location.t -> Localise.error_desc -(** explain a class cast exception *) val explain_class_cast_exception : - Tenv.t -> Typ.Procname.t option -> Exp.t -> Exp.t -> Exp.t -> - Procdesc.Node.t -> Location.t -> Localise.error_desc + Tenv.t -> Typ.Procname.t option -> Exp.t -> Exp.t -> Exp.t -> Procdesc.Node.t -> Location.t + -> Localise.error_desc +(** explain a class cast exception *) -(** Explain a deallocate stack variable error *) val explain_deallocate_stack_var : Pvar.t -> PredSymb.res_action -> Localise.error_desc +(** Explain a deallocate stack variable error *) -(** Explain a deallocate constant string error *) val explain_deallocate_constant_string : string -> PredSymb.res_action -> Localise.error_desc +(** Explain a deallocate constant string error *) -(** Produce a description of which expression is dereferenced in the current instruction, if any. *) val explain_dereference : - Tenv.t -> ?use_buckets:bool -> ?is_nullable:bool -> ?is_premature_nil:bool -> - Localise.deref_str -> 'a Prop.t -> Location.t -> Localise.error_desc + Tenv.t -> ?use_buckets:bool -> ?is_nullable:bool -> ?is_premature_nil:bool -> Localise.deref_str + -> 'a Prop.t -> Location.t -> Localise.error_desc +(** Produce a description of which expression is dereferenced in the current instruction, if any. *) +val explain_dereference_as_caller_expression : + Tenv.t -> ?use_buckets:bool -> Localise.deref_str -> 'a Prop.t -> 'b Prop.t -> Exp.t + -> Procdesc.Node.t -> Location.t -> Pvar.t list -> Localise.error_desc (** return a description explaining value [exp] in [prop] in terms of a source expression using the formal parameters of the call *) -val explain_dereference_as_caller_expression : - Tenv.t -> ?use_buckets:bool -> - Localise.deref_str -> 'a Prop.t -> 'b Prop.t -> Exp.t -> - Procdesc.Node.t -> Location.t -> Pvar.t list -> Localise.error_desc +val explain_divide_by_zero : + Tenv.t -> Exp.t -> Procdesc.Node.t -> Location.t -> Localise.error_desc (** explain a division by zero *) -val explain_divide_by_zero : Tenv.t -> Exp.t -> Procdesc.Node.t -> Location.t -> Localise.error_desc -(** explain a return expression required *) val explain_return_expression_required : Location.t -> Typ.t -> Localise.error_desc +(** explain a return expression required *) -(** explain a comparing floats for equality *) val explain_comparing_floats_for_equality : Location.t -> Localise.error_desc +(** explain a comparing floats for equality *) -(** explain a condition is an assignment *) val explain_condition_is_assignment : Location.t -> Localise.error_desc +(** explain a condition is an assignment *) -(** explain a condition which is always true or false *) val explain_condition_always_true_false : Tenv.t -> IntLit.t -> Exp.t -> Procdesc.Node.t -> Location.t -> Localise.error_desc +(** explain a condition which is always true or false *) val explain_unreachable_code_after : Location.t -> Localise.error_desc -(** explain the escape of a stack variable address from its scope *) val explain_stack_variable_address_escape : Location.t -> Pvar.t -> DecompiledExp.t option -> Localise.error_desc +(** explain the escape of a stack variable address from its scope *) -(** explain frontend warning *) val explain_frontend_warning : string -> string option -> Location.t -> Localise.error_desc +(** explain frontend warning *) -(** explain a return statement missing *) val explain_return_statement_missing : Location.t -> Localise.error_desc +(** explain a return statement missing *) -(** explain a retain cycle *) val explain_retain_cycle : - ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> - Location.t -> string option -> Localise.error_desc + ((Sil.strexp * Typ.t) * Typ.Fieldname.t * Sil.strexp) list -> Location.t -> string option + -> Localise.error_desc +(** explain a retain cycle *) -(** explain unary minus applied to unsigned expression *) val explain_unary_minus_applied_to_unsigned_expression : Tenv.t -> Exp.t -> Typ.t -> Procdesc.Node.t -> Location.t -> Localise.error_desc +(** explain unary minus applied to unsigned expression *) -(** Explain a tainted value error *) val explain_tainted_value_reaching_sensitive_function : - Prop.normal Prop.t -> Exp.t -> PredSymb.taint_info -> Typ.Procname.t -> Location.t -> - Localise.error_desc + Prop.normal Prop.t -> Exp.t -> PredSymb.taint_info -> Typ.Procname.t -> Location.t + -> Localise.error_desc +(** Explain a tainted value error *) +val explain_leak : + Tenv.t -> Sil.hpred -> 'a Prop.t -> PredSymb.t option -> string option + -> Exceptions.visibility * Localise.error_desc (** Produce a description of a leak by looking at the current state. If the current instruction is a variable nullify, blame the variable. If it is an abstraction, blame any variable nullify at the current node. If there is an alloc attribute, print the function call and line number. *) -val explain_leak : - Tenv.t -> Sil.hpred -> 'a Prop.t -> PredSymb.t option -> string option -> - Exceptions.visibility * Localise.error_desc +val explain_memory_access : + Tenv.t -> Localise.deref_str -> 'a Prop.t -> Location.t -> Localise.error_desc (** Produce a description of the memory access performed in the current instruction, if any. *) -val explain_memory_access : Tenv.t -> Localise.deref_str -> 'a Prop.t -> Location.t -> Localise.error_desc -(** explain a test for NULL of a dereferenced pointer *) val explain_null_test_after_dereference : Tenv.t -> Exp.t -> Procdesc.Node.t -> int -> Location.t -> Localise.error_desc +(** explain a test for NULL of a dereferenced pointer *) -(** warn at the given location *) val warning_err : Location.t -> ('a, Format.formatter, unit) format -> 'a +(** warn at the given location *) (* offset of an expression found following a program variable *) -type pvar_off = - | Fpvar (* value of a pvar *) - | Fstruct of Typ.Fieldname.t list (* value obtained by dereferencing the pvar and following a sequence of fields *) -(** Find a program variable whose value is [exp] or pointing to a struct containing [exp] *) +type pvar_off = Fpvar (* value of a pvar *) + | Fstruct of Typ.Fieldname.t list + +(* value obtained by dereferencing the pvar and following a sequence of fields *) + val find_with_exp : 'a Prop.t -> Exp.t -> (Pvar.t * pvar_off) option +(** Find a program variable whose value is [exp] or pointing to a struct containing [exp] *) diff --git a/infer/src/backend/exe_env.ml b/infer/src/backend/exe_env.ml index c2969a4cb..39fb1b847 100644 --- a/infer/src/backend/exe_env.ml +++ b/infer/src/backend/exe_env.ml @@ -19,210 +19,189 @@ module F = Format (** per-file data: type environment and cfg *) type file_data = - { source: SourceFile.t; - tenv_file: DB.filename; - mutable tenv: Tenv.t option; - cfg_file: DB.filename; - mutable cfg: Cfg.cfg option; - } + { source: SourceFile.t + ; tenv_file: DB.filename + ; mutable tenv: Tenv.t option + ; cfg_file: DB.filename + ; mutable cfg: Cfg.cfg option } (** get the path to the tenv file, which either one tenv file per source file or a global tenv file *) let tenv_filename file_base = let per_source_tenv_filename = DB.filename_add_suffix file_base ".tenv" in if Sys.file_exists (DB.filename_to_string per_source_tenv_filename) = `Yes then per_source_tenv_filename - else - DB.global_tenv_fname + else DB.global_tenv_fname -module FilenameHash = Hashtbl.Make( - struct - type t = DB.filename - let equal file1 file2 = DB.equal_filename file1 file2 - let hash = Hashtbl.hash - end) +module FilenameHash = Hashtbl.Make (struct + type t = DB.filename + + let equal file1 file2 = DB.equal_filename file1 file2 + + let hash = Hashtbl.hash +end) (** create a new file_data *) let new_file_data source cg_fname = let file_base = DB.chop_extension cg_fname in let tenv_file = tenv_filename file_base in let cfg_file = DB.filename_add_suffix file_base ".cfg" in - { source = source; - tenv_file = tenv_file; - tenv = None; (* Sil.load_tenv_from_file tenv_file *) - cfg_file = cfg_file; - cfg = None; (* Cfg.load_cfg_from_file cfg_file *) - } + { source + ; tenv_file + ; tenv= None + ; (* Sil.load_tenv_from_file tenv_file *) + cfg_file + ; cfg= None (* Cfg.load_cfg_from_file cfg_file *) } let create_file_data table source cg_fname = match FilenameHash.find table cg_fname with - | file_data -> - file_data - | exception Not_found -> - let file_data = new_file_data source cg_fname in - FilenameHash.add table cg_fname file_data; - file_data + | file_data + -> file_data + | exception Not_found + -> let file_data = new_file_data source cg_fname in + FilenameHash.add table cg_fname file_data ; file_data (** execution environment *) type t = - { cg: Cg.t; (** global call graph *) - proc_map: file_data Typ.Procname.Hash.t; (** map from procedure name to file data *) - file_map: file_data FilenameHash.t; (** map from cg fname to file data *) - mutable source_files : SourceFile.Set.t; (** Source files in the execution environment *) - } + { cg: Cg.t (** global call graph *) + ; proc_map: file_data Typ.Procname.Hash.t (** map from procedure name to file data *) + ; file_map: file_data FilenameHash.t (** map from cg fname to file data *) + ; mutable source_files: SourceFile.Set.t (** Source files in the execution environment *) } (** initial state, used to add cg's *) type initial = t (** create a new execution environment *) let create () = - { cg = Cg.create (SourceFile.invalid __FILE__); - proc_map = Typ.Procname.Hash.create 17; - file_map = FilenameHash.create 1; - source_files = SourceFile.Set.empty; - } + { cg= Cg.create (SourceFile.invalid __FILE__) + ; proc_map= Typ.Procname.Hash.create 17 + ; file_map= FilenameHash.create 1 + ; source_files= SourceFile.Set.empty } (** add call graph from fname in the spec db, with relative tenv and cfg, to the execution environment *) -let add_cg (exe_env: t) (source_dir : DB.source_dir) = +let add_cg (exe_env: t) (source_dir: DB.source_dir) = let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in match Cg.load_from_file cg_fname with - | None -> - L.internal_error "Error: cannot load %s@." (DB.filename_to_string cg_fname) - | Some cg -> - let source = Cg.get_source cg in - exe_env.source_files <- SourceFile.Set.add source exe_env.source_files; + | None + -> L.internal_error "Error: cannot load %s@." (DB.filename_to_string cg_fname) + | Some cg + -> let source = Cg.get_source cg in + exe_env.source_files <- SourceFile.Set.add source exe_env.source_files ; let defined_procs = Cg.get_defined_nodes cg in - let duplicate_procs_to_print = List.filter_map defined_procs - ~f:(fun pname -> - (match AttributesTable.find_file_capturing_procedure ~cache:false pname with - | None -> None - | Some (source_captured, origin) -> - let multiply_defined = SourceFile.compare source source_captured <> 0 in - if multiply_defined then Cg.remove_node_defined cg pname; - if multiply_defined && origin <> `Include then - Some (pname, source_captured) - else None - )) in - (if Config.dump_duplicate_symbols then - Out_channel.with_file (Config.results_dir ^/ Config.duplicates_filename) - ~append:true ~perm:0o666 ~f:(fun outc -> - let fmt = F.formatter_of_out_channel outc in - List.iter duplicate_procs_to_print ~f:(fun (pname, source_captured) -> - F.fprintf fmt "@.DUPLICATE_SYMBOLS source: %a source_captured:%a pname:%a@." - SourceFile.pp source - SourceFile.pp source_captured - Typ.Procname.pp pname - ); - ) - ); + let duplicate_procs_to_print = + List.filter_map defined_procs ~f:(fun pname -> + match AttributesTable.find_file_capturing_procedure ~cache:false pname with + | None + -> None + | Some (source_captured, origin) + -> let multiply_defined = SourceFile.compare source source_captured <> 0 in + if multiply_defined then Cg.remove_node_defined cg pname ; + if multiply_defined && origin <> `Include then Some (pname, source_captured) + else None ) + in + if Config.dump_duplicate_symbols then + Out_channel.with_file (Config.results_dir ^/ Config.duplicates_filename) ~append:true + ~perm:0o666 ~f:(fun outc -> + let fmt = F.formatter_of_out_channel outc in + List.iter duplicate_procs_to_print ~f:(fun (pname, source_captured) -> + F.fprintf fmt "@.DUPLICATE_SYMBOLS source: %a source_captured:%a pname:%a@." + SourceFile.pp source SourceFile.pp source_captured Typ.Procname.pp pname ) ) ; Cg.extend exe_env.cg cg (** get the global call graph *) -let get_cg exe_env = - exe_env.cg +let get_cg exe_env = exe_env.cg let get_file_data exe_env pname = - try - Some (Typ.Procname.Hash.find exe_env.proc_map pname) + try Some (Typ.Procname.Hash.find exe_env.proc_map pname) with Not_found -> - begin - let source_file_opt = - match AttributesTable.load_attributes ~cache:true pname with - | None -> - L.(debug Analysis Medium) "can't find tenv_cfg_object for %a@." Typ.Procname.pp pname; - None - | Some proc_attributes when Config.reactive_capture -> - let get_captured_file {ProcAttributes.source_file_captured} = source_file_captured in - OndemandCapture.try_capture proc_attributes |> Option.map ~f:get_captured_file - | Some proc_attributes -> - Some proc_attributes.ProcAttributes.source_file_captured in - let get_file_data_for_source source_file = - let source_dir = DB.source_dir_from_source_file source_file in - let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in - let file_data = create_file_data exe_env.file_map source_file cg_fname in - Typ.Procname.Hash.replace exe_env.proc_map pname file_data; - file_data in - Option.map ~f:get_file_data_for_source source_file_opt - end + let source_file_opt = + match AttributesTable.load_attributes ~cache:true pname with + | None + -> L.(debug Analysis Medium) "can't find tenv_cfg_object for %a@." Typ.Procname.pp pname ; + None + | Some proc_attributes when Config.reactive_capture + -> let get_captured_file {ProcAttributes.source_file_captured} = source_file_captured in + OndemandCapture.try_capture proc_attributes |> Option.map ~f:get_captured_file + | Some proc_attributes + -> Some proc_attributes.ProcAttributes.source_file_captured + in + let get_file_data_for_source source_file = + let source_dir = DB.source_dir_from_source_file source_file in + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + let file_data = create_file_data exe_env.file_map source_file cg_fname in + Typ.Procname.Hash.replace exe_env.proc_map pname file_data ; file_data + in + Option.map ~f:get_file_data_for_source source_file_opt (** return the source file associated to the procedure *) let get_source exe_env pname = - Option.map - ~f:(fun file_data -> file_data.source) - (get_file_data exe_env pname) + Option.map ~f:(fun file_data -> file_data.source) (get_file_data exe_env pname) let file_data_to_tenv file_data = - if is_none file_data.tenv - then file_data.tenv <- Tenv.load_from_file file_data.tenv_file; + if is_none file_data.tenv then file_data.tenv <- Tenv.load_from_file file_data.tenv_file ; file_data.tenv let file_data_to_cfg file_data = - if is_none file_data.cfg - then file_data.cfg <- Cfg.load_cfg_from_file file_data.cfg_file; + if is_none file_data.cfg then file_data.cfg <- Cfg.load_cfg_from_file file_data.cfg_file ; file_data.cfg let java_global_tenv = - lazy - (match Tenv.load_from_file DB.global_tenv_fname with - | None -> - failwithf - "Could not load the global tenv at path %s@." - (DB.filename_to_string DB.global_tenv_fname) - | Some tenv -> tenv) + ( lazy + ( match Tenv.load_from_file DB.global_tenv_fname with + | None + -> failwithf "Could not load the global tenv at path %s@." + (DB.filename_to_string DB.global_tenv_fname) + | Some tenv + -> tenv ) ) (** return the type environment associated to the procedure *) let get_tenv exe_env proc_name = match proc_name with - | Typ.Procname.Java _ -> - Lazy.force java_global_tenv + | Typ.Procname.Java _ + -> Lazy.force java_global_tenv | _ -> - begin - match get_file_data exe_env proc_name with - | Some file_data -> ( - match file_data_to_tenv file_data with - | Some tenv -> - tenv - | None -> - failwithf "get_tenv: tenv not found for %a in file %s" - Typ.Procname.pp proc_name (DB.filename_to_string file_data.tenv_file) - ) - | None -> - failwithf "get_tenv: file_data not found for %a" Typ.Procname.pp proc_name - end + match get_file_data exe_env proc_name with + | Some file_data -> ( + match file_data_to_tenv file_data with + | Some tenv + -> tenv + | None + -> failwithf "get_tenv: tenv not found for %a in file %s" Typ.Procname.pp proc_name + (DB.filename_to_string file_data.tenv_file) ) + | None + -> failwithf "get_tenv: file_data not found for %a" Typ.Procname.pp proc_name (** return the cfg associated to the procedure *) let get_cfg exe_env pname = match get_file_data exe_env pname with - | None -> - None - | Some file_data -> - file_data_to_cfg file_data + | None + -> None + | Some file_data + -> file_data_to_cfg file_data (** return the proc desc associated to the procedure *) let get_proc_desc exe_env pname = match get_cfg exe_env pname with - | Some cfg -> - Cfg.find_proc_desc_from_name cfg pname - | None -> - None + | Some cfg + -> Cfg.find_proc_desc_from_name cfg pname + | None + -> None (** Create an exe_env from a source dir *) let from_cluster cluster = let exe_env = create () in - add_cg exe_env cluster; - exe_env + add_cg exe_env cluster ; exe_env (** [iter_files f exe_env] applies [f] to the filename and tenv and cfg for each file in [exe_env] *) let iter_files f exe_env = let do_file _ file_data seen_files_acc = let fname = file_data.source in - if SourceFile.Set.mem fname seen_files_acc || - (* only files added with add_cg* functions *) - not (SourceFile.Set.mem fname exe_env.source_files) + if SourceFile.Set.mem fname seen_files_acc + || (* only files added with add_cg* functions *) + not (SourceFile.Set.mem fname exe_env.source_files) then seen_files_acc - else - begin - Option.iter ~f:(fun cfg -> f fname cfg) (file_data_to_cfg file_data); - SourceFile.Set.add fname seen_files_acc - end in + else ( + Option.iter ~f:(fun cfg -> f fname cfg) (file_data_to_cfg file_data) ; + SourceFile.Set.add fname seen_files_acc ) + in ignore (Typ.Procname.Hash.fold do_file exe_env.proc_map SourceFile.Set.empty) diff --git a/infer/src/backend/exe_env.mli b/infer/src/backend/exe_env.mli index 1a8569b17..0eed6be82 100644 --- a/infer/src/backend/exe_env.mli +++ b/infer/src/backend/exe_env.mli @@ -18,27 +18,27 @@ type initial (** execution environment: a global call graph, and map from procedure names to cfg and tenv *) type t +val add_cg : initial -> DB.source_dir -> unit (** add call graph from the source dir in the spec db, with relative tenv and cfg, to the execution environment *) -val add_cg : initial -> DB.source_dir -> unit -(** Create an exe_env from a cluster *) val from_cluster : Cluster.t -> t +(** Create an exe_env from a cluster *) -(** get the global call graph *) val get_cg : t -> Cg.t +(** get the global call graph *) -(** return the source file associated to the procedure *) val get_source : t -> Typ.Procname.t -> SourceFile.t option +(** return the source file associated to the procedure *) -(** return the type environment associated to the procedure *) val get_tenv : t -> Typ.Procname.t -> Tenv.t +(** return the type environment associated to the procedure *) -(** return the cfg associated to the procedure *) val get_cfg : t -> Typ.Procname.t -> Cfg.cfg option +(** return the cfg associated to the procedure *) -(** return the proc desc associated to the procedure *) val get_proc_desc : t -> Typ.Procname.t -> Procdesc.t option +(** return the proc desc associated to the procedure *) -(** [iter_files f exe_env] applies [f] to the source file and tenv and cfg for each file in [exe_env] *) val iter_files : (SourceFile.t -> Cfg.cfg -> unit) -> t -> unit +(** [iter_files f exe_env] applies [f] to the source file and tenv and cfg for each file in [exe_env] *) diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index cc50a2b10..65a99080c 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -17,17 +17,16 @@ module CLOpt = CommandLineOption module L = Logging module F = Format - let read_config_changed_files () = match Config.changed_files_index with - | None -> - None - | Some index -> match Utils.read_file index with - | Ok lines -> - Some (SourceFile.changed_sources_from_changed_files lines) - | Error error -> - L.external_error "Error reading the changed files index '%s': %s@." index error ; - None + | None + -> None + | Some index -> + match Utils.read_file index with + | Ok lines + -> Some (SourceFile.changed_sources_from_changed_files lines) + | Error error + -> L.external_error "Error reading the changed files index '%s': %s@." index error ; None let run driver_mode = let open Driver in @@ -37,77 +36,84 @@ let run driver_mode = analyze_and_report driver_mode ~changed_files ; run_epilogue driver_mode -let results_dir_dir_markers = List.map ~f:(Filename.concat Config.results_dir) [ - Config.attributes_dir_name; Config.captured_dir_name; Config.specs_dir_name; - ] +let results_dir_dir_markers = + List.map ~f:(Filename.concat Config.results_dir) + [Config.attributes_dir_name; Config.captured_dir_name; Config.specs_dir_name] let is_results_dir () = let not_found = ref "" in - let has_all_markers = List.for_all results_dir_dir_markers ~f:(fun d -> - Sys.is_directory d = `Yes || (not_found := d; false)) in + let has_all_markers = + List.for_all results_dir_dir_markers ~f:(fun d -> + Sys.is_directory d = `Yes + || + (not_found := d ; + false) ) + in Result.ok_if_true has_all_markers ~error:(Printf.sprintf "'%s/' not found" !not_found) -let create_results_dir () = - List.iter ~f:Unix.mkdir_p results_dir_dir_markers; - L.setup_log_file () +let create_results_dir () = List.iter ~f:Unix.mkdir_p results_dir_dir_markers ; L.setup_log_file () let assert_results_dir advice = Result.iter_error (is_results_dir ()) ~f:(fun err -> - L.user_error "ERROR: No results directory at '%s': %s@\nERROR: %s@." - Config.results_dir err advice; - exit 1 - ); + L.user_error "ERROR: No results directory at '%s': %s@\nERROR: %s@." Config.results_dir err + advice ; + exit 1 ) ; L.setup_log_file () let remove_results_dir () = if Sys.is_directory Config.results_dir = `Yes then ( Result.iter_error (is_results_dir ()) ~f:(fun err -> - L.user_error "ERROR: '%s' exists but does not seem to be an infer results directory: %s@\n\ - ERROR: Please delete '%s' and try again@." - Config.results_dir err Config.results_dir; - exit 1 - ); - Utils.rmtree Config.results_dir - ) + L.user_error + "ERROR: '%s' exists but does not seem to be an infer results directory: %s@\nERROR: Please delete '%s' and try again@." + Config.results_dir err Config.results_dir ; + exit 1 ) ; + Utils.rmtree Config.results_dir ) let setup_results_dir () = match Config.command with - | Analyze -> assert_results_dir "have you run capture before?" - | Clang | Report | ReportDiff -> create_results_dir () - | Capture | Compile | Run -> - let driver_mode = Lazy.force Driver.mode_from_command_line in - if not (Driver.(equal_driver_mode driver_mode Analyze) || - Config.(buck || continue_capture || maven || reactive_mode)) then - remove_results_dir (); + | Analyze + -> assert_results_dir "have you run capture before?" + | Clang | Report | ReportDiff + -> create_results_dir () + | Capture | Compile | Run + -> let driver_mode = Lazy.force Driver.mode_from_command_line in + if not + ( Driver.(equal_driver_mode driver_mode Analyze) + || Config.(buck || continue_capture || maven || reactive_mode) ) + then remove_results_dir () ; create_results_dir () let () = - if Config.print_builtins then Builtin.print_and_exit (); - setup_results_dir (); - if Config.debug_mode then - L.progress "Logs in %s@." (Config.results_dir ^/ Config.log_file); + if Config.print_builtins then Builtin.print_and_exit () ; + setup_results_dir () ; + if Config.debug_mode then L.progress "Logs in %s@." (Config.results_dir ^/ Config.log_file) ; match Config.command with - | Analyze -> - let pp_cluster_opt fmt = function - | None -> F.fprintf fmt "(no cluster)" - | Some cluster -> F.fprintf fmt "of cluster %s" (Filename.basename cluster) in - L.environment_info "Starting analysis %a" pp_cluster_opt Config.cluster_cmdline; - InferAnalyze.register_perf_stats_report (); + | Analyze + -> let pp_cluster_opt fmt = function + | None + -> F.fprintf fmt "(no cluster)" + | Some cluster + -> F.fprintf fmt "of cluster %s" (Filename.basename cluster) + in + L.environment_info "Starting analysis %a" pp_cluster_opt Config.cluster_cmdline ; + InferAnalyze.register_perf_stats_report () ; Driver.analyze_and_report Analyze ~changed_files:(read_config_changed_files ()) - | Clang -> - let prog, args = match Array.to_list Sys.argv with - | prog::args -> prog, args - | [] -> assert false (* Sys.argv is never empty *) in + | Clang + -> let prog, args = + match Array.to_list Sys.argv with prog :: args -> (prog, args) | [] -> assert false + (* Sys.argv is never empty *) + in ClangWrapper.exe ~prog ~args - | Report -> - InferPrint.main_from_config () - | ReportDiff -> - (* at least one report must be passed in input to compute differential *) - (match Config.report_current, Config.report_previous with - | None, None -> - failwith "Expected at least one argument among 'report-current' and 'report-previous'\n" - | _ -> ()); + | Report + -> InferPrint.main_from_config () + | ReportDiff + -> (* at least one report must be passed in input to compute differential *) + ( match (Config.report_current, Config.report_previous) with + | None, None + -> failwith "Expected at least one argument among 'report-current' and 'report-previous'\n" + | _ + -> () ) ; ReportDiff.reportdiff ~current_report:Config.report_current ~previous_report:Config.report_previous - | Capture | Compile | Run -> - run (Lazy.force Driver.mode_from_command_line) + | Capture | Compile | Run + -> run (Lazy.force Driver.mode_from_command_line) diff --git a/infer/src/backend/inferconfig.ml b/infer/src/backend/inferconfig.ml index 70326a10a..1b2c26da5 100644 --- a/infer/src/backend/inferconfig.ml +++ b/infer/src/backend/inferconfig.ml @@ -8,57 +8,48 @@ *) open! IStd - module CLOpt = CommandLineOption module F = Format module L = Logging type path_filter = SourceFile.t -> bool + type error_filter = Localise.t -> bool + type proc_filter = Typ.Procname.t -> bool -type filters = - { - path_filter : path_filter; - error_filter : error_filter; - proc_filter : proc_filter; - } +type filters = {path_filter: path_filter; error_filter: error_filter; proc_filter: proc_filter} let default_path_filter : path_filter = function _ -> true + let default_error_filter : error_filter = function _ -> true + let default_proc_filter : proc_filter = function _ -> true let do_not_filter : filters = - { - path_filter = default_path_filter; - error_filter = default_error_filter; - proc_filter = default_proc_filter; - } + { path_filter= default_path_filter + ; error_filter= default_error_filter + ; proc_filter= default_proc_filter } type filter_config = - { - whitelist: string list; - blacklist: string list; - blacklist_files_containing : string list; - suppress_errors: string list; - } - -let is_matching patterns = - fun source_file -> - let path = SourceFile.to_rel_path source_file in - List.exists - ~f:(fun pattern -> - try - Int.equal (Str.search_forward pattern path 0) 0 - with Not_found -> false) - patterns - + { whitelist: string list + ; blacklist: string list + ; blacklist_files_containing: string list + ; suppress_errors: string list } + +let is_matching patterns source_file = + let path = SourceFile.to_rel_path source_file in + List.exists + ~f:(fun pattern -> + try Int.equal (Str.search_forward pattern path 0) 0 + with Not_found -> false) + patterns (** Check if a proc name is matching the name given as string. *) let match_method language proc_name method_name = - not (BuiltinDecl.is_declared proc_name) && - Config.equal_language (Typ.Procname.get_language proc_name) language && - String.equal (Typ.Procname.get_method proc_name) method_name + not (BuiltinDecl.is_declared proc_name) + && Config.equal_language (Typ.Procname.get_language proc_name) language + && String.equal (Typ.Procname.get_method proc_name) method_name (* Module to create matcher based on strings present in the source file *) module FileContainsStringMatcher = struct @@ -68,38 +59,33 @@ module FileContainsStringMatcher = struct let file_contains regexp file_in = let rec loop () = - try - (Str.search_forward regexp (In_channel.input_line_exn file_in) 0) >= 0 - with - | Not_found -> loop () - | End_of_file -> false in + try Str.search_forward regexp (In_channel.input_line_exn file_in) 0 >= 0 with + | Not_found + -> loop () + | End_of_file + -> false + in loop () let create_matcher s_patterns = - if List.is_empty s_patterns then - default_matcher + if List.is_empty s_patterns then default_matcher else let source_map = ref SourceFile.Map.empty in - let regexp = - Str.regexp (String.concat ~sep:"\\|" s_patterns) in + let regexp = Str.regexp (String.concat ~sep:"\\|" s_patterns) in fun source_file -> - try - SourceFile.Map.find source_file !source_map + try SourceFile.Map.find source_file !source_map with Not_found -> - try - let file_in = In_channel.create (SourceFile.to_abs_path source_file) in - let pattern_found = file_contains regexp file_in in - In_channel.close file_in; - source_map := SourceFile.Map.add source_file pattern_found !source_map; - pattern_found - with Sys_error _ -> false + try + let file_in = In_channel.create (SourceFile.to_abs_path source_file) in + let pattern_found = file_contains regexp file_in in + In_channel.close file_in ; + source_map := SourceFile.Map.add source_file pattern_found !source_map ; + pattern_found + with Sys_error _ -> false end -type method_pattern = { - class_name : string; - method_name : string option; - parameters : (string list) option; -} +type method_pattern = + {class_name: string; method_name: string option; parameters: string list option} type pattern = | Method_pattern of Config.language * method_pattern @@ -107,26 +93,23 @@ type pattern = (* Module to create matcher based on source file names or class names and method names *) module FileOrProcMatcher = struct - type matcher = SourceFile.t -> Typ.Procname.t -> bool - let default_matcher : matcher = - fun _ _ -> false + let default_matcher : matcher = fun _ _ -> false let create_method_matcher m_patterns = - if List.is_empty m_patterns then - default_matcher + if List.is_empty m_patterns then default_matcher else let pattern_map = List.fold ~f:(fun map pattern -> - let previous = - try - String.Map.find_exn map pattern.class_name - with Not_found -> [] in - String.Map.add ~key:pattern.class_name ~data:(pattern :: previous) map) - ~init:String.Map.empty - m_patterns in + let previous = + try String.Map.find_exn map pattern.class_name + with Not_found -> [] + in + String.Map.add ~key:pattern.class_name ~data:(pattern :: previous) map) + ~init:String.Map.empty m_patterns + in let do_java pname_java = let class_name = Typ.Procname.java_get_class_name pname_java and method_name = Typ.Procname.java_get_method pname_java in @@ -134,170 +117,173 @@ module FileOrProcMatcher = struct let class_patterns = String.Map.find_exn pattern_map class_name in List.exists ~f:(fun p -> - match p.method_name with - | None -> true - | Some m -> String.equal m method_name) + match p.method_name with None -> true | Some m -> String.equal m method_name) class_patterns - with Not_found -> false in - + with Not_found -> false + in fun _ proc_name -> - match proc_name with - | Typ.Procname.Java pname_java -> - do_java pname_java - | _ -> - false + match proc_name with Typ.Procname.Java pname_java -> do_java pname_java | _ -> false let create_file_matcher patterns = let s_patterns, m_patterns = let collect (s_patterns, m_patterns) = function - | Source_contains (_, s) -> (s:: s_patterns, m_patterns) - | Method_pattern (_, mp) -> (s_patterns, mp :: m_patterns) in - List.fold ~f:collect ~init:([], []) patterns in + | Source_contains (_, s) + -> (s :: s_patterns, m_patterns) + | Method_pattern (_, mp) + -> (s_patterns, mp :: m_patterns) + in + List.fold ~f:collect ~init:([], []) patterns + in let s_matcher = let matcher = FileContainsStringMatcher.create_matcher s_patterns in fun source_file _ -> matcher source_file and m_matcher = create_method_matcher m_patterns in - fun source_file proc_name -> - m_matcher source_file proc_name || s_matcher source_file proc_name + fun source_file proc_name -> m_matcher source_file proc_name || s_matcher source_file proc_name let load_matcher = create_file_matcher let _pp_pattern fmt pattern = - let pp_string fmt s = - Format.fprintf fmt "%s" s in + let pp_string fmt s = Format.fprintf fmt "%s" s in let pp_option pp_value fmt = function - | None -> pp_string fmt "None" - | Some value -> Format.fprintf fmt "%a" pp_value value in + | None + -> pp_string fmt "None" + | Some value + -> Format.fprintf fmt "%a" pp_value value + in let pp_key_value pp_value fmt (key, value) = - Format.fprintf fmt " %s: %a,@\n" key (pp_option pp_value) value in + Format.fprintf fmt " %s: %a,@\n" key (pp_option pp_value) value + in let pp_method_pattern fmt mp = let pp_params fmt l = - Format.fprintf fmt "[%a]" - (Pp.semicolon_seq_oneline Pp.text pp_string) l in - Format.fprintf fmt "%a%a%a" - (pp_key_value pp_string) ("class", Some mp.class_name) - (pp_key_value pp_string) ("method", mp.method_name) - (pp_key_value pp_params) ("parameters", mp.parameters) - and pp_source_contains fmt sc = - Format.fprintf fmt " pattern: %s@\n" sc in + Format.fprintf fmt "[%a]" (Pp.semicolon_seq_oneline Pp.text pp_string) l + in + Format.fprintf fmt "%a%a%a" (pp_key_value pp_string) ("class", Some mp.class_name) + (pp_key_value pp_string) ("method", mp.method_name) (pp_key_value pp_params) + ("parameters", mp.parameters) + and pp_source_contains fmt sc = Format.fprintf fmt " pattern: %s@\n" sc in match pattern with - | Method_pattern (language, mp) -> - Format.fprintf fmt "Method pattern (%s) {@\n%a}@\n" - (Config.string_of_language language) pp_method_pattern mp - | Source_contains (language, sc) -> - Format.fprintf fmt "Source contains (%s) {@\n%a}@\n" - (Config.string_of_language language) pp_source_contains sc - -end (* of module FileOrProcMatcher *) + | Method_pattern (language, mp) + -> Format.fprintf fmt "Method pattern (%s) {@\n%a}@\n" (Config.string_of_language language) + pp_method_pattern mp + | Source_contains (language, sc) + -> Format.fprintf fmt "Source contains (%s) {@\n%a}@\n" (Config.string_of_language language) + pp_source_contains sc +end +(* of module FileOrProcMatcher *) (* Module to create patterns that will match all overriding methods in the pattern *) module OverridesMatcher = struct - - let load_matcher patterns = - fun is_subtype proc_name -> - let is_matching = function - | Method_pattern (language, mp) -> - is_subtype mp.class_name - && (Option.value_map ~f:(match_method language proc_name) ~default:false mp.method_name) - | _ -> failwith "Expecting method pattern" in - List.exists ~f:is_matching patterns - + let load_matcher patterns is_subtype proc_name = + let is_matching = function + | Method_pattern (language, mp) + -> is_subtype mp.class_name + && Option.value_map ~f:(match_method language proc_name) ~default:false mp.method_name + | _ + -> failwith "Expecting method pattern" + in + List.exists ~f:is_matching patterns end let patterns_of_json_with_key (json_key, json) = - let default_method_pattern = { - class_name = ""; - method_name = None; - parameters = None - } in - + let default_method_pattern = {class_name= ""; method_name= None; parameters= None} in let default_source_contains = "" in - let language_of_string = function - | "Java" -> - Ok Config.Java - | l -> - Error ("JSON key " ^ json_key ^ " not supported for language " ^ l) in - + | "Java" + -> Ok Config.Java + | l + -> Error ("JSON key " ^ json_key ^ " not supported for language " ^ l) + in let rec detect_language = function - | [] -> - Error ("No language found for " ^ json_key) - | ("language", `String s) :: _ -> - language_of_string s - | _:: tl -> - detect_language tl in - + | [] + -> Error ("No language found for " ^ json_key) + | ("language", `String s) :: _ + -> language_of_string s + | _ :: tl + -> detect_language tl + in (* Detect the kind of pattern, method pattern or pattern based on the content of the source file. Detecting the kind of patterns in a first step makes it easier to parse the parts of the pattern in a second step *) let detect_pattern assoc = match detect_language assoc with - | Ok language -> - let is_method_pattern key = List.exists ~f:(String.equal key) ["class"; "method"] + | Ok language + -> let is_method_pattern key = List.exists ~f:(String.equal key) ["class"; "method"] and is_source_contains key = List.exists ~f:(String.equal key) ["source_contains"] in let rec loop = function - | [] -> - Error ("Unknown pattern for " ^ json_key) - | (key, _) :: _ when is_method_pattern key -> - Ok (Method_pattern (language, default_method_pattern)) - | (key, _) :: _ when is_source_contains key -> - Ok (Source_contains (language, default_source_contains)) - | _:: tl -> loop tl in + | [] + -> Error ("Unknown pattern for " ^ json_key) + | (key, _) :: _ when is_method_pattern key + -> Ok (Method_pattern (language, default_method_pattern)) + | (key, _) :: _ when is_source_contains key + -> Ok (Source_contains (language, default_source_contains)) + | _ :: tl + -> loop tl + in loop assoc - | Error _ as error -> - error in - + | Error _ as error + -> error + in (* Translate a JSON entry into a matching pattern *) - let create_pattern (assoc : (string * Yojson.Basic.json) list) = + let create_pattern (assoc: (string * Yojson.Basic.json) list) = let collect_params l = let collect accu = function - | `String s -> s:: accu - | _ -> failwith ("Unrecognised parameters in " ^ Yojson.Basic.to_string (`Assoc assoc)) in - List.rev (List.fold ~f:collect ~init:[] l) in + | `String s + -> s :: accu + | _ + -> failwith ("Unrecognised parameters in " ^ Yojson.Basic.to_string (`Assoc assoc)) + in + List.rev (List.fold ~f:collect ~init:[] l) + in let create_method_pattern assoc = let loop mp = function - | (key, `String s) when String.equal key "class" -> - { mp with class_name = s } - | (key, `String s) when String.equal key "method" -> - { mp with method_name = Some s } - | (key, `List l) when String.equal key "parameters" -> - { mp with parameters = Some (collect_params l) } - | (key, _) when String.equal key "language" -> mp - | _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in + | key, `String s when String.equal key "class" + -> {mp with class_name= s} + | key, `String s when String.equal key "method" + -> {mp with method_name= Some s} + | key, `List l when String.equal key "parameters" + -> {mp with parameters= Some (collect_params l)} + | key, _ when String.equal key "language" + -> mp + | _ + -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) + in List.fold ~f:loop ~init:default_method_pattern assoc and create_string_contains assoc = let loop sc = function - | (key, `String pattern) when String.equal key "source_contains" -> pattern - | (key, _) when String.equal key "language" -> sc - | _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in - List.fold ~f:loop ~init:default_source_contains assoc in + | key, `String pattern when String.equal key "source_contains" + -> pattern + | key, _ when String.equal key "language" + -> sc + | _ + -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) + in + List.fold ~f:loop ~init:default_source_contains assoc + in match detect_pattern assoc with - | Ok (Method_pattern (language, _)) -> - Ok (Method_pattern (language, create_method_pattern assoc)) - | Ok (Source_contains (language, _)) -> - Ok (Source_contains (language, create_string_contains assoc)) - | Error _ as error -> - error in - - let warn_user msg = - CLOpt.warnf "WARNING: error parsing option %s@\n%s@." json_key msg in - + | Ok Method_pattern (language, _) + -> Ok (Method_pattern (language, create_method_pattern assoc)) + | Ok Source_contains (language, _) + -> Ok (Source_contains (language, create_string_contains assoc)) + | Error _ as error + -> error + in + let warn_user msg = CLOpt.warnf "WARNING: error parsing option %s@\n%s@." json_key msg in (* Translate all the JSON entries into matching patterns *) let rec translate accu = function | `Assoc l -> ( - match create_pattern l with - | Ok pattern -> - pattern :: accu - | Error msg -> - warn_user msg; - accu) - | `List l -> - List.fold ~f:translate ~init:accu l - | json -> - warn_user (Printf.sprintf "expected list or assoc json type, but got value %s" - (Yojson.Basic.to_string json)); - accu in - + match create_pattern l with + | Ok pattern + -> pattern :: accu + | Error msg + -> warn_user msg ; accu ) + | `List l + -> List.fold ~f:translate ~init:accu l + | json + -> warn_user + (Printf.sprintf "expected list or assoc json type, but got value %s" + (Yojson.Basic.to_string json)) ; + accu + in translate [] json let modeled_expensive_matcher = @@ -310,35 +296,34 @@ let skip_translation_matcher = FileOrProcMatcher.load_matcher (patterns_of_json_with_key Config.patterns_skip_translation) let load_filters analyzer = - { - whitelist = Config.analysis_path_regex_whitelist analyzer; - blacklist = Config.analysis_path_regex_blacklist analyzer; - blacklist_files_containing = Config.analysis_blacklist_files_containing analyzer; - suppress_errors = Config.analysis_suppress_errors analyzer; - } + { whitelist= Config.analysis_path_regex_whitelist analyzer + ; blacklist= Config.analysis_path_regex_blacklist analyzer + ; blacklist_files_containing= Config.analysis_blacklist_files_containing analyzer + ; suppress_errors= Config.analysis_suppress_errors analyzer } let filters_from_inferconfig inferconfig : filters = let path_filter = let whitelist_filter : path_filter = if List.is_empty inferconfig.whitelist then default_path_filter - else is_matching (List.map ~f:Str.regexp inferconfig.whitelist) in + else is_matching (List.map ~f:Str.regexp inferconfig.whitelist) + in let blacklist_filter : path_filter = - is_matching (List.map ~f:Str.regexp inferconfig.blacklist) in + is_matching (List.map ~f:Str.regexp inferconfig.blacklist) + in let blacklist_files_containing_filter : path_filter = - FileContainsStringMatcher.create_matcher inferconfig.blacklist_files_containing in - function source_file -> - whitelist_filter source_file && - not (blacklist_filter source_file) && - not (blacklist_files_containing_filter source_file) in - let error_filter = - function error_name -> - let error_str = Localise.to_issue_id error_name in - not (List.exists ~f:(String.equal error_str) inferconfig.suppress_errors) in - { - path_filter = path_filter; - error_filter = error_filter; - proc_filter = default_proc_filter; - } + FileContainsStringMatcher.create_matcher inferconfig.blacklist_files_containing + in + function + | source_file + -> whitelist_filter source_file && not (blacklist_filter source_file) + && not (blacklist_files_containing_filter source_file) + in + let error_filter = function + | error_name + -> let error_str = Localise.to_issue_id error_name in + not (List.exists ~f:(String.equal error_str) inferconfig.suppress_errors) + in + {path_filter; error_filter; proc_filter= default_proc_filter} (* Create filters based on .inferconfig *) let create_filters analyzer = @@ -349,13 +334,13 @@ let create_filters analyzer = (* white/black listing in .inferconfig and the default value *) let is_checker_enabled checker_name = (* no-filtering takes priority over both whitelist and blacklist *) - (not Config.filtering) + not Config.filtering (* whitelist takes priority over blacklist *) - || (List.mem ~equal:String.(=) Config.enable_checks checker_name) + || List.mem ~equal:String.( = ) Config.enable_checks checker_name (* if it's blacklisted and not whitelisted then it should be disabled *) - || ((not (List.mem ~equal:String.(=) Config.disable_checks checker_name)) - (* if it's not amond white/black listed then we use default value *) - && not (List.mem ~equal:String.(=) Config.checks_disabled_by_default checker_name)) + || not (List.mem ~equal:String.( = ) Config.disable_checks checker_name) + (* if it's not amond white/black listed then we use default value *) + && not (List.mem ~equal:String.( = ) Config.checks_disabled_by_default checker_name) (* This function loads and list the path that are being filtered by the analyzer. The results *) (* are of the form: path/to/file.java -> {infer, eradicate} meaning that analysis results will *) @@ -364,20 +349,19 @@ let test () = let filters = List.map ~f:(fun (name, analyzer) -> (name, analyzer, create_filters analyzer)) - Config.string_to_analyzer in + Config.string_to_analyzer + in let matching_analyzers path = List.fold - ~f:(fun l (n, a, f) -> if f.path_filter path then (n,a) :: l else l) - ~init:[] - filters in + ~f:(fun l (n, a, f) -> if f.path_filter path then (n, a) :: l else l) + ~init:[] filters + in Utils.directory_iter (fun path -> - if DB.is_source_file path then - let source_file = SourceFile.from_abs_path path in - let matching = matching_analyzers source_file in - if matching <> [] then - let matching_s = String.concat ~sep:", " (List.map ~f:fst matching) in - L.result "%s -> {%s}@." - (SourceFile.to_rel_path source_file) - matching_s) + if DB.is_source_file path then + let source_file = SourceFile.from_abs_path path in + let matching = matching_analyzers source_file in + if matching <> [] then + let matching_s = String.concat ~sep:", " (List.map ~f:fst matching) in + L.result "%s -> {%s}@." (SourceFile.to_rel_path source_file) matching_s) (Sys.getcwd ()) diff --git a/infer/src/backend/inferconfig.mli b/infer/src/backend/inferconfig.mli index 7ff93e02d..6afcc3b0e 100644 --- a/infer/src/backend/inferconfig.mli +++ b/infer/src/backend/inferconfig.mli @@ -18,26 +18,23 @@ type error_filter = Localise.t -> bool (** Filter type for a procedure name *) type proc_filter = Typ.Procname.t -> bool -type filters = - { - path_filter : path_filter; - error_filter : error_filter; - proc_filter : proc_filter; - } +type filters = {path_filter: path_filter; error_filter: error_filter; proc_filter: proc_filter} -(** Filters that accept everything. *) val do_not_filter : filters +(** Filters that accept everything. *) -(** Create filters based on the config file *) val create_filters : Config.analyzer -> filters +(** Create filters based on the config file *) val never_return_null_matcher : SourceFile.t -> Typ.Procname.t -> bool + val skip_translation_matcher : SourceFile.t -> Typ.Procname.t -> bool + val modeled_expensive_matcher : (string -> bool) -> Typ.Procname.t -> bool +val test : unit -> unit (** Load the config file and list the files to report on *) -val test: unit -> unit +val is_checker_enabled : string -> bool (** is_checker_enabled [error_name] is [true] if [error_name] is whitelisted in .inferconfig or if it's enabled by default *) -val is_checker_enabled : string -> bool diff --git a/infer/src/backend/interproc.ml b/infer/src/backend/interproc.ml index 80321bb94..5c97917f1 100644 --- a/infer/src/backend/interproc.ml +++ b/infer/src/backend/interproc.ml @@ -10,7 +10,6 @@ open! IStd open! PVariant - module Hashtbl = Caml.Hashtbl (** Interprocedural Analysis *) @@ -19,331 +18,328 @@ module L = Logging module F = Format (** A node with a number of visits *) -type visitednode = - { - node: Procdesc.Node.t; - visits: int; - } +type visitednode = {node: Procdesc.Node.t; visits: int} (** Set of nodes with number of visits *) -module NodeVisitSet = - Caml.Set.Make(struct - type t = visitednode - let compare_ids n1 n2 = - (* higher id is better *) - Procdesc.Node.compare n2 n1 - let compare_distance_to_exit { node = n1 } { node = n2 } = - (* smaller means higher priority *) - let n = - match Procdesc.Node.get_distance_to_exit n1, Procdesc.Node.get_distance_to_exit n2 with - | None, None -> - 0 - | None, Some _ -> - 1 - | Some _, None -> - - 1 - | Some d1, Some d2 -> - (* shorter distance to exit is better *) - Int.compare d1 d2 in - if n <> 0 then n else compare_ids n1 n2 - let compare_number_of_visits x1 x2 = - let n = Int.compare x1.visits x2.visits in (* visited fewer times is better *) - if n <> 0 then n else compare_distance_to_exit x1 x2 - let compare x1 x2 = - if !Config.footprint then - match Config.worklist_mode with - | 0 -> compare_ids x1.node x2.node - | 1 -> compare_distance_to_exit x1 x2 - | _ -> compare_number_of_visits x1 x2 - else compare_ids x1.node x2.node - end) +module NodeVisitSet = Caml.Set.Make (struct + type t = visitednode + + let compare_ids n1 n2 = + (* higher id is better *) + Procdesc.Node.compare n2 n1 + + let compare_distance_to_exit {node= n1} {node= n2} = + (* smaller means higher priority *) + let n = + match (Procdesc.Node.get_distance_to_exit n1, Procdesc.Node.get_distance_to_exit n2) with + | None, None + -> 0 + | None, Some _ + -> 1 + | Some _, None + -> -1 + | Some d1, Some d2 + -> (* shorter distance to exit is better *) + Int.compare d1 d2 + in + if n <> 0 then n else compare_ids n1 n2 + + let compare_number_of_visits x1 x2 = + let n = Int.compare x1.visits x2.visits in + (* visited fewer times is better *) + if n <> 0 then n else compare_distance_to_exit x1 x2 + + let compare x1 x2 = + if !Config.footprint then + match Config.worklist_mode with + | 0 + -> compare_ids x1.node x2.node + | 1 + -> compare_distance_to_exit x1 x2 + | _ + -> compare_number_of_visits x1 x2 + else compare_ids x1.node x2.node +end) (** Table for the results of the join operation on nodes. *) module Join_table : sig type t val add : t -> Procdesc.Node.id -> Paths.PathSet.t -> unit + val create : unit -> t + val find : t -> Procdesc.Node.id -> Paths.PathSet.t end = struct type t = (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t - let create () : t = - Hashtbl.create 11 + let create () : t = Hashtbl.create 11 let find table i = - try Hashtbl.find table i with - | Not_found -> Paths.PathSet.empty + try Hashtbl.find table i + with Not_found -> Paths.PathSet.empty - let add table i dset = - Hashtbl.replace table i dset + let add table i dset = Hashtbl.replace table i dset end (* =============== START of module Worklist =============== *) module Worklist = struct - - type t = { - join_table : Join_table.t; (** Table of join results *) - path_set_todo : (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t; (** Pathset todo *) - path_set_visited : (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t; (** Pathset visited *) - mutable todo_set : NodeVisitSet.t; (** Set of nodes still to do, with visit count *) - mutable visit_map : int Procdesc.NodeMap.t; (** Map from nodes done to visit count *) - } - - let create () = { - join_table = Join_table.create (); - path_set_todo = Hashtbl.create 11; - path_set_visited = Hashtbl.create 11; - todo_set = NodeVisitSet.empty; - visit_map = Procdesc.NodeMap.empty; - } - - let is_empty (wl : t) : bool = - NodeVisitSet.is_empty wl.todo_set - - let add (wl : t) (node : Procdesc.Node.t) : unit = - let visits = (* recover visit count if it was visited before *) - try Procdesc.NodeMap.find node wl.visit_map with - | Not_found -> 0 in - wl.todo_set <- NodeVisitSet.add { node; visits } wl.todo_set + type t = + { join_table: Join_table.t (** Table of join results *) + ; path_set_todo: (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t (** Pathset todo *) + ; path_set_visited: (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t (** Pathset visited *) + ; mutable todo_set: NodeVisitSet.t (** Set of nodes still to do, with visit count *) + ; mutable visit_map: int Procdesc.NodeMap.t (** Map from nodes done to visit count *) } + + let create () = + { join_table= Join_table.create () + ; path_set_todo= Hashtbl.create 11 + ; path_set_visited= Hashtbl.create 11 + ; todo_set= NodeVisitSet.empty + ; visit_map= Procdesc.NodeMap.empty } + + let is_empty (wl: t) : bool = NodeVisitSet.is_empty wl.todo_set + + let add (wl: t) (node: Procdesc.Node.t) : unit = + let visits = + (* recover visit count if it was visited before *) + try Procdesc.NodeMap.find node wl.visit_map + with Not_found -> 0 + in + wl.todo_set <- NodeVisitSet.add {node; visits} wl.todo_set (** remove the minimum element from the worklist, and increase its number of visits *) - let remove (wl : t) : Procdesc.Node.t = + let remove (wl: t) : Procdesc.Node.t = try let min = NodeVisitSet.min_elt wl.todo_set in - wl.todo_set <- - NodeVisitSet.remove min wl.todo_set; - wl.visit_map <- - Procdesc.NodeMap.add min.node (min.visits + 1) wl.visit_map; (* increase the visits *) + wl.todo_set <- NodeVisitSet.remove min wl.todo_set ; + wl.visit_map <- Procdesc.NodeMap.add min.node (min.visits + 1) wl.visit_map ; + (* increase the visits *) min.node with Not_found -> - L.internal_error "@\n...Work list is empty! Impossible to remove edge...@\n"; + L.internal_error "@\n...Work list is empty! Impossible to remove edge...@\n" ; assert false end -(* =============== END of module Worklist =============== *) +(* =============== END of module Worklist =============== *) let path_set_create_worklist pdesc = - State.reset (); - Procdesc.compute_distance_to_exit_node pdesc; - Worklist.create () + State.reset () ; Procdesc.compute_distance_to_exit_node pdesc ; Worklist.create () -let htable_retrieve - (htable : (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t) (key : Procdesc.Node.id) - : Paths.PathSet.t = - try - Hashtbl.find htable key - with Not_found -> - Hashtbl.replace htable key Paths.PathSet.empty; - Paths.PathSet.empty +let htable_retrieve (htable: (Procdesc.Node.id, Paths.PathSet.t) Hashtbl.t) (key: Procdesc.Node.id) + : Paths.PathSet.t = + try Hashtbl.find htable key + with Not_found -> Hashtbl.replace htable key Paths.PathSet.empty ; Paths.PathSet.empty (** Add [d] to the pathset todo at [node] returning true if changed *) -let path_set_put_todo (wl : Worklist.t) (node: Procdesc.Node.t) (d: Paths.PathSet.t) : bool = +let path_set_put_todo (wl: Worklist.t) (node: Procdesc.Node.t) (d: Paths.PathSet.t) : bool = let changed = if Paths.PathSet.is_empty d then false else let node_id = Procdesc.Node.get_id node in let old_todo = htable_retrieve wl.Worklist.path_set_todo node_id in let old_visited = htable_retrieve wl.Worklist.path_set_visited node_id in - let d' = Paths.PathSet.diff d old_visited in (* differential fixpoint *) + let d' = Paths.PathSet.diff d old_visited in + (* differential fixpoint *) let todo_new = Paths.PathSet.union old_todo d' in - Hashtbl.replace wl.Worklist.path_set_todo node_id todo_new; - not (Paths.PathSet.equal old_todo todo_new) in + Hashtbl.replace wl.Worklist.path_set_todo node_id todo_new ; + not (Paths.PathSet.equal old_todo todo_new) + in changed -let path_set_checkout_todo (wl : Worklist.t) (node: Procdesc.Node.t) : Paths.PathSet.t = +let path_set_checkout_todo (wl: Worklist.t) (node: Procdesc.Node.t) : Paths.PathSet.t = try let node_id = Procdesc.Node.get_id node in let todo = Hashtbl.find wl.Worklist.path_set_todo node_id in - Hashtbl.replace wl.Worklist.path_set_todo node_id Paths.PathSet.empty; + Hashtbl.replace wl.Worklist.path_set_todo node_id Paths.PathSet.empty ; let visited = Hashtbl.find wl.Worklist.path_set_visited node_id in let new_visited = Paths.PathSet.union visited todo in - Hashtbl.replace wl.Worklist.path_set_visited node_id new_visited; - todo + Hashtbl.replace wl.Worklist.path_set_visited node_id new_visited ; todo with Not_found -> - L.internal_error "@\n@\nERROR: could not find todo for node %a@\n@." Procdesc.Node.pp node; + L.internal_error "@\n@\nERROR: could not find todo for node %a@\n@." Procdesc.Node.pp node ; assert false (* =============== END of the edge_set object =============== *) -let collect_do_abstract_pre pname tenv (pset : Propset.t) : Propset.t = - if !Config.footprint - then - Config.run_in_re_execution_mode - (Abs.lifted_abstract pname tenv) - pset - else - Abs.lifted_abstract pname tenv pset +let collect_do_abstract_pre pname tenv (pset: Propset.t) : Propset.t = + if !Config.footprint then Config.run_in_re_execution_mode (Abs.lifted_abstract pname tenv) pset + else Abs.lifted_abstract pname tenv pset -let collect_do_abstract_post pname tenv (pathset : Paths.PathSet.t) : Paths.PathSet.t = +let collect_do_abstract_post pname tenv (pathset: Paths.PathSet.t) : Paths.PathSet.t = let abs_option p = - if Prover.check_inconsistency tenv p then None - else Some (Abs.abstract pname tenv p) in - if !Config.footprint - then - Config.run_in_re_execution_mode - (Paths.PathSet.map_option abs_option) - pathset - else - Paths.PathSet.map_option abs_option pathset - -let do_join_pre plist = - Dom.proplist_collapse_pre plist + if Prover.check_inconsistency tenv p then None else Some (Abs.abstract pname tenv p) + in + if !Config.footprint then + Config.run_in_re_execution_mode (Paths.PathSet.map_option abs_option) pathset + else Paths.PathSet.map_option abs_option pathset + +let do_join_pre plist = Dom.proplist_collapse_pre plist let do_join_post pname tenv (pset: Paths.PathSet.t) = - if Config.spec_abs_level <= 0 then - Dom.pathset_collapse tenv pset - else - Dom.pathset_collapse tenv (Dom.pathset_collapse_impl pname tenv pset) + if Config.spec_abs_level <= 0 then Dom.pathset_collapse tenv pset + else Dom.pathset_collapse tenv (Dom.pathset_collapse_impl pname tenv pset) let do_meet_pre tenv pset = - if Config.meet_level > 0 then - Dom.propset_meet_generate_pre tenv pset - else - Propset.to_proplist pset + if Config.meet_level > 0 then Dom.propset_meet_generate_pre tenv pset + else Propset.to_proplist pset (** Find the preconditions in the current spec table, apply meet then join, and return the joined preconditions *) let collect_preconditions tenv summary : Prop.normal Specs.Jprop.t list = let proc_name = Specs.get_proc_name summary in let collect_do_abstract_one tenv prop = - if !Config.footprint - then - Config.run_in_re_execution_mode - (Abs.abstract_no_symop tenv) - prop - else - Abs.abstract_no_symop tenv prop in + if !Config.footprint then Config.run_in_re_execution_mode (Abs.abstract_no_symop tenv) prop + else Abs.abstract_no_symop tenv prop + in let pres = List.map ~f:(fun spec -> Specs.Jprop.to_prop spec.Specs.pre) - (Specs.get_specs_from_payload summary) in + (Specs.get_specs_from_payload summary) + in let pset = Propset.from_proplist tenv pres in let pset' = let f p = Prop.prop_normal_vars_to_primed_vars tenv p in - Propset.map tenv f pset in - - L.d_strln ("#### Extracted footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####"); - L.d_increase_indent 1; Propset.d Prop.prop_emp pset'; L.d_decrease_indent 1; L.d_ln (); - L.d_ln (); + Propset.map tenv f pset + in + L.d_strln ("#### Extracted footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####") ; + L.d_increase_indent 1 ; + Propset.d Prop.prop_emp pset' ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_ln () ; let pset'' = collect_do_abstract_pre proc_name tenv pset' in let plist_meet = do_meet_pre tenv pset'' in - L.d_strln ("#### Footprint of " ^ Typ.Procname.to_string proc_name ^ " after Meet ####"); - L.d_increase_indent 1; Propgraph.d_proplist Prop.prop_emp plist_meet; - L.d_decrease_indent 1; L.d_ln (); - L.d_ln (); - L.d_increase_indent 2; (* Indent for the join output *) + L.d_strln ("#### Footprint of " ^ Typ.Procname.to_string proc_name ^ " after Meet ####") ; + L.d_increase_indent 1 ; + Propgraph.d_proplist Prop.prop_emp plist_meet ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_ln () ; + L.d_increase_indent 2 ; + (* Indent for the join output *) let jplist = do_join_pre tenv plist_meet in - L.d_decrease_indent 2; L.d_ln (); - L.d_strln ("#### Footprint of " ^ Typ.Procname.to_string proc_name ^ " after Join ####"); - L.d_increase_indent 1; Specs.Jprop.d_list false jplist; L.d_decrease_indent 1; L.d_ln (); + L.d_decrease_indent 2 ; + L.d_ln () ; + L.d_strln ("#### Footprint of " ^ Typ.Procname.to_string proc_name ^ " after Join ####") ; + L.d_increase_indent 1 ; + Specs.Jprop.d_list false jplist ; + L.d_decrease_indent 1 ; + L.d_ln () ; let jplist' = - List.map ~f:(Specs.Jprop.map (Prop.prop_rename_primed_footprint_vars tenv)) jplist in - L.d_strln ("#### Renamed footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####"); - L.d_increase_indent 1; Specs.Jprop.d_list false jplist'; L.d_decrease_indent 1; L.d_ln (); + List.map ~f:(Specs.Jprop.map (Prop.prop_rename_primed_footprint_vars tenv)) jplist + in + L.d_strln ("#### Renamed footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####") ; + L.d_increase_indent 1 ; + Specs.Jprop.d_list false jplist' ; + L.d_decrease_indent 1 ; + L.d_ln () ; let jplist'' = let f p = - Prop.prop_primed_vars_to_normal_vars tenv - (collect_do_abstract_one proc_name tenv p) in - List.map ~f:(Specs.Jprop.map f) jplist' in - L.d_strln ("#### Abstracted footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####"); - L.d_increase_indent 1; Specs.Jprop.d_list false jplist''; L.d_decrease_indent 1; L.d_ln(); + Prop.prop_primed_vars_to_normal_vars tenv (collect_do_abstract_one proc_name tenv p) + in + List.map ~f:(Specs.Jprop.map f) jplist' + in + L.d_strln ("#### Abstracted footprint of " ^ Typ.Procname.to_string proc_name ^ ": ####") ; + L.d_increase_indent 1 ; + Specs.Jprop.d_list false jplist'' ; + L.d_decrease_indent 1 ; + L.d_ln () ; jplist'' (* =============== START of symbolic execution =============== *) (** propagate a set of results to the given node *) -let propagate - (wl : Worklist.t) pname ~is_exception (pset: Paths.PathSet.t) (curr_node: Procdesc.Node.t) = +let propagate (wl: Worklist.t) pname ~is_exception (pset: Paths.PathSet.t) + (curr_node: Procdesc.Node.t) = let edgeset_todo = (* prop must be a renamed prop by the invariant preserved by PropSet *) let f prop path edgeset_curr = - let exn_opt = - if is_exception - then Tabulation.prop_get_exn_name pname prop - else None in - Paths.PathSet.add_renamed_prop - prop + let exn_opt = if is_exception then Tabulation.prop_get_exn_name pname prop else None in + Paths.PathSet.add_renamed_prop prop (Paths.Path.extend curr_node exn_opt (State.get_session ()) path) - edgeset_curr in - Paths.PathSet.fold f pset Paths.PathSet.empty in + edgeset_curr + in + Paths.PathSet.fold f pset Paths.PathSet.empty + in let changed = path_set_put_todo wl curr_node edgeset_todo in - if changed then - Worklist.add wl curr_node + if changed then Worklist.add wl curr_node (** propagate a set of results, including exceptions and divergence *) -let propagate_nodes_divergence - tenv (pdesc: Procdesc.t) (pset: Paths.PathSet.t) - (succ_nodes: Procdesc.Node.t list) (exn_nodes: Procdesc.Node.t list) (wl : Worklist.t) = +let propagate_nodes_divergence tenv (pdesc: Procdesc.t) (pset: Paths.PathSet.t) + (succ_nodes: Procdesc.Node.t list) (exn_nodes: Procdesc.Node.t list) (wl: Worklist.t) = let pname = Procdesc.get_proc_name pdesc in let pset_exn, pset_ok = Paths.PathSet.partition (Tabulation.prop_is_exn pname) pset in - if !Config.footprint && not (Paths.PathSet.is_empty (State.get_diverging_states_node ())) then - begin - Errdesc.warning_err (State.get_loc ()) "Propagating Divergence@."; - let exit_node = Procdesc.get_exit_node pdesc in - let diverging_states = State.get_diverging_states_node () in - let prop_incons = - let mk_incons prop = - let p_abs = Abs.abstract pname tenv prop in - let p_zero = Prop.set p_abs ~sub:Sil.exp_sub_empty ~sigma:[] in - Prop.normalize tenv (Prop.set p_zero ~pi:[Sil.Aneq (Exp.zero, Exp.zero)]) in - Paths.PathSet.map mk_incons diverging_states in - (L.d_strln_color Orange) "Propagating Divergence -- diverging states:"; - Propgraph.d_proplist Prop.prop_emp (Paths.PathSet.to_proplist prop_incons); L.d_ln (); - propagate wl pname ~is_exception:false prop_incons exit_node - end; - List.iter ~f:(propagate wl pname ~is_exception:false pset_ok) succ_nodes; + if !Config.footprint && not (Paths.PathSet.is_empty (State.get_diverging_states_node ())) then ( + Errdesc.warning_err (State.get_loc ()) "Propagating Divergence@." ; + let exit_node = Procdesc.get_exit_node pdesc in + let diverging_states = State.get_diverging_states_node () in + let prop_incons = + let mk_incons prop = + let p_abs = Abs.abstract pname tenv prop in + let p_zero = Prop.set p_abs ~sub:Sil.exp_sub_empty ~sigma:[] in + Prop.normalize tenv (Prop.set p_zero ~pi:[Sil.Aneq (Exp.zero, Exp.zero)]) + in + Paths.PathSet.map mk_incons diverging_states + in + L.d_strln_color Orange "Propagating Divergence -- diverging states:" ; + Propgraph.d_proplist Prop.prop_emp (Paths.PathSet.to_proplist prop_incons) ; + L.d_ln () ; + propagate wl pname ~is_exception:false prop_incons exit_node ) ; + List.iter ~f:(propagate wl pname ~is_exception:false pset_ok) succ_nodes ; List.iter ~f:(propagate wl pname ~is_exception:true pset_exn) exn_nodes (* ===================== END of symbolic execution ===================== *) - (* =============== START of forward_tabulate =============== *) (** Symbolic execution for a Join node *) -let do_symexec_join pname tenv wl curr_node (edgeset_todo : Paths.PathSet.t) = +let do_symexec_join pname tenv wl curr_node (edgeset_todo: Paths.PathSet.t) = let curr_node_id = Procdesc.Node.get_id curr_node in let succ_nodes = Procdesc.Node.get_succs curr_node in let new_dset = edgeset_todo in let old_dset = Join_table.find wl.Worklist.join_table curr_node_id in let old_dset', new_dset' = Dom.pathset_join pname tenv old_dset new_dset in - Join_table.add wl.Worklist.join_table curr_node_id (Paths.PathSet.union old_dset' new_dset'); - List.iter ~f:(fun node -> - Paths.PathSet.iter (fun prop path -> - State.set_path path None; + Join_table.add wl.Worklist.join_table curr_node_id (Paths.PathSet.union old_dset' new_dset') ; + List.iter + ~f:(fun node -> + Paths.PathSet.iter + (fun prop path -> + State.set_path path None ; propagate wl pname ~is_exception:false - (Paths.PathSet.from_renamed_list [(prop, path)]) node) - new_dset') succ_nodes + (Paths.PathSet.from_renamed_list [(prop, path)]) + node) + new_dset') + succ_nodes let prop_max_size = ref (0, Prop.prop_emp) + let prop_max_chain_size = ref (0, Prop.prop_emp) (* Check if the prop exceeds the current max *) let check_prop_size_ p _ = let size = Prop.Metrics.prop_size p in - if size > fst !prop_max_size then - (prop_max_size := (size, p); - L.d_strln ("Prop with new max size " ^ string_of_int size ^ ":"); - Prop.d_prop p; - L.d_ln ()) + if size > fst !prop_max_size then ( + prop_max_size := (size, p) ; + L.d_strln ("Prop with new max size " ^ string_of_int size ^ ":") ; + Prop.d_prop p ; + L.d_ln () ) (* Check prop size and filter out possible unabstracted lists *) let check_prop_size edgeset_todo = if Config.monitor_prop_size then Paths.PathSet.iter check_prop_size_ edgeset_todo let reset_prop_metrics () = - prop_max_size := (0, Prop.prop_emp); + prop_max_size := (0, Prop.prop_emp) ; prop_max_chain_size := (0, Prop.prop_emp) exception RE_EXE_ERROR let do_before_node session node = - State.set_node node; - State.set_session session; - L.reset_delayed_prints (); + State.set_node node ; + State.set_session session ; + L.reset_delayed_prints () ; Printer.node_start_session node (session :> int) -let do_after_node node = - Printer.node_finish_session node +let do_after_node node = Printer.node_finish_session node (** Return the list of normal ids occurring in the instructions *) let instrs_get_normal_vars instrs = @@ -351,10 +347,9 @@ let instrs_get_normal_vars instrs = let do_instr instr = let do_e e = Sil.exp_fav_add fav e in let exps = Sil.instr_get_exps instr in - List.iter ~f:do_e exps in - List.iter ~f:do_instr instrs; - Sil.fav_filter_ident fav Ident.is_normal; - Sil.fav_to_list fav + List.iter ~f:do_e exps + in + List.iter ~f:do_instr instrs ; Sil.fav_filter_ident fav Ident.is_normal ; Sil.fav_to_list fav (* checks that boolean conditions on a conditional are assignment *) (* The check is done as follows: we check that the successors or a node that make an *) @@ -367,27 +362,13 @@ let check_assignement_guard pdesc node = let verbose = false in let node_contains_call n = let instrs = Procdesc.Node.get_instrs n in - let is_call = function - | Sil.Call _ -> true - | _ -> false in - List.exists ~f:is_call instrs in - let is_set_instr i = - match i with - | Sil.Store _ -> true - | _ -> false in - let is_prune_instr i = - match i with - | Sil.Prune _ -> true - | _ -> false in - let is_load_instr i = - match i with - | Sil.Load _ -> true - | _ -> false in - let is_frontend_tmp e = - match e with - | Exp.Lvar pv -> - Pvar.is_frontend_tmp pv - | _ -> false in + let is_call = function Sil.Call _ -> true | _ -> false in + List.exists ~f:is_call instrs + in + let is_set_instr i = match i with Sil.Store _ -> true | _ -> false in + let is_prune_instr i = match i with Sil.Prune _ -> true | _ -> false in + let is_load_instr i = match i with Sil.Load _ -> true | _ -> false in + let is_frontend_tmp e = match e with Exp.Lvar pv -> Pvar.is_frontend_tmp pv | _ -> false in let succs = Procdesc.Node.get_succs node in let l_node = Procdesc.Node.get_last_loc node in (* e is prune if in all successors prune nodes we have for some temp n$1: *) @@ -397,298 +378,314 @@ let check_assignement_guard pdesc node = let ins = Procdesc.Node.get_instrs n in let pi = List.filter ~f:is_prune_instr ins in let leti = List.filter ~f:is_load_instr ins in - match pi, leti with - | [Sil.Prune (Exp.Var (e1), _, _, _)], [Sil.Load (e2, e', _, _)] - | [Sil.Prune (Exp.UnOp (Unop.LNot, Exp.Var e1, _), _, _, _)], - [Sil.Load (e2, e', _, _)] - when (Ident.equal e1 e2) -> - if verbose - then - L.d_strln ("Found " ^ (Exp.to_string e') ^ " as prune var"); + match (pi, leti) with + | [(Sil.Prune (Exp.Var e1, _, _, _))], [(Sil.Load (e2, e', _, _))] + | [(Sil.Prune (Exp.UnOp (Unop.LNot, Exp.Var e1, _), _, _, _))], [(Sil.Load (e2, e', _, _))] + when Ident.equal e1 e2 + -> if verbose then L.d_strln ("Found " ^ Exp.to_string e' ^ " as prune var") ; [e'] - | _ -> [] in + | _ + -> [] + in let prune_vars = List.concat_map ~f:(fun n -> prune_var n) succs in - List.for_all ~f:(fun e' -> Exp.equal e' e) prune_vars in + List.for_all ~f:(fun e' -> Exp.equal e' e) prune_vars + in let succs_loc = List.map ~f:(fun n -> Procdesc.Node.get_loc n) succs in let succs_are_all_prune_nodes () = - List.for_all ~f:(fun n -> match Procdesc.Node.get_kind n with - | Procdesc.Node.Prune_node(_) -> true - | _ -> false) succs in + List.for_all + ~f:(fun n -> + match Procdesc.Node.get_kind n with Procdesc.Node.Prune_node _ -> true | _ -> false) + succs + in let succs_same_loc_as_node () = - if verbose then - (L.d_str ("LOCATION NODE: line: " ^ (string_of_int l_node.Location.line)); - L.d_strln " "); - List.for_all ~f:(fun l -> - if verbose then - (L.d_str ("LOCATION l: line: " ^ (string_of_int l.Location.line)); - L.d_strln " "); - Location.equal l l_node) succs_loc in + if verbose then ( + L.d_str ("LOCATION NODE: line: " ^ string_of_int l_node.Location.line) ; + L.d_strln " " ) ; + List.for_all + ~f:(fun l -> + if verbose then ( + L.d_str ("LOCATION l: line: " ^ string_of_int l.Location.line) ; + L.d_strln " " ) ; + Location.equal l l_node) + succs_loc + in (* check that the guards of the succs are a var or its negation *) let succs_have_simple_guards () = let check_instr = function - | Sil.Prune (Exp.Var _, _, _, _) -> true - | Sil.Prune (Exp.UnOp(Unop.LNot, Exp.Var _, _), _, _, _) -> true - | Sil.Prune _ -> false - | _ -> true in - let check_guard n = - List.for_all ~f:check_instr (Procdesc.Node.get_instrs n) in - List.for_all ~f:check_guard succs in - if Config.curr_language_is Config.Clang && - succs_are_all_prune_nodes () && - succs_same_loc_as_node () && - succs_have_simple_guards () then - (let instr = Procdesc.Node.get_instrs node in - match succs_loc with - (* at this point all successors are at the same location, so we can take the first*) - | loc_succ:: _ -> - let set_instr_at_succs_loc = - List.filter - ~f:(fun i -> - Location.equal (Sil.instr_get_loc i) loc_succ && - is_set_instr i) - instr in - (match set_instr_at_succs_loc with - | [Sil.Store (e, _, _, _)] -> - (* we now check if e is the same expression used to prune*) - if (is_prune_exp e) && not ((node_contains_call node) && (is_frontend_tmp e)) then ( - let desc = Errdesc.explain_condition_is_assignment l_node in - let exn = Exceptions.Condition_is_assignment (desc, __POS__) in - Reporting.log_warning_deprecated pname ~loc:l_node exn - ) - else () - | _ -> - ()) - | _ -> - if verbose then L.d_strln "NOT FOUND loc_succ" - ) else () + | Sil.Prune (Exp.Var _, _, _, _) + -> true + | Sil.Prune (Exp.UnOp (Unop.LNot, Exp.Var _, _), _, _, _) + -> true + | Sil.Prune _ + -> false + | _ + -> true + in + let check_guard n = List.for_all ~f:check_instr (Procdesc.Node.get_instrs n) in + List.for_all ~f:check_guard succs + in + if Config.curr_language_is Config.Clang && succs_are_all_prune_nodes () + && succs_same_loc_as_node () && succs_have_simple_guards () + then ( + let instr = Procdesc.Node.get_instrs node in + match succs_loc with + (* at this point all successors are at the same location, so we can take the first*) + | loc_succ :: _ + -> ( + let set_instr_at_succs_loc = + List.filter + ~f:(fun i -> Location.equal (Sil.instr_get_loc i) loc_succ && is_set_instr i) + instr + in + match set_instr_at_succs_loc with + | [(Sil.Store (e, _, _, _))] + -> (* we now check if e is the same expression used to prune*) + if is_prune_exp e && not (node_contains_call node && is_frontend_tmp e) then + let desc = Errdesc.explain_condition_is_assignment l_node in + let exn = Exceptions.Condition_is_assignment (desc, __POS__) in + Reporting.log_warning_deprecated pname ~loc:l_node exn + else () + | _ + -> () ) + | _ + -> if verbose then L.d_strln "NOT FOUND loc_succ" ) + else () (** Perform symbolic execution for a node starting from an initial prop *) -let do_symbolic_execution pdesc handle_exn tenv - (node : Procdesc.Node.t) (prop: Prop.normal Prop.t) (path : Paths.Path.t) = - State.mark_execution_start node; +let do_symbolic_execution pdesc handle_exn tenv (node: Procdesc.Node.t) (prop: Prop.normal Prop.t) + (path: Paths.Path.t) = + State.mark_execution_start node ; (* build the const map lazily *) - State.set_const_map (ConstantPropagation.build_const_map tenv pdesc); - check_assignement_guard pdesc node; + State.set_const_map (ConstantPropagation.build_const_map tenv pdesc) ; + check_assignement_guard pdesc node ; let instrs = Procdesc.Node.get_instrs node in (* fresh normal vars must be fresh w.r.t. instructions *) - Ident.update_name_generator (instrs_get_normal_vars instrs); + Ident.update_name_generator (instrs_get_normal_vars instrs) ; let pset = - SymExec.node handle_exn tenv pdesc node (Paths.PathSet.from_renamed_list [(prop, path)]) in - L.d_strln ".... After Symbolic Execution ...."; - Propset.d prop (Paths.PathSet.to_propset tenv pset); - L.d_ln (); L.d_ln(); - State.mark_execution_end node; + SymExec.node handle_exn tenv pdesc node (Paths.PathSet.from_renamed_list [(prop, path)]) + in + L.d_strln ".... After Symbolic Execution ...." ; + Propset.d prop (Paths.PathSet.to_propset tenv pset) ; + L.d_ln () ; + L.d_ln () ; + State.mark_execution_end node ; pset let mark_visited summary node = let node_id = Procdesc.Node.get_id node in let stats = summary.Specs.stats in - if !Config.footprint - then + if !Config.footprint then stats.Specs.nodes_visited_fp <- IntSet.add (node_id :> int) stats.Specs.nodes_visited_fp - else - stats.Specs.nodes_visited_re <- IntSet.add (node_id :> int) stats.Specs.nodes_visited_re + else stats.Specs.nodes_visited_re <- IntSet.add (node_id :> int) stats.Specs.nodes_visited_re let add_taint_attrs tenv proc_name proc_desc prop = match Taint.tainted_params proc_name with - | [] -> prop - | tainted_param_nums -> - let formal_params = Procdesc.get_formals proc_desc in - let formal_params' = - List.map ~f:(fun (p, _) -> Pvar.mk p proc_name) formal_params in + | [] + -> prop + | tainted_param_nums + -> let formal_params = Procdesc.get_formals proc_desc in + let formal_params' = List.map ~f:(fun (p, _) -> Pvar.mk p proc_name) formal_params in Taint.get_params_to_taint tainted_param_nums formal_params' |> List.fold - ~f:(fun prop_acc (param, taint_kind) -> - let attr = - PredSymb.Ataint { taint_source = proc_name; taint_kind; } in - Taint.add_tainting_attribute tenv attr param prop_acc) - ~init:prop + ~f:(fun prop_acc (param, taint_kind) -> + let attr = PredSymb.Ataint {taint_source= proc_name; taint_kind} in + Taint.add_tainting_attribute tenv attr param prop_acc) + ~init:prop let forward_tabulate tenv pdesc wl = let pname = Procdesc.get_proc_name pdesc in let handle_exn_node curr_node exn = - Exceptions.print_exception_html "Failure of symbolic execution: " exn; - let pre_opt = (* precondition leading to error, if any *) - State.get_normalized_pre (Abs.abstract_no_symop pname) in - (match pre_opt with - | Some pre -> - L.d_strln "Precondition:"; Prop.d_prop pre; L.d_ln () - | None -> ()); - L.d_strln "SIL INSTR:"; - Procdesc.Node.d_instrs ~sub_instrs: true (State.get_instr ()) curr_node; L.d_ln (); - Reporting.log_error_deprecated pname exn; - State.mark_instr_fail exn in - + Exceptions.print_exception_html "Failure of symbolic execution: " exn ; + let pre_opt = + (* precondition leading to error, if any *) + State.get_normalized_pre (Abs.abstract_no_symop pname) + in + ( match pre_opt with + | Some pre + -> L.d_strln "Precondition:" ; Prop.d_prop pre ; L.d_ln () + | None + -> () ) ; + L.d_strln "SIL INSTR:" ; + Procdesc.Node.d_instrs ~sub_instrs:true (State.get_instr ()) curr_node ; + L.d_ln () ; + Reporting.log_error_deprecated pname exn ; + State.mark_instr_fail exn + in let exe_iter f pathset = let ps_size = Paths.PathSet.size pathset in let cnt = ref 0 in let exe prop path = - State.set_path path None; - incr cnt; - f prop path !cnt ps_size in - Paths.PathSet.iter exe pathset in - + State.set_path path None ; + incr cnt ; + f prop path !cnt ps_size + in + Paths.PathSet.iter exe pathset + in let print_node_preamble curr_node session pathset_todo = let log_string proc_name = let summary = Specs.get_summary_unsafe "forward_tabulate" proc_name in let phase_string = - if Specs.equal_phase (Specs.get_phase summary) Specs.FOOTPRINT then "FP" else "RE" in + if Specs.equal_phase (Specs.get_phase summary) Specs.FOOTPRINT then "FP" else "RE" + in let status = Specs.get_status summary in - F.sprintf - "[%s:%s] %s" phase_string (Specs.string_of_status status) (Typ.Procname.to_string proc_name) in - L.d_strln ("**** " ^ (log_string pname) ^ " " ^ - "Node: " ^ string_of_int (Procdesc.Node.get_id curr_node :> int) ^ ", " ^ - "Procedure: " ^ Typ.Procname.to_string pname ^ ", " ^ - "Session: " ^ string_of_int session ^ ", " ^ - "Todo: " ^ string_of_int (Paths.PathSet.size pathset_todo) ^ " ****"); - L.d_increase_indent 1; - Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv pathset_todo); - L.d_strln ".... Instructions: .... "; - Procdesc.Node.d_instrs ~sub_instrs: true (State.get_instr ()) curr_node; - L.d_ln (); L.d_ln () in - - let do_prop curr_node handle_exn prop_ path cnt num_paths = - let prop = - if Config.taint_analysis - then add_taint_attrs tenv pname pdesc prop_ - else prop_ in + F.sprintf "[%s:%s] %s" phase_string (Specs.string_of_status status) + (Typ.Procname.to_string proc_name) + in L.d_strln - ("Processing prop " ^ string_of_int cnt ^ "/" ^ string_of_int num_paths); - L.d_increase_indent 1; + ( "**** " ^ log_string pname ^ " " ^ "Node: " + ^ string_of_int (Procdesc.Node.get_id curr_node :> int) ^ ", " ^ "Procedure: " + ^ Typ.Procname.to_string pname ^ ", " ^ "Session: " ^ string_of_int session ^ ", " ^ "Todo: " + ^ string_of_int (Paths.PathSet.size pathset_todo) ^ " ****" ) ; + L.d_increase_indent 1 ; + Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv pathset_todo) ; + L.d_strln ".... Instructions: .... " ; + Procdesc.Node.d_instrs ~sub_instrs:true (State.get_instr ()) curr_node ; + L.d_ln () ; + L.d_ln () + in + let do_prop curr_node handle_exn prop_ path cnt num_paths = + let prop = if Config.taint_analysis then add_taint_attrs tenv pname pdesc prop_ else prop_ in + L.d_strln ("Processing prop " ^ string_of_int cnt ^ "/" ^ string_of_int num_paths) ; + L.d_increase_indent 1 ; try - State.reset_diverging_states_node (); - let pset = - do_symbolic_execution pdesc handle_exn tenv curr_node prop path in + State.reset_diverging_states_node () ; + let pset = do_symbolic_execution pdesc handle_exn tenv curr_node prop path in let succ_nodes = Procdesc.Node.get_succs curr_node in let exn_nodes = Procdesc.Node.get_exn curr_node in - propagate_nodes_divergence tenv pdesc pset succ_nodes exn_nodes wl; - L.d_decrease_indent 1; L.d_ln(); - with - | exn when Exceptions.handle_exception exn && !Config.footprint -> - handle_exn exn; - L.d_decrease_indent 1; L.d_ln () in - + propagate_nodes_divergence tenv pdesc pset succ_nodes exn_nodes wl ; + L.d_decrease_indent 1 ; + L.d_ln () + with exn when Exceptions.handle_exception exn && !Config.footprint -> + handle_exn exn ; L.d_decrease_indent 1 ; L.d_ln () + in let do_node curr_node pathset_todo session handle_exn = - check_prop_size pathset_todo; - print_node_preamble curr_node session pathset_todo; - + check_prop_size pathset_todo ; + print_node_preamble curr_node session pathset_todo ; match Procdesc.Node.get_kind curr_node with - | Procdesc.Node.Join_node -> - do_symexec_join pname tenv wl curr_node pathset_todo + | Procdesc.Node.Join_node + -> do_symexec_join pname tenv wl curr_node pathset_todo | Procdesc.Node.Stmt_node _ | Procdesc.Node.Prune_node _ | Procdesc.Node.Exit_node _ | Procdesc.Node.Skip_node _ - | Procdesc.Node.Start_node _ -> - exe_iter (do_prop curr_node handle_exn) pathset_todo in - + | Procdesc.Node.Start_node _ + -> exe_iter (do_prop curr_node handle_exn) pathset_todo + in let do_node_and_handle curr_node session = let pathset_todo = path_set_checkout_todo wl curr_node in try - begin - let handle_exn_called = ref false in - let handle_exn exn = - handle_exn_called := true; - handle_exn_node curr_node exn in - do_node curr_node pathset_todo session handle_exn; - if !handle_exn_called then Printer.force_delayed_prints (); - do_after_node curr_node - end - with - | exn when Exceptions.handle_exception exn -> - handle_exn_node curr_node exn; - Printer.force_delayed_prints (); - do_after_node curr_node; - if not !Config.footprint then raise RE_EXE_ERROR in - + let handle_exn_called = ref false in + let handle_exn exn = + handle_exn_called := true ; + handle_exn_node curr_node exn + in + do_node curr_node pathset_todo session handle_exn ; + if !handle_exn_called then Printer.force_delayed_prints () ; + do_after_node curr_node + with exn when Exceptions.handle_exception exn -> + handle_exn_node curr_node exn ; + Printer.force_delayed_prints () ; + do_after_node curr_node ; + if not !Config.footprint then raise RE_EXE_ERROR + in while not (Worklist.is_empty wl) do let curr_node = Worklist.remove wl in let summary = Specs.get_summary_unsafe "forward_tabulate" pname in - mark_visited summary curr_node; (* mark nodes visited in fp and re phases *) - let session = - incr summary.Specs.sessions; - !(summary.Specs.sessions) in - do_before_node session curr_node; - do_node_and_handle curr_node session - done; - L.d_strln ".... Work list empty. Stop ...."; L.d_ln () - + mark_visited summary curr_node ; + (* mark nodes visited in fp and re phases *) + let session = incr summary.Specs.sessions ; !(summary.Specs.sessions) in + do_before_node session curr_node ; do_node_and_handle curr_node session + done ; + L.d_strln ".... Work list empty. Stop ...." ; + L.d_ln () (** if possible, produce a (fieldname, typ) path from one of the [src_exps] to [sink_exp] using [reachable_hpreds]. *) let get_fld_typ_path_opt src_exps sink_exp_ reachable_hpreds_ = let strexp_matches target_exp = function - | Sil.Eexp (e, _) -> Exp.equal target_exp e - | _ -> false in - let extend_path hpred (sink_exp, path, reachable_hpreds) = match hpred with - | Sil.Hpointsto (lhs, Sil.Estruct (flds, _), Exp.Sizeof {typ}) -> - List.find ~f:(function _, se -> strexp_matches sink_exp se) flds |> - Option.value_map ~f:(function fld, _ -> - let reachable_hpreds' = Sil.HpredSet.remove hpred reachable_hpreds in - (lhs, (Some fld, typ) :: path, reachable_hpreds')) - ~default:(sink_exp, path, reachable_hpreds) - | Sil.Hpointsto (lhs, Sil.Earray (_, elems, _), Exp.Sizeof {typ}) -> - if List.exists ~f:(function _, se -> strexp_matches sink_exp se) elems - then + | Sil.Eexp (e, _) + -> Exp.equal target_exp e + | _ + -> false + in + let extend_path hpred (sink_exp, path, reachable_hpreds) = + match hpred with + | Sil.Hpointsto (lhs, Sil.Estruct (flds, _), Exp.Sizeof {typ}) + -> List.find ~f:(function _, se -> strexp_matches sink_exp se) flds + |> Option.value_map + ~f:(function + | fld, _ + -> let reachable_hpreds' = Sil.HpredSet.remove hpred reachable_hpreds in + (lhs, (Some fld, typ) :: path, reachable_hpreds')) + ~default:(sink_exp, path, reachable_hpreds) + | Sil.Hpointsto (lhs, Sil.Earray (_, elems, _), Exp.Sizeof {typ}) + -> if List.exists ~f:(function _, se -> strexp_matches sink_exp se) elems then let reachable_hpreds' = Sil.HpredSet.remove hpred reachable_hpreds in (* None means "no field name" ~=~ nameless array index *) (lhs, (None, typ) :: path, reachable_hpreds') else (sink_exp, path, reachable_hpreds) - | _ -> (sink_exp, path, reachable_hpreds) in + | _ + -> (sink_exp, path, reachable_hpreds) + in (* terminates because [reachable_hpreds] is shrinking on each recursive call *) let rec get_fld_typ_path sink_exp path reachable_hpreds = - let (sink_exp', path', reachable_hpreds') = - Sil.HpredSet.fold extend_path reachable_hpreds (sink_exp, path, reachable_hpreds) in - if Exp.Set.mem sink_exp' src_exps - then Some path' - else - if Sil.HpredSet.cardinal reachable_hpreds' >= Sil.HpredSet.cardinal reachable_hpreds - then None (* can't find a path from [src_exps] to [sink_exp] *) - else get_fld_typ_path sink_exp' path' reachable_hpreds' in + let sink_exp', path', reachable_hpreds' = + Sil.HpredSet.fold extend_path reachable_hpreds (sink_exp, path, reachable_hpreds) + in + if Exp.Set.mem sink_exp' src_exps then Some path' + else if Sil.HpredSet.cardinal reachable_hpreds' >= Sil.HpredSet.cardinal reachable_hpreds then + None (* can't find a path from [src_exps] to [sink_exp] *) + else get_fld_typ_path sink_exp' path' reachable_hpreds' + in get_fld_typ_path sink_exp_ [] reachable_hpreds_ - (** report an error if any Context is reachable from a static field *) let report_context_leaks pname sigma tenv = (* report an error if an expression in [context_exps] is reachable from [field_strexp] *) let check_reachable_context_from_fld (fld_name, fld_strexp) context_exps = let fld_exps = Prop.strexp_get_exps fld_strexp in - let reachable_hpreds, reachable_exps = - Prop.compute_reachable_hpreds sigma fld_exps in + let reachable_hpreds, reachable_exps = Prop.compute_reachable_hpreds sigma fld_exps in (* raise an error if any Context expression is in [reachable_exps] *) List.iter ~f:(fun (context_exp, name) -> - if Exp.Set.mem context_exp reachable_exps then - let leak_path = - match get_fld_typ_path_opt fld_exps context_exp reachable_hpreds with - | Some path -> path - | None -> assert false (* a path must exist in order for a leak to be reported *) in - let err_desc = - Errdesc.explain_context_leak pname (Typ.mk (Tstruct name)) fld_name leak_path in - let exn = Exceptions.Context_leak (err_desc, __POS__) in - Reporting.log_error_deprecated pname exn) - context_exps in + if Exp.Set.mem context_exp reachable_exps then + let leak_path = + match get_fld_typ_path_opt fld_exps context_exp reachable_hpreds with + | Some path + -> path + | None + -> assert false + (* a path must exist in order for a leak to be reported *) + in + let err_desc = + Errdesc.explain_context_leak pname (Typ.mk (Tstruct name)) fld_name leak_path + in + let exn = Exceptions.Context_leak (err_desc, __POS__) in + Reporting.log_error_deprecated pname exn) + context_exps + in (* get the set of pointed-to expressions of type T <: Context *) let context_exps = List.fold - ~f:(fun exps hpred -> match hpred with - | Sil.Hpointsto (_, Eexp (exp, _), Sizeof {typ={desc=Tptr ({desc=Tstruct name}, _)}}) - when not (Exp.is_null_literal exp) - && AndroidFramework.is_context tenv name - && not (AndroidFramework.is_application tenv name) -> - (exp, name) :: exps - | _ -> exps) - ~init:[] - sigma in + ~f:(fun exps hpred -> + match hpred with + | Sil.Hpointsto (_, Eexp (exp, _), Sizeof {typ= {desc= Tptr ({desc= Tstruct name}, _)}}) + when not (Exp.is_null_literal exp) && AndroidFramework.is_context tenv name + && not (AndroidFramework.is_application tenv name) + -> (exp, name) :: exps + | _ + -> exps) + ~init:[] sigma + in List.iter ~f:(function - | Sil.Hpointsto (Exp.Lvar pv, Sil.Estruct (static_flds, _), _) - when Pvar.is_global pv -> - List.iter + | Sil.Hpointsto (Exp.Lvar pv, Sil.Estruct (static_flds, _), _) when Pvar.is_global pv + -> List.iter ~f:(fun (f_name, f_strexp) -> - check_reachable_context_from_fld (f_name, f_strexp) context_exps) + check_reachable_context_from_fld (f_name, f_strexp) context_exps) static_flds - | _ -> ()) + | _ + -> ()) sigma (** Remove locals and formals, @@ -701,9 +698,9 @@ let remove_locals_formals_and_check tenv pdesc p = let dexp_opt, _ = Errdesc.vpath_find tenv p (Exp.Lvar pvar) in let desc = Errdesc.explain_stack_variable_address_escape loc pvar dexp_opt in let exn = Exceptions.Stack_variable_address_escape (desc, __POS__) in - Reporting.log_warning_deprecated pname exn in - List.iter ~f:check_pvar pvars; - p' + Reporting.log_warning_deprecated pname exn + in + List.iter ~f:check_pvar pvars ; p' (** Collect the analysis results for the exit node. *) let collect_analysis_result tenv wl pdesc : Paths.PathSet.t = @@ -712,16 +709,14 @@ let collect_analysis_result tenv wl pdesc : Paths.PathSet.t = let pathset = htable_retrieve wl.Worklist.path_set_visited exit_node_id in Paths.PathSet.map (remove_locals_formals_and_check tenv pdesc) pathset -module Pmap = Caml.Map.Make - (struct - type t = Prop.normal Prop.t - let compare = Prop.compare_prop - end) +module Pmap = Caml.Map.Make (struct + type t = Prop.normal Prop.t + + let compare = Prop.compare_prop +end) let vset_ref_add_path vset_ref path = - Paths.Path.iter_all_nodes_nocalls - (fun n -> vset_ref := Procdesc.NodeSet.add n !vset_ref) - path + Paths.Path.iter_all_nodes_nocalls (fun n -> vset_ref := Procdesc.NodeSet.add n !vset_ref) path let vset_ref_add_pathset vset_ref pathset = Paths.PathSet.iter (fun _ path -> vset_ref_add_path vset_ref path) pathset @@ -732,26 +727,26 @@ let compute_visited vset = let node_loc = Procdesc.Node.get_loc n in let instrs_loc = List.map ~f:Sil.instr_get_loc (Procdesc.Node.get_instrs n) in let lines = List.map ~f:(fun loc -> loc.Location.line) (node_loc :: instrs_loc) in - List.remove_consecutive_duplicates ~equal:Int.equal (List.sort ~cmp:Int.compare lines) in + List.remove_consecutive_duplicates ~equal:Int.equal (List.sort ~cmp:Int.compare lines) + in let do_node n = - res := - Specs.Visitedset.add (Procdesc.Node.get_id n, node_get_all_lines n) !res in - Procdesc.NodeSet.iter do_node vset; - !res + res := Specs.Visitedset.add (Procdesc.Node.get_id n, node_get_all_lines n) !res + in + Procdesc.NodeSet.iter do_node vset ; !res (** Extract specs from a pathset *) let extract_specs tenv pdesc pathset : Prop.normal Specs.spec list = let pname = Procdesc.get_proc_name pdesc in let sub = let fav = Sil.fav_new () in - Paths.PathSet.iter - (fun prop _ -> Prop.prop_fav_add fav prop) - pathset; + Paths.PathSet.iter (fun prop _ -> Prop.prop_fav_add fav prop) pathset ; let sub_list = List.map - ~f:(fun id -> (id, Exp.Var (Ident.create_fresh (Ident.knormal)))) - (Sil.fav_to_list fav) in - Sil.exp_subst_of_list sub_list in + ~f:(fun id -> (id, Exp.Var (Ident.create_fresh Ident.knormal))) + (Sil.fav_to_list fav) + in + Sil.exp_subst_of_list sub_list + in let pre_post_visited_list = let pplist = Paths.PathSet.elements pathset in let f (prop, path) = @@ -759,209 +754,219 @@ let extract_specs tenv pdesc pathset : Prop.normal Specs.spec list = let prop'' = Abs.abstract pname tenv prop' in let pre, post = Prop.extract_spec prop'' in let pre' = Prop.normalize tenv (Prop.prop_sub (`Exp sub) pre) in - if Config.curr_language_is Config.Java && - Procdesc.get_access pdesc <> PredSymb.Private then - report_context_leaks pname post.Prop.sigma tenv; + if Config.curr_language_is Config.Java && Procdesc.get_access pdesc <> PredSymb.Private then + report_context_leaks pname post.Prop.sigma tenv ; let post' = if Prover.check_inconsistency_base tenv prop then None - else Some (Prop.normalize tenv (Prop.prop_sub (`Exp sub) post), path) in + else Some (Prop.normalize tenv (Prop.prop_sub (`Exp sub) post), path) + in let visited = let vset_ref = ref Procdesc.NodeSet.empty in - vset_ref_add_path vset_ref path; - compute_visited !vset_ref in - (pre', post', visited) in - List.map ~f pplist in + vset_ref_add_path vset_ref path ; + compute_visited !vset_ref + in + (pre', post', visited) + in + List.map ~f pplist + in let pre_post_map = let add map (pre, post, visited) = let current_posts, current_visited = try Pmap.find pre map - with Not_found -> - (Paths.PathSet.empty, Specs.Visitedset.empty) in - let new_posts = match post with - | None -> current_posts - | Some (post, path) -> Paths.PathSet.add_renamed_prop post path current_posts in + with Not_found -> (Paths.PathSet.empty, Specs.Visitedset.empty) + in + let new_posts = + match post with + | None + -> current_posts + | Some (post, path) + -> Paths.PathSet.add_renamed_prop post path current_posts + in let new_visited = Specs.Visitedset.union visited current_visited in - Pmap.add pre (new_posts, new_visited) map in - List.fold ~f:add ~init:Pmap.empty pre_post_visited_list in + Pmap.add pre (new_posts, new_visited) map + in + List.fold ~f:add ~init:Pmap.empty pre_post_visited_list + in let specs = ref [] in - let add_spec pre ((posts : Paths.PathSet.t), visited) = + let add_spec pre ((posts: Paths.PathSet.t), visited) = let posts' = List.map ~f:(fun (p, path) -> (PropUtil.remove_seed_vars tenv p, path)) - (Paths.PathSet.elements (do_join_post pname tenv posts)) in + (Paths.PathSet.elements (do_join_post pname tenv posts)) + in let spec = - { Specs.pre = Specs.Jprop.Prop (1, pre); - Specs.posts = posts'; - Specs.visited = visited } in - specs := spec :: !specs in - Pmap.iter add_spec pre_post_map; - !specs + {Specs.pre= Specs.Jprop.Prop (1, pre); Specs.posts= posts'; Specs.visited= visited} + in + specs := spec :: !specs + in + Pmap.iter add_spec pre_post_map ; !specs let collect_postconditions wl tenv pdesc : Paths.PathSet.t * Specs.Visitedset.t = let pname = Procdesc.get_proc_name pdesc in let pathset = collect_analysis_result tenv wl pdesc in - (* Assuming C++ developers use RAII, remove resources from the constructor posts *) - let pathset = match pname with - | Typ.Procname.ObjC_Cpp _ -> - if (Typ.Procname.is_constructor pname) then - Paths.PathSet.map (fun prop -> + let pathset = + match pname with + | Typ.Procname.ObjC_Cpp _ + -> if Typ.Procname.is_constructor pname then + Paths.PathSet.map + (fun prop -> Attribute.remove_resource tenv Racquire (Rmemory Mobjc) (Attribute.remove_resource tenv Racquire (Rmemory Mmalloc) - (Attribute.remove_resource tenv Racquire Rfile prop)) - ) pathset + (Attribute.remove_resource tenv Racquire Rfile prop))) + pathset else pathset - | _ -> pathset in - - L.d_strln - ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] Analysis result ####"); - Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv pathset); - L.d_ln (); + | _ + -> pathset + in + L.d_strln ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] Analysis result ####") ; + Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv pathset) ; + L.d_ln () ; let res = try let pathset = collect_do_abstract_post pname tenv pathset in let pathset_diverging = State.get_diverging_states_proc () in let visited = let vset_ref = ref Procdesc.NodeSet.empty in - vset_ref_add_pathset vset_ref pathset; + vset_ref_add_pathset vset_ref pathset ; (* nodes from diverging states were also visited *) - vset_ref_add_pathset vset_ref pathset_diverging; - compute_visited !vset_ref in - do_join_post pname tenv pathset, visited with - | exn when (match exn with Exceptions.Leak _ -> true | _ -> false) -> - L.d_strln"Leak in post collection"; assert false in - L.d_strln - ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] Postconditions after join ####"); - L.d_increase_indent 1; - Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv (fst res)); - L.d_decrease_indent 1; - L.d_ln (); + vset_ref_add_pathset vset_ref pathset_diverging ; + compute_visited !vset_ref + in + (do_join_post pname tenv pathset, visited) + with exn when match exn with Exceptions.Leak _ -> true | _ -> false -> + L.d_strln "Leak in post collection" ; + assert false + in + L.d_strln ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] Postconditions after join ####") ; + L.d_increase_indent 1 ; + Propset.d Prop.prop_emp (Paths.PathSet.to_propset tenv (fst res)) ; + L.d_decrease_indent 1 ; + L.d_ln () ; res let create_seed_vars sigma = let hpred_add_seed sigma = function - | Sil.Hpointsto (Exp.Lvar pv, se, typ) when not (Pvar.is_abduced pv) -> - Sil.Hpointsto(Exp.Lvar (Pvar.to_seed pv), se, typ) :: sigma - | _ -> sigma in + | Sil.Hpointsto (Exp.Lvar pv, se, typ) when not (Pvar.is_abduced pv) + -> Sil.Hpointsto (Exp.Lvar (Pvar.to_seed pv), se, typ) :: sigma + | _ + -> sigma + in List.fold ~f:hpred_add_seed ~init:[] sigma (** Initialize proposition for execution given formal and global parameters. The footprint is initialized according to the execution mode. The prop is not necessarily emp, so it should be incorporated when the footprint is constructed. *) -let prop_init_formals_seed tenv new_formals (prop : 'a Prop.t) : Prop.exposed Prop.t = +let prop_init_formals_seed tenv new_formals (prop: 'a Prop.t) : Prop.exposed Prop.t = let sigma_new_formals = let do_formal (pv, typ) = - let texp = match !Config.curr_language with - | Config.Clang -> - Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact} - | Config.Java -> - Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.subtypes} in - Prop.mk_ptsto_lvar tenv Prop.Fld_init Sil.inst_formal (pv, texp, None) in - List.map ~f:do_formal new_formals in + let texp = + match !Config.curr_language with + | Config.Clang + -> Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + | Config.Java + -> Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.subtypes} + in + Prop.mk_ptsto_lvar tenv Prop.Fld_init Sil.inst_formal (pv, texp, None) + in + List.map ~f:do_formal new_formals + in let sigma_seed = - create_seed_vars - (* formals already there plus new ones *) - (prop.Prop.sigma @ sigma_new_formals) in + create_seed_vars (* formals already there plus new ones *) + (prop.Prop.sigma @ sigma_new_formals) + in let sigma = sigma_seed @ sigma_new_formals in - let new_pi = - prop.Prop.pi in - let prop' = - Prop.set (Prop.prop_sigma_star prop sigma) ~pi:new_pi in + let new_pi = prop.Prop.pi in + let prop' = Prop.set (Prop.prop_sigma_star prop sigma) ~pi:new_pi in Prop.set prop' ~sigma_fp:(prop'.Prop.sigma_fp @ sigma_new_formals) (** Construct an initial prop by extending [prop] with locals, and formals if [add_formals] is true as well as seed variables *) -let initial_prop - tenv (curr_f: Procdesc.t) (prop : 'a Prop.t) add_formals - : Prop.normal Prop.t = - let construct_decl (x, typ) = - (Pvar.mk x (Procdesc.get_proc_name curr_f), typ) in +let initial_prop tenv (curr_f: Procdesc.t) (prop: 'a Prop.t) add_formals : Prop.normal Prop.t = + let construct_decl (x, typ) = (Pvar.mk x (Procdesc.get_proc_name curr_f), typ) in let new_formals = - if add_formals - then List.map ~f:construct_decl (Procdesc.get_formals curr_f) - else [] (* no new formals added *) in + if add_formals then List.map ~f:construct_decl (Procdesc.get_formals curr_f) else [] + (* no new formals added *) + in let prop1 = - Prop.prop_reset_inst - (fun inst_old -> Sil.update_inst inst_old Sil.inst_formal) - prop in - let prop2 = - prop_init_formals_seed tenv new_formals prop1 in + Prop.prop_reset_inst (fun inst_old -> Sil.update_inst inst_old Sil.inst_formal) prop + in + let prop2 = prop_init_formals_seed tenv new_formals prop1 in Prop.prop_rename_primed_footprint_vars tenv (Prop.normalize tenv prop2) (** Construct an initial prop from the empty prop *) -let initial_prop_from_emp tenv curr_f = - initial_prop tenv curr_f Prop.prop_emp true +let initial_prop_from_emp tenv curr_f = initial_prop tenv curr_f Prop.prop_emp true (** Construct an initial prop from an existing pre with formals *) let initial_prop_from_pre tenv curr_f pre = if !Config.footprint then let vars = Sil.fav_to_list (Prop.prop_fav pre) in let sub_list = - List.map - ~f:(fun id -> (id, Exp.Var (Ident.create_fresh (Ident.kfootprint)))) - vars in + List.map ~f:(fun id -> (id, Exp.Var (Ident.create_fresh Ident.kfootprint))) vars + in let sub = Sil.subst_of_list sub_list in let pre2 = Prop.prop_sub sub pre in - let pre3 = - Prop.set pre2 ~pi_fp:(Prop.get_pure pre2) ~sigma_fp:pre2.Prop.sigma in + let pre3 = Prop.set pre2 ~pi_fp:(Prop.get_pure pre2) ~sigma_fp:pre2.Prop.sigma in initial_prop tenv curr_f pre3 false - else - initial_prop tenv curr_f pre false + else initial_prop tenv curr_f pre false (** Re-execute one precondition and return some spec if there was no re-execution error. *) -let execute_filter_prop wl tenv pdesc init_node (precondition : Prop.normal Specs.Jprop.t) - : Prop.normal Specs.spec option = +let execute_filter_prop wl tenv pdesc init_node (precondition: Prop.normal Specs.Jprop.t) + : Prop.normal Specs.spec option = let pname = Procdesc.get_proc_name pdesc in - do_before_node 0 init_node; - L.d_strln ("#### Start: RE-execution for " ^ Typ.Procname.to_string pname ^ " ####"); - L.d_indent 1; - L.d_strln "Precond:"; Specs.Jprop.d_shallow precondition; - L.d_ln (); L.d_ln (); - let init_prop = - initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) in + do_before_node 0 init_node ; + L.d_strln ("#### Start: RE-execution for " ^ Typ.Procname.to_string pname ^ " ####") ; + L.d_indent 1 ; + L.d_strln "Precond:" ; + Specs.Jprop.d_shallow precondition ; + L.d_ln () ; + L.d_ln () ; + let init_prop = initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) in let init_edgeset = - Paths.PathSet.add_renamed_prop - init_prop - (Paths.Path.start init_node) - Paths.PathSet.empty in - do_after_node init_node; + Paths.PathSet.add_renamed_prop init_prop (Paths.Path.start init_node) Paths.PathSet.empty + in + do_after_node init_node ; try - Worklist.add wl init_node; - ignore (path_set_put_todo wl init_node init_edgeset); - forward_tabulate tenv pdesc wl; - do_before_node 0 init_node; + Worklist.add wl init_node ; + ignore (path_set_put_todo wl init_node init_edgeset) ; + forward_tabulate tenv pdesc wl ; + do_before_node 0 init_node ; L.d_strln_color Green - ("#### Finished: RE-execution for " ^ Typ.Procname.to_string pname ^ " ####"); - L.d_increase_indent 1; - L.d_strln "Precond:"; Prop.d_prop (Specs.Jprop.to_prop precondition); - L.d_ln (); + ("#### Finished: RE-execution for " ^ Typ.Procname.to_string pname ^ " ####") ; + L.d_increase_indent 1 ; + L.d_strln "Precond:" ; + Prop.d_prop (Specs.Jprop.to_prop precondition) ; + L.d_ln () ; let posts, visited = let pset, visited = collect_postconditions wl tenv pdesc in let plist = List.map ~f:(fun (p, path) -> (PropUtil.remove_seed_vars tenv p, path)) - (Paths.PathSet.elements pset) in - plist, visited in + (Paths.PathSet.elements pset) + in + (plist, visited) + in let pre = let p = PropUtil.remove_locals_ret tenv pdesc (Specs.Jprop.to_prop precondition) in match precondition with - | Specs.Jprop.Prop (n, _) -> Specs.Jprop.Prop (n, p) - | Specs.Jprop.Joined (n, _, jp1, jp2) -> Specs.Jprop.Joined (n, p, jp1, jp2) in - let spec = { Specs.pre = pre; Specs.posts = posts; Specs.visited = visited } in - L.d_decrease_indent 1; - do_after_node init_node; - Some spec + | Specs.Jprop.Prop (n, _) + -> Specs.Jprop.Prop (n, p) + | Specs.Jprop.Joined (n, _, jp1, jp2) + -> Specs.Jprop.Joined (n, p, jp1, jp2) + in + let spec = {Specs.pre= pre; Specs.posts= posts; Specs.visited= visited} in + L.d_decrease_indent 1 ; do_after_node init_node ; Some spec with RE_EXE_ERROR -> - do_before_node 0 init_node; - Printer.force_delayed_prints (); - L.d_strln_color Red ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] ...ERROR"); - L.d_increase_indent 1; - L.d_strln "when starting from pre:"; - Prop.d_prop (Specs.Jprop.to_prop precondition); - L.d_strln "This precondition is filtered out."; - L.d_decrease_indent 1; - do_after_node init_node; + do_before_node 0 init_node ; + Printer.force_delayed_prints () ; + L.d_strln_color Red ("#### [FUNCTION " ^ Typ.Procname.to_string pname ^ "] ...ERROR") ; + L.d_increase_indent 1 ; + L.d_strln "when starting from pre:" ; + Prop.d_prop (Specs.Jprop.to_prop precondition) ; + L.d_strln "This precondition is filtered out." ; + L.d_decrease_indent 1 ; + do_after_node init_node ; None let pp_intra_stats wl proc_desc fmt _ = @@ -969,11 +974,11 @@ let pp_intra_stats wl proc_desc fmt _ = let nodes = Procdesc.get_nodes proc_desc in List.iter ~f:(fun node -> - nstates := - !nstates + - Paths.PathSet.size - (htable_retrieve wl.Worklist.path_set_visited (Procdesc.Node.get_id node))) - nodes; + nstates + := !nstates + + Paths.PathSet.size + (htable_retrieve wl.Worklist.path_set_visited (Procdesc.Node.get_id node))) + nodes ; F.fprintf fmt "(%d nodes containing %d states)" (List.length nodes) !nstates type exe_phase = (unit -> unit) * (unit -> Prop.normal Specs.spec list * Specs.phase) @@ -983,117 +988,123 @@ type exe_phase = (unit -> unit) * (unit -> Prop.normal Specs.spec list * Specs.p and [get_results ()] returns the results computed. This function is architected so that [get_results ()] can be called even after [go ()] was interrupted by and exception. *) -let perform_analysis_phase tenv (summary : Specs.summary) (pdesc : Procdesc.t) - : exe_phase = +let perform_analysis_phase tenv (summary: Specs.summary) (pdesc: Procdesc.t) : exe_phase = let pname = Specs.get_proc_name summary in let start_node = Procdesc.get_start_node pdesc in - let compute_footprint () : exe_phase = - let go (wl : Worklist.t) () = + let go (wl: Worklist.t) () = let init_prop = initial_prop_from_emp tenv pdesc in (* use existing pre's (in recursion some might exist) as starting points *) let init_props_from_pres = let specs = Specs.get_specs_from_payload summary in (* rename spec vars to footrpint vars, and copy current to footprint *) let mk_init precondition = - initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) in - List.map ~f:(fun spec -> mk_init spec.Specs.pre) specs in + initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) + in + List.map ~f:(fun spec -> mk_init spec.Specs.pre) specs + in let init_props = Propset.from_proplist tenv (init_prop :: init_props_from_pres) in let init_edgeset = let add pset prop = - Paths.PathSet.add_renamed_prop prop (Paths.Path.start start_node) pset in - Propset.fold add Paths.PathSet.empty init_props in - L.(debug Analysis Medium) "@\n#### Start: Footprint Computation for %a ####@." - Typ.Procname.pp pname; - L.d_increase_indent 1; - L.d_strln "initial props ="; - Propset.d Prop.prop_emp init_props; L.d_ln (); L.d_ln(); - L.d_decrease_indent 1; - Worklist.add wl start_node; - Config.arc_mode := - Hashtbl.mem - (Procdesc.get_flags pdesc) - Mleak_buckets.objc_arc_flag; - ignore (path_set_put_todo wl start_node init_edgeset); - forward_tabulate tenv pdesc wl in - let get_results (wl : Worklist.t) () = - State.process_execution_failures Reporting.log_warning_deprecated pname; + Paths.PathSet.add_renamed_prop prop (Paths.Path.start start_node) pset + in + Propset.fold add Paths.PathSet.empty init_props + in + L.(debug Analysis Medium) + "@\n#### Start: Footprint Computation for %a ####@." Typ.Procname.pp pname ; + L.d_increase_indent 1 ; + L.d_strln "initial props =" ; + Propset.d Prop.prop_emp init_props ; + L.d_ln () ; + L.d_ln () ; + L.d_decrease_indent 1 ; + Worklist.add wl start_node ; + Config.arc_mode := Hashtbl.mem (Procdesc.get_flags pdesc) Mleak_buckets.objc_arc_flag ; + ignore (path_set_put_todo wl start_node init_edgeset) ; + forward_tabulate tenv pdesc wl + in + let get_results (wl: Worklist.t) () = + State.process_execution_failures Reporting.log_warning_deprecated pname ; let results = collect_analysis_result tenv wl pdesc in - L.(debug Analysis Medium) "#### [FUNCTION %a] ... OK #####@\n" Typ.Procname.pp pname; - L.(debug Analysis Medium) "#### Finished: Footprint Computation for %a %a ####@." - Typ.Procname.pp pname - (pp_intra_stats wl pdesc) pname; - L.(debug Analysis Medium) "#### [FUNCTION %a] Footprint Analysis result ####@\n%a@." - Typ.Procname.pp pname - (Paths.PathSet.pp Pp.text) results; - let specs = try extract_specs tenv pdesc results with - | Exceptions.Leak _ -> - let exn = - Exceptions.Internal_error - (Localise.verbatim_desc "Leak_while_collecting_specs_after_footprint") in - Reporting.log_error_deprecated pname exn; - [] (* retuning no specs *) in - specs, Specs.FOOTPRINT in + L.(debug Analysis Medium) "#### [FUNCTION %a] ... OK #####@\n" Typ.Procname.pp pname ; + L.(debug Analysis Medium) + "#### Finished: Footprint Computation for %a %a ####@." Typ.Procname.pp pname + (pp_intra_stats wl pdesc) pname ; + L.(debug Analysis Medium) + "#### [FUNCTION %a] Footprint Analysis result ####@\n%a@." Typ.Procname.pp pname + (Paths.PathSet.pp Pp.text) results ; + let specs = + try extract_specs tenv pdesc results + with Exceptions.Leak _ -> + let exn = + Exceptions.Internal_error + (Localise.verbatim_desc "Leak_while_collecting_specs_after_footprint") + in + Reporting.log_error_deprecated pname exn ; [] + (* retuning no specs *) + in + (specs, Specs.FOOTPRINT) + in let wl = path_set_create_worklist pdesc in - go wl, get_results wl in - + (go wl, get_results wl) + in let re_execution () : exe_phase = let candidate_preconditions = - List.map - ~f:(fun spec -> spec.Specs.pre) - (Specs.get_specs_from_payload summary) in + List.map ~f:(fun spec -> spec.Specs.pre) (Specs.get_specs_from_payload summary) + in let valid_specs = ref [] in let go () = - L.(debug Analysis Medium) "@.#### Start: Re-Execution for %a ####@." Typ.Procname.pp pname; + L.(debug Analysis Medium) "@.#### Start: Re-Execution for %a ####@." Typ.Procname.pp pname ; let filter p = let wl = path_set_create_worklist pdesc in let speco = execute_filter_prop wl tenv pdesc start_node p in - let is_valid = match speco with - | None -> false - | Some spec -> - valid_specs := !valid_specs @ [spec]; - true in + let is_valid = + match speco with + | None + -> false + | Some spec + -> valid_specs := !valid_specs @ [spec] ; + true + in let outcome = if is_valid then "pass" else "fail" in - L.(debug Analysis Medium) "Finished re-execution for precondition %d %a (%s)@." - (Specs.Jprop.to_number p) - (pp_intra_stats wl pdesc) pname - outcome; - speco in - if Config.undo_join then - ignore (Specs.Jprop.filter filter candidate_preconditions) - else - ignore (List.map ~f:filter candidate_preconditions) in + L.(debug Analysis Medium) + "Finished re-execution for precondition %d %a (%s)@." (Specs.Jprop.to_number p) + (pp_intra_stats wl pdesc) pname outcome ; + speco + in + if Config.undo_join then ignore (Specs.Jprop.filter filter candidate_preconditions) + else ignore (List.map ~f:filter candidate_preconditions) + in let get_results () = let specs = !valid_specs in - L.(debug Analysis Medium) "#### [FUNCTION %a] ... OK #####@\n" Typ.Procname.pp pname; - L.(debug Analysis Medium) "#### Finished: Re-Execution for %a ####@." Typ.Procname.pp pname; - let valid_preconditions = - List.map ~f:(fun spec -> spec.Specs.pre) specs in + L.(debug Analysis Medium) "#### [FUNCTION %a] ... OK #####@\n" Typ.Procname.pp pname ; + L.(debug Analysis Medium) "#### Finished: Re-Execution for %a ####@." Typ.Procname.pp pname ; + let valid_preconditions = List.map ~f:(fun spec -> spec.Specs.pre) specs in let source = (Procdesc.get_loc pdesc).file in let filename = - DB.Results_dir.path_to_filename - (DB.Results_dir.Abs_source_dir source) - [(Typ.Procname.to_filename pname)] in - if Config.write_dotty then - Dotty.pp_speclist_dotty_file filename specs; - L.(debug Analysis Medium) "@\n@\n================================================"; - L.(debug Analysis Medium) "@\n *** CANDIDATE PRECONDITIONS FOR %a: " Typ.Procname.pp pname; - L.(debug Analysis Medium) "@\n================================================@\n"; - L.(debug Analysis Medium) "@\n%a @\n@\n" - (Specs.Jprop.pp_list Pp.text false) candidate_preconditions; - L.(debug Analysis Medium) "@\n@\n================================================"; - L.(debug Analysis Medium) "@\n *** VALID PRECONDITIONS FOR %a: " Typ.Procname.pp pname; - L.(debug Analysis Medium) "@\n================================================@\n"; - L.(debug Analysis Medium) "@\n%a @\n@." - (Specs.Jprop.pp_list Pp.text true) valid_preconditions; - specs, Specs.RE_EXECUTION in - go, get_results in - + DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) + [Typ.Procname.to_filename pname] + in + if Config.write_dotty then Dotty.pp_speclist_dotty_file filename specs ; + L.(debug Analysis Medium) "@\n@\n================================================" ; + L.(debug Analysis Medium) "@\n *** CANDIDATE PRECONDITIONS FOR %a: " Typ.Procname.pp pname ; + L.(debug Analysis Medium) "@\n================================================@\n" ; + L.(debug Analysis Medium) + "@\n%a @\n@\n" (Specs.Jprop.pp_list Pp.text false) candidate_preconditions ; + L.(debug Analysis Medium) "@\n@\n================================================" ; + L.(debug Analysis Medium) "@\n *** VALID PRECONDITIONS FOR %a: " Typ.Procname.pp pname ; + L.(debug Analysis Medium) "@\n================================================@\n" ; + L.(debug Analysis Medium) + "@\n%a @\n@." (Specs.Jprop.pp_list Pp.text true) valid_preconditions ; + (specs, Specs.RE_EXECUTION) + in + (go, get_results) + in match Specs.get_phase summary with - | Specs.FOOTPRINT -> - compute_footprint () - | Specs.RE_EXECUTION -> - re_execution () + | Specs.FOOTPRINT + -> compute_footprint () + | Specs.RE_EXECUTION + -> re_execution () let set_current_language proc_desc = let language = (Procdesc.get_attributes proc_desc).ProcAttributes.language in @@ -1101,65 +1112,74 @@ let set_current_language proc_desc = (** reset global values before analysing a procedure *) let reset_global_values proc_desc = - Config.reset_abs_val (); - Ident.NameGenerator.reset (); - SymOp.reset_total (); - reset_prop_metrics (); - Abs.reset_current_rules (); + Config.reset_abs_val () ; + Ident.NameGenerator.reset () ; + SymOp.reset_total () ; + reset_prop_metrics () ; + Abs.reset_current_rules () ; set_current_language proc_desc (* Collect all pairs of the kind (precondition, runtime exception) from a summary *) let exception_preconditions tenv pname summary = let collect_exceptions pre (exns, all_post_exn) (prop, _) = match Tabulation.prop_get_exn_name pname prop with - | Some exn_name when PatternMatch.is_runtime_exception tenv exn_name -> - ((pre, exn_name) :: exns, all_post_exn) - | _ -> (exns, false) in + | Some exn_name when PatternMatch.is_runtime_exception tenv exn_name + -> ((pre, exn_name) :: exns, all_post_exn) + | _ + -> (exns, false) + in let collect_spec errors spec = - List.fold ~f:(collect_exceptions spec.Specs.pre) ~init:errors spec.Specs.posts in + List.fold ~f:(collect_exceptions spec.Specs.pre) ~init:errors spec.Specs.posts + in List.fold ~f:collect_spec ~init:([], true) (Specs.get_specs_from_payload summary) (* Collect all pairs of the kind (precondition, custom error) from a summary *) let custom_error_preconditions summary = let collect_errors pre (errors, all_post_error) (prop, _) = match Tabulation.lookup_custom_errors prop with - | None -> (errors, false) - | Some e -> ((pre, e) :: errors, all_post_error) in + | None + -> (errors, false) + | Some e + -> ((pre, e) :: errors, all_post_error) + in let collect_spec errors spec = - List.fold ~f:(collect_errors spec.Specs.pre) ~init:errors spec.Specs.posts in + List.fold ~f:(collect_errors spec.Specs.pre) ~init:errors spec.Specs.posts + in List.fold ~f:collect_spec ~init:([], true) (Specs.get_specs_from_payload summary) - (* Remove the constrain of the form this != null which is true for all Java virtual calls *) let remove_this_not_null tenv prop = let collect_hpred (var_option, hpreds) = function | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp (Exp.Var var, _), _) - when Config.curr_language_is Config.Java && Pvar.is_this pvar -> - (Some var, hpreds) - | hpred -> (var_option, hpred:: hpreds) in + when Config.curr_language_is Config.Java && Pvar.is_this pvar + -> (Some var, hpreds) + | hpred + -> (var_option, hpred :: hpreds) + in let collect_atom var atoms = function - | Sil.Aneq (Exp.Var v, e) - when Ident.equal v var && Exp.equal e Exp.null -> atoms - | a -> a:: atoms in + | Sil.Aneq (Exp.Var v, e) when Ident.equal v var && Exp.equal e Exp.null + -> atoms + | a + -> a :: atoms + in match List.fold ~f:collect_hpred ~init:(None, []) prop.Prop.sigma with - | None, _ -> prop - | Some var, filtered_hpreds -> - let filtered_atoms = - List.fold ~f:(collect_atom var) ~init:[] prop.Prop.pi in + | None, _ + -> prop + | Some var, filtered_hpreds + -> let filtered_atoms = List.fold ~f:(collect_atom var) ~init:[] prop.Prop.pi in let prop' = Prop.set Prop.prop_emp ~pi:filtered_atoms ~sigma:filtered_hpreds in Prop.normalize tenv prop' - (** Is true when the precondition does not contain constrains that can be false at call site. This means that the post-conditions associated with this precondition cannot be prevented by the calling context. *) let is_unavoidable tenv pre = let prop = remove_this_not_null tenv (Specs.Jprop.to_prop pre) in match Prop.CategorizePreconditions.categorize [prop] with - | Prop.CategorizePreconditions.NoPres - | Prop.CategorizePreconditions.Empty -> true - | _ -> false - + | Prop.CategorizePreconditions.NoPres | Prop.CategorizePreconditions.Empty + -> true + | _ + -> false (** Detects if there are specs of the form {precondition} proc {runtime exception} and report an error in that case, generating the trace that lead to the runtime exception if the method is @@ -1167,52 +1187,53 @@ let is_unavoidable tenv pre = let report_runtime_exceptions tenv pdesc summary = let pname = Specs.get_proc_name summary in let is_public_method = - PredSymb.equal_access (Specs.get_attributes summary).access PredSymb.Public in + PredSymb.equal_access (Specs.get_attributes summary).access PredSymb.Public + in let is_main = is_public_method && - (match pname with - | Typ.Procname.Java pname_java -> - Typ.Procname.java_is_static pname - && String.equal (Typ.Procname.java_get_method pname_java) "main" - | _ -> - false) in - let is_annotated pdesc = - Annotations.pdesc_has_return_annot pdesc Annotations.ia_is_verify in - let (exn_preconditions, all_post_exn) = - exception_preconditions tenv pname summary in + match pname with + | Typ.Procname.Java pname_java + -> Typ.Procname.java_is_static pname + && String.equal (Typ.Procname.java_get_method pname_java) "main" + | _ + -> false + in + let is_annotated pdesc = Annotations.pdesc_has_return_annot pdesc Annotations.ia_is_verify in + let exn_preconditions, all_post_exn = exception_preconditions tenv pname summary in let should_report pre = - all_post_exn || is_main || is_annotated pdesc || is_unavoidable tenv pre in + all_post_exn || is_main || is_annotated pdesc || is_unavoidable tenv pre + in let report (pre, runtime_exception) = if should_report pre then - let pre_str = - F.asprintf "%a" (Prop.pp_prop Pp.text) (Specs.Jprop.to_prop pre) in + let pre_str = F.asprintf "%a" (Prop.pp_prop Pp.text) (Specs.Jprop.to_prop pre) in let exn_desc = Localise.java_unchecked_exn_desc pname runtime_exception pre_str in let exn = Exceptions.Java_runtime_exception (runtime_exception, pre_str, exn_desc) in - Reporting.log_error_deprecated pname exn in + Reporting.log_error_deprecated pname exn + in List.iter ~f:report exn_preconditions - let report_custom_errors tenv summary = let pname = Specs.get_proc_name summary in - let error_preconditions, all_post_error = - custom_error_preconditions summary in + let error_preconditions, all_post_error = custom_error_preconditions summary in let report (pre, custom_error) = if all_post_error || is_unavoidable tenv pre then let loc = summary.Specs.attributes.ProcAttributes.loc in let err_desc = Localise.desc_custom_error loc in let exn = Exceptions.Custom_error (custom_error, err_desc) in - Reporting.log_error_deprecated pname exn in + Reporting.log_error_deprecated pname exn + in List.iter ~f:report error_preconditions module SpecMap = Caml.Map.Make (struct - type t = Prop.normal Specs.Jprop.t - let compare = Specs.Jprop.compare - end) + type t = Prop.normal Specs.Jprop.t + + let compare = Specs.Jprop.compare +end) (** Update the specs of the current proc after the execution of one phase *) -let update_specs tenv prev_summary phase (new_specs : Specs.NormSpec.t list) - : Specs.NormSpec.t list * bool = +let update_specs tenv prev_summary phase (new_specs: Specs.NormSpec.t list) + : Specs.NormSpec.t list * bool = let new_specs = Specs.normalized_specs_to_specs new_specs in let old_specs = Specs.get_specs_from_payload prev_summary in let changed = ref false in @@ -1220,127 +1241,113 @@ let update_specs tenv prev_summary phase (new_specs : Specs.NormSpec.t list) ref (List.fold ~f:(fun map spec -> - SpecMap.add - spec.Specs.pre - (Paths.PathSet.from_renamed_list spec.Specs.posts, spec.Specs.visited) map) - ~init:SpecMap.empty old_specs) in - let re_exe_filter old_spec = (* filter out pres which failed re-exe *) - if Specs.equal_phase phase Specs.RE_EXECUTION && - not (List.exists - ~f:(fun new_spec -> Specs.Jprop.equal new_spec.Specs.pre old_spec.Specs.pre) - new_specs) - then begin - changed:= true; - L.(debug Analysis Medium) "Specs changed: removing pre of spec@\n%a@." - (Specs.pp_spec Pp.text None) old_spec; - current_specs := SpecMap.remove old_spec.Specs.pre !current_specs end - else () in - let add_spec spec = (* add a new spec by doing union of the posts *) + SpecMap.add spec.Specs.pre + (Paths.PathSet.from_renamed_list spec.Specs.posts, spec.Specs.visited) map) + ~init:SpecMap.empty old_specs) + in + let re_exe_filter old_spec = + (* filter out pres which failed re-exe *) + if Specs.equal_phase phase Specs.RE_EXECUTION + && not + (List.exists + ~f:(fun new_spec -> Specs.Jprop.equal new_spec.Specs.pre old_spec.Specs.pre) + new_specs) + then ( + changed := true ; + L.(debug Analysis Medium) + "Specs changed: removing pre of spec@\n%a@." (Specs.pp_spec Pp.text None) old_spec ; + current_specs := SpecMap.remove old_spec.Specs.pre !current_specs ) + else () + in + let add_spec spec = + (* add a new spec by doing union of the posts *) try let old_post, old_visited = SpecMap.find spec.Specs.pre !current_specs in let new_post, new_visited = - Paths.PathSet.union - old_post - (Paths.PathSet.from_renamed_list spec.Specs.posts), - Specs.Visitedset.union old_visited spec.Specs.visited in - if not (Paths.PathSet.equal old_post new_post) then begin - changed := true; - L.(debug Analysis Medium) "Specs changed: added new post@\n%a@." + ( Paths.PathSet.union old_post (Paths.PathSet.from_renamed_list spec.Specs.posts) + , Specs.Visitedset.union old_visited spec.Specs.visited ) + in + if not (Paths.PathSet.equal old_post new_post) then ( + changed := true ; + L.(debug Analysis Medium) + "Specs changed: added new post@\n%a@." (Propset.pp Pp.text (Specs.Jprop.to_prop spec.Specs.pre)) - (Paths.PathSet.to_propset tenv new_post); - current_specs := - SpecMap.add spec.Specs.pre (new_post, new_visited) - (SpecMap.remove spec.Specs.pre !current_specs) end - + (Paths.PathSet.to_propset tenv new_post) ; + current_specs + := SpecMap.add spec.Specs.pre (new_post, new_visited) + (SpecMap.remove spec.Specs.pre !current_specs) ) with Not_found -> - changed := true; - L.(debug Analysis Medium) "Specs changed: added new pre@\n%a@." - (Specs.Jprop.pp_short Pp.text) spec.Specs.pre; - current_specs := - SpecMap.add - spec.Specs.pre - ((Paths.PathSet.from_renamed_list spec.Specs.posts), spec.Specs.visited) - !current_specs in + changed := true ; + L.(debug Analysis Medium) + "Specs changed: added new pre@\n%a@." (Specs.Jprop.pp_short Pp.text) spec.Specs.pre ; + current_specs + := SpecMap.add spec.Specs.pre + (Paths.PathSet.from_renamed_list spec.Specs.posts, spec.Specs.visited) !current_specs + in let res = ref [] in let convert pre (post_set, visited) = - res := - Specs.spec_normalize tenv - { Specs.pre = pre; - Specs.posts = Paths.PathSet.elements post_set; - Specs.visited = visited }:: !res in - List.iter ~f:re_exe_filter old_specs; (* filter out pre's which failed re-exe *) - List.iter ~f:add_spec new_specs; (* add new specs *) - SpecMap.iter convert !current_specs; - !res,!changed + res + := Specs.spec_normalize tenv + {Specs.pre= pre; Specs.posts= Paths.PathSet.elements post_set; Specs.visited= visited} + :: !res + in + List.iter ~f:re_exe_filter old_specs ; + (* filter out pre's which failed re-exe *) + List.iter ~f:add_spec new_specs ; + (* add new specs *) + SpecMap.iter convert !current_specs ; + (!res, !changed) (** update a summary after analysing a procedure *) let update_summary tenv prev_summary specs phase res = let normal_specs = List.map ~f:(Specs.spec_normalize tenv) specs in let new_specs, _ = update_specs tenv prev_summary phase normal_specs in let symops = prev_summary.Specs.stats.Specs.symops + SymOp.get_total () in - let stats_failure = match res with - | None -> prev_summary.Specs.stats.Specs.stats_failure - | Some _ -> res in - let stats = - { prev_summary.Specs.stats with - symops; - stats_failure; - } in + let stats_failure = + match res with None -> prev_summary.Specs.stats.Specs.stats_failure | Some _ -> res + in + let stats = {prev_summary.Specs.stats with symops; stats_failure} in let preposts = match phase with - | Specs.FOOTPRINT -> - Some new_specs - | Specs.RE_EXECUTION -> - Some (List.map ~f:(Specs.NormSpec.erase_join_info_pre tenv) new_specs) in - let payload = { prev_summary.Specs.payload with Specs.preposts; } in - { prev_summary with - Specs.phase; - stats; - payload; - } - + | Specs.FOOTPRINT + -> Some new_specs + | Specs.RE_EXECUTION + -> Some (List.map ~f:(Specs.NormSpec.erase_join_info_pre tenv) new_specs) + in + let payload = {prev_summary.Specs.payload with Specs.preposts= preposts} in + {prev_summary with Specs.phase= phase; stats; payload} (** Analyze the procedure and return the resulting summary. *) let analyze_proc tenv proc_desc : Specs.summary = let proc_name = Procdesc.get_proc_name proc_desc in - reset_global_values proc_desc; + reset_global_values proc_desc ; let summary = Specs.get_summary_unsafe "analyze_proc" proc_name in let go, get_results = perform_analysis_phase tenv summary proc_desc in let res = Timeout.exe_timeout go () in let specs, phase = get_results () in - let updated_summary = - update_summary tenv summary specs phase res in + let updated_summary = update_summary tenv summary specs phase res in if Config.curr_language_is Config.Clang && Config.report_custom_error then - report_custom_errors tenv updated_summary; + report_custom_errors tenv updated_summary ; if Config.curr_language_is Config.Java && Config.tracing then - report_runtime_exceptions tenv proc_desc updated_summary; + report_runtime_exceptions tenv proc_desc updated_summary ; updated_summary (** Perform the transition from [FOOTPRINT] to [RE_EXECUTION] in spec table *) let transition_footprint_re_exe tenv proc_name joined_pres = - L.(debug Analysis Medium) "Transition %a from footprint to re-exe@." Typ.Procname.pp proc_name; + L.(debug Analysis Medium) "Transition %a from footprint to re-exe@." Typ.Procname.pp proc_name ; let summary = Specs.get_summary_unsafe "transition_footprint_re_exe" proc_name in let summary' = - if Config.only_footprint then - { summary with - Specs.phase = Specs.RE_EXECUTION; - } + if Config.only_footprint then {summary with Specs.phase= Specs.RE_EXECUTION} else let specs = List.map ~f:(fun jp -> - Specs.spec_normalize tenv - { Specs.pre = jp; - posts = []; - visited = Specs.Visitedset.empty }) - joined_pres in - let payload = - { summary.Specs.payload with - Specs.preposts = Some specs; } in - { summary with - Specs.phase = Specs.RE_EXECUTION; - payload; - } in + Specs.spec_normalize tenv {Specs.pre= jp; posts= []; visited= Specs.Visitedset.empty}) + joined_pres + in + let payload = {summary.Specs.payload with Specs.preposts= Some specs} in + {summary with Specs.phase= Specs.RE_EXECUTION; payload} + in Specs.add_summary proc_name summary' (** Perform phase transition from [FOOTPRINT] to [RE_EXECUTION] for @@ -1352,73 +1359,72 @@ let perform_transition proc_desc tenv proc_name = let allow_leak = !Config.allow_leak in (* apply the start node to f, and do nothing in case of exception *) let apply_start_node f = - try - f (Procdesc.get_start_node proc_desc) - with exn when SymOp.exn_not_failure exn -> () in - apply_start_node (do_before_node 0); + try f (Procdesc.get_start_node proc_desc) + with exn when SymOp.exn_not_failure exn -> () + in + apply_start_node (do_before_node 0) ; try - Config.allow_leak := true; + Config.allow_leak := true ; let res = collect_preconditions tenv summary in - Config.allow_leak := allow_leak; - apply_start_node do_after_node; + Config.allow_leak := allow_leak ; + apply_start_node do_after_node ; res with exn when SymOp.exn_not_failure exn -> - apply_start_node do_after_node; - Config.allow_leak := allow_leak; - L.(debug Analysis Medium) "Error in collect_preconditions for %a@." - Typ.Procname.pp proc_name; + apply_start_node do_after_node ; + Config.allow_leak := allow_leak ; + L.(debug Analysis Medium) + "Error in collect_preconditions for %a@." Typ.Procname.pp proc_name ; let err_name, _, ml_loc_opt, _, _, _, _ = Exceptions.recognize_exception exn in - let err_str = "exception raised " ^ (Localise.to_issue_id err_name) in - L.(debug Analysis Medium) "Error: %s %a@." err_str L.pp_ml_loc_opt ml_loc_opt; - [] in - transition_footprint_re_exe tenv proc_name joined_pres in + let err_str = "exception raised " ^ Localise.to_issue_id err_name in + L.(debug Analysis Medium) "Error: %s %a@." err_str L.pp_ml_loc_opt ml_loc_opt ; [] + in + transition_footprint_re_exe tenv proc_name joined_pres + in match Specs.get_summary proc_name with - | Some summary when Specs.equal_phase (Specs.get_phase summary) Specs.FOOTPRINT -> - transition summary - | _ -> () - + | Some summary when Specs.equal_phase (Specs.get_phase summary) Specs.FOOTPRINT + -> transition summary + | _ + -> () (* Create closures for the interprocedural algorithm *) let interprocedural_algorithm_closures ~prepare_proc exe_env : Tasks.closure list = let call_graph = Exe_env.get_cg exe_env in let process_one_proc proc_name = - prepare_proc proc_name; + prepare_proc proc_name ; let analyze proc_desc = - ignore (Ondemand.analyze_proc_desc ~propagate_exceptions:false proc_desc proc_desc) in + ignore (Ondemand.analyze_proc_desc ~propagate_exceptions:false proc_desc proc_desc) + in match Exe_env.get_proc_desc exe_env proc_name with | Some proc_desc - when Config.reactive_mode (* in reactive mode, only analyze changed procedures *) - && (Procdesc.get_attributes proc_desc).ProcAttributes.changed -> - analyze proc_desc - | Some proc_desc -> analyze proc_desc - | None -> () in + when Config.reactive_mode + (* in reactive mode, only analyze changed procedures *) + && (Procdesc.get_attributes proc_desc).ProcAttributes.changed + -> analyze proc_desc + | Some proc_desc + -> analyze proc_desc + | None + -> () + in let procs_to_analyze = Cg.get_defined_nodes call_graph in - let create_closure proc_name = - fun () -> process_one_proc proc_name in + let create_closure proc_name () = process_one_proc proc_name in List.map ~f:create_closure procs_to_analyze let analyze_procedure_aux cg_opt tenv proc_desc = let proc_name = Procdesc.get_proc_name proc_desc in - if not (Procdesc.did_preanalysis proc_desc) - then - begin - Preanal.do_liveness proc_desc tenv; - Preanal.do_abstraction proc_desc; - Option.iter cg_opt ~f:(fun cg -> Preanal.do_dynamic_dispatch proc_desc cg tenv); - end; - let summaryfp = - Config.run_in_footprint_mode (analyze_proc tenv) proc_desc in - Specs.add_summary proc_name summaryfp; - perform_transition proc_desc tenv proc_name; - let summaryre = - Config.run_in_re_execution_mode (analyze_proc tenv) proc_desc in - Specs.add_summary proc_name summaryre; - summaryre - -let analyze_procedure { Callbacks.summary; proc_desc; tenv } : Specs.summary = + if not (Procdesc.did_preanalysis proc_desc) then ( + Preanal.do_liveness proc_desc tenv ; + Preanal.do_abstraction proc_desc ; + Option.iter cg_opt ~f:(fun cg -> Preanal.do_dynamic_dispatch proc_desc cg tenv) ) ; + let summaryfp = Config.run_in_footprint_mode (analyze_proc tenv) proc_desc in + Specs.add_summary proc_name summaryfp ; + perform_transition proc_desc tenv proc_name ; + let summaryre = Config.run_in_re_execution_mode (analyze_proc tenv) proc_desc in + Specs.add_summary proc_name summaryre ; summaryre + +let analyze_procedure {Callbacks.summary; proc_desc; tenv} : Specs.summary = let proc_name = Procdesc.get_proc_name proc_desc in - Specs.add_summary proc_name summary; - ignore (analyze_procedure_aux None tenv proc_desc); + Specs.add_summary proc_name summary ; + ignore (analyze_procedure_aux None tenv proc_desc) ; Specs.get_summary_unsafe __FILE__ proc_name (** Create closures to perform the analysis of an exe_env *) @@ -1426,82 +1432,80 @@ let do_analysis_closures exe_env : Tasks.closure list = let get_calls caller_pdesc = let calls = ref [] in let f (callee_pname, loc) = calls := (callee_pname, loc) :: !calls in - Procdesc.iter_calls f caller_pdesc; - List.rev !calls in + Procdesc.iter_calls f caller_pdesc ; + List.rev !calls + in let init_proc pname = - let pdesc = match Exe_env.get_proc_desc exe_env pname with - | Some pdesc -> - pdesc - | None -> - assert false in + let pdesc = + match Exe_env.get_proc_desc exe_env pname with Some pdesc -> pdesc | None -> assert false + in let nodes = List.map ~f:(fun n -> Procdesc.Node.get_id n) (Procdesc.get_nodes pdesc) in let proc_flags = Procdesc.get_flags pdesc in - let static_err_log = Procdesc.get_err_log pdesc in (* err log from translation *) + let static_err_log = Procdesc.get_err_log pdesc in + (* err log from translation *) let calls = get_calls pdesc in let attributes = - { (Procdesc.get_attributes pdesc) with - ProcAttributes.err_log = static_err_log; } in - let proc_desc_option = - if Config.dynamic_dispatch = `Lazy - then Some pdesc - else None in - ignore (Specs.init_summary (nodes, proc_flags, calls, attributes, proc_desc_option)) in - + {(Procdesc.get_attributes pdesc) with ProcAttributes.err_log= static_err_log} + in + let proc_desc_option = if Config.dynamic_dispatch = `Lazy then Some pdesc else None in + ignore (Specs.init_summary (nodes, proc_flags, calls, attributes, proc_desc_option)) + in let callbacks = let get_proc_desc proc_name = match Exe_env.get_proc_desc exe_env proc_name with - | Some pdesc -> Some pdesc - | None when Config.dynamic_dispatch = `Lazy -> - Option.bind (Specs.get_summary proc_name) - ~f:(fun summary -> summary.Specs.proc_desc_option) - | None -> None in + | Some pdesc + -> Some pdesc + | None when Config.dynamic_dispatch = `Lazy + -> Option.bind (Specs.get_summary proc_name) ~f:(fun summary -> + summary.Specs.proc_desc_option ) + | None + -> None + in let analyze_ondemand _ proc_desc = let proc_name = Procdesc.get_proc_name proc_desc in let tenv = Exe_env.get_tenv exe_env proc_name in let cg = Exe_env.get_cg exe_env in - analyze_procedure_aux (Some cg) tenv proc_desc in - { - Ondemand.analyze_ondemand; - get_proc_desc; - } in - + analyze_procedure_aux (Some cg) tenv proc_desc + in + {Ondemand.analyze_ondemand= analyze_ondemand; get_proc_desc} + in let prepare_proc pn = - let should_init = - Config.models_mode || - is_none (Specs.get_summary pn) in - if should_init then init_proc pn in - + let should_init = Config.models_mode || is_none (Specs.get_summary pn) in + if should_init then init_proc pn + in let closures = List.map ~f:(fun closure () -> - Ondemand.set_callbacks callbacks; - closure (); - Ondemand.unset_callbacks ()) - (interprocedural_algorithm_closures ~prepare_proc exe_env) in + Ondemand.set_callbacks callbacks ; closure () ; Ondemand.unset_callbacks ()) + (interprocedural_algorithm_closures ~prepare_proc exe_env) + in closures - let visited_and_total_nodes ~filter cfg = let filter_node pdesc n = - Procdesc.is_defined pdesc && - filter pdesc && + Procdesc.is_defined pdesc && filter pdesc + && match Procdesc.Node.get_kind n with - | Procdesc.Node.Stmt_node _ | Procdesc.Node.Prune_node _ - | Procdesc.Node.Start_node _ | Procdesc.Node.Exit_node _ -> true - | Procdesc.Node.Skip_node _ | Procdesc.Node.Join_node -> false in + | Procdesc.Node.Stmt_node _ + | Procdesc.Node.Prune_node _ + | Procdesc.Node.Start_node _ + | Procdesc.Node.Exit_node _ + -> true + | Procdesc.Node.Skip_node _ | Procdesc.Node.Join_node + -> false + in let counted_nodes, visited_nodes_re = let set = ref Procdesc.NodeSet.empty in let set_visited_re = ref Procdesc.NodeSet.empty in let add pdesc n = - if filter_node pdesc n then - begin - set := Procdesc.NodeSet.add n !set; - if snd (Printer.node_is_visited n) - then set_visited_re := Procdesc.NodeSet.add n !set_visited_re - end in - Cfg.iter_all_nodes add cfg; - !set, !set_visited_re in - Procdesc.NodeSet.elements visited_nodes_re, Procdesc.NodeSet.elements counted_nodes + if filter_node pdesc n then ( + set := Procdesc.NodeSet.add n !set ; + if snd (Printer.node_is_visited n) then set_visited_re + := Procdesc.NodeSet.add n !set_visited_re ) + in + Cfg.iter_all_nodes add cfg ; (!set, !set_visited_re) + in + (Procdesc.NodeSet.elements visited_nodes_re, Procdesc.NodeSet.elements counted_nodes) (** Print the stats for the given cfg. Consider every defined proc unless a proc with the same name @@ -1510,8 +1514,11 @@ let print_stats_cfg proc_shadowed source cfg = let err_table = Errlog.create_err_table () in let filter pdesc = match Specs.get_summary (Procdesc.get_proc_name pdesc) with - | None -> false - | Some summary -> Specs.get_specs_from_payload summary <> [] in + | None + -> false + | Some summary + -> Specs.get_specs_from_payload summary <> [] + in let nodes_visited, nodes_total = visited_and_total_nodes ~filter cfg in let num_proc = ref 0 in let num_nospec_noerror_proc = ref 0 in @@ -1524,30 +1531,39 @@ let print_stats_cfg proc_shadowed source cfg = let compute_stats_proc proc_desc = let proc_name = Procdesc.get_proc_name proc_desc in match Specs.get_summary proc_name with - | None -> () - | Some _ when proc_shadowed proc_desc -> - L.(debug Analysis Medium) + | None + -> () + | Some _ when proc_shadowed proc_desc + -> L.(debug Analysis Medium) "print_stats: ignoring function %a which is also defined in another file@." Typ.Procname.pp proc_name - | Some summary -> - let stats = summary.Specs.stats in + | Some summary + -> let stats = summary.Specs.stats in let err_log = summary.Specs.attributes.ProcAttributes.err_log in - incr num_proc; + incr num_proc ; let specs = Specs.get_specs_from_payload summary in - tot_specs := (List.length specs) + !tot_specs; + tot_specs := List.length specs + !tot_specs ; let () = - match specs, - Errlog.size - (fun ekind in_footprint -> - Exceptions.equal_err_kind ekind Exceptions.Kerror && in_footprint) - err_log with - | [], 0 -> incr num_nospec_noerror_proc - | _, 0 -> incr num_spec_noerror_proc - | [], _ -> incr num_nospec_error_proc - | _, _ -> incr num_spec_error_proc in - tot_symops := !tot_symops + stats.Specs.symops; - if Option.is_some stats.Specs.stats_failure then incr num_timeout; - Errlog.extend_table err_table err_log in + match + ( specs + , Errlog.size + (fun ekind in_footprint -> + Exceptions.equal_err_kind ekind Exceptions.Kerror && in_footprint) + err_log ) + with + | [], 0 + -> incr num_nospec_noerror_proc + | _, 0 + -> incr num_spec_noerror_proc + | [], _ + -> incr num_nospec_error_proc + | _, _ + -> incr num_spec_error_proc + in + tot_symops := !tot_symops + stats.Specs.symops ; + if Option.is_some stats.Specs.stats_failure then incr num_timeout ; + Errlog.extend_table err_table err_log + in let print_file_stats fmt () = let num_errors = Errlog.err_table_size_footprint Exceptions.Kerror err_table in let num_warnings = Errlog.err_table_size_footprint Exceptions.Kwarning err_table in @@ -1555,36 +1571,34 @@ let print_stats_cfg proc_shadowed source cfg = let num_ok_proc = !num_spec_noerror_proc + !num_spec_error_proc in (* F.fprintf fmt "VISITED: %a@\n" (pp_seq pp_node) nodes_visited; F.fprintf fmt "TOTAL: %a@\n" (pp_seq pp_node) nodes_total; *) - F.fprintf fmt "@\n++++++++++++++++++++++++++++++++++++++++++++++++++@\n"; - F.fprintf fmt "+ FILE: %a VISITED: %d/%d SYMOPS: %d@\n" - SourceFile.pp source - (List.length nodes_visited) - (List.length nodes_total) - !tot_symops; + F.fprintf fmt "@\n++++++++++++++++++++++++++++++++++++++++++++++++++@\n" ; + F.fprintf fmt "+ FILE: %a VISITED: %d/%d SYMOPS: %d@\n" SourceFile.pp source + (List.length nodes_visited) (List.length nodes_total) !tot_symops ; F.fprintf fmt "+ num_procs: %d (%d ok, %d timeouts, %d errors, %d warnings, %d infos)@\n" - !num_proc num_ok_proc !num_timeout num_errors num_warnings num_infos; - F.fprintf fmt "+ detail procs:@\n"; - F.fprintf fmt "+ - No Errors and No Specs: %d@\n" !num_nospec_noerror_proc; - F.fprintf fmt "+ - Some Errors and No Specs: %d@\n" !num_nospec_error_proc; - F.fprintf fmt "+ - No Errors and Some Specs: %d@\n" !num_spec_noerror_proc; - F.fprintf fmt "+ - Some Errors and Some Specs: %d@\n" !num_spec_error_proc; - F.fprintf fmt "+ errors: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kerror) err_table; - F.fprintf fmt "+ warnings: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kwarning) err_table; - F.fprintf fmt "+ infos: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kinfo) err_table; - F.fprintf fmt "+ specs: %d@\n" !tot_specs; - F.fprintf fmt "++++++++++++++++++++++++++++++++++++++++++++++++++@\n"; - Errlog.print_err_table_details fmt err_table in + !num_proc num_ok_proc !num_timeout num_errors num_warnings num_infos ; + F.fprintf fmt "+ detail procs:@\n" ; + F.fprintf fmt "+ - No Errors and No Specs: %d@\n" !num_nospec_noerror_proc ; + F.fprintf fmt "+ - Some Errors and No Specs: %d@\n" !num_nospec_error_proc ; + F.fprintf fmt "+ - No Errors and Some Specs: %d@\n" !num_spec_noerror_proc ; + F.fprintf fmt "+ - Some Errors and Some Specs: %d@\n" !num_spec_error_proc ; + F.fprintf fmt "+ errors: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kerror) err_table ; + F.fprintf fmt "+ warnings: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kwarning) err_table ; + F.fprintf fmt "+ infos: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kinfo) err_table ; + F.fprintf fmt "+ specs: %d@\n" !tot_specs ; + F.fprintf fmt "++++++++++++++++++++++++++++++++++++++++++++++++++@\n" ; + Errlog.print_err_table_details fmt err_table + in let save_file_stats () = let source_dir = DB.source_dir_from_source_file source in let stats_file = DB.source_dir_get_internal_file source_dir ".stats" in try let outc = Out_channel.create (DB.filename_to_string stats_file) in let fmt = F.formatter_of_out_channel outc in - print_file_stats fmt (); - Out_channel.close outc - with Sys_error _ -> () in - List.iter ~f:compute_stats_proc (Cfg.get_defined_procs cfg); - L.(debug Analysis Medium) "%a" print_file_stats (); + print_file_stats fmt () ; Out_channel.close outc + with Sys_error _ -> () + in + List.iter ~f:compute_stats_proc (Cfg.get_defined_procs cfg) ; + L.(debug Analysis Medium) "%a" print_file_stats () ; save_file_stats () (** Print the stats for all the files in the cluster *) @@ -1592,9 +1606,10 @@ let print_stats cluster = let exe_env = Exe_env.from_cluster cluster in Exe_env.iter_files (fun source cfg -> - let proc_shadowed proc_desc = - (* return true if a proc with the same name in another module was analyzed instead *) - let proc_name = Procdesc.get_proc_name proc_desc in - Exe_env.get_source exe_env proc_name <> Some source in - print_stats_cfg proc_shadowed source cfg) + let proc_shadowed proc_desc = + (* return true if a proc with the same name in another module was analyzed instead *) + let proc_name = Procdesc.get_proc_name proc_desc in + Exe_env.get_source exe_env proc_name <> Some source + in + print_stats_cfg proc_shadowed source cfg) exe_env diff --git a/infer/src/backend/interproc.mli b/infer/src/backend/interproc.mli index ce1c3b83b..43b9fd804 100644 --- a/infer/src/backend/interproc.mli +++ b/infer/src/backend/interproc.mli @@ -12,11 +12,11 @@ open! IStd (** Interprocedural Analysis *) -(** Run the biabduction analysis on the given procedure *) val analyze_procedure : Callbacks.proc_callback_t +(** Run the biabduction analysis on the given procedure *) -(** Create closures to perform the analysis of an exe_env *) val do_analysis_closures : Exe_env.t -> Tasks.closure list +(** Create closures to perform the analysis of an exe_env *) -(** Print the stats for all the files in the cluster *) val print_stats : Cluster.t -> unit +(** Print the stats for all the files in the cluster *) diff --git a/infer/src/backend/joinState.ml b/infer/src/backend/joinState.ml index 0101d871a..7b7033f49 100644 --- a/infer/src/backend/joinState.ml +++ b/infer/src/backend/joinState.ml @@ -11,10 +11,7 @@ open! IStd (** Object representing the status of the join operation *) -type mode = - | Pre - | Post -[@@deriving compare] +type mode = Pre | Post [@@deriving compare] let equal_mode = [%compare.equal : mode] diff --git a/infer/src/backend/joinState.mli b/infer/src/backend/joinState.mli index 2c2b48a6a..f6d1ad2d8 100644 --- a/infer/src/backend/joinState.mli +++ b/infer/src/backend/joinState.mli @@ -11,12 +11,10 @@ open! IStd (** Object representing the status of the join operation *) -type mode = - | Pre - | Post -[@@deriving compare] +type mode = Pre | Post [@@deriving compare] val equal_mode : mode -> mode -> bool val get_footprint : unit -> bool + val set_footprint : bool -> unit diff --git a/infer/src/backend/match.ml b/infer/src/backend/match.ml index 305e3cb00..31d8bcdc2 100644 --- a/infer/src/backend/match.ml +++ b/infer/src/backend/match.ml @@ -15,78 +15,91 @@ open! IStd module L = Logging module F = Format -let mem_idlist i l = - List.exists ~f:(Ident.equal i) l +let mem_idlist i l = List.exists ~f:(Ident.equal i) l (** Type for a hpred pattern. flag=false means that the implication between hpreds is not considered, and flag = true means that it is considered during pattern matching *) -type hpred_pat = { hpred : Sil.hpred; flag : bool } +type hpred_pat = {hpred: Sil.hpred; flag: bool} -let pp_hpat pe f hpat = - F.fprintf f "%a" (Sil.pp_hpred pe) hpat.hpred +let pp_hpat pe f hpat = F.fprintf f "%a" (Sil.pp_hpred pe) hpat.hpred let rec pp_hpat_list pe f = function - | [] -> () - | [hpat] -> - F.fprintf f "%a" (pp_hpat pe) hpat - | hpat:: hpats -> - F.fprintf f "%a * %a" (pp_hpat pe) hpat (pp_hpat_list pe) hpats + | [] + -> () + | [hpat] + -> F.fprintf f "%a" (pp_hpat pe) hpat + | hpat :: hpats + -> F.fprintf f "%a * %a" (pp_hpat pe) hpat (pp_hpat_list pe) hpats (** Checks e1 = e2[sub ++ sub'] for some sub' with dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). *) let rec exp_match e1 sub vars e2 : (Sil.exp_subst * Ident.t list) option = let check_equal sub vars e1 e2 = - let e2_inst = Sil.exp_sub (`Exp sub) e2 - in if (Exp.equal e1 e2_inst) then Some(sub, vars) else None in - match e1, e2 with - | _, Exp.Var id2 when (Ident.is_primed id2 && mem_idlist id2 vars) -> - let vars_new = List.filter ~f:(fun id -> not (Ident.equal id id2)) vars in - let sub_new = match (Sil.extend_sub sub id2 e1) with - | None -> assert false (* happens when vars contains the same variable twice. *) - | Some sub_new -> sub_new - in Some (sub_new, vars_new) - | _, Exp.Var _ -> - check_equal sub vars e1 e2 - | Exp.Var _, _ -> - None - | Exp.Const _, _ | _, Exp.Const _ -> - check_equal sub vars e1 e2 - | Exp.Sizeof _, _ | _, Exp.Sizeof _ -> - check_equal sub vars e1 e2 - | Exp.Cast (_, e1'), Exp.Cast (_, e2') -> (* we are currently ignoring cast *) - exp_match e1' sub vars e2' - | Exp.Cast _, _ | _, Exp.Cast _ -> - None - | Exp.UnOp(o1, e1', _), Exp.UnOp(o2, e2', _) when Unop.equal o1 o2 -> - exp_match e1' sub vars e2' - | Exp.UnOp _, _ | _, Exp.UnOp _ -> - None (* Naive *) - | Exp.BinOp(b1, e1', e1''), Exp.BinOp(b2, e2', e2'') when Binop.equal b1 b2 -> - (match exp_match e1' sub vars e2' with - | None -> None - | Some (sub', vars') -> exp_match e1'' sub' vars' e2'') - | Exp.BinOp _, _ | _, Exp.BinOp _ -> - None (* Naive *) - | Exp.Exn _, _ | _, Exp.Exn _ -> - check_equal sub vars e1 e2 - | Exp.Closure _, _ | _, Exp.Closure _ -> - check_equal sub vars e1 e2 - | Exp.Lvar _, _ | _, Exp.Lvar _ -> - check_equal sub vars e1 e2 - | Exp.Lfield(e1', fld1, _), Exp.Lfield(e2', fld2, _) when (Typ.Fieldname.equal fld1 fld2) -> + let e2_inst = Sil.exp_sub (`Exp sub) e2 in + if Exp.equal e1 e2_inst then Some (sub, vars) else None + in + match (e1, e2) with + | _, Exp.Var id2 when Ident.is_primed id2 && mem_idlist id2 vars + -> let vars_new = List.filter ~f:(fun id -> not (Ident.equal id id2)) vars in + let sub_new = + match Sil.extend_sub sub id2 e1 with + | None + -> assert false (* happens when vars contains the same variable twice. *) + | Some sub_new + -> sub_new + in + Some (sub_new, vars_new) + | _, Exp.Var _ + -> check_equal sub vars e1 e2 + | Exp.Var _, _ + -> None + | Exp.Const _, _ | _, Exp.Const _ + -> check_equal sub vars e1 e2 + | Exp.Sizeof _, _ | _, Exp.Sizeof _ + -> check_equal sub vars e1 e2 + | Exp.Cast (_, e1'), Exp.Cast (_, e2') + -> (* we are currently ignoring cast *) exp_match e1' sub vars e2' - | Exp.Lfield _, _ | _, Exp.Lfield _ -> - None - | Exp.Lindex(base1, idx1), Exp.Lindex(base2, idx2) -> - (match exp_match base1 sub vars base2 with - | None -> None - | Some (sub', vars') -> exp_match idx1 sub' vars' idx2) + | Exp.Cast _, _ | _, Exp.Cast _ + -> None + | Exp.UnOp (o1, e1', _), Exp.UnOp (o2, e2', _) when Unop.equal o1 o2 + -> exp_match e1' sub vars e2' + | Exp.UnOp _, _ | _, Exp.UnOp _ + -> None (* Naive *) + | Exp.BinOp (b1, e1', e1''), Exp.BinOp (b2, e2', e2'') when Binop.equal b1 b2 -> ( + match exp_match e1' sub vars e2' with + | None + -> None + | Some (sub', vars') + -> exp_match e1'' sub' vars' e2'' ) + | Exp.BinOp _, _ | _, Exp.BinOp _ + -> None (* Naive *) + | Exp.Exn _, _ | _, Exp.Exn _ + -> check_equal sub vars e1 e2 + | Exp.Closure _, _ | _, Exp.Closure _ + -> check_equal sub vars e1 e2 + | Exp.Lvar _, _ | _, Exp.Lvar _ + -> check_equal sub vars e1 e2 + | Exp.Lfield (e1', fld1, _), Exp.Lfield (e2', fld2, _) when Typ.Fieldname.equal fld1 fld2 + -> exp_match e1' sub vars e2' + | Exp.Lfield _, _ | _, Exp.Lfield _ + -> None + | Exp.Lindex (base1, idx1), Exp.Lindex (base2, idx2) -> + match exp_match base1 sub vars base2 with + | None + -> None + | Some (sub', vars') + -> exp_match idx1 sub' vars' idx2 let exp_list_match es1 sub vars es2 = - let f res_acc (e1, e2) = match res_acc with - | None -> None - | Some (sub_acc, vars_leftover) -> exp_match e1 sub_acc vars_leftover e2 in + let f res_acc (e1, e2) = + match res_acc with + | None + -> None + | Some (sub_acc, vars_leftover) + -> exp_match e1 sub_acc vars_leftover e2 + in Option.find_map ~f:(fun es_combined -> List.fold ~f ~init:(Some (sub, vars)) es_combined) (List.zip es1 es2) @@ -96,68 +109,72 @@ let exp_list_match es1 sub vars es2 = WARNING: This function does not consider the fact that the analyzer sometimes forgets fields of hpred. It can possibly cause a problem. *) let rec strexp_match sexp1 sub vars sexp2 : (Sil.exp_subst * Ident.t list) option = - match sexp1, sexp2 with - | Sil.Eexp (exp1, _), Sil.Eexp (exp2, _) -> - exp_match exp1 sub vars exp2 - | Sil.Eexp _, _ | _, Sil.Eexp _ -> - None - | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) -> - fsel_match fsel1 sub vars fsel2 - | Sil.Estruct _, _ | _, Sil.Estruct _ -> - None + match (sexp1, sexp2) with + | Sil.Eexp (exp1, _), Sil.Eexp (exp2, _) + -> exp_match exp1 sub vars exp2 + | Sil.Eexp _, _ | _, Sil.Eexp _ + -> None + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) + -> fsel_match fsel1 sub vars fsel2 + | Sil.Estruct _, _ | _, Sil.Estruct _ + -> None | Sil.Earray (len1, isel1, _), Sil.Earray (len2, isel2, _) -> - (match exp_match len1 sub vars len2 with - | Some (sub', vars') -> isel_match isel1 sub' vars' isel2 - | None -> None) - + match exp_match len1 sub vars len2 with + | Some (sub', vars') + -> isel_match isel1 sub' vars' isel2 + | None + -> None (** Checks fsel1 = fsel2[sub ++ sub'] for some sub' with dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). *) and fsel_match fsel1 sub vars fsel2 = - match fsel1, fsel2 with - | [], [] -> Some (sub, vars) - | [], _ -> None - | _, [] -> - if (Config.abs_struct <= 0) then None - else Some (sub, vars) (* This can lead to great information loss *) - | (fld1, se1') :: fsel1', (fld2, se2') :: fsel2' -> - let n = Typ.Fieldname.compare fld1 fld2 in - if Int.equal n 0 then begin + match (fsel1, fsel2) with + | [], [] + -> Some (sub, vars) + | [], _ + -> None + | _, [] + -> if Config.abs_struct <= 0 then None else Some (sub, vars) + (* This can lead to great information loss *) + | (fld1, se1') :: fsel1', (fld2, se2') :: fsel2' + -> let n = Typ.Fieldname.compare fld1 fld2 in + if Int.equal n 0 then match strexp_match se1' sub vars se2' with - | None -> None - | Some (sub', vars') -> fsel_match fsel1' sub' vars' fsel2' - end - else if (n < 0 && Config.abs_struct > 0) then - fsel_match fsel1' sub vars fsel2 + | None + -> None + | Some (sub', vars') + -> fsel_match fsel1' sub' vars' fsel2' + else if n < 0 && Config.abs_struct > 0 then fsel_match fsel1' sub vars fsel2 (* This can lead to great information loss *) else None (** Checks isel1 = isel2[sub ++ sub'] for some sub' with dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). *) and isel_match isel1 sub vars isel2 = - match isel1, isel2 with - | [], [] -> Some (sub, vars) - | [], _ | _, [] -> None - | (idx1, se1') :: isel1', (idx2, se2') :: isel2' -> - let idx2 = Sil.exp_sub (`Exp sub) idx2 in + match (isel1, isel2) with + | [], [] + -> Some (sub, vars) + | [], _ | _, [] + -> None + | (idx1, se1') :: isel1', (idx2, se2') :: isel2' + -> let idx2 = Sil.exp_sub (`Exp sub) idx2 in let sanity_check = not (List.exists ~f:(fun id -> Sil.ident_in_exp id idx2) vars) in - if (not sanity_check) then begin + if not sanity_check then let pe = Pp.text in - L.internal_error "@[.... Sanity Check Failure while Matching Index-Strexps ....@\n"; - L.internal_error "@[<4> IDX1: %a, STREXP1: %a@\n" - (Sil.pp_exp_printenv pe) idx1 (Sil.pp_sexp pe) se1'; - L.internal_error "@[<4> IDX2: %a, STREXP2: %a@\n@." - (Sil.pp_exp_printenv pe) idx2 (Sil.pp_sexp pe) se2'; + L.internal_error "@[.... Sanity Check Failure while Matching Index-Strexps ....@\n" ; + L.internal_error "@[<4> IDX1: %a, STREXP1: %a@\n" (Sil.pp_exp_printenv pe) idx1 + (Sil.pp_sexp pe) se1' ; + L.internal_error "@[<4> IDX2: %a, STREXP2: %a@\n@." (Sil.pp_exp_printenv pe) idx2 + (Sil.pp_sexp pe) se2' ; assert false - end - else if Exp.equal idx1 idx2 then begin + else if Exp.equal idx1 idx2 then match strexp_match se1' sub vars se2' with - | None -> None - | Some (sub', vars') -> isel_match isel1' sub' vars' isel2' - end + | None + -> None + | Some (sub', vars') + -> isel_match isel1' sub' vars' isel2' else None - (* extends substitution sub by creating a new substitution for vars *) let sub_extend_with_ren (sub: Sil.exp_subst) vars = let f id = (id, Exp.Var (Ident.create_fresh Ident.kprimed)) in @@ -167,46 +184,57 @@ let sub_extend_with_ren (sub: Sil.exp_subst) vars = type sidecondition = Prop.normal Prop.t -> Sil.exp_subst -> bool let rec execute_with_backtracking = function - | [] -> None - | [f] -> f () - | f:: fs -> - let res_f = f () - in match res_f with - | None -> execute_with_backtracking fs - | Some _ -> res_f - -let rec instantiate_to_emp p condition (sub : Sil.exp_subst) vars = function - | [] -> if condition p sub then Some(sub, p) else None - | hpat:: hpats -> - if not hpat.flag then None - else match hpat.hpred with - | Sil.Hpointsto _ | Sil.Hlseg (Sil.Lseg_NE, _, _, _, _) | Sil.Hdllseg (Sil.Lseg_NE, _, _, _, _, _, _) -> None - | Sil.Hlseg (_, _, e1, e2, _) -> - let fully_instantiated = not (List.exists ~f:(fun id -> Sil.ident_in_exp id e1) vars) - in if (not fully_instantiated) then None else - let e1' = Sil.exp_sub (`Exp sub) e1 - in begin - match exp_match e1' sub vars e2 with - | None -> None - | Some (sub_new, vars_leftover) -> - instantiate_to_emp p condition sub_new vars_leftover hpats - end - | Sil.Hdllseg (_, _, iF, oB, oF, iB, _) -> + | [] + -> None + | [f] + -> f () + | f :: fs + -> let res_f = f () in + match res_f with None -> execute_with_backtracking fs | Some _ -> res_f + +let rec instantiate_to_emp p condition (sub: Sil.exp_subst) vars = function + | [] + -> if condition p sub then Some (sub, p) else None + | hpat :: hpats + -> if not hpat.flag then None + else + match hpat.hpred with + | Sil.Hpointsto _ + | Sil.Hlseg (Sil.Lseg_NE, _, _, _, _) + | Sil.Hdllseg (Sil.Lseg_NE, _, _, _, _, _, _) + -> None + | Sil.Hlseg (_, _, e1, e2, _) + -> ( let fully_instantiated = - not (List.exists ~f:(fun id -> Sil.ident_in_exp id iF || Sil.ident_in_exp id oB) vars) - in if (not fully_instantiated) then None else + not (List.exists ~f:(fun id -> Sil.ident_in_exp id e1) vars) + in + if not fully_instantiated then None + else + let e1' = Sil.exp_sub (`Exp sub) e1 in + match exp_match e1' sub vars e2 with + | None + -> None + | Some (sub_new, vars_leftover) + -> instantiate_to_emp p condition sub_new vars_leftover hpats ) + | Sil.Hdllseg (_, _, iF, oB, oF, iB, _) + -> let fully_instantiated = + not + (List.exists ~f:(fun id -> Sil.ident_in_exp id iF || Sil.ident_in_exp id oB) vars) + in + if not fully_instantiated then None + else let iF' = Sil.exp_sub (`Exp sub) iF in - let oB' = Sil.exp_sub (`Exp sub) oB - in match exp_list_match [iF'; oB'] sub vars [oF; iB] with - | None -> None - | Some (sub_new, vars_leftover) -> - instantiate_to_emp p condition sub_new vars_leftover hpats + let oB' = Sil.exp_sub (`Exp sub) oB in + match exp_list_match [iF'; oB'] sub vars [oF; iB] with + | None + -> None + | Some (sub_new, vars_leftover) + -> instantiate_to_emp p condition sub_new vars_leftover hpats (* This function has to be changed in order to * implement the idea "All lsegs outside are NE, and all lsegs inside * are PE" *) let rec iter_match_with_impl tenv iter condition sub vars hpat hpats = - (* L.out "@[.... iter_match_with_impl ....@."; L.out "@[<4> sub: %a@\n@." pp_sub sub; @@ -214,14 +242,18 @@ let rec iter_match_with_impl tenv iter condition sub vars hpat hpats = L.out "@[<4> hpred: %a@\n@." pp_hpat hpat; L.out "@[<4> hpred_rest: %a@\n@." pp_hpat_list hpats; *) - - let do_next iter_cur _ = match Prop.prop_iter_next iter_cur with - | None -> None - | Some iter_next -> iter_match_with_impl tenv iter_next condition sub vars hpat hpats + let do_next iter_cur _ = + match Prop.prop_iter_next iter_cur with + | None + -> None + | Some iter_next + -> iter_match_with_impl tenv iter_next condition sub vars hpat hpats in let do_empty_hpats iter_cur _ = - let (sub_new, vars_leftover) = match Prop.prop_iter_current tenv iter_cur with - | _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) in + let sub_new, vars_leftover = + match Prop.prop_iter_current tenv iter_cur + with _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) + in let sub_res = sub_extend_with_ren sub_new vars_leftover in let p_leftover = Prop.prop_iter_remove_curr_then_to_prop tenv iter_cur in (* @@ -232,165 +264,219 @@ let rec iter_match_with_impl tenv iter condition sub vars hpat hpats = if condition p_leftover sub_res then Some (sub_res, p_leftover) else None in let do_nonempty_hpats iter_cur _ = - let (sub_new, vars_leftover) = match Prop.prop_iter_current tenv iter_cur with - | _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) in - let (hpat_next, hpats_rest) = match hpats with - | [] -> assert false - | hpat_next :: hpats_rest -> (hpat_next, hpats_rest) in - let p_rest = Prop.prop_iter_remove_curr_then_to_prop tenv iter_cur - in prop_match_with_impl_sub tenv p_rest condition sub_new vars_leftover hpat_next hpats_rest + let sub_new, vars_leftover = + match Prop.prop_iter_current tenv iter_cur + with _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) + in + let hpat_next, hpats_rest = + match hpats with [] -> assert false | hpat_next :: hpats_rest -> (hpat_next, hpats_rest) + in + let p_rest = Prop.prop_iter_remove_curr_then_to_prop tenv iter_cur in + prop_match_with_impl_sub tenv p_rest condition sub_new vars_leftover hpat_next hpats_rest in let gen_filter_pointsto lexp2 strexp2 te2 = function - | Sil.Hpointsto (lexp1, strexp1, te1) when Exp.equal te1 te2 -> - (match (exp_match lexp1 sub vars lexp2) with - | None -> None - | Some (sub', vars_leftover) -> strexp_match strexp1 sub' vars_leftover strexp2) - | _ -> None + | Sil.Hpointsto (lexp1, strexp1, te1) when Exp.equal te1 te2 -> ( + match exp_match lexp1 sub vars lexp2 with + | None + -> None + | Some (sub', vars_leftover) + -> strexp_match strexp1 sub' vars_leftover strexp2 ) + | _ + -> None in let gen_filter_lseg k2 para2 e_start2 e_end2 es_shared2 = function - | Sil.Hpointsto _ -> None - | Sil.Hlseg (k1, para1, e_start1, e_end1, es_shared1) -> - let do_kinds_match = match k1, k2 with - | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE -> true - | Sil.Lseg_PE, Sil.Lseg_NE -> false in + | Sil.Hpointsto _ + -> None + | Sil.Hlseg (k1, para1, e_start1, e_end1, es_shared1) + -> let do_kinds_match = + match (k1, k2) with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE + -> true + | Sil.Lseg_PE, Sil.Lseg_NE + -> false + in (* let do_paras_match = hpara_match_with_impl tenv hpat.flag para1 para2 *) - let do_paras_match = hpara_match_with_impl tenv true para1 para2 - in if not (do_kinds_match && do_paras_match) then None + let do_paras_match = hpara_match_with_impl tenv true para1 para2 in + if not (do_kinds_match && do_paras_match) then None else - let es1 = [e_start1; e_end1]@es_shared1 in - let es2 = [e_start2; e_end2]@es_shared2 - in exp_list_match es1 sub vars es2 - | Sil.Hdllseg _ -> None + let es1 = [e_start1; e_end1] @ es_shared1 in + let es2 = [e_start2; e_end2] @ es_shared2 in + exp_list_match es1 sub vars es2 + | Sil.Hdllseg _ + -> None in let gen_filter_dllseg k2 para2 iF2 oB2 oF2 iB2 es_shared2 = function - | Sil.Hpointsto _ | Sil.Hlseg _ -> None - | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, es_shared1) -> - let do_kinds_match = match k1, k2 with - | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE -> true - | Sil.Lseg_PE, Sil.Lseg_NE -> false in + | Sil.Hpointsto _ | Sil.Hlseg _ + -> None + | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, es_shared1) + -> let do_kinds_match = + match (k1, k2) with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE + -> true + | Sil.Lseg_PE, Sil.Lseg_NE + -> false + in (* let do_paras_match = hpara_dll_match_with_impl tenv hpat.flag para1 para2 *) - let do_paras_match = hpara_dll_match_with_impl tenv true para1 para2 - in if not (do_kinds_match && do_paras_match) then None + let do_paras_match = hpara_dll_match_with_impl tenv true para1 para2 in + if not (do_kinds_match && do_paras_match) then None else - let es1 = [iF1; oB1; oF1; iB1]@es_shared1 in - let es2 = [iF2; oB2; oF2; iB2]@es_shared2 - in exp_list_match es1 sub vars es2 - - in match hpat.hpred with - | Sil.Hpointsto (lexp2, strexp2, te2) -> - let filter = gen_filter_pointsto lexp2 strexp2 te2 - in begin match (Prop.prop_iter_find iter filter), hpats with - | (None, _) -> None - | (Some iter_cur, []) -> - do_empty_hpats iter_cur () - | (Some iter_cur, _) -> - execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] - end - | Sil.Hlseg (k2, para2, e_start2, e_end2, es_shared2) -> + let es1 = [iF1; oB1; oF1; iB1] @ es_shared1 in + let es2 = [iF2; oB2; oF2; iB2] @ es_shared2 in + exp_list_match es1 sub vars es2 + in + match hpat.hpred with + | Sil.Hpointsto (lexp2, strexp2, te2) + -> ( + let filter = gen_filter_pointsto lexp2 strexp2 te2 in + match (Prop.prop_iter_find iter filter, hpats) with + | None, _ + -> None + | Some iter_cur, [] + -> do_empty_hpats iter_cur () + | Some iter_cur, _ + -> execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] ) + | Sil.Hlseg (k2, para2, e_start2, e_end2, es_shared2) + -> ( let filter = gen_filter_lseg k2 para2 e_start2 e_end2 es_shared2 in let do_emp_lseg _ = let fully_instantiated_start2 = - not (List.exists ~f:(fun id -> Sil.ident_in_exp id e_start2) vars) in - if (not fully_instantiated_start2) then None + not (List.exists ~f:(fun id -> Sil.ident_in_exp id e_start2) vars) + in + if not fully_instantiated_start2 then None else let e_start2' = Sil.exp_sub (`Exp sub) e_start2 in match (exp_match e_start2' sub vars e_end2, hpats) with - | None, _ -> - (* + | None, _ + -> (* L.out "@.... iter_match_with_impl (empty_case, fail) ....@\n@."; L.out "@[<4> sub: %a@\n@." pp_sub sub; L.out "@[<4> e_start2': %a@\n@." pp_exp e_start2'; L.out "@[<4> e_end2: %a@\n@." pp_exp e_end2; *) None - | Some (sub_new, vars_leftover), [] -> - let sub_res = sub_extend_with_ren sub_new vars_leftover in + | Some (sub_new, vars_leftover), [] + -> let sub_res = sub_extend_with_ren sub_new vars_leftover in let p_leftover = Prop.prop_iter_to_prop tenv iter in - if condition p_leftover sub_res then Some(sub_res, p_leftover) else None - | Some (sub_new, vars_leftover), hpat_next:: hpats_rest -> - let p = Prop.prop_iter_to_prop tenv iter in - prop_match_with_impl_sub tenv p condition sub_new vars_leftover hpat_next hpats_rest in + if condition p_leftover sub_res then Some (sub_res, p_leftover) else None + | Some (sub_new, vars_leftover), hpat_next :: hpats_rest + -> let p = Prop.prop_iter_to_prop tenv iter in + prop_match_with_impl_sub tenv p condition sub_new vars_leftover hpat_next hpats_rest + in let do_para_lseg _ = - let (para2_exist_vars, para2_inst) = Sil.hpara_instantiate para2 e_start2 e_end2 es_shared2 in + let para2_exist_vars, para2_inst = + Sil.hpara_instantiate para2 e_start2 e_end2 es_shared2 + in (* let allow_impl hpred = {hpred=hpred; flag=hpat.flag} in *) - let allow_impl hpred = { hpred = hpred; flag = true } in - let (para2_hpat, para2_hpats) = match List.map ~f:allow_impl para2_inst with - | [] -> assert false (* the body of a parameter should contain at least one * conjunct *) - | para2_pat :: para2_pats -> (para2_pat, para2_pats) in + let allow_impl hpred = {hpred; flag= true} in + let para2_hpat, para2_hpats = + match List.map ~f:allow_impl para2_inst with + | [] + -> assert false (* the body of a parameter should contain at least one * conjunct *) + | para2_pat :: para2_pats + -> (para2_pat, para2_pats) + in let new_vars = para2_exist_vars @ vars in - let new_hpats = para2_hpats @ hpats - in match (iter_match_with_impl tenv iter condition sub new_vars para2_hpat new_hpats) with - | None -> None - | Some (sub_res, p_leftover) when condition p_leftover sub_res -> - let not_in_para2_exist_vars id = - not (List.exists ~f:(fun id' -> Ident.equal id id') para2_exist_vars) in - let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res - in Some (sub_res', p_leftover) - | Some _ -> None - in begin match ((Prop.prop_iter_find iter filter), hpats) with - | (None, _) when not hpat.flag -> - (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) - None - | (None, _) when Sil.equal_lseg_kind k2 Sil.Lseg_NE -> - (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) - do_para_lseg () - | (None, _) -> - (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) - execute_with_backtracking [do_emp_lseg; do_para_lseg] - | (Some iter_cur, []) -> - (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) - do_empty_hpats iter_cur () - | (Some iter_cur, _) -> - (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) - execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] - end - | Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, es_shared2) -> - let filter = gen_filter_dllseg k2 para2 iF2 oB2 oF2 iB2 es_shared2 in + let new_hpats = para2_hpats @ hpats in + match iter_match_with_impl tenv iter condition sub new_vars para2_hpat new_hpats with + | None + -> None + | Some (sub_res, p_leftover) when condition p_leftover sub_res + -> let not_in_para2_exist_vars id = + not (List.exists ~f:(fun id' -> Ident.equal id id') para2_exist_vars) + in + let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res in + Some (sub_res', p_leftover) + | Some _ + -> None + in + match (Prop.prop_iter_find iter filter, hpats) with + | None, _ when not hpat.flag + -> (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + None + | None, _ when Sil.equal_lseg_kind k2 Sil.Lseg_NE + -> (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + do_para_lseg () + | None, _ + -> (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + execute_with_backtracking [do_emp_lseg; do_para_lseg] + | Some iter_cur, [] + -> (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) + do_empty_hpats iter_cur () + | Some iter_cur, _ + -> (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) + execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] ) + | Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, es_shared2) + -> let filter = gen_filter_dllseg k2 para2 iF2 oB2 oF2 iB2 es_shared2 in let do_emp_dllseg _ = let fully_instantiated_iFoB2 = not (List.exists ~f:(fun id -> Sil.ident_in_exp id iF2 || Sil.ident_in_exp id oB2) vars) - in if (not fully_instantiated_iFoB2) then None else + in + if not fully_instantiated_iFoB2 then None + else let iF2' = Sil.exp_sub (`Exp sub) iF2 in - let oB2' = Sil.exp_sub (`Exp sub) oB2 - in match (exp_list_match [iF2'; oB2'] sub vars [oF2; iB2], hpats) with - | None, _ -> None - | Some (sub_new, vars_leftover), [] -> - let sub_res = sub_extend_with_ren sub_new vars_leftover in - let p_leftover = Prop.prop_iter_to_prop tenv iter - in if condition p_leftover sub_res then Some(sub_res, p_leftover) else None - | Some (sub_new, vars_leftover), hpat_next:: hpats_rest -> - let p = Prop.prop_iter_to_prop tenv iter - in prop_match_with_impl_sub tenv p condition sub_new vars_leftover hpat_next hpats_rest in + let oB2' = Sil.exp_sub (`Exp sub) oB2 in + match (exp_list_match [iF2'; oB2'] sub vars [oF2; iB2], hpats) with + | None, _ + -> None + | Some (sub_new, vars_leftover), [] + -> let sub_res = sub_extend_with_ren sub_new vars_leftover in + let p_leftover = Prop.prop_iter_to_prop tenv iter in + if condition p_leftover sub_res then Some (sub_res, p_leftover) else None + | Some (sub_new, vars_leftover), hpat_next :: hpats_rest + -> let p = Prop.prop_iter_to_prop tenv iter in + prop_match_with_impl_sub tenv p condition sub_new vars_leftover hpat_next hpats_rest + in let do_para_dllseg _ = - let fully_instantiated_iF2 = not (List.exists ~f:(fun id -> Sil.ident_in_exp id iF2) vars) - in if (not fully_instantiated_iF2) then None else - let iF2' = Sil.exp_sub (`Exp sub) iF2 - in match exp_match iF2' sub vars iB2 with - | None -> None - | Some (sub_new, vars_leftover) -> - let (para2_exist_vars, para2_inst) = Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 es_shared2 in + let fully_instantiated_iF2 = + not (List.exists ~f:(fun id -> Sil.ident_in_exp id iF2) vars) + in + if not fully_instantiated_iF2 then None + else + let iF2' = Sil.exp_sub (`Exp sub) iF2 in + match exp_match iF2' sub vars iB2 with + | None + -> None + | Some (sub_new, vars_leftover) + -> let para2_exist_vars, para2_inst = + Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 es_shared2 + in (* let allow_impl hpred = {hpred=hpred; flag=hpat.flag} in *) - let allow_impl hpred = { hpred = hpred; flag = true } in - let (para2_hpat, para2_hpats) = match List.map ~f:allow_impl para2_inst with - | [] -> assert false (* the body of a parameter should contain at least one * conjunct *) - | para2_pat :: para2_pats -> (para2_pat, para2_pats) in + let allow_impl hpred = {hpred; flag= true} in + let para2_hpat, para2_hpats = + match List.map ~f:allow_impl para2_inst with + | [] + -> assert false + (* the body of a parameter should contain at least one * conjunct *) + | para2_pat :: para2_pats + -> (para2_pat, para2_pats) + in let new_vars = para2_exist_vars @ vars_leftover in - let new_hpats = para2_hpats @ hpats - in match (iter_match_with_impl tenv iter condition sub_new new_vars para2_hpat new_hpats) with - | None -> None - | Some (sub_res, p_leftover) when condition p_leftover sub_res -> - let not_in_para2_exist_vars id = - not (List.exists ~f:(fun id' -> Ident.equal id id') para2_exist_vars) in - let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res - in Some (sub_res', p_leftover) - | Some _ -> None - in begin match ((Prop.prop_iter_find iter filter), hpats) with - | (None, _) when not hpat.flag -> None - | (None, _) when Sil.equal_lseg_kind k2 Sil.Lseg_NE -> do_para_dllseg () - | (None, _) -> execute_with_backtracking [do_emp_dllseg; do_para_dllseg] - | (Some iter_cur, []) -> do_empty_hpats iter_cur () - | (Some iter_cur, _) -> execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] - end + let new_hpats = para2_hpats @ hpats in + match + iter_match_with_impl tenv iter condition sub_new new_vars para2_hpat new_hpats + with + | None + -> None + | Some (sub_res, p_leftover) when condition p_leftover sub_res + -> let not_in_para2_exist_vars id = + not (List.exists ~f:(fun id' -> Ident.equal id id') para2_exist_vars) + in + let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res in + Some (sub_res', p_leftover) + | Some _ + -> None + in + match (Prop.prop_iter_find iter filter, hpats) with + | None, _ when not hpat.flag + -> None + | None, _ when Sil.equal_lseg_kind k2 Sil.Lseg_NE + -> do_para_dllseg () + | None, _ + -> execute_with_backtracking [do_emp_dllseg; do_para_dllseg] + | Some iter_cur, [] + -> do_empty_hpats iter_cur () + | Some iter_cur, _ + -> execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] and prop_match_with_impl_sub tenv p condition sub vars hpat hpats = (* @@ -401,41 +487,49 @@ and prop_match_with_impl_sub tenv p condition sub vars hpat hpats = L.out "@[<4> hpred_rest: %a@\n@." pp_hpat_list hpats; *) match Prop.prop_iter_create p with - | None -> - instantiate_to_emp p condition sub vars (hpat:: hpats) - | Some iter -> - iter_match_with_impl tenv iter condition sub vars hpat hpats + | None + -> instantiate_to_emp p condition sub vars (hpat :: hpats) + | Some iter + -> iter_match_with_impl tenv iter condition sub vars hpat hpats and hpara_common_match_with_impl tenv impl_ok ids1 sigma1 eids2 ids2 sigma2 = try let sub_ids = let ren_ids = List.zip_exn ids2 ids1 in let f (id2, id1) = (id2, Exp.Var id1) in - List.map ~f ren_ids in - let (sub_eids, eids_fresh) = + List.map ~f ren_ids + in + let sub_eids, eids_fresh = let f id = (id, Ident.create_fresh Ident.kprimed) in let ren_eids = List.map ~f eids2 in let eids_fresh = List.map ~f:snd ren_eids in let sub_eids = List.map ~f:(fun (id2, id1) -> (id2, Exp.Var id1)) ren_eids in - (sub_eids, eids_fresh) in + (sub_eids, eids_fresh) + in let sub = Sil.exp_subst_of_list (sub_ids @ sub_eids) in match sigma2 with - | [] -> if List.is_empty sigma1 then true else false - | hpred2 :: sigma2 -> - let (hpat2, hpats2) = - let (hpred2_ren, sigma2_ren) = (Sil.hpred_sub (`Exp sub) hpred2, Prop.sigma_sub (`Exp sub) sigma2) in - let allow_impl hpred = { hpred = hpred; flag = impl_ok } in - (allow_impl hpred2_ren, List.map ~f:allow_impl sigma2_ren) in + | [] + -> if List.is_empty sigma1 then true else false + | hpred2 :: sigma2 + -> let hpat2, hpats2 = + let hpred2_ren, sigma2_ren = + (Sil.hpred_sub (`Exp sub) hpred2, Prop.sigma_sub (`Exp sub) sigma2) + in + let allow_impl hpred = {hpred; flag= impl_ok} in + (allow_impl hpred2_ren, List.map ~f:allow_impl sigma2_ren) + in let condition _ _ = true in let p1 = Prop.normalize tenv (Prop.from_sigma sigma1) in - begin - match (prop_match_with_impl_sub tenv p1 condition Sil.exp_sub_empty eids_fresh hpat2 hpats2) with - | None -> false - | Some (_, p1') when Prop.prop_is_emp p1' -> true - | _ -> false - end - with - | Invalid_argument _ -> false + match + prop_match_with_impl_sub tenv p1 condition Sil.exp_sub_empty eids_fresh hpat2 hpats2 + with + | None + -> false + | Some (_, p1') when Prop.prop_is_emp p1' + -> true + | _ + -> false + with Invalid_argument _ -> false and hpara_match_with_impl tenv impl_ok para1 para2 : bool = (* @@ -445,8 +539,8 @@ and hpara_match_with_impl tenv impl_ok para1 para2 : bool = *) let ids1 = para1.Sil.root :: para1.Sil.next :: para1.Sil.svars in let ids2 = para2.Sil.root :: para2.Sil.next :: para2.Sil.svars in - let eids2 = para2.Sil.evars - in hpara_common_match_with_impl tenv impl_ok ids1 para1.Sil.body eids2 ids2 para2.Sil.body + let eids2 = para2.Sil.evars in + hpara_common_match_with_impl tenv impl_ok ids1 para1.Sil.body eids2 ids2 para2.Sil.body and hpara_dll_match_with_impl tenv impl_ok para1 para2 : bool = (* @@ -459,7 +553,6 @@ and hpara_dll_match_with_impl tenv impl_ok para1 para2 : bool = let eids2 = para2.Sil.evars_dll in hpara_common_match_with_impl tenv impl_ok ids1 para1.Sil.body_dll eids2 ids2 para2.Sil.body_dll - (** [prop_match_with_impl p condition vars hpat hpats] returns [(subst, p_leftover)] such that 1) [dom(subst) = vars] @@ -472,12 +565,17 @@ let sigma_remove_hpred eq sigma e = let filter = function | Sil.Hpointsto (root, _, _) | Sil.Hlseg (_, _, root, _, _) - | Sil.Hdllseg (_, _, root, _, _, _, _) -> eq root e in + | Sil.Hdllseg (_, _, root, _, _, _, _) + -> eq root e + in let sigma_e, sigma_no_e = List.partition_tf ~f:filter sigma in match sigma_e with - | [] -> (None, sigma) - | [hpred_e] -> (Some hpred_e, sigma_no_e) - | _ -> assert false + | [] + -> (None, sigma) + | [hpred_e] + -> (Some hpred_e, sigma_no_e) + | _ + -> assert false (** {2 Routines used when finding disjoint isomorphic sigmas from a single sigma} *) @@ -486,91 +584,94 @@ type iso_mode = Exact | LFieldForget | RFieldForget [@@deriving compare] let equal_iso_mode = [%compare.equal : iso_mode] let rec generate_todos_from_strexp mode todos sexp1 sexp2 = - match sexp1, sexp2 with - | Sil.Eexp (exp1, _), Sil.Eexp (exp2, _) -> - let new_todos = (exp1, exp2) :: todos in + match (sexp1, sexp2) with + | Sil.Eexp (exp1, _), Sil.Eexp (exp2, _) + -> let new_todos = (exp1, exp2) :: todos in Some new_todos - | Sil.Eexp _, _ -> - None - | Sil.Estruct (fel1, _), Sil.Estruct (fel2, _) -> (* assume sorted w.r.t. fields *) - if (List.length fel1 <> List.length fel2) && equal_iso_mode mode Exact - then None + | Sil.Eexp _, _ + -> None + | Sil.Estruct (fel1, _), Sil.Estruct (fel2, _) + -> (* assume sorted w.r.t. fields *) + if List.length fel1 <> List.length fel2 && equal_iso_mode mode Exact then None else generate_todos_from_fel mode todos fel1 fel2 - | Sil.Estruct _, _ -> - None - | Sil.Earray (len1, iel1, _), Sil.Earray (len2, iel2, _) -> - if (not (Exp.equal len1 len2) || List.length iel1 <> List.length iel2) - then None + | Sil.Estruct _, _ + -> None + | Sil.Earray (len1, iel1, _), Sil.Earray (len2, iel2, _) + -> if not (Exp.equal len1 len2) || List.length iel1 <> List.length iel2 then None else generate_todos_from_iel mode todos iel1 iel2 - | Sil.Earray _, _ -> - None + | Sil.Earray _, _ + -> None and generate_todos_from_fel mode todos fel1 fel2 = - match fel1, fel2 with - | [], [] -> - Some todos - | [], _ -> - if equal_iso_mode mode RFieldForget then Some todos else None - | _, [] -> - if equal_iso_mode mode LFieldForget then Some todos else None - | (fld1, strexp1) :: fel1', (fld2, strexp2) :: fel2' -> - let n = Typ.Fieldname.compare fld1 fld2 in + match (fel1, fel2) with + | [], [] + -> Some todos + | [], _ + -> if equal_iso_mode mode RFieldForget then Some todos else None + | _, [] + -> if equal_iso_mode mode LFieldForget then Some todos else None + | (fld1, strexp1) :: fel1', (fld2, strexp2) :: fel2' + -> let n = Typ.Fieldname.compare fld1 fld2 in if Int.equal n 0 then - begin - match generate_todos_from_strexp mode todos strexp1 strexp2 with - | None -> None - | Some todos' -> generate_todos_from_fel mode todos' fel1' fel2' - end - else if (n < 0 && equal_iso_mode mode LFieldForget) then + match generate_todos_from_strexp mode todos strexp1 strexp2 with + | None + -> None + | Some todos' + -> generate_todos_from_fel mode todos' fel1' fel2' + else if n < 0 && equal_iso_mode mode LFieldForget then generate_todos_from_fel mode todos fel1' fel2 - else if (n > 0 && equal_iso_mode mode RFieldForget) then + else if n > 0 && equal_iso_mode mode RFieldForget then generate_todos_from_fel mode todos fel1 fel2' - else - None + else None and generate_todos_from_iel mode todos iel1 iel2 = - match iel1, iel2 with - | [],[] -> - Some todos - | (idx1, strexp1) :: iel1', (idx2, strexp2) :: iel2' -> - begin - match generate_todos_from_strexp mode todos strexp1 strexp2 with - | None -> None - | Some todos' -> - let new_todos = (idx1, idx2) :: todos' in - generate_todos_from_iel mode new_todos iel1' iel2' - end - | _ -> - None + match (iel1, iel2) with + | [], [] + -> Some todos + | (idx1, strexp1) :: iel1', (idx2, strexp2) :: iel2' -> ( + match generate_todos_from_strexp mode todos strexp1 strexp2 with + | None + -> None + | Some todos' + -> let new_todos = (idx1, idx2) :: todos' in + generate_todos_from_iel mode new_todos iel1' iel2' ) + | _ + -> None (** add (e1,e2) at the front of corres, if necessary. *) let corres_extend_front e1 e2 corres = - let filter (e1', e2') = (Exp.equal e1 e1') || (Exp.equal e2 e2') in - let checker e1' e2' = (Exp.equal e1 e1') && (Exp.equal e2 e2') - in match (List.filter ~f:filter corres) with - | [] -> Some ((e1, e2) :: corres) - | [(e1', e2')] when checker e1' e2' -> Some corres - | _ -> None + let filter (e1', e2') = Exp.equal e1 e1' || Exp.equal e2 e2' in + let checker e1' e2' = Exp.equal e1 e1' && Exp.equal e2 e2' in + match List.filter ~f:filter corres with + | [] + -> Some ((e1, e2) :: corres) + | [(e1', e2')] when checker e1' e2' + -> Some corres + | _ + -> None let corres_extensible corres e1 e2 = - let predicate (e1', e2') = (Exp.equal e1 e1') || (Exp.equal e2 e2') - in not (List.exists ~f:predicate corres) && not (Exp.equal e1 e2) + let predicate (e1', e2') = Exp.equal e1 e1' || Exp.equal e2 e2' in + not (List.exists ~f:predicate corres) && not (Exp.equal e1 e2) let corres_related corres e1 e2 = - let filter (e1', e2') = (Exp.equal e1 e1') || (Exp.equal e2 e2') in - let checker e1' e2' = (Exp.equal e1 e1') && (Exp.equal e2 e2') in - match (List.filter ~f:filter corres) with - | [] -> Exp.equal e1 e2 - | [(e1', e2')] when checker e1' e2' -> true - | _ -> false + let filter (e1', e2') = Exp.equal e1 e1' || Exp.equal e2 e2' in + let checker e1' e2' = Exp.equal e1 e1' && Exp.equal e2 e2' in + match List.filter ~f:filter corres with + | [] + -> Exp.equal e1 e2 + | [(e1', e2')] when checker e1' e2' + -> true + | _ + -> false (* TO DO. Perhaps OK. Need to implemenet a better isomorphism check later.*) let hpara_iso tenv para1 para2 = hpara_match_with_impl tenv false para1 para2 && hpara_match_with_impl tenv false para2 para1 let hpara_dll_iso tenv para1 para2 = - hpara_dll_match_with_impl tenv false para1 para2 && hpara_dll_match_with_impl tenv false para2 para1 - + hpara_dll_match_with_impl tenv false para1 para2 + && hpara_dll_match_with_impl tenv false para2 para1 (** [generic_find_partial_iso] finds isomorphic subsigmas of [sigma_todo]. The function [update] is used to get rid of hpred pairs from [sigma_todo]. @@ -579,87 +680,106 @@ let hpara_dll_iso tenv para1 para2 = isomorphism finding. *) let rec generic_find_partial_iso tenv mode update corres sigma_corres todos sigma_todo = match todos with - | [] -> - let sigma1, sigma2 = sigma_corres in + | [] + -> let sigma1, sigma2 = sigma_corres in Some (List.rev corres, List.rev sigma1, List.rev sigma2, sigma_todo) - | (e1, e2) :: todos' when corres_related corres e1 e2 -> - begin - match corres_extend_front e1 e2 corres with - | None -> assert false - | Some new_corres -> generic_find_partial_iso tenv mode update new_corres sigma_corres todos' sigma_todo - end - | (e1, e2) :: todos' when corres_extensible corres e1 e2 -> + | (e1, e2) :: todos' when corres_related corres e1 e2 -> ( + match corres_extend_front e1 e2 corres with + | None + -> assert false + | Some new_corres + -> generic_find_partial_iso tenv mode update new_corres sigma_corres todos' sigma_todo ) + | (e1, e2) :: todos' when corres_extensible corres e1 e2 + -> ( let hpredo1, hpredo2, new_sigma_todo = update e1 e2 sigma_todo in - begin - match hpredo1, hpredo2 with - | None, None -> - begin + match (hpredo1, hpredo2) with + | None, None -> ( + match corres_extend_front e1 e2 corres with + | None + -> assert false + | Some new_corres + -> generic_find_partial_iso tenv mode update new_corres sigma_corres todos' sigma_todo ) + | None, _ | _, None + -> None + | Some Sil.Hpointsto (_, _, te1), Some Sil.Hpointsto (_, _, te2) when not (Exp.equal te1 te2) + -> None + | Some (Sil.Hpointsto (_, se1, _) as hpred1), Some (Sil.Hpointsto (_, se2, _) as hpred2) -> ( + match generate_todos_from_strexp mode [] se1 se2 with + | None + -> None + | Some todos'' + -> let new_corres = match corres_extend_front e1 e2 corres with - | None -> assert false - | Some new_corres -> generic_find_partial_iso tenv mode update new_corres sigma_corres todos' sigma_todo - end - | None, _ | _, None -> - None - | Some (Sil.Hpointsto (_, _, te1)), Some (Sil.Hpointsto (_, _, te2)) - when not (Exp.equal te1 te2) -> - None - | Some (Sil.Hpointsto (_, se1, _) as hpred1), - Some (Sil.Hpointsto (_, se2, _) as hpred2) -> - begin - match generate_todos_from_strexp mode [] se1 se2 with - | None -> None - | Some todos'' -> - let new_corres = match corres_extend_front e1 e2 corres with - | None -> assert false - | Some new_corres -> new_corres in - let new_sigma_corres = - let sigma1, sigma2 = sigma_corres in - let new_sigma1 = hpred1 :: sigma1 in - let new_sigma2 = hpred2 :: sigma2 in - (new_sigma1, new_sigma2) in - let new_todos = todos'' @ todos' in - generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos new_sigma_todo - - end - | Some (Sil.Hlseg (k1, para1, root1, next1, shared1) as hpred1), - Some (Sil.Hlseg (k2, para2, root2, next2, shared2) as hpred2) -> - if k1 <> k2 || not (hpara_iso tenv para1 para2) then None - else - (try - let new_corres = match corres_extend_front e1 e2 corres with - | None -> assert false - | Some new_corres -> new_corres in - let new_sigma_corres = - let sigma1, sigma2 = sigma_corres in - let new_sigma1 = hpred1 :: sigma1 in - let new_sigma2 = hpred2 :: sigma2 in - (new_sigma1, new_sigma2) in - let new_todos = - let shared12 = List.zip_exn shared1 shared2 in - (root1, root2) :: (next1, next2) :: shared12 @ todos' in - generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos new_sigma_todo - with Invalid_argument _ -> None) - | Some (Sil.Hdllseg(k1, para1, iF1, oB1, oF1, iB1, shared1) as hpred1), - Some (Sil.Hdllseg(k2, para2, iF2, oB2, oF2, iB2, shared2) as hpred2) -> - if k1 <> k2 || not (hpara_dll_iso tenv para1 para2) then None - else - (try - let new_corres = match corres_extend_front e1 e2 corres with - | None -> assert false - | Some new_corres -> new_corres in - let new_sigma_corres = - let sigma1, sigma2 = sigma_corres in - let new_sigma1 = hpred1 :: sigma1 in - let new_sigma2 = hpred2 :: sigma2 in - (new_sigma1, new_sigma2) in - let new_todos = - let shared12 = List.zip_exn shared1 shared2 in - (iF1, iF2):: (oB1, oB2):: (oF1, oF2):: (iB1, iB2):: shared12@todos' in - generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos new_sigma_todo - with Invalid_argument _ -> None) - | _ -> None - end - | _ -> None + | None + -> assert false + | Some new_corres + -> new_corres + in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) + in + let new_todos = todos'' @ todos' in + generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos + new_sigma_todo ) + | ( Some (Sil.Hlseg (k1, para1, root1, next1, shared1) as hpred1) + , Some (Sil.Hlseg (k2, para2, root2, next2, shared2) as hpred2) ) + -> ( + if k1 <> k2 || not (hpara_iso tenv para1 para2) then None + else + try + let new_corres = + match corres_extend_front e1 e2 corres with + | None + -> assert false + | Some new_corres + -> new_corres + in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) + in + let new_todos = + let shared12 = List.zip_exn shared1 shared2 in + (root1, root2) :: (next1, next2) :: shared12 @ todos' + in + generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos + new_sigma_todo + with Invalid_argument _ -> None ) + | ( Some (Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1) as hpred1) + , Some (Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) as hpred2) ) + -> ( + if k1 <> k2 || not (hpara_dll_iso tenv para1 para2) then None + else + try + let new_corres = + match corres_extend_front e1 e2 corres with + | None + -> assert false + | Some new_corres + -> new_corres + in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) + in + let new_todos = + let shared12 = List.zip_exn shared1 shared2 in + (iF1, iF2) :: (oB1, oB2) :: (oF1, oF2) :: (iB1, iB2) :: shared12 @ todos' + in + generic_find_partial_iso tenv mode update new_corres new_sigma_corres new_todos + new_sigma_todo + with Invalid_argument _ -> None ) + | _ + -> None ) + | _ + -> None (** [find_partial_iso] finds disjoint isomorphic sub-sigmas inside a given sigma. The function returns a partial iso and three sigmas. The first sigma is the first @@ -669,9 +789,10 @@ let rec generic_find_partial_iso tenv mode update corres sigma_corres todos sigm part of the input sigma. *) let find_partial_iso tenv eq corres todos sigma = let update e1 e2 sigma0 = - let (hpredo1, sigma0_no_e1) = sigma_remove_hpred eq sigma0 e1 in - let (hpredo2, sigma0_no_e12) = sigma_remove_hpred eq sigma0_no_e1 e2 in - (hpredo1, hpredo2, sigma0_no_e12) in + let hpredo1, sigma0_no_e1 = sigma_remove_hpred eq sigma0 e1 in + let hpredo2, sigma0_no_e12 = sigma_remove_hpred eq sigma0_no_e1 e2 in + (hpredo1, hpredo2, sigma0_no_e12) + in let init_sigma_corres = ([], []) in let init_sigma_todo = sigma in generic_find_partial_iso tenv Exact update corres init_sigma_corres todos init_sigma_todo @@ -687,8 +808,9 @@ let find_partial_iso_from_two_sigmas tenv mode eq corres todos sigma1 sigma2 = let sigma_todo1, sigma_todo2 = sigma_todo in let hpredo1, sigma_todo1_no_e1 = sigma_remove_hpred eq sigma_todo1 e1 in let hpredo2, sigma_todo2_no_e2 = sigma_remove_hpred eq sigma_todo2 e2 in - let new_sigma_todo = sigma_todo1_no_e1, sigma_todo2_no_e2 in - (hpredo1, hpredo2, new_sigma_todo) in + let new_sigma_todo = (sigma_todo1_no_e1, sigma_todo2_no_e2) in + (hpredo1, hpredo2, new_sigma_todo) + in let init_sigma_corres = ([], []) in let init_sigma_todo = (sigma1, sigma2) in generic_find_partial_iso tenv mode update corres init_sigma_corres todos init_sigma_todo @@ -696,13 +818,15 @@ let find_partial_iso_from_two_sigmas tenv mode eq corres todos sigma1 sigma2 = (** Lift the kind of list segment predicates to PE *) let hpred_lift_to_pe hpred = match hpred with - | Sil.Hpointsto _ -> hpred - | Sil.Hlseg (_, para, root, next, shared) -> Sil.Hlseg (Sil.Lseg_PE, para, root, next, shared) - | Sil.Hdllseg(_, para, iF, oB, oF, iB, shared) -> Sil.Hdllseg (Sil.Lseg_PE, para, iF, oB, oF, iB, shared) + | Sil.Hpointsto _ + -> hpred + | Sil.Hlseg (_, para, root, next, shared) + -> Sil.Hlseg (Sil.Lseg_PE, para, root, next, shared) + | Sil.Hdllseg (_, para, iF, oB, oF, iB, shared) + -> Sil.Hdllseg (Sil.Lseg_PE, para, iF, oB, oF, iB, shared) (** Lift the kind of list segment predicates to PE in a given sigma *) -let sigma_lift_to_pe sigma = - List.map ~f:hpred_lift_to_pe sigma +let sigma_lift_to_pe sigma = List.map ~f:hpred_lift_to_pe sigma (** [generic_para_create] takes a correspondence, and a sigma and a list of expressions for the first part of this @@ -714,23 +838,29 @@ let sigma_lift_to_pe sigma = let generic_para_create tenv corres sigma1 elist1 = let corres_ids = let not_same_consts = function - | Exp.Const c1, Exp.Const c2 -> not (Const.equal c1 c2) - | _ -> true in + | Exp.Const c1, Exp.Const c2 + -> not (Const.equal c1 c2) + | _ + -> true + in let new_corres' = List.filter ~f:not_same_consts corres in let add_fresh_id pair = (pair, Ident.create_fresh Ident.kprimed) in - List.map ~f:add_fresh_id new_corres' in - let (es_shared, ids_shared, ids_exists) = + List.map ~f:add_fresh_id new_corres' + in + let es_shared, ids_shared, ids_exists = let not_in_elist1 ((e1, _), _) = not (List.exists ~f:(Exp.equal e1) elist1) in let corres_ids_no_elist1 = List.filter ~f:not_in_elist1 corres_ids in let should_be_shared ((e1, e2), _) = Exp.equal e1 e2 in let shared, exists = List.partition_tf ~f:should_be_shared corres_ids_no_elist1 in let es_shared = List.map ~f:(fun ((e1, _), _) -> e1) shared in - (es_shared, List.map ~f:snd shared, List.map ~f:snd exists) in + (es_shared, List.map ~f:snd shared, List.map ~f:snd exists) + in let renaming = List.map ~f:(fun ((e1, _), id) -> (e1, id)) corres_ids in let body = let sigma1' = sigma_lift_to_pe sigma1 in let renaming_exp = List.map ~f:(fun (e1, id) -> (e1, Exp.Var id)) renaming in - Prop.sigma_replace_exp tenv renaming_exp sigma1' in + Prop.sigma_replace_exp tenv renaming_exp sigma1' + in (renaming, body, ids_exists, ids_shared, es_shared) (** [hpara_create] takes a correspondence, and a sigma, a root @@ -739,20 +869,21 @@ let generic_para_create tenv corres sigma1 elist1 = passed as arguments to hpara. Both of them are returned as a result. *) let hpara_create tenv corres sigma1 root1 next1 = let renaming, body, ids_exists, ids_shared, es_shared = - generic_para_create tenv corres sigma1 [root1; next1] in + generic_para_create tenv corres sigma1 [root1; next1] + in let get_id1 e1 = let is_equal_to_e1 (e1', _) = Exp.equal e1 e1' in - match List.find ~f:is_equal_to_e1 renaming with - | Some (_, id) -> id - | None -> assert false in + match List.find ~f:is_equal_to_e1 renaming with Some (_, id) -> id | None -> assert false + in let id_root = get_id1 root1 in let id_next = get_id1 next1 in let hpara = - { Sil.root = id_root; - Sil.next = id_next; - Sil.svars = ids_shared; - Sil.evars = ids_exists; - Sil.body = body } in + { Sil.root= id_root + ; Sil.next= id_next + ; Sil.svars= ids_shared + ; Sil.evars= ids_exists + ; Sil.body= body } + in (hpara, es_shared) (** [hpara_dll_create] takes a correspondence, and a sigma, a root, @@ -761,20 +892,21 @@ let hpara_create tenv corres sigma1 root1 next1 = passed as arguments to hpara. Both of them are returned as a result. *) let hpara_dll_create tenv corres sigma1 root1 blink1 flink1 = let renaming, body, ids_exists, ids_shared, es_shared = - generic_para_create tenv corres sigma1 [root1; blink1; flink1] in + generic_para_create tenv corres sigma1 [root1; blink1; flink1] + in let get_id1 e1 = let is_equal_to_e1 (e1', _) = Exp.equal e1 e1' in - match List.find ~f:is_equal_to_e1 renaming with - | Some (_, id) -> id - | None -> assert false in + match List.find ~f:is_equal_to_e1 renaming with Some (_, id) -> id | None -> assert false + in let id_root = get_id1 root1 in let id_blink = get_id1 blink1 in let id_flink = get_id1 flink1 in let hpara_dll = - { Sil.cell = id_root; - Sil.blink = id_blink; - Sil.flink = id_flink; - Sil.svars_dll = ids_shared; - Sil.evars_dll = ids_exists; - Sil.body_dll = body } in + { Sil.cell= id_root + ; Sil.blink= id_blink + ; Sil.flink= id_flink + ; Sil.svars_dll= ids_shared + ; Sil.evars_dll= ids_exists + ; Sil.body_dll= body } + in (hpara_dll, es_shared) diff --git a/infer/src/backend/match.mli b/infer/src/backend/match.mli index 138bd0250..a8b575631 100644 --- a/infer/src/backend/match.mli +++ b/infer/src/backend/match.mli @@ -17,13 +17,15 @@ open! IStd *) (* TODO: missing documentation *) + val hpara_match_with_impl : Tenv.t -> bool -> Sil.hpara -> Sil.hpara -> bool + val hpara_dll_match_with_impl : Tenv.t -> bool -> Sil.hpara_dll -> Sil.hpara_dll -> bool (** Type for a hpred pattern. [flag=false] means that the implication between hpreds is not considered, and [flag = true] means that it is considered during pattern matching. *) -type hpred_pat = { hpred : Sil.hpred; flag : bool } +type hpred_pat = {hpred: Sil.hpred; flag: bool} val pp_hpat : Pp.env -> Format.formatter -> hpred_pat -> unit @@ -31,13 +33,19 @@ val pp_hpat_list : Pp.env -> Format.formatter -> hpred_pat list -> unit type sidecondition = Prop.normal Prop.t -> Sil.exp_subst -> bool +val prop_match_with_impl : + Tenv.t -> Prop.normal Prop.t -> sidecondition -> Ident.t list -> hpred_pat -> hpred_pat list + -> (Sil.exp_subst * Prop.normal Prop.t) option (** [prop_match_with_impl p condition vars hpat hpats] returns [(subst, p_leftover)] such that 1) [dom(subst) = vars] 2) [p |- (hpat.hpred * hpats.hpred)[subst] * p_leftover]. Using the flag [field], we can control the strength of |-. *) -val prop_match_with_impl : Tenv.t -> Prop.normal Prop.t -> sidecondition -> Ident.t list -> hpred_pat -> hpred_pat list -> (Sil.exp_subst * Prop.normal Prop.t) option +val find_partial_iso : + Tenv.t -> (Exp.t -> Exp.t -> bool) -> (Exp.t * Exp.t) list -> (Exp.t * Exp.t) list + -> Sil.hpred list + -> ((Exp.t * Exp.t) list * Sil.hpred list * Sil.hpred list * Sil.hpred list) option (** [find_partial_iso] finds disjoint isomorphic sub-sigmas inside a given sigma. The first argument is an equality checker. The function returns a partial iso and three sigmas. The first sigma is the first @@ -45,16 +53,15 @@ val prop_match_with_impl : Tenv.t -> Prop.normal Prop.t -> sidecondition -> Iden the returned isomorphism. The second is the second copy of the two isomorphic sigmas, and it uses expressions in the range of the isomorphism. The third is the unused part of the input sigma. *) -val find_partial_iso : Tenv.t -> - (Exp.t -> Exp.t -> bool) -> - (Exp.t * Exp.t) list -> - (Exp.t * Exp.t) list -> - Sil.hpred list -> - ((Exp.t * Exp.t) list * Sil.hpred list * Sil.hpred list * Sil.hpred list) option (** This mode expresses the flexibility allowed during the isomorphism check *) type iso_mode = Exact | LFieldForget | RFieldForget +val find_partial_iso_from_two_sigmas : + Tenv.t -> iso_mode -> (Exp.t -> Exp.t -> bool) -> (Exp.t * Exp.t) list -> (Exp.t * Exp.t) list + -> Sil.hpred list -> Sil.hpred list + -> ((Exp.t * Exp.t) list * Sil.hpred list * Sil.hpred list * (Sil.hpred list * Sil.hpred list)) + option (** [find_partial_iso_from_two_sigmas] finds isomorphic sub-sigmas inside two given sigmas. The second argument is an equality checker. The function returns a partial iso and four sigmas. The first @@ -62,42 +69,24 @@ type iso_mode = Exact | LFieldForget | RFieldForget the returned isomorphism. The second is the second copy of the two isomorphic sigmas, and it uses expressions in the range of the isomorphism. The third and fourth are the unused parts of the two input sigmas. *) -val find_partial_iso_from_two_sigmas : Tenv.t -> - iso_mode -> - (Exp.t -> Exp.t -> bool) -> - (Exp.t * Exp.t) list -> - (Exp.t * Exp.t) list -> - Sil.hpred list -> - Sil.hpred list -> - ((Exp.t * Exp.t) list * Sil.hpred list * Sil.hpred list * (Sil.hpred list * Sil.hpred list)) - option -(** [hpara_iso] soundly checks whether two hparas are isomorphic. *) val hpara_iso : Tenv.t -> Sil.hpara -> Sil.hpara -> bool +(** [hpara_iso] soundly checks whether two hparas are isomorphic. *) -(** [hpara_dll_iso] soundly checks whether two hpara_dlls are isomorphic. *) val hpara_dll_iso : Tenv.t -> Sil.hpara_dll -> Sil.hpara_dll -> bool +(** [hpara_dll_iso] soundly checks whether two hpara_dlls are isomorphic. *) - +val hpara_create : + Tenv.t -> (Exp.t * Exp.t) list -> Sil.hpred list -> Exp.t -> Exp.t -> Sil.hpara * Exp.t list (** [hpara_create] takes a correspondence, and a sigma, a root and a next for the first part of this correspondence. Then, it creates a hpara and discovers a list of shared expressions that are passed as arguments to hpara. Both of them are returned as a result. *) -val hpara_create : Tenv.t -> - (Exp.t * Exp.t) list -> - Sil.hpred list -> - Exp.t -> - Exp.t -> - Sil.hpara * Exp.t list +val hpara_dll_create : + Tenv.t -> (Exp.t * Exp.t) list -> Sil.hpred list -> Exp.t -> Exp.t -> Exp.t + -> Sil.hpara_dll * Exp.t list (** [hpara_dll_create] takes a correspondence, and a sigma, a root, a blink and a flink for the first part of this correspondence. Then, it creates a hpara_dll and discovers a list of shared expressions that are passed as arguments to hpara. Both of them are returned as a result. *) -val hpara_dll_create : Tenv.t -> - (Exp.t * Exp.t) list -> - Sil.hpred list -> - Exp.t -> - Exp.t -> - Exp.t -> - Sil.hpara_dll * Exp.t list diff --git a/infer/src/backend/mergeCapture.ml b/infer/src/backend/mergeCapture.ml index 1781d4e3e..53a41f5ba 100644 --- a/infer/src/backend/mergeCapture.ml +++ b/infer/src/backend/mergeCapture.ml @@ -9,7 +9,6 @@ open! IStd open! PVariant - module L = Logging module F = Format @@ -27,25 +26,15 @@ let modified_targets = ref String.Set.empty let record_modified_targets_from_file file = match Utils.read_file file with - | Ok targets -> - modified_targets := List.fold ~f:String.Set.add ~init:String.Set.empty targets - | Error error -> - L.user_error "Failed to read modified targets file '%s': %s@." file error ; - () + | Ok targets + -> modified_targets := List.fold ~f:String.Set.add ~init:String.Set.empty targets + | Error error + -> L.user_error "Failed to read modified targets file '%s': %s@." file error ; () type stats = - { - mutable files_linked: int; - mutable files_multilinked: int; - mutable targets_merged: int; - } - -let empty_stats () = - { - files_linked = 0; - files_multilinked = 0; - targets_merged = 0; - } + {mutable files_linked: int; mutable files_multilinked: int; mutable targets_merged: int} + +let empty_stats () = {files_linked= 0; files_multilinked= 0; targets_merged= 0} let link_exists s = try @@ -57,67 +46,62 @@ let link_exists s = Used for the hashed directories where attrbute files are stored. *) let multilinks_dir_table = String.Table.create ~size:16 () - (* Add a multilink for attributes to the internal per-directory table. The files will be created by create_multilinks. *) let add_multilink_attr ~stats src dst = let attr_dir = Filename.dirname dst in let attr_dir_name = Filename.basename attr_dir in let multilinks = - try - String.Table.find_exn multilinks_dir_table attr_dir_name - with - | Not_found -> - let multilinks = match Multilinks.read ~dir:attr_dir with - | Some multilinks -> - (* incremental merge: start from the existing file on disk *) - multilinks - | None -> - Multilinks.create () in - String.Table.set multilinks_dir_table ~key:attr_dir_name ~data:multilinks; - multilinks in - Multilinks.add multilinks src; + try String.Table.find_exn multilinks_dir_table attr_dir_name + with Not_found -> + let multilinks = + match Multilinks.read ~dir:attr_dir with + | Some multilinks + -> (* incremental merge: start from the existing file on disk *) + multilinks + | None + -> Multilinks.create () + in + String.Table.set multilinks_dir_table ~key:attr_dir_name ~data:multilinks ; multilinks + in + Multilinks.add multilinks src ; stats.files_multilinked <- stats.files_multilinked + 1 let create_link ~stats src dst = - if link_exists dst then Unix.unlink dst; - Unix.symlink ~src ~dst; + if link_exists dst then Unix.unlink dst ; + Unix.symlink ~src ~dst ; (* Set the accessed and modified time of the original file slightly in the past. Due to the coarse precision of the timestamps, it is possible for the source and destination of a link to have the same modification time. When this happens, the files will be considered to need re-analysis every time, indefinitely. *) let near_past = Unix.gettimeofday () -. 1. in - Unix.utimes src ~access:near_past ~modif:near_past; + Unix.utimes src ~access:near_past ~modif:near_past ; stats.files_linked <- stats.files_linked + 1 let create_multilinks () = let do_dir ~key:dir ~data:multilinks = let attributes_dir = - Filename.concat (Filename.concat Config.results_dir Config.attributes_dir_name) dir in - Multilinks.write multilinks ~dir:attributes_dir in + Filename.concat (Filename.concat Config.results_dir Config.attributes_dir_name) dir + in + Multilinks.write multilinks ~dir:attributes_dir + in String.Table.iteri ~f:do_dir multilinks_dir_table - (** Create symbolic links recursively from the destination to the source. Replicate the structure of the source directory in the destination, with files replaced by links to the source. *) let rec slink ~stats ~skiplevels src dst = - L.(debug MergeCapture Verbose) "slink src:%s dst:%s skiplevels:%d@." src dst skiplevels; - if Sys.is_directory src = `Yes - then - begin - if (Sys.file_exists dst) <> `Yes - then Unix.mkdir dst ~perm:0o700; - let items = Sys.readdir src in - Array.iter - ~f:(fun item -> - slink ~stats ~skiplevels:(skiplevels - 1) - (Filename.concat src item) (Filename.concat dst item)) - items - end + L.(debug MergeCapture Verbose) "slink src:%s dst:%s skiplevels:%d@." src dst skiplevels ; + if Sys.is_directory src = `Yes then ( + if Sys.file_exists dst <> `Yes then Unix.mkdir dst ~perm:0o700 ; + let items = Sys.readdir src in + Array.iter + ~f:(fun item -> + slink ~stats ~skiplevels:(skiplevels - 1) (Filename.concat src item) + (Filename.concat dst item)) + items ) else if skiplevels > 0 then () - else if Config.merge && Filename.check_suffix dst ".attr" - then add_multilink_attr ~stats src dst + else if Config.merge && Filename.check_suffix dst ".attr" then add_multilink_attr ~stats src dst else create_link ~stats src dst (** Determine if the destination should link to the source. @@ -131,46 +115,40 @@ let should_link ~target ~target_results_dir ~stats infer_out_src infer_out_dst = let filename = DB.filename_from_string file in let time_orig = DB.file_modified_time ~symlink:false filename in let time_link = DB.file_modified_time ~symlink:true filename in - L.(debug MergeCapture Verbose) "file:%s time_orig:%f time_link:%f@." file time_orig time_link; - time_link > time_orig in + L.(debug MergeCapture Verbose) "file:%s time_orig:%f time_link:%f@." file time_orig time_link ; + time_link > time_orig + in let symlinks_up_to_date captured_file = if Sys.is_directory captured_file = `Yes then let contents = Array.to_list (Sys.readdir captured_file) in List.for_all ~f:(fun file -> - let file_path = Filename.concat captured_file file in - Sys.file_exists file_path = `Yes && - (not check_timestamp_of_symlinks || symlink_up_to_date file_path)) + let file_path = Filename.concat captured_file file in + Sys.file_exists file_path = `Yes + && (not check_timestamp_of_symlinks || symlink_up_to_date file_path)) contents - else true in + else true + in let check_file captured_file = - Sys.file_exists captured_file = `Yes && - symlinks_up_to_date captured_file in + Sys.file_exists captured_file = `Yes && symlinks_up_to_date captured_file + in let was_copied () = let captured_src = Filename.concat infer_out_src Config.captured_dir_name in let captured_dst = Filename.concat infer_out_dst Config.captured_dir_name in - if Sys.file_exists captured_src = `Yes && - Sys.is_directory captured_src = `Yes - then - begin - let captured_files = Array.to_list (Sys.readdir captured_src) in - num_captured_files := List.length captured_files; - List.for_all - ~f:(fun file -> - check_file (Filename.concat captured_dst file)) - captured_files - end - else - true in - let was_modified () = - String.Set.mem !modified_targets target in - let r = - was_modified () || - not (was_copied ()) in - if r then stats.targets_merged <- stats.targets_merged + 1; - L.(debug MergeCapture Verbose) "lnk:%s:%d %s@." (if r then "T" else "F") !num_captured_files - target_results_dir; - if r then L.(debug MergeCapture Medium) "%s@."target_results_dir; + if Sys.file_exists captured_src = `Yes && Sys.is_directory captured_src = `Yes then + let captured_files = Array.to_list (Sys.readdir captured_src) in + num_captured_files := List.length captured_files ; + List.for_all ~f:(fun file -> check_file (Filename.concat captured_dst file)) captured_files + else true + in + let was_modified () = String.Set.mem !modified_targets target in + let r = was_modified () || not (was_copied ()) in + if r then stats.targets_merged <- stats.targets_merged + 1 ; + L.(debug MergeCapture Verbose) + "lnk:%s:%d %s@." + (if r then "T" else "F") + !num_captured_files target_results_dir ; + if r then L.(debug MergeCapture Medium) "%s@." target_results_dir ; r (** should_link needs to know whether the source file has changed, @@ -181,27 +159,28 @@ let process_merge_file deps_file = let stats = empty_stats () in let process_line line = match Str.split_delim (Str.regexp (Str.quote "\t")) line with - | target :: _ :: target_results_dir :: _ -> - let infer_out_src = - if Filename.is_relative target_results_dir then - Filename.dirname (buck_out ()) ^/ target_results_dir - else target_results_dir in - let skiplevels = 2 in (* Don't link toplevel files, definitely not .start *) - if should_link ~target ~target_results_dir ~stats infer_out_src infer_out_dst - then slink ~stats ~skiplevels infer_out_src infer_out_dst - | _ -> - () in - ( - match Utils.read_file deps_file with - | Ok lines -> List.iter ~f:process_line lines - | Error error -> L.internal_error "Couldn't read deps file '%s': %s" deps_file error - ); - create_multilinks (); - L.progress "Captured results merged.@."; - L.progress "Targets merged: %d@." stats.targets_merged; - L.progress "Files linked: %d@." stats.files_linked; + | target :: _ :: target_results_dir :: _ + -> let infer_out_src = + if Filename.is_relative target_results_dir then Filename.dirname (buck_out ()) + ^/ target_results_dir + else target_results_dir + in + let skiplevels = 2 in + (* Don't link toplevel files, definitely not .start *) + if should_link ~target ~target_results_dir ~stats infer_out_src infer_out_dst then + slink ~stats ~skiplevels infer_out_src infer_out_dst + | _ + -> () + in + ( match Utils.read_file deps_file with + | Ok lines + -> List.iter ~f:process_line lines + | Error error + -> L.internal_error "Couldn't read deps file '%s': %s" deps_file error ) ; + create_multilinks () ; + L.progress "Captured results merged.@." ; + L.progress "Targets merged: %d@." stats.targets_merged ; + L.progress "Files linked: %d@." stats.files_linked ; L.progress "Files multilinked: %d@." stats.files_multilinked - -let merge_captured_targets () = - process_merge_file (infer_deps ()) +let merge_captured_targets () = process_merge_file (infer_deps ()) diff --git a/infer/src/backend/ondemand.ml b/infer/src/backend/ondemand.ml index 16eab1679..d502db196 100644 --- a/infer/src/backend/ondemand.ml +++ b/infer/src/backend/ondemand.ml @@ -19,203 +19,170 @@ type analyze_ondemand = Specs.summary -> Procdesc.t -> Specs.summary type get_proc_desc = Typ.Procname.t -> Procdesc.t option -type callbacks = - { - analyze_ondemand : analyze_ondemand; - get_proc_desc : get_proc_desc; - } +type callbacks = {analyze_ondemand: analyze_ondemand; get_proc_desc: get_proc_desc} let callbacks_ref = ref None -let set_callbacks (callbacks : callbacks) = - callbacks_ref := Some callbacks +let set_callbacks (callbacks: callbacks) = callbacks_ref := Some callbacks -let unset_callbacks () = - callbacks_ref := None +let unset_callbacks () = callbacks_ref := None let nesting = ref 0 let is_active, add_active, remove_active = let currently_analyzed = ref Typ.Procname.Set.empty in - let is_active proc_name = - Typ.Procname.Set.mem proc_name !currently_analyzed + let is_active proc_name = Typ.Procname.Set.mem proc_name !currently_analyzed and add_active proc_name = currently_analyzed := Typ.Procname.Set.add proc_name !currently_analyzed and remove_active proc_name = - currently_analyzed := Typ.Procname.Set.remove proc_name !currently_analyzed in + currently_analyzed := Typ.Procname.Set.remove proc_name !currently_analyzed + in (is_active, add_active, remove_active) let should_be_analyzed proc_name proc_attributes = let already_analyzed () = match Specs.get_summary proc_name with - | Some summary -> - Specs.equal_status (Specs.get_status summary) Specs.Analyzed - | None -> - false in - proc_attributes.ProcAttributes.is_defined && (* we have the implementation *) - not (is_active proc_name) && (* avoid infinite loops *) - not (already_analyzed ()) (* avoid re-analysis of the same procedure *) + | Some summary + -> Specs.equal_status (Specs.get_status summary) Specs.Analyzed + | None + -> false + in + proc_attributes.ProcAttributes.is_defined + (* we have the implementation *) + && not (is_active proc_name) && (* avoid infinite loops *) + not (already_analyzed ()) + +(* avoid re-analysis of the same procedure *) let procedure_should_be_analyzed proc_name = match Specs.proc_resolve_attributes proc_name with - | Some proc_attributes when Config.reactive_capture && not proc_attributes.is_defined -> - (* try to capture procedure first *) + | Some proc_attributes when Config.reactive_capture && not proc_attributes.is_defined + -> (* try to capture procedure first *) let defined_proc_attributes = OndemandCapture.try_capture proc_attributes in Option.value_map ~f:(should_be_analyzed proc_name) ~default:false defined_proc_attributes - | Some proc_attributes -> - should_be_analyzed proc_name proc_attributes - | None -> - false + | Some proc_attributes + -> should_be_analyzed proc_name proc_attributes + | None + -> false type global_state = - { - abs_val : int; - abstraction_rules : Abs.rules; - delayed_prints : L.print_action list; - footprint_mode : bool; - html_formatter : F.formatter; - name_generator : Ident.NameGenerator.t; - symexec_state : State.t - } + { abs_val: int + ; abstraction_rules: Abs.rules + ; delayed_prints: L.print_action list + ; footprint_mode: bool + ; html_formatter: F.formatter + ; name_generator: Ident.NameGenerator.t + ; symexec_state: State.t } let save_global_state () = - Timeout.suspend_existing_timeout - ~keep_symop_total:false; (* use a new global counter for the callee *) - { - abs_val = !Config.abs_val; - abstraction_rules = Abs.get_current_rules (); - delayed_prints = L.get_delayed_prints (); - footprint_mode = !Config.footprint; - html_formatter = !Printer.curr_html_formatter; - name_generator = Ident.NameGenerator.get_current (); - symexec_state = State.save_state (); - } + Timeout.suspend_existing_timeout ~keep_symop_total:false ; + (* use a new global counter for the callee *) + { abs_val= !Config.abs_val + ; abstraction_rules= Abs.get_current_rules () + ; delayed_prints= L.get_delayed_prints () + ; footprint_mode= !Config.footprint + ; html_formatter= !Printer.curr_html_formatter + ; name_generator= Ident.NameGenerator.get_current () + ; symexec_state= State.save_state () } let restore_global_state st = - Config.abs_val := st.abs_val; - Abs.set_current_rules st.abstraction_rules; - L.set_delayed_prints st.delayed_prints; - Config.footprint := st.footprint_mode; - Printer.curr_html_formatter := st.html_formatter; - Ident.NameGenerator.set_current st.name_generator; - State.restore_state st.symexec_state; + Config.abs_val := st.abs_val ; + Abs.set_current_rules st.abstraction_rules ; + L.set_delayed_prints st.delayed_prints ; + Config.footprint := st.footprint_mode ; + Printer.curr_html_formatter := st.html_formatter ; + Ident.NameGenerator.set_current st.name_generator ; + State.restore_state st.symexec_state ; Timeout.resume_previous_timeout () - let run_proc_analysis ~propagate_exceptions analyze_proc curr_pdesc callee_pdesc = let curr_pname = Procdesc.get_proc_name curr_pdesc in let callee_pname = Procdesc.get_proc_name callee_pdesc in - let log_elapsed_time = let start_time = Unix.gettimeofday () in fun () -> let elapsed_time = Unix.gettimeofday () -. start_time in - L.(debug Analysis Medium) "Elapsed analysis time: %a: %f@\n" - Typ.Procname.pp callee_pname - elapsed_time in - - L.progressbar_procedure (); - if Config.trace_ondemand then L.progress "[%d] run_proc_analysis %a -> %a@." - !nesting - Typ.Procname.pp curr_pname - Typ.Procname.pp callee_pname; - + L.(debug Analysis Medium) + "Elapsed analysis time: %a: %f@\n" Typ.Procname.pp callee_pname elapsed_time + in + L.progressbar_procedure () ; + if Config.trace_ondemand then + L.progress "[%d] run_proc_analysis %a -> %a@." !nesting Typ.Procname.pp curr_pname + Typ.Procname.pp callee_pname ; let preprocess () = - incr nesting; + incr nesting ; let initial_summary = Specs.reset_summary callee_pdesc in - add_active callee_pname; - initial_summary in - + add_active callee_pname ; initial_summary + in let postprocess summary = - decr nesting; - Specs.store_summary summary; - remove_active callee_pname; - Printer.write_proc_html callee_pdesc; - log_elapsed_time (); - summary in - + decr nesting ; + Specs.store_summary summary ; + remove_active callee_pname ; + Printer.write_proc_html callee_pdesc ; + log_elapsed_time () ; + summary + in let log_error_and_continue exn summary kind = - Reporting.log_error summary exn; - let stats = { summary.Specs.stats with Specs.stats_failure = Some kind } in - let payload = - { summary.Specs.payload with Specs.preposts = Some []; } in - let new_summary = { summary with Specs.stats; payload } in - Specs.store_summary new_summary; - remove_active callee_pname; - log_elapsed_time (); - new_summary in - + Reporting.log_error summary exn ; + let stats = {summary.Specs.stats with Specs.stats_failure= Some kind} in + let payload = {summary.Specs.payload with Specs.preposts= Some []} in + let new_summary = {summary with Specs.stats= stats; payload} in + Specs.store_summary new_summary ; + remove_active callee_pname ; + log_elapsed_time () ; + new_summary + in let old_state = save_global_state () in let initial_summary = preprocess () in try - let summary = - analyze_proc initial_summary callee_pdesc - |> postprocess in - restore_global_state old_state; - summary + let summary = analyze_proc initial_summary callee_pdesc |> postprocess in + restore_global_state old_state ; summary with exn -> - L.internal_error "@\nONDEMAND EXCEPTION %a %s@.@.BACK TRACE@.%s@?" - Typ.Procname.pp callee_pname - (Exn.to_string exn) - (Printexc.get_backtrace ()); - restore_global_state old_state; - if propagate_exceptions - then - raise exn + L.internal_error "@\nONDEMAND EXCEPTION %a %s@.@.BACK TRACE@.%s@?" Typ.Procname.pp callee_pname + (Exn.to_string exn) (Printexc.get_backtrace ()) ; + restore_global_state old_state ; + if propagate_exceptions then raise exn else match exn with - | SymOp.Analysis_failure_exe kind -> - (* in production mode, log the timeout/crash and continue with the summary we had before + | SymOp.Analysis_failure_exe kind + -> (* in production mode, log the timeout/crash and continue with the summary we had before the failure occurred *) log_error_and_continue exn initial_summary kind - | _ -> - (* this happens with assert false or some other unrecognized exception *) + | _ + -> (* this happens with assert false or some other unrecognized exception *) log_error_and_continue exn initial_summary (FKcrash (Exn.to_string exn)) - let analyze_proc_desc ~propagate_exceptions curr_pdesc callee_pdesc : Specs.summary option = let callee_pname = Procdesc.get_proc_name callee_pdesc in let proc_attributes = Procdesc.get_attributes callee_pdesc in match !callbacks_ref with - | None -> - failwithf - "No callbacks registered to analyze proc desc %a when analyzing %a@." - Typ.Procname.pp callee_pname - Typ.Procname.pp (Procdesc.get_proc_name curr_pdesc) - | Some callbacks -> - if should_be_analyzed callee_pname proc_attributes then - Some (run_proc_analysis - ~propagate_exceptions callbacks.analyze_ondemand curr_pdesc callee_pdesc) - else - Specs.get_summary callee_pname - + | None + -> failwithf "No callbacks registered to analyze proc desc %a when analyzing %a@." + Typ.Procname.pp callee_pname Typ.Procname.pp (Procdesc.get_proc_name curr_pdesc) + | Some callbacks + -> if should_be_analyzed callee_pname proc_attributes then + Some + (run_proc_analysis ~propagate_exceptions callbacks.analyze_ondemand curr_pdesc + callee_pdesc) + else Specs.get_summary callee_pname (** analyze_proc_name curr_pdesc proc_name performs an on-demand analysis of proc_name triggered during the analysis of curr_pname. *) let analyze_proc_name ~propagate_exceptions curr_pdesc callee_pname : Specs.summary option = match !callbacks_ref with - | None -> - failwithf - "No callbacks registered to analyze proc name %a when analyzing %a@." - Typ.Procname.pp callee_pname - Typ.Procname.pp (Procdesc.get_proc_name curr_pdesc) - | Some callbacks -> - if procedure_should_be_analyzed callee_pname then - begin - match callbacks.get_proc_desc callee_pname with - | Some callee_pdesc -> - analyze_proc_desc ~propagate_exceptions curr_pdesc callee_pdesc - | None -> Specs.get_summary callee_pname - end - else - Specs.get_summary callee_pname - + | None + -> failwithf "No callbacks registered to analyze proc name %a when analyzing %a@." + Typ.Procname.pp callee_pname Typ.Procname.pp (Procdesc.get_proc_name curr_pdesc) + | Some callbacks + -> if procedure_should_be_analyzed callee_pname then + match callbacks.get_proc_desc callee_pname with + | Some callee_pdesc + -> analyze_proc_desc ~propagate_exceptions curr_pdesc callee_pdesc + | None + -> Specs.get_summary callee_pname + else Specs.get_summary callee_pname (** Find a proc desc for the procedure, perhaps loading it from disk. *) let get_proc_desc callee_pname = - match !callbacks_ref with - | Some callbacks -> - callbacks.get_proc_desc callee_pname - | None -> - None + match !callbacks_ref with Some callbacks -> callbacks.get_proc_desc callee_pname | None -> None diff --git a/infer/src/backend/ondemand.mli b/infer/src/backend/ondemand.mli index e4ebe4084..6630450b7 100644 --- a/infer/src/backend/ondemand.mli +++ b/infer/src/backend/ondemand.mli @@ -15,32 +15,28 @@ type analyze_ondemand = Specs.summary -> Procdesc.t -> Specs.summary type get_proc_desc = Typ.Procname.t -> Procdesc.t option -type callbacks = - { - analyze_ondemand : analyze_ondemand; - get_proc_desc : get_proc_desc; - } +type callbacks = {analyze_ondemand: analyze_ondemand; get_proc_desc: get_proc_desc} -(** Find a proc desc for the procedure, perhaps loading it from disk. *) val get_proc_desc : get_proc_desc +(** Find a proc desc for the procedure, perhaps loading it from disk. *) +val analyze_proc_desc : + propagate_exceptions:bool -> Procdesc.t -> Procdesc.t -> Specs.summary option (** analyze_proc_desc curr_pdesc callee_pdesc performs an on-demand analysis of callee_pdesc triggered during the analysis of curr_pdesc. *) -val analyze_proc_desc : - propagate_exceptions:bool -> Procdesc.t -> Procdesc.t -> Specs.summary option +val analyze_proc_name : + propagate_exceptions:bool -> Procdesc.t -> Typ.Procname.t -> Specs.summary option (** analyze_proc_name curr_pdesc proc_name performs an on-demand analysis of proc_name triggered during the analysis of curr_pdesc. *) -val analyze_proc_name : - propagate_exceptions:bool -> Procdesc.t -> Typ.Procname.t -> Specs.summary option -(** Check if the procedure called needs to be analyzed. *) val procedure_should_be_analyzed : Typ.Procname.t -> bool +(** Check if the procedure called needs to be analyzed. *) -(** Set the callbacks used to perform on-demand analysis. *) val set_callbacks : callbacks -> unit +(** Set the callbacks used to perform on-demand analysis. *) -(** Unset the callbacks used to perform on-demand analysis. *) val unset_callbacks : unit -> unit +(** Unset the callbacks used to perform on-demand analysis. *) diff --git a/infer/src/backend/paths.ml b/infer/src/backend/paths.ml index 14163b9c8..854a2cfed 100644 --- a/infer/src/backend/paths.ml +++ b/infer/src/backend/paths.ml @@ -23,55 +23,54 @@ module Path : sig type session = int - (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) val add_call : bool -> t -> Typ.Procname.t -> t -> t + (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) - (** add a call to a procname that's had to be skipped, along with the reason *) val add_skipped_call : t -> Typ.Procname.t -> string -> t + (** add a call to a procname that's had to be skipped, along with the reason *) - (** check whether a path contains another path *) val contains : t -> t -> bool + (** check whether a path contains another path *) - (** check wether the path contains the given position *) val contains_position : t -> PredSymb.path_pos -> bool + (** check wether the path contains the given position *) - (** Create the location trace of the path, up to the path position if specified *) val create_loc_trace : t -> PredSymb.path_pos option -> Errlog.loc_trace + (** Create the location trace of the path, up to the path position if specified *) - (** return the current node of the path *) val curr_node : t -> Procdesc.Node.t option + (** return the current node of the path *) - (** dump a path *) val d : t -> unit + (** dump a path *) - (** dump statistics of the path *) val d_stats : t -> unit + (** dump statistics of the path *) - (** extend a path with a new node reached from the given session, with an optional string for exceptions *) val extend : Procdesc.Node.t -> Typ.Name.t option -> session -> t -> t - (** extend a path with a new node reached from the given session, with an optional string for exceptions *) + val add_description : t -> string -> t + (** extend a path with a new node reached from the given session, with an optional string for exceptions *) - (** iterate over each node in the path, excluding calls, once *) val iter_all_nodes_nocalls : (Procdesc.Node.t -> unit) -> t -> unit + (** iterate over each node in the path, excluding calls, once *) val iter_shortest_sequence : (int -> t -> int -> Typ.Name.t option -> unit) -> PredSymb.path_pos option -> t -> unit - (** join two paths *) val join : t -> t -> t + (** join two paths *) - (** pretty print a path *) val pp : Format.formatter -> t -> unit + (** pretty print a path *) - (** pretty print statistics of the path *) val pp_stats : Format.formatter -> t -> unit + (** pretty print statistics of the path *) - (** create a new path with given start node *) val start : Procdesc.Node.t -> t - -(* + (** create a new path with given start node *) + (* (** equality for paths *) val equal : t -> t -> bool @@ -79,123 +78,114 @@ module Path : sig *) end = struct type session = int [@@deriving compare] + type stats = - { mutable max_length : int; (* length of the longest linear sequence *) - mutable linear_num : float; (* number of linear sequences described by the path *) } + { mutable max_length: int + ; (* length of the longest linear sequence *) + mutable linear_num: float + (* number of linear sequences described by the path *) } (* type aliases for components of t values that compare should ignore *) type _stats = stats + let compare__stats _ _ = 0 type _procname = Typ.Procname.t + let compare__procname _ _ = 0 type _string_option = string option + let compare__string_option _ _ = 0 type _path_exec = - | ExecSkipped of string (** call was skipped with a reason *) - | ExecCompleted of t (** call was completed *) + | ExecSkipped of string (** call was skipped with a reason *) + | ExecCompleted of t (** call was completed *) and t = (* INVARIANT: stats are always set to dummy_stats unless we are in the middle of a traversal *) (* in particular: a new traversal cannot be initiated during an existing traversal *) - | Pstart of Procdesc.Node.t * _stats (** start node *) + | Pstart of Procdesc.Node.t * _stats (** start node *) | Pnode of Procdesc.Node.t * Typ.Name.t option * session * t * _stats * _string_option - (** we got to [node] from last [session] perhaps propagating exception [exn_opt], + (** we got to [node] from last [session] perhaps propagating exception [exn_opt], and continue with [path]. *) - | Pjoin of t * t * _stats (** join of two paths *) - | Pcall of t * _procname * _path_exec * _stats (** add a sub-path originating from a call *) - [@@deriving compare] + | Pjoin of t * t * _stats (** join of two paths *) + | Pcall of t * _procname * _path_exec * _stats (** add a sub-path originating from a call *) + [@@deriving compare] - let get_dummy_stats () = - { max_length = - 1; - linear_num = - 1.0 } + let get_dummy_stats () = {max_length= -1; linear_num= -1.0} let get_description path = - match path with - | Pnode (_, _, _, _, _, descr_opt) -> - descr_opt - | _ -> None + match path with Pnode (_, _, _, _, _, descr_opt) -> descr_opt | _ -> None let add_description path description = let add_descr descr_option description = - match descr_option with - | Some descr -> descr^" "^description - | None -> description in + match descr_option with Some descr -> descr ^ " " ^ description | None -> description + in match path with - | Pnode (node, exn_opt, session, path, stats, descr_opt) -> - let description = add_descr descr_opt description in + | Pnode (node, exn_opt, session, path, stats, descr_opt) + -> let description = add_descr descr_opt description in Pnode (node, exn_opt, session, path, stats, Some description) - | _ -> path + | _ + -> path let set_dummy_stats stats = - stats.max_length <- - 1; - stats.linear_num <- - 1.0 + stats.max_length <- -1 ; + stats.linear_num <- -1.0 let rec curr_node = function - | Pstart (node, _) -> Some node - | Pnode (node, _, _, _, _, _) -> Some node - | Pcall(path, _, _, _) -> curr_node path - | Pjoin _ -> - None + | Pstart (node, _) + -> Some node + | Pnode (node, _, _, _, _, _) + -> Some node + | Pcall (path, _, _, _) + -> curr_node path + | Pjoin _ + -> None let start node = Pstart (node, get_dummy_stats ()) let extend (node: Procdesc.Node.t) exn_opt session path = Pnode (node, exn_opt, session, path, get_dummy_stats (), None) - let join p1 p2 = - Pjoin (p1, p2, get_dummy_stats ()) + let join p1 p2 = Pjoin (p1, p2, get_dummy_stats ()) let add_call include_subtrace p pname p_sub = - if include_subtrace then Pcall(p, pname, ExecCompleted p_sub, get_dummy_stats ()) - else p + if include_subtrace then Pcall (p, pname, ExecCompleted p_sub, get_dummy_stats ()) else p - let add_skipped_call p pname reason = - Pcall (p, pname, ExecSkipped reason, get_dummy_stats ()) + let add_skipped_call p pname reason = Pcall (p, pname, ExecSkipped reason, get_dummy_stats ()) (** functions in this module either do not assume, or do not re-establish, the invariant on dummy stats *) module Invariant = struct (** check whether a stats is the dummy stats *) - let stats_is_dummy stats = - Int.equal stats.max_length (-1) + let stats_is_dummy stats = Int.equal stats.max_length (-1) (** return the stats of the path, assumes that the stats are computed *) let get_stats = function - | Pstart (_, stats) -> stats - | Pnode (_, _, _, _, stats, _) -> stats - | Pjoin (_, _, stats) -> stats - | Pcall (_, _, _, stats) -> stats + | Pstart (_, stats) + -> stats + | Pnode (_, _, _, _, stats, _) + -> stats + | Pjoin (_, _, stats) + -> stats + | Pcall (_, _, _, stats) + -> stats (** restore the invariant that all the stats are dummy, so the path is ready for another traversal assumes that the stats are computed beforehand, and ensures that the invariant holds afterwards *) let rec reset_stats = function - | Pstart (_, stats) -> - if not (stats_is_dummy stats) then set_dummy_stats stats - | Pnode (_, _, _, path, stats, _) - | Pcall (path, _, ExecSkipped _, stats) -> - if not (stats_is_dummy stats) then - begin - reset_stats path; - set_dummy_stats stats - end - | Pjoin (path1, path2, stats) -> - if not (stats_is_dummy stats) then - begin - reset_stats path1; - reset_stats path2; - set_dummy_stats stats - end - | Pcall (path1, _, ExecCompleted path2, stats) -> - if not (stats_is_dummy stats) then - begin - reset_stats path1; - reset_stats path2; - set_dummy_stats stats - end + | Pstart (_, stats) + -> if not (stats_is_dummy stats) then set_dummy_stats stats + | Pnode (_, _, _, path, stats, _) | Pcall (path, _, ExecSkipped _, stats) + -> if not (stats_is_dummy stats) then ( reset_stats path ; set_dummy_stats stats ) + | Pjoin (path1, path2, stats) + -> if not (stats_is_dummy stats) then ( + reset_stats path1 ; reset_stats path2 ; set_dummy_stats stats ) + | Pcall (path1, _, ExecCompleted path2, stats) + -> if not (stats_is_dummy stats) then ( + reset_stats path1 ; reset_stats path2 ; set_dummy_stats stats ) (** Iterate [f] over the path and compute the stats, assuming the invariant: all the stats are dummy. Function [f] (typically with side-effects) is applied once to every node, and @@ -204,68 +194,61 @@ end = struct satisfying [f] was found. Assumes that the invariant holds beforehand, and ensures that all the stats are computed afterwards. Since this breaks the invariant, it must be followed by reset_stats. *) - let rec compute_stats do_calls (f : Procdesc.Node.t -> bool) = + let rec compute_stats do_calls (f: Procdesc.Node.t -> bool) = let nodes_found stats = stats.max_length > 0 in function - | Pstart (node, stats) -> - if stats_is_dummy stats then - begin + | Pstart (node, stats) + -> if stats_is_dummy stats then let found = f node in - stats.max_length <- if found then 1 else 0; - stats.linear_num <- 1.0; - end - | Pnode (node, _, _, path, stats, _) -> - if stats_is_dummy stats then - begin - compute_stats do_calls f path; + stats.max_length <- (if found then 1 else 0) ; + stats.linear_num <- 1.0 + | Pnode (node, _, _, path, stats, _) + -> if stats_is_dummy stats then ( + compute_stats do_calls f path ; let stats1 = get_stats path in - let found = f node || nodes_found stats1 (* the order is important as f has side-effects *) in - stats.max_length <- if found then 1 + stats1.max_length else 0; - stats.linear_num <- stats1.linear_num; - end - | Pjoin (path1, path2, stats) -> - if stats_is_dummy stats then - begin - compute_stats do_calls f path1; - compute_stats do_calls f path2; - let stats1, stats2 = get_stats path1, get_stats path2 in - stats.max_length <- max stats1.max_length stats2.max_length; - stats.linear_num <- stats1.linear_num +. stats2.linear_num - end - | Pcall (path1, _, ExecCompleted path2, stats) -> - if stats_is_dummy stats then - begin - let stats2 = match do_calls with - | true -> - compute_stats do_calls f path2; - get_stats path2 - | false -> - { max_length = 0; - linear_num = 0.0 } in + let found = + f node || nodes_found stats1 + (* the order is important as f has side-effects *) + in + stats.max_length <- (if found then 1 + stats1.max_length else 0) ; + stats.linear_num <- stats1.linear_num ) + | Pjoin (path1, path2, stats) + -> if stats_is_dummy stats then ( + compute_stats do_calls f path1 ; + compute_stats do_calls f path2 ; + let stats1, stats2 = (get_stats path1, get_stats path2) in + stats.max_length <- max stats1.max_length stats2.max_length ; + stats.linear_num <- stats1.linear_num +. stats2.linear_num ) + | Pcall (path1, _, ExecCompleted path2, stats) + -> if stats_is_dummy stats then + let stats2 = + match do_calls with + | true + -> compute_stats do_calls f path2 ; get_stats path2 + | false + -> {max_length= 0; linear_num= 0.0} + in let stats1 = let f' = - if nodes_found stats2 - then fun _ -> true (* already found in call, no need to search before the call *) - else f in - compute_stats do_calls f' path1; - get_stats path1 in - stats.max_length <- stats1.max_length + stats2.max_length; - stats.linear_num <- stats1.linear_num; - end - | Pcall (path, _, ExecSkipped _, stats) -> - if stats_is_dummy stats then - begin - let stats1 = - compute_stats do_calls f path; - get_stats path in - stats.max_length <- stats1.max_length; - stats.linear_num <- stats1.linear_num; - end - end (* End of module Invariant *) - + if nodes_found stats2 then fun _ -> true + (* already found in call, no need to search before the call *) + else f + in + compute_stats do_calls f' path1 ; get_stats path1 + in + stats.max_length <- stats1.max_length + stats2.max_length ; + stats.linear_num <- stats1.linear_num + | Pcall (path, _, ExecSkipped _, stats) + -> if stats_is_dummy stats then + let stats1 = compute_stats do_calls f path ; get_stats path in + stats.max_length <- stats1.max_length ; + stats.linear_num <- stats1.linear_num + end + + (* End of module Invariant *) (** iterate over each node in the path, excluding calls, once *) let iter_all_nodes_nocalls f path = - Invariant.compute_stats false (fun node -> f node; true) path; + Invariant.compute_stats false (fun node -> f node ; true) path ; Invariant.reset_stats path let get_path_pos node = @@ -276,78 +259,79 @@ end = struct let contains_position path pos = let found = ref false in let f node = - if PredSymb.equal_path_pos (get_path_pos node) pos then found := true; - true in - Invariant.compute_stats true f path; - Invariant.reset_stats path; - !found + if PredSymb.equal_path_pos (get_path_pos node) pos then found := true ; + true + in + Invariant.compute_stats true f path ; Invariant.reset_stats path ; !found (** iterate over the longest sequence belonging to the path, restricting to those where [filter] holds of some element. If a node is reached via an exception, pass the exception information to [f] on the previous node *) - let iter_shortest_sequence_filter - (f : int -> t -> int -> Typ.Name.t option -> unit) + let iter_shortest_sequence_filter (f: int -> t -> int -> Typ.Name.t option -> unit) (filter: Procdesc.Node.t -> bool) (path: t) : unit = - let rec doit level session path prev_exn_opt = match path with - | Pstart _ -> f level path session prev_exn_opt - | Pnode (_, exn_opt, session', p, _, _) -> - (* no two consecutive exceptions *) + let rec doit level session path prev_exn_opt = + match path with + | Pstart _ + -> f level path session prev_exn_opt + | Pnode (_, exn_opt, session', p, _, _) + -> (* no two consecutive exceptions *) let next_exn_opt = if prev_exn_opt <> None then None else exn_opt in - doit level (session' :> int) p next_exn_opt; + doit level (session' :> int) p next_exn_opt ; f level path session prev_exn_opt - | Pjoin (p1, p2, _) -> - if (Invariant.get_stats p1).max_length <= (Invariant.get_stats p2).max_length then + | Pjoin (p1, p2, _) + -> if (Invariant.get_stats p1).max_length <= (Invariant.get_stats p2).max_length then doit level session p1 prev_exn_opt - else - doit level session p2 prev_exn_opt - | Pcall (p1, _, ExecCompleted p2, _) -> - let next_exn_opt = None in (* exn must already be inside the call *) - doit level session p1 next_exn_opt; - doit (level +1) session p2 next_exn_opt - | Pcall (p, _, ExecSkipped _, _) -> - let next_exn_opt = None in - doit level session p next_exn_opt; - f level path session prev_exn_opt in - Invariant.compute_stats true filter path; - doit 0 0 path None; - Invariant.reset_stats path + else doit level session p2 prev_exn_opt + | Pcall (p1, _, ExecCompleted p2, _) + -> let next_exn_opt = None in + (* exn must already be inside the call *) + doit level session p1 next_exn_opt ; + doit (level + 1) session p2 next_exn_opt + | Pcall (p, _, ExecSkipped _, _) + -> let next_exn_opt = None in + doit level session p next_exn_opt ; f level path session prev_exn_opt + in + Invariant.compute_stats true filter path ; doit 0 0 path None ; Invariant.reset_stats path (** iterate over the shortest sequence belonging to the path, restricting to those containing the given position if given. Do not iterate past the last occurrence of the given position. [f level path session exn_opt] is passed the current nesting [level] and [path] and previous [session] and possible exception [exn_opt] *) - let iter_shortest_sequence - (f : int -> t -> int -> Typ.Name.t option -> unit) - (pos_opt : PredSymb.path_pos option) (path: t) : unit = - let filter node = match pos_opt with - | None -> true - | Some pos -> PredSymb.equal_path_pos (get_path_pos node) pos in + let iter_shortest_sequence (f: int -> t -> int -> Typ.Name.t option -> unit) + (pos_opt: PredSymb.path_pos option) (path: t) : unit = + let filter node = + match pos_opt with + | None + -> true + | Some pos + -> PredSymb.equal_path_pos (get_path_pos node) pos + in let path_pos_at_path p = - try - match curr_node p with - | Some node -> - pos_opt <> None && filter node - | None -> false - with exn when SymOp.exn_not_failure exn -> false in + try match curr_node p with Some node -> pos_opt <> None && filter node | None -> false + with exn when SymOp.exn_not_failure exn -> false + in let position_seen = ref false in let inverse_sequence = let log = ref [] in let g level p session exn_opt = - if path_pos_at_path p then position_seen := true; - log := (level, p, session, exn_opt) :: !log in - iter_shortest_sequence_filter g filter path; - !log in + if path_pos_at_path p then position_seen := true ; + log := (level, p, session, exn_opt) :: !log + in + iter_shortest_sequence_filter g filter path ; !log + in let sequence_up_to_last_seen = if !position_seen then let rec remove_until_seen = function - | ((_, p, _, _) as x):: l -> - if path_pos_at_path p then List.rev (x :: l) - else remove_until_seen l - | [] -> [] in + | (_, p, _, _ as x) :: l + -> if path_pos_at_path p then List.rev (x :: l) else remove_until_seen l + | [] + -> [] + in remove_until_seen inverse_sequence - else List.rev inverse_sequence in + else List.rev inverse_sequence + in List.iter ~f:(fun (level, p, session, exn_opt) -> f level p session exn_opt) sequence_up_to_last_seen @@ -356,273 +340,287 @@ end = struct let repetitions path = let map = ref Procdesc.NodeMap.empty in let add_node = function - | Some node -> - begin - try - let n = Procdesc.NodeMap.find node !map in - map := Procdesc.NodeMap.add node (n + 1) !map - with Not_found -> - map := Procdesc.NodeMap.add node 1 !map - end - | None -> - () in - iter_shortest_sequence (fun _ p _ _ -> add_node (curr_node p)) None path; + | Some node -> ( + try + let n = Procdesc.NodeMap.find node !map in + map := Procdesc.NodeMap.add node (n + 1) !map + with Not_found -> map := Procdesc.NodeMap.add node 1 !map ) + | None + -> () + in + iter_shortest_sequence (fun _ p _ _ -> add_node (curr_node p)) None path ; let max_rep_node = ref (Procdesc.Node.dummy None) in let max_rep_num = ref 0 in Procdesc.NodeMap.iter - (fun node num -> if num > !max_rep_num then (max_rep_node := node; max_rep_num := num)) - !map; + (fun node num -> + if num > !max_rep_num then ( + max_rep_node := node ; + max_rep_num := num )) + !map ; (!max_rep_node, !max_rep_num) let stats_string path = - Invariant.compute_stats true (fun _ -> true) path; + Invariant.compute_stats true (fun _ -> true) path ; let node, repetitions = repetitions path in let str = - "linear paths: " ^ string_of_float (Invariant.get_stats path).linear_num ^ - " max length: " ^ string_of_int (Invariant.get_stats path).max_length ^ - " has repetitions: " ^ string_of_int repetitions ^ - " of node " ^ (string_of_int (Procdesc.Node.get_id node :> int)) in - Invariant.reset_stats path; - str + "linear paths: " ^ string_of_float (Invariant.get_stats path).linear_num ^ " max length: " + ^ string_of_int (Invariant.get_stats path).max_length ^ " has repetitions: " + ^ string_of_int repetitions ^ " of node " ^ string_of_int (Procdesc.Node.get_id node :> int) + in + Invariant.reset_stats path ; str - let pp_stats fmt path = - F.fprintf fmt "%s" (stats_string path) + let pp_stats fmt path = F.fprintf fmt "%s" (stats_string path) - let d_stats path = - L.d_str (stats_string path) + let d_stats path = L.d_str (stats_string path) module PathMap = Caml.Map.Make (struct - type nonrec t = t - let compare = compare - end) + type nonrec t = t + + let compare = compare + end) let pp fmt path = let delayed_num = ref 0 in let delayed = ref PathMap.empty in let add_path p = - try ignore (PathMap.find p !delayed) with Not_found -> - incr delayed_num; - delayed := PathMap.add p !delayed_num !delayed in - let path_seen p = (* path seen before *) - PathMap.mem p !delayed in + try ignore (PathMap.find p !delayed) + with Not_found -> + incr delayed_num ; + delayed := PathMap.add p !delayed_num !delayed + in + let path_seen p = + (* path seen before *) + PathMap.mem p !delayed + in let rec add_delayed path = - if not (path_seen path) (* avoid exponential blowup *) - then match path with (* build a map from delayed paths to a unique number *) - | Pstart _ -> () - | Pnode (_, _, _, p, _, _) | Pcall(p, _, ExecSkipped _, _) -> add_delayed p - | Pjoin (p1, p2, _) | Pcall(p1, _, ExecCompleted p2, _) -> - (* delay paths occurring in a join *) - add_delayed p1; - add_delayed p2; - add_path p1; - add_path p2 in + if not (path_seen path) (* avoid exponential blowup *) then + match path with + (* build a map from delayed paths to a unique number *) + | Pstart _ + -> () + | Pnode (_, _, _, p, _, _) | Pcall (p, _, ExecSkipped _, _) + -> add_delayed p + | Pjoin (p1, p2, _) | Pcall (p1, _, ExecCompleted p2, _) + -> (* delay paths occurring in a join *) + add_delayed p1 ; add_delayed p2 ; add_path p1 ; add_path p2 + in let rec doit n fmt path = try - if n > 0 then raise Not_found; + if n > 0 then raise Not_found ; let num = PathMap.find path !delayed in F.fprintf fmt "P%d" num with Not_found -> - match path with - | Pstart (node, _) -> - F.fprintf fmt "n%a" Procdesc.Node.pp node - | Pnode (node, _, session, path, _, _) -> - F.fprintf fmt "%a(s%d).n%a" (doit (n - 1)) path (session :> int) Procdesc.Node.pp node - | Pjoin (path1, path2, _) -> - F.fprintf fmt "(%a + %a)" (doit (n - 1)) path1 (doit (n - 1)) path2 - | Pcall (path1, _, ExecCompleted path2, _) -> - F.fprintf fmt "(%a{%a})" (doit (n - 1)) path1 (doit (n - 1)) path2 - | Pcall (path, _, ExecSkipped reason, _) -> - F.fprintf fmt "(%a: %s)" (doit (n - 1)) path reason in + match path with + | Pstart (node, _) + -> F.fprintf fmt "n%a" Procdesc.Node.pp node + | Pnode (node, _, session, path, _, _) + -> F.fprintf fmt "%a(s%d).n%a" (doit (n - 1)) path (session :> int) Procdesc.Node.pp node + | Pjoin (path1, path2, _) + -> F.fprintf fmt "(%a + %a)" (doit (n - 1)) path1 (doit (n - 1)) path2 + | Pcall (path1, _, ExecCompleted path2, _) + -> F.fprintf fmt "(%a{%a})" (doit (n - 1)) path1 (doit (n - 1)) path2 + | Pcall (path, _, ExecSkipped reason, _) + -> F.fprintf fmt "(%a: %s)" (doit (n - 1)) path reason + in let print_delayed () = - if not (PathMap.is_empty !delayed) then begin + if not (PathMap.is_empty !delayed) then let f path num = F.fprintf fmt "P%d = %a@\n" num (doit 1) path in - F.fprintf fmt "where@\n"; + F.fprintf fmt "where@\n" ; PathMap.iter f !delayed - end in - add_delayed path; - doit 0 fmt path; - print_delayed () + in + add_delayed path ; doit 0 fmt path ; print_delayed () - let d p = - L.add_print_action (L.PTpath, Obj.repr p) + let d p = L.add_print_action (L.PTpath, Obj.repr p) - let rec contains p1 p2 = match p2 with - | Pjoin (p2', p2'', _) -> - contains p1 p2' || contains p1 p2'' - | _ -> phys_equal p1 p2 + let rec contains p1 p2 = + match p2 with + | Pjoin (p2', p2'', _) + -> contains p1 p2' || contains p1 p2'' + | _ + -> phys_equal p1 p2 let create_loc_trace path pos_opt : Errlog.loc_trace = let trace = ref [] in let g level path _ exn_opt = - match path, curr_node path with - | Pcall (_, _, ExecSkipped reason, _), Some curr_node -> - let curr_loc = Procdesc.Node.get_loc curr_node in + match (path, curr_node path) with + | Pcall (_, _, ExecSkipped reason, _), Some curr_node + -> let curr_loc = Procdesc.Node.get_loc curr_node in let descr = "Skipped call: " ^ reason in let node_tags = [] in trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace - | _, Some curr_node -> - begin - let curr_loc = Procdesc.Node.get_loc curr_node in - match Procdesc.Node.get_kind curr_node with - | Procdesc.Node.Join_node -> () (* omit join nodes from error traces *) - | Procdesc.Node.Start_node pname -> - let descr = "start of procedure " ^ (Typ.Procname.to_simplified_string pname) in - let node_tags = - [Errlog.Procedure_start pname] in - trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace - | Procdesc.Node.Prune_node (is_true_branch, if_kind, _) -> - let descr = match is_true_branch, if_kind with - | true, Sil.Ik_if -> - "Taking true branch" - | false, Sil.Ik_if -> - "Taking false branch" - | true, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) -> - "Loop condition is true. Entering loop body" - | false, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) -> - "Loop condition is false. Leaving loop" - | true, Sil.Ik_switch -> "Switch condition is true. Entering switch case" - | false, Sil.Ik_switch -> "Switch condition is false. Skipping switch case" - | true, (Sil.Ik_bexp | Sil.Ik_land_lor) -> - "Condition is true" - | false, (Sil.Ik_bexp | Sil.Ik_land_lor) -> - "Condition is false" in - let node_tags = [Errlog.Condition is_true_branch] in - trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace - | Procdesc.Node.Exit_node pname -> - let descr = "return from a call to " ^ (Typ.Procname.to_string pname) in - let node_tags = [Errlog.Procedure_end pname] in - trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace - | _ -> - let descr, node_tags = - match exn_opt with - | None -> "", [] - | Some exn_name -> - let exn_str = Typ.Name.name exn_name in - let desc = - if String.is_empty exn_str then "exception" else "exception " ^ exn_str in - desc, [Errlog.Exception exn_name] in - let descr = - match get_description path with - | Some path_descr -> - if String.length descr > 0 then descr^" "^path_descr else path_descr - | None -> descr in - trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace - end - | _, None -> - () in - iter_shortest_sequence g pos_opt path; + | _, Some curr_node + -> ( + let curr_loc = Procdesc.Node.get_loc curr_node in + match Procdesc.Node.get_kind curr_node with + | Procdesc.Node.Join_node + -> () (* omit join nodes from error traces *) + | Procdesc.Node.Start_node pname + -> let descr = "start of procedure " ^ Typ.Procname.to_simplified_string pname in + let node_tags = [Errlog.Procedure_start pname] in + trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace + | Procdesc.Node.Prune_node (is_true_branch, if_kind, _) + -> let descr = + match (is_true_branch, if_kind) with + | true, Sil.Ik_if + -> "Taking true branch" + | false, Sil.Ik_if + -> "Taking false branch" + | true, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) + -> "Loop condition is true. Entering loop body" + | false, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) + -> "Loop condition is false. Leaving loop" + | true, Sil.Ik_switch + -> "Switch condition is true. Entering switch case" + | false, Sil.Ik_switch + -> "Switch condition is false. Skipping switch case" + | true, (Sil.Ik_bexp | Sil.Ik_land_lor) + -> "Condition is true" + | false, (Sil.Ik_bexp | Sil.Ik_land_lor) + -> "Condition is false" + in + let node_tags = [Errlog.Condition is_true_branch] in + trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace + | Procdesc.Node.Exit_node pname + -> let descr = "return from a call to " ^ Typ.Procname.to_string pname in + let node_tags = [Errlog.Procedure_end pname] in + trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace + | _ + -> let descr, node_tags = + match exn_opt with + | None + -> ("", []) + | Some exn_name + -> let exn_str = Typ.Name.name exn_name in + let desc = + if String.is_empty exn_str then "exception" else "exception " ^ exn_str + in + (desc, [Errlog.Exception exn_name]) + in + let descr = + match get_description path with + | Some path_descr + -> if String.length descr > 0 then descr ^ " " ^ path_descr else path_descr + | None + -> descr + in + trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace ) + | _, None + -> () + in + iter_shortest_sequence g pos_opt path ; let compare lt1 lt2 = let n = Int.compare lt1.Errlog.lt_level lt2.Errlog.lt_level in - if n <> 0 then n else Location.compare lt1.Errlog.lt_loc lt2.Errlog.lt_loc in + if n <> 0 then n else Location.compare lt1.Errlog.lt_loc lt2.Errlog.lt_loc + in let relevant lt = lt.Errlog.lt_node_tags <> [] in IList.remove_irrelevant_duplicates compare relevant (List.rev !trace) - end + (* =============== END of the Path module ===============*) module PropMap = Caml.Map.Make (struct - type t = Prop.normal Prop.t - let compare = Prop.compare_prop - end) + type t = Prop.normal Prop.t + + let compare = Prop.compare_prop +end) (* =============== START of the PathSet module ===============*) module PathSet : sig type t - (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) val add_renamed_prop : Prop.normal Prop.t -> Path.t -> t -> t + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) - (** dump the pathset *) val d : t -> unit + (** dump the pathset *) - (** difference between two pathsets *) val diff : t -> t -> t + (** difference between two pathsets *) - (** empty pathset *) val empty : t + (** empty pathset *) - (** list of elements in a pathset *) val elements : t -> (Prop.normal Prop.t * Path.t) list + (** list of elements in a pathset *) - (** equality for pathsets *) val equal : t -> t -> bool + (** equality for pathsets *) - (** filter a pathset on the prop component *) val filter : (Prop.normal Prop.t -> bool) -> t -> t + (** filter a pathset on the prop component *) - (** find the list of props whose associated path contains the given path *) val filter_path : Path.t -> t -> Prop.normal Prop.t list + (** find the list of props whose associated path contains the given path *) - (** fold over a pathset *) val fold : (Prop.normal Prop.t -> Path.t -> 'a -> 'a) -> t -> 'a -> 'a + (** fold over a pathset *) + val from_renamed_list : (Prop.normal Prop.t * Path.t) list -> t (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) - val from_renamed_list: (Prop.normal Prop.t * Path.t) list -> t - (** check whether the pathset is empty *) val is_empty : t -> bool + (** check whether the pathset is empty *) - (** iterate over a pathset *) val iter : (Prop.normal Prop.t -> Path.t -> unit) -> t -> unit + (** iterate over a pathset *) - (** map over the prop component of a pathset *) val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t + (** map over the prop component of a pathset *) - (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t + (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) - (** partition a pathset on the prop component *) val partition : (Prop.normal Prop.t -> bool) -> t -> t * t + (** partition a pathset on the prop component *) - (** pretty print the pathset *) val pp : Pp.env -> Format.formatter -> t -> unit + (** pretty print the pathset *) - (** number of elements in the pathset *) val size : t -> int + (** number of elements in the pathset *) - (** convert to a list of props *) val to_proplist : t -> Prop.normal Prop.t list + (** convert to a list of props *) - (** convert to a set of props *) val to_propset : Tenv.t -> t -> Propset.t + (** convert to a set of props *) - (** union of two pathsets *) val union : t -> t -> t + (** union of two pathsets *) end = struct type t = Path.t PropMap.t - let equal = PropMap.equal (fun _ _ -> true) (* only discriminate props, and ignore paths *) + let equal = PropMap.equal (fun _ _ -> true) + + (* only discriminate props, and ignore paths *) let empty : t = PropMap.empty let elements ps = let plist = ref [] in let f prop path = plist := (prop, path) :: !plist in - PropMap.iter f ps; - !plist + PropMap.iter f ps ; !plist - let to_proplist ps = - List.map ~f:fst (elements ps) + let to_proplist ps = List.map ~f:fst (elements ps) - let to_propset tenv ps = - Propset.from_proplist tenv (to_proplist ps) + let to_propset tenv ps = Propset.from_proplist tenv (to_proplist ps) let filter f ps = let elements = ref [] in - PropMap.iter (fun p _ -> elements := p :: !elements) ps; - elements := List.filter ~f:(fun p -> not (f p)) !elements; + PropMap.iter (fun p _ -> elements := p :: !elements) ps ; + elements := List.filter ~f:(fun p -> not (f p)) !elements ; let filtered_map = ref ps in - List.iter ~f:(fun p -> filtered_map := PropMap.remove p !filtered_map) !elements; + List.iter ~f:(fun p -> filtered_map := PropMap.remove p !filtered_map) !elements ; !filtered_map let partition f ps = let elements = ref [] in - PropMap.iter (fun p _ -> elements := p :: !elements) ps; - let el1, el2 = ref ps, ref ps in + PropMap.iter (fun p _ -> elements := p :: !elements) ps ; + let el1, el2 = (ref ps, ref ps) in List.iter ~f:(fun p -> if f p then el2 := PropMap.remove p !el2 else el1 := PropMap.remove p !el1) - !elements; - !el1, !el2 + !elements ; + (!el1, !el2) (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) let add_renamed_prop (p: Prop.normal Prop.t) (path: Path.t) (ps: t) : t = @@ -630,18 +628,19 @@ end = struct try let path_old = PropMap.find p ps in Path.join path_old path - with Not_found -> path in + with Not_found -> path + in PropMap.add p path_new ps - let union (ps1: t) (ps2: t) : t = - PropMap.fold add_renamed_prop ps1 ps2 + let union (ps1: t) (ps2: t) : t = PropMap.fold add_renamed_prop ps1 ps2 (** check if the nodes in path p1 are a subset of those in p2 (not trace subset) *) let path_nodes_subset p1 p2 = let get_nodes p = let s = ref Procdesc.NodeSet.empty in - Path.iter_all_nodes_nocalls (fun n -> s := Procdesc.NodeSet.add n !s) p; - !s in + Path.iter_all_nodes_nocalls (fun n -> s := Procdesc.NodeSet.add n !s) p ; + !s + in Procdesc.NodeSet.subset (get_nodes p1) (get_nodes p2) (** difference between pathsets for the differential fixpoint *) @@ -652,10 +651,9 @@ end = struct let path_old = PropMap.find p !res in if path_nodes_subset path path_old (* do not propagate new path if it has no new nodes *) then res := PropMap.remove p !res - with Not_found -> - res := PropMap.remove p !res in - PropMap.iter rem ps2; - !res + with Not_found -> res := PropMap.remove p !res + in + PropMap.iter rem ps2 ; !res let is_empty = PropMap.is_empty @@ -665,42 +663,38 @@ end = struct let map_option f ps = let res = ref empty in - let do_elem prop path = match f prop with - | None -> () - | Some prop' -> res := add_renamed_prop prop' path !res in - iter do_elem ps; - !res + let do_elem prop path = + match f prop with None -> () | Some prop' -> res := add_renamed_prop prop' path !res + in + iter do_elem ps ; !res - let map f ps = - map_option (fun p -> Some (f p)) ps + let map f ps = map_option (fun p -> Some (f p)) ps let size ps = let res = ref 0 in let add _ _ = incr res in - let () = PropMap.iter add ps - in !res + let () = PropMap.iter add ps in + !res let pp pe fmt ps = let count = ref 0 in - let pp_path fmt path = - F.fprintf fmt "[path: %a@\n%a]" Path.pp_stats path Path.pp path in + let pp_path fmt path = F.fprintf fmt "[path: %a@\n%a]" Path.pp_stats path Path.pp path in let f prop path = - incr count; - F.fprintf fmt "PROP %d:%a@\n%a@\n" !count pp_path path (Prop.pp_prop pe) prop in + incr count ; + F.fprintf fmt "PROP %d:%a@\n%a@\n" !count pp_path path (Prop.pp_prop pe) prop + in iter f ps let d (ps: t) = L.add_print_action (L.PTpathset, Obj.repr ps) let filter_path path ps = let plist = ref [] in - let f prop path' = - if Path.contains path path' - then plist := prop :: !plist in - iter f ps; - !plist + let f prop path' = if Path.contains path path' then plist := prop :: !plist in + iter f ps ; !plist (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) - let from_renamed_list (pl : ('a Prop.t * Path.t) list) : t = + let from_renamed_list (pl: ('a Prop.t * Path.t) list) : t = List.fold ~f:(fun ps (p, pa) -> add_renamed_prop p pa ps) ~init:empty pl end + (* =============== END of the PathSet module ===============*) diff --git a/infer/src/backend/paths.mli b/infer/src/backend/paths.mli index 51f904a1b..4625d58a8 100644 --- a/infer/src/backend/paths.mli +++ b/infer/src/backend/paths.mli @@ -18,120 +18,120 @@ module Path : sig type session = int - (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) val add_call : bool -> t -> Typ.Procname.t -> t -> t + (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) - (** add a call to a procname that's had to be skipped, along with the reason *) val add_skipped_call : t -> Typ.Procname.t -> string -> t + (** add a call to a procname that's had to be skipped, along with the reason *) - (** check whether a path contains another path *) val contains : t -> t -> bool + (** check whether a path contains another path *) - (** check wether the path contains the given position *) val contains_position : t -> PredSymb.path_pos -> bool + (** check wether the path contains the given position *) - (** Create the location trace of the path, up to the path position if specified *) val create_loc_trace : t -> PredSymb.path_pos option -> Errlog.loc_trace + (** Create the location trace of the path, up to the path position if specified *) - (** return the current node of the path *) val curr_node : t -> Procdesc.Node.t option + (** return the current node of the path *) - (** dump a path *) val d : t -> unit + (** dump a path *) - (** dump statistics of the path *) val d_stats : t -> unit + (** dump statistics of the path *) - (** extend a path with a new node reached from the given session, with an optional string for exceptions *) val extend : Procdesc.Node.t -> Typ.Name.t option -> session -> t -> t + (** extend a path with a new node reached from the given session, with an optional string for exceptions *) val add_description : t -> string -> t - (** iterate over each node in the path, excluding calls, once *) val iter_all_nodes_nocalls : (Procdesc.Node.t -> unit) -> t -> unit + (** iterate over each node in the path, excluding calls, once *) + val iter_shortest_sequence : + (int -> t -> int -> Typ.Name.t option -> unit) -> PredSymb.path_pos option -> t -> unit (** iterate over the shortest sequence belonging to the path, restricting to those containing the given position if given. Do not iterate past the last occurrence of the given position. [f level path session exn_opt] is passed the current nesting [level] and [path] and previous [session] and possible exception [exn_opt] *) - val iter_shortest_sequence : - (int -> t -> int -> Typ.Name.t option -> unit) -> PredSymb.path_pos option -> t -> unit - (** join two paths *) val join : t -> t -> t + (** join two paths *) - (** pretty print a path *) val pp : Format.formatter -> t -> unit + (** pretty print a path *) - (** pretty print statistics of the path *) val pp_stats : Format.formatter -> t -> unit + (** pretty print statistics of the path *) - (** create a new path with given start node *) val start : Procdesc.Node.t -> t + (** create a new path with given start node *) end (** Set of (prop,path) pairs, where the identity is given by prop *) module PathSet : sig type t - (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) val add_renamed_prop : Prop.normal Prop.t -> Path.t -> t -> t + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) - (** dump the pathset *) val d : t -> unit + (** dump the pathset *) - (** difference between two pathsets *) val diff : t -> t -> t + (** difference between two pathsets *) - (** empty pathset *) val empty : t + (** empty pathset *) - (** list of elements in a pathset *) val elements : t -> (Prop.normal Prop.t * Path.t) list + (** list of elements in a pathset *) - (** equality for pathsets *) val equal : t -> t -> bool + (** equality for pathsets *) - (** filter a pathset on the prop component *) val filter : (Prop.normal Prop.t -> bool) -> t -> t + (** filter a pathset on the prop component *) - (** find the list of props whose associated path contains the given path *) val filter_path : Path.t -> t -> Prop.normal Prop.t list + (** find the list of props whose associated path contains the given path *) - (** fold over a pathset *) val fold : (Prop.normal Prop.t -> Path.t -> 'a -> 'a) -> t -> 'a -> 'a + (** fold over a pathset *) + val from_renamed_list : (Prop.normal Prop.t * Path.t) list -> t (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) - val from_renamed_list: (Prop.normal Prop.t * Path.t) list -> t - (** check whether the pathset is empty *) val is_empty : t -> bool + (** check whether the pathset is empty *) - (** iterate over a pathset *) val iter : (Prop.normal Prop.t -> Path.t -> unit) -> t -> unit + (** iterate over a pathset *) - (** map over the prop component of a pathset. *) val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t + (** map over the prop component of a pathset. *) - (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t + (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) - (** partition a pathset on the prop component *) val partition : (Prop.normal Prop.t -> bool) -> t -> t * t + (** partition a pathset on the prop component *) - (** pretty print the pathset *) val pp : Pp.env -> Format.formatter -> t -> unit + (** pretty print the pathset *) - (** number of elements in the pathset *) val size : t -> int + (** number of elements in the pathset *) - (** convert to a list of props *) val to_proplist : t -> Prop.normal Prop.t list + (** convert to a list of props *) - (** convert to a set of props *) val to_propset : Tenv.t -> t -> Propset.t + (** convert to a set of props *) - (** union of two pathsets *) val union : t -> t -> t + (** union of two pathsets *) end diff --git a/infer/src/backend/preanal.ml b/infer/src/backend/preanal.ml index ef3de930b..4a1c1ea97 100644 --- a/infer/src/backend/preanal.ml +++ b/infer/src/backend/preanal.ml @@ -13,52 +13,61 @@ open! PVariant (** mutate the cfg/cg to add dynamic dispatch handling *) let add_dispatch_calls pdesc cg tenv = - let sound_dynamic_dispatch = (Config.dynamic_dispatch = `Sound) in + let sound_dynamic_dispatch = Config.dynamic_dispatch = `Sound in let node_add_dispatch_calls caller_pname node = let call_flags_is_dispatch call_flags = (* if sound dispatch is turned off, only consider dispatch for interface calls *) - (sound_dynamic_dispatch && call_flags.CallFlags.cf_virtual) || - call_flags.CallFlags.cf_interface in + sound_dynamic_dispatch && call_flags.CallFlags.cf_virtual + || call_flags.CallFlags.cf_interface + in let instr_is_dispatch_call = function - | Sil.Call (_, _, _, _, call_flags) -> call_flags_is_dispatch call_flags - | _ -> false in - let has_dispatch_call instrs = - List.exists ~f:instr_is_dispatch_call instrs in + | Sil.Call (_, _, _, _, call_flags) + -> call_flags_is_dispatch call_flags + | _ + -> false + in + let has_dispatch_call instrs = List.exists ~f:instr_is_dispatch_call instrs in let replace_dispatch_calls = function - | Sil.Call (ret_id, (Exp.Const (Const.Cfun callee_pname) as call_exp), - (((_, receiver_typ) :: _) as args), loc, call_flags) as instr - when call_flags_is_dispatch call_flags -> + | Sil.Call + ( ret_id + , (Exp.Const Const.Cfun callee_pname as call_exp) + , ((_, receiver_typ) :: _ as args) + , loc + , call_flags ) as instr + when call_flags_is_dispatch call_flags + -> ( (* the frontend should not populate the list of targets *) - assert (List.is_empty call_flags.CallFlags.cf_targets); - let receiver_typ_no_ptr = match receiver_typ.Typ.desc with - | Typ.Tptr (typ', _) -> - typ' - | _ -> - receiver_typ in + assert (List.is_empty call_flags.CallFlags.cf_targets) ; + let receiver_typ_no_ptr = + match receiver_typ.Typ.desc with Typ.Tptr (typ', _) -> typ' | _ -> receiver_typ + in let sorted_overrides = let overrides = Prover.get_overrides_of tenv receiver_typ_no_ptr callee_pname in - List.sort ~cmp:(fun (_, p1) (_, p2) -> Typ.Procname.compare p1 p2) overrides in - (match sorted_overrides with - | ((_, target_pname) :: _) as all_targets -> - let targets_to_add = - if sound_dynamic_dispatch then - List.map ~f:snd all_targets - else - (* if sound dispatch is turned off, consider only the first target. we do this + List.sort ~cmp:(fun (_, p1) (_, p2) -> Typ.Procname.compare p1 p2) overrides + in + match sorted_overrides with + | (_, target_pname) :: _ as all_targets + -> let targets_to_add = + if sound_dynamic_dispatch then List.map ~f:snd all_targets + else + (* if sound dispatch is turned off, consider only the first target. we do this because choosing all targets is too expensive for everyday use *) - [target_pname] in - List.iter - ~f:(fun target_pname -> Cg.add_edge cg caller_pname target_pname) - targets_to_add; - let call_flags' = { call_flags with CallFlags.cf_targets = targets_to_add; } in - Sil.Call (ret_id, call_exp, args, loc, call_flags') - | [] -> instr) - - | instr -> instr in + [target_pname] + in + List.iter + ~f:(fun target_pname -> Cg.add_edge cg caller_pname target_pname) + targets_to_add ; + let call_flags' = {call_flags with CallFlags.cf_targets= targets_to_add} in + Sil.Call (ret_id, call_exp, args, loc, call_flags') + | [] + -> instr ) + | instr + -> instr + in let instrs = Procdesc.Node.get_instrs node in - if has_dispatch_call instrs then - List.map ~f:replace_dispatch_calls instrs - |> Procdesc.Node.replace_instrs node in + if has_dispatch_call instrs then List.map ~f:replace_dispatch_calls instrs + |> Procdesc.Node.replace_instrs node + in let pname = Procdesc.get_proc_name pdesc in Procdesc.iter_nodes (node_add_dispatch_calls pname) pdesc @@ -67,34 +76,27 @@ let add_abstraction_instructions pdesc = let open Procdesc in (* true if there is a succ node s.t.: it is an exit node, or the succ of >1 nodes *) let converging_node node = - let is_exit node = match Node.get_kind node with - | Node.Exit_node _ -> true - | _ -> false in + let is_exit node = match Node.get_kind node with Node.Exit_node _ -> true | _ -> false in let succ_nodes = Node.get_succs node in if List.exists ~f:is_exit succ_nodes then true - else match succ_nodes with - | [] -> false - | [h] -> List.length (Node.get_preds h) > 1 - | _ -> false in + else + match succ_nodes with [] -> false | [h] -> List.length (Node.get_preds h) > 1 | _ -> false + in let node_requires_abstraction node = match Node.get_kind node with - | Node.Start_node _ - | Node.Join_node -> - false - | Node.Exit_node _ - | Node.Stmt_node _ - | Node.Prune_node _ - | Node.Skip_node _ -> - converging_node node in + | Node.Start_node _ | Node.Join_node + -> false + | Node.Exit_node _ | Node.Stmt_node _ | Node.Prune_node _ | Node.Skip_node _ + -> converging_node node + in let do_node node = let loc = Node.get_last_loc node in - if node_requires_abstraction node then Node.append_instrs node [Sil.Abstract loc] in + if node_requires_abstraction node then Node.append_instrs node [Sil.Abstract loc] + in Procdesc.iter_nodes do_node pdesc -module BackwardCfg = ProcCfg.OneInstrPerNode(ProcCfg.Backward(ProcCfg.Exceptional)) - +module BackwardCfg = ProcCfg.OneInstrPerNode (ProcCfg.Backward (ProcCfg.Exceptional)) module LivenessAnalysis = AbstractInterpreter.Make (BackwardCfg) (Liveness.TransferFunctions) - module VarDomain = Liveness.Domain (** computes the non-nullified reaching definitions at the end of each node by building on the @@ -109,127 +111,125 @@ module NullifyTransferFunctions = struct (* (reaching non-nullified vars) * (vars to nullify) *) module Domain = AbstractDomain.Pair (VarDomain) (VarDomain) module CFG = ProcCfg.Exceptional + type extras = LivenessAnalysis.invariant_map - let postprocess ((reaching_defs, _) as astate) node { ProcData.extras; } = - let node_id = Procdesc.Node.get_id (CFG.underlying_node node), ProcCfg.Node_index in + let postprocess (reaching_defs, _ as astate) node {ProcData.extras} = + let node_id = (Procdesc.Node.get_id (CFG.underlying_node node), ProcCfg.Node_index) in match LivenessAnalysis.extract_state node_id extras with (* note: because the analysis is backward, post and pre are reversed *) - | Some { AbstractInterpreter.post = live_before; pre = live_after; } -> - let to_nullify = VarDomain.diff (VarDomain.union live_before reaching_defs) live_after in + | Some {AbstractInterpreter.post= live_before; pre= live_after} + -> let to_nullify = VarDomain.diff (VarDomain.union live_before reaching_defs) live_after in let reaching_defs' = VarDomain.diff reaching_defs to_nullify in (reaching_defs', to_nullify) - | None -> astate + | None + -> astate let cache_node = ref (Procdesc.Node.dummy None) + let cache_instr = ref Sil.skip_instr let last_instr_in_node node = let get_last_instr () = let instrs = CFG.instrs node in - match List.rev instrs with - | instr :: _ -> instr - | [] -> Sil.skip_instr in - if phys_equal node !cache_node - then !cache_instr + match List.rev instrs with instr :: _ -> instr | [] -> Sil.skip_instr + in + if phys_equal node !cache_node then !cache_instr else - begin - let last_instr = get_last_instr () in - cache_node := node; - cache_instr := last_instr; - last_instr - end + let last_instr = get_last_instr () in + cache_node := node ; + cache_instr := last_instr ; + last_instr - let is_last_instr_in_node instr node = - phys_equal (last_instr_in_node node) instr + let is_last_instr_in_node instr node = phys_equal (last_instr_in_node node) instr - let exec_instr ((active_defs, to_nullify) as astate) extras node instr = - let astate' = match instr with - | Sil.Load (lhs_id, _, _, _) -> - VarDomain.add (Var.of_id lhs_id) active_defs, to_nullify - | Sil.Call (lhs_id, _, _, _, _) -> - let active_defs' = + let exec_instr (active_defs, to_nullify as astate) extras node instr = + let astate' = + match instr with + | Sil.Load (lhs_id, _, _, _) + -> (VarDomain.add (Var.of_id lhs_id) active_defs, to_nullify) + | Sil.Call (lhs_id, _, _, _, _) + -> let active_defs' = Option.value_map ~f:(fun (id, _) -> VarDomain.add (Var.of_id id) active_defs) - ~default:active_defs - lhs_id in - active_defs', to_nullify - | Sil.Store (Exp.Lvar lhs_pvar, _, _, _) -> - VarDomain.add (Var.of_pvar lhs_pvar) active_defs, to_nullify - | Sil.Store _ | Prune _ | Declare_locals _ | Remove_temps _ | Abstract _ -> - astate - | Sil.Nullify _ -> - failwith "Should not add nullify instructions before running nullify analysis!" in - if is_last_instr_in_node instr node - then postprocess astate' node extras - else astate' + ~default:active_defs lhs_id + in + (active_defs', to_nullify) + | Sil.Store (Exp.Lvar lhs_pvar, _, _, _) + -> (VarDomain.add (Var.of_pvar lhs_pvar) active_defs, to_nullify) + | Sil.Store _ | Prune _ | Declare_locals _ | Remove_temps _ | Abstract _ + -> astate + | Sil.Nullify _ + -> failwith "Should not add nullify instructions before running nullify analysis!" + in + if is_last_instr_in_node instr node then postprocess astate' node extras else astate' end module NullifyAnalysis = - AbstractInterpreter.MakeNoCFG - (Scheduler.ReversePostorder (ProcCfg.Exceptional)) + AbstractInterpreter.MakeNoCFG (Scheduler.ReversePostorder (ProcCfg.Exceptional)) (NullifyTransferFunctions) let add_nullify_instrs pdesc tenv liveness_inv_map = let address_taken_vars = - if Typ.Procname.is_java (Procdesc.get_proc_name pdesc) - then AddressTaken.Domain.empty (* can't take the address of a variable in Java *) + if Typ.Procname.is_java (Procdesc.get_proc_name pdesc) then AddressTaken.Domain.empty + (* can't take the address of a variable in Java *) else let initial = AddressTaken.Domain.empty in match AddressTaken.Analyzer.compute_post (ProcData.make_default pdesc tenv) ~initial with - | Some post -> post - | None -> AddressTaken.Domain.empty in - + | Some post + -> post + | None + -> AddressTaken.Domain.empty + in let nullify_proc_cfg = ProcCfg.Exceptional.from_pdesc pdesc in let nullify_proc_data = ProcData.make pdesc tenv liveness_inv_map in - let initial = VarDomain.empty, VarDomain.empty in + let initial = (VarDomain.empty, VarDomain.empty) in let nullify_inv_map = - NullifyAnalysis.exec_cfg nullify_proc_cfg nullify_proc_data ~initial ~debug:false in - + NullifyAnalysis.exec_cfg nullify_proc_cfg nullify_proc_data ~initial ~debug:false + in (* only nullify pvars that are local; don't nullify those that can escape *) - let is_local pvar = - not (Pvar.is_return pvar || Pvar.is_global pvar) in - + let is_local pvar = not (Pvar.is_return pvar || Pvar.is_global pvar) in let node_add_nullify_instructions node pvars = let loc = Procdesc.Node.get_last_loc node in let nullify_instrs = - List.filter ~f:is_local pvars - |> List.map ~f:(fun pvar -> Sil.Nullify (pvar, loc)) in - if nullify_instrs <> [] - then Procdesc.Node.append_instrs node (List.rev nullify_instrs) in - + List.filter ~f:is_local pvars |> List.map ~f:(fun pvar -> Sil.Nullify (pvar, loc)) + in + if nullify_instrs <> [] then Procdesc.Node.append_instrs node (List.rev nullify_instrs) + in let node_add_removetmps_instructions node ids = if ids <> [] then let loc = Procdesc.Node.get_last_loc node in - Procdesc.Node.append_instrs node [Sil.Remove_temps (List.rev ids, loc)] in - + Procdesc.Node.append_instrs node [Sil.Remove_temps (List.rev ids, loc)] + in List.iter ~f:(fun node -> - match NullifyAnalysis.extract_post (ProcCfg.Exceptional.id node) nullify_inv_map with - | Some (_, to_nullify) -> - let pvars_to_nullify, ids_to_remove = - VarDomain.fold - (fun var (pvars_acc, ids_acc) -> match Var.to_exp var with - (* we nullify all address taken variables at the end of the procedure *) - | Exp.Lvar pvar when not (AddressTaken.Domain.mem pvar address_taken_vars) -> - pvar :: pvars_acc, ids_acc - | Exp.Var id -> - pvars_acc, id :: ids_acc - | _ -> pvars_acc, ids_acc) - to_nullify - ([], []) in - node_add_removetmps_instructions node ids_to_remove; - node_add_nullify_instructions node pvars_to_nullify - | None -> ()) - (ProcCfg.Exceptional.nodes nullify_proc_cfg); + match NullifyAnalysis.extract_post (ProcCfg.Exceptional.id node) nullify_inv_map with + | Some (_, to_nullify) + -> let pvars_to_nullify, ids_to_remove = + VarDomain.fold + (fun var (pvars_acc, ids_acc) -> + match Var.to_exp var with + (* we nullify all address taken variables at the end of the procedure *) + | Exp.Lvar pvar + when not (AddressTaken.Domain.mem pvar address_taken_vars) + -> (pvar :: pvars_acc, ids_acc) + | Exp.Var id + -> (pvars_acc, id :: ids_acc) + | _ + -> (pvars_acc, ids_acc)) + to_nullify ([], []) + in + node_add_removetmps_instructions node ids_to_remove ; + node_add_nullify_instructions node pvars_to_nullify + | None + -> ()) + (ProcCfg.Exceptional.nodes nullify_proc_cfg) ; (* nullify all address taken variables *) - if not (AddressTaken.Domain.is_empty address_taken_vars) - then + if not (AddressTaken.Domain.is_empty address_taken_vars) then let exit_node = ProcCfg.Exceptional.exit_node nullify_proc_cfg in node_add_nullify_instructions exit_node (AddressTaken.Domain.elements address_taken_vars) -module ExceptionalOneInstrPerNodeCfg = ProcCfg.OneInstrPerNode(ProcCfg.Exceptional) - +module ExceptionalOneInstrPerNodeCfg = ProcCfg.OneInstrPerNode (ProcCfg.Exceptional) module CopyProp = AbstractInterpreter.Make (ExceptionalOneInstrPerNodeCfg) (CopyPropagation.TransferFunctions) @@ -237,7 +237,8 @@ let do_copy_propagation pdesc tenv = let proc_cfg = ExceptionalOneInstrPerNodeCfg.from_pdesc pdesc in let initial = CopyPropagation.Domain.empty in let copy_prop_inv_map = - CopyProp.exec_cfg proc_cfg (ProcData.make_default pdesc tenv) ~initial ~debug:false in + CopyProp.exec_cfg proc_cfg (ProcData.make_default pdesc tenv) ~initial ~debug:false + in (* [var_map] represents a chain of variable. copies v_0 -> v_1 ... -> v_n. starting from some ident v_j, we want to walk backward through the chain to find the lowest v_i that is also an ident. *) @@ -246,60 +247,51 @@ let do_copy_propagation pdesc tenv = let rec id_sub_inner var_map var last_id = try let var' = CopyPropagation.Domain.find var var_map in - let last_id' = match var' with - | Var.LogicalVar id -> id - | _ -> last_id in + let last_id' = match var' with Var.LogicalVar id -> id | _ -> last_id in id_sub_inner var_map var' last_id' - with Not_found -> - Exp.Var last_id in - id_sub_inner var_map (Var.of_id id) id in - + with Not_found -> Exp.Var last_id + in + id_sub_inner var_map (Var.of_id id) id + in (* perform copy-propagation on each instruction in [node] *) let rev_transform_node_instrs node = List.fold ~f:(fun (instrs, changed) (instr, id_opt) -> - match id_opt with - | Some id -> - begin - match CopyProp.extract_pre id copy_prop_inv_map with - | Some pre when not (CopyPropagation.Domain.is_empty pre) -> - let instr' = - Sil.instr_sub_ids ~sub_id_binders:false (`Exp (id_sub pre)) instr in - instr' :: instrs, changed || not (phys_equal instr' instr) - | _ -> - instr :: instrs, changed - end - | None -> instr :: instrs, changed) - ~init:([], false) - (ExceptionalOneInstrPerNodeCfg.instr_ids node) in - + match id_opt with + | Some id -> ( + match CopyProp.extract_pre id copy_prop_inv_map with + | Some pre when not (CopyPropagation.Domain.is_empty pre) + -> let instr' = Sil.instr_sub_ids ~sub_id_binders:false (`Exp (id_sub pre)) instr in + (instr' :: instrs, changed || not (phys_equal instr' instr)) + | _ + -> (instr :: instrs, changed) ) + | None + -> (instr :: instrs, changed)) + ~init:([], false) (ExceptionalOneInstrPerNodeCfg.instr_ids node) + in List.iter ~f:(fun node -> - let instrs, changed = rev_transform_node_instrs node in - if changed - then Procdesc.Node.replace_instrs node (List.rev instrs)) + let instrs, changed = rev_transform_node_instrs node in + if changed then Procdesc.Node.replace_instrs node (List.rev instrs)) (Procdesc.get_nodes pdesc) let do_liveness pdesc tenv = let liveness_proc_cfg = BackwardCfg.from_pdesc pdesc in let initial = Liveness.Domain.empty in let liveness_inv_map = - LivenessAnalysis.exec_cfg - liveness_proc_cfg - (ProcData.make_default pdesc tenv) - ~initial - ~debug:false in - if Config.copy_propagation then do_copy_propagation pdesc tenv; - add_nullify_instrs pdesc tenv liveness_inv_map; + LivenessAnalysis.exec_cfg liveness_proc_cfg (ProcData.make_default pdesc tenv) ~initial + ~debug:false + in + if Config.copy_propagation then do_copy_propagation pdesc tenv ; + add_nullify_instrs pdesc tenv liveness_inv_map ; Procdesc.signal_did_preanalysis pdesc let do_abstraction pdesc = - add_abstraction_instructions pdesc; - Procdesc.signal_did_preanalysis pdesc + add_abstraction_instructions pdesc ; Procdesc.signal_did_preanalysis pdesc let do_dynamic_dispatch pdesc cg tenv = let pname = Procdesc.get_proc_name pdesc in - if Typ.Procname.is_java pname && - (Config.dynamic_dispatch = `Interface || Config.dynamic_dispatch = `Sound) - then add_dispatch_calls pdesc cg tenv; + if Typ.Procname.is_java pname + && (Config.dynamic_dispatch = `Interface || Config.dynamic_dispatch = `Sound) + then add_dispatch_calls pdesc cg tenv ; Procdesc.signal_did_preanalysis pdesc diff --git a/infer/src/backend/preanal.mli b/infer/src/backend/preanal.mli index 8948643b5..cb47def9f 100644 --- a/infer/src/backend/preanal.mli +++ b/infer/src/backend/preanal.mli @@ -12,13 +12,13 @@ open! IStd (** Various preanalysis passes for transforming the IR in useful ways *) +val do_liveness : Procdesc.t -> Tenv.t -> unit (** perform liveness analysis and insert Nullify/Remove_temps instructions into the IR to make it easy for analyses to do abstract garbage collection *) -val do_liveness : Procdesc.t -> Tenv.t -> unit +val do_abstraction : Procdesc.t -> unit (** add Abstract instructions into the IR to give hints about when abstraction should be performed *) -val do_abstraction : Procdesc.t -> unit -(** add possible dynamic dispatch targets to the call_flags of each call site *) val do_dynamic_dispatch : Procdesc.t -> Cg.t -> Tenv.t -> unit +(** add possible dynamic dispatch targets to the call_flags of each call site *) diff --git a/infer/src/backend/printer.ml b/infer/src/backend/printer.ml index 5714454b6..bcc8baff1 100644 --- a/infer/src/backend/printer.ml +++ b/infer/src/backend/printer.ml @@ -18,13 +18,11 @@ module F = Format (** Module to read specific lines from files. The data from any file will stay in memory until the handle is collected by the gc. *) -module LineReader = -struct +module LineReader = struct (** Map a file name to an array of string, one for each line in the file. *) type t = (SourceFile.t, string array) Hashtbl.t - let create () = - Hashtbl.create 1 + let create () = Hashtbl.create 1 let read_file fname = let cin = In_channel.create fname in @@ -34,57 +32,56 @@ struct let line_raw = In_channel.input_line_exn cin in let line = let len = String.length line_raw in - if len > 0 && Char.equal (String.get line_raw (len -1)) '\013' then - String.sub line_raw ~pos:0 ~len:(len -1) - else line_raw in + if len > 0 && Char.equal line_raw.[len - 1] '\r' then + String.sub line_raw ~pos:0 ~len:(len - 1) + else line_raw + in lines := line :: !lines - done; - assert false (* execution never reaches here *) + done ; + assert false + (* execution never reaches here *) with End_of_file -> - (In_channel.close cin; - Array.of_list (List.rev !lines)) + In_channel.close cin ; + Array.of_list (List.rev !lines) let file_data (hash: t) fname = - try - Some (Hashtbl.find hash fname) + try Some (Hashtbl.find hash fname) with Not_found -> - try - let lines_arr = read_file (SourceFile.to_abs_path fname) in - Hashtbl.add hash fname lines_arr; - Some lines_arr - with exn when SymOp.exn_not_failure exn -> None + try + let lines_arr = read_file (SourceFile.to_abs_path fname) in + Hashtbl.add hash fname lines_arr ; Some lines_arr + with exn when SymOp.exn_not_failure exn -> None let from_file_linenum_original hash fname linenum = match file_data hash fname with - | None -> None - | Some lines_arr -> - if linenum > 0 && linenum <= Array.length lines_arr - then Some lines_arr.(linenum -1) + | None + -> None + | Some lines_arr + -> if linenum > 0 && linenum <= Array.length lines_arr then Some lines_arr.(linenum - 1) else None - let from_file_linenum hash fname linenum = - from_file_linenum_original hash fname linenum + let from_file_linenum hash fname linenum = from_file_linenum_original hash fname linenum - let from_loc hash loc = - from_file_linenum hash loc.Location.file loc.Location.line + let from_loc hash loc = from_file_linenum hash loc.Location.file loc.Location.line end - (** Current formatter for the html output *) let curr_html_formatter = ref F.std_formatter (** Return true if the node was visited during footprint and during re-execution*) let node_is_visited node = match Specs.get_summary (Procdesc.Node.get_proc_name node) with - | None -> - false, false - | Some summary -> - let stats = summary.Specs.stats in + | None + -> (false, false) + | Some summary + -> let stats = summary.Specs.stats in let is_visited_fp = - IntSet.mem (Procdesc.Node.get_id node :> int) stats.Specs.nodes_visited_fp in + IntSet.mem (Procdesc.Node.get_id node :> int) stats.Specs.nodes_visited_fp + in let is_visited_re = - IntSet.mem (Procdesc.Node.get_id node :> int) stats.Specs.nodes_visited_re in - is_visited_fp, is_visited_re + IntSet.mem (Procdesc.Node.get_id node :> int) stats.Specs.nodes_visited_re + in + (is_visited_fp, is_visited_re) (** Return true if the node was visited during analysis *) let is_visited node = @@ -97,283 +94,235 @@ let is_visited node = when starting and finishing the processing of a node *) module NodesHtml : sig val start_node : - int -> Location.t -> Typ.Procname.t -> Procdesc.Node.t list -> - Procdesc.Node.t list -> Procdesc.Node.t list -> - SourceFile.t -> bool + int -> Location.t -> Typ.Procname.t -> Procdesc.Node.t list -> Procdesc.Node.t list + -> Procdesc.Node.t list -> SourceFile.t -> bool + val finish_node : Typ.Procname.t -> int -> SourceFile.t -> unit end = struct let log_files = Hashtbl.create 11 let pp_node_link fmt node = - Io_infer.Html.pp_node_link - [".."] - (Procdesc.Node.get_proc_name node) - ~description:"" + Io_infer.Html.pp_node_link [".."] (Procdesc.Node.get_proc_name node) ~description:"" ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds node) :> int list) ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs node) :> int list) ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn node) :> int list) - ~isvisited:(is_visited node) - ~isproof:false - fmt (Procdesc.Node.get_id node :> int) + ~isvisited:(is_visited node) ~isproof:false fmt + (Procdesc.Node.get_id node :> int) let start_node nodeid loc proc_name preds succs exns source = let node_fname = Io_infer.Html.node_filename proc_name nodeid in let modified = Io_infer.Html.modified_during_analysis source ["nodes"; node_fname] in let needs_initialization, (fd, fmt) = - if modified then - (false, Io_infer.Html.open_out source ["nodes"; node_fname]) - else - (true, - Io_infer.Html.create - (DB.Results_dir.Abs_source_dir source) - ["nodes"; node_fname]) in - curr_html_formatter := fmt; - Hashtbl.replace log_files (node_fname, source) fd; - if needs_initialization then - (F.fprintf fmt "

Cfg Node %a

" - (Io_infer.Html.pp_line_link source ~text: (Some (string_of_int nodeid)) [".."]) - loc.Location.line; - F.fprintf fmt "PROC: %a LINE:%a@\n" - (Io_infer.Html.pp_proc_link [".."] proc_name) - (Escape.escape_xml (Typ.Procname.to_string proc_name)) - (Io_infer.Html.pp_line_link source [".."]) loc.Location.line; - F.fprintf fmt "
PREDS:@\n"; - List.iter ~f:(pp_node_link fmt) preds; - F.fprintf fmt "
SUCCS: @\n"; - List.iter ~f:(pp_node_link fmt) succs; - F.fprintf fmt "
EXN: @\n"; - List.iter ~f:(pp_node_link fmt) exns; - F.fprintf fmt "
@\n"; - F.pp_print_flush fmt (); - true - ) + if modified then (false, Io_infer.Html.open_out source ["nodes"; node_fname]) + else (true, Io_infer.Html.create (DB.Results_dir.Abs_source_dir source) ["nodes"; node_fname]) + in + curr_html_formatter := fmt ; + Hashtbl.replace log_files (node_fname, source) fd ; + if needs_initialization then ( + F.fprintf fmt "

Cfg Node %a

" + (Io_infer.Html.pp_line_link source ~text:(Some (string_of_int nodeid)) [".."]) + loc.Location.line ; + F.fprintf fmt "PROC: %a LINE:%a@\n" + (Io_infer.Html.pp_proc_link [".."] proc_name) + (Escape.escape_xml (Typ.Procname.to_string proc_name)) + (Io_infer.Html.pp_line_link source [".."]) + loc.Location.line ; + F.fprintf fmt "
PREDS:@\n" ; + List.iter ~f:(pp_node_link fmt) preds ; + F.fprintf fmt "
SUCCS: @\n" ; + List.iter ~f:(pp_node_link fmt) succs ; + F.fprintf fmt "
EXN: @\n" ; + List.iter ~f:(pp_node_link fmt) exns ; + F.fprintf fmt "
@\n" ; + F.pp_print_flush fmt () ; + true ) else false let finish_node proc_name nodeid source = let node_fname = Io_infer.Html.node_filename proc_name nodeid in let fd = Hashtbl.find log_files (node_fname, source) in - Unix.close fd; + Unix.close fd ; curr_html_formatter := F.std_formatter end -(* =============== END of module NodesHtml =============== *) +(* =============== END of module NodesHtml =============== *) (* =============== Printing functions =============== *) (** Execute the delayed print actions *) let force_delayed_print fmt = - let pe_default = - if Config.write_html then Pp.html Black else Pp.text in + let pe_default = if Config.write_html then Pp.html Black else Pp.text in function - | (L.PTatom, a) -> - let (a: Sil.atom) = Obj.obj a in - Sil.pp_atom pe_default fmt a - | (L.PTattribute, a) -> - let (a: PredSymb.t) = Obj.obj a in - F.pp_print_string fmt (PredSymb.to_string pe_default a) - | (L.PTdecrease_indent, n) -> - let (n: int) = Obj.obj n in - for _ = 1 to n do F.fprintf fmt "@]" done - | (L.PTexp, e) -> - let (e: Exp.t) = Obj.obj e in - Sil.pp_exp_printenv pe_default fmt e - | (L.PTexp_list, el) -> - let (el: Exp.t list) = Obj.obj el in - Sil.pp_exp_list pe_default fmt el - | (L.PThpred, hpred) -> - let (hpred: Sil.hpred) = Obj.obj hpred in - Sil.pp_hpred pe_default fmt hpred - | (L.PTincrease_indent, n) -> - let (n: int) = Obj.obj n in - let s = ref "" in - for _ = 1 to n do s := " " ^ !s done; - F.fprintf fmt "%s@[" !s - | (L.PTinstr, i) -> - let (i: Sil.instr) = Obj.obj i in - if Config.write_html - then - F.fprintf fmt "%a%a%a" - Io_infer.Html.pp_start_color Pp.Green - (Sil.pp_instr (Pp.html Green)) i - Io_infer.Html.pp_end_color () - else - Sil.pp_instr Pp.text fmt i - | (L.PTinstr_list, il) -> - let (il: Sil.instr list) = Obj.obj il in - if Config.write_html - then - F.fprintf fmt "%a%a%a" - Io_infer.Html.pp_start_color Pp.Green - (Sil.pp_instr_list (Pp.html Green)) il - Io_infer.Html.pp_end_color () - else - Sil.pp_instr_list Pp.text fmt il - | (L.PTjprop_list, shallow_jpl) -> - let ((shallow: bool), (jpl: Prop.normal Specs.Jprop.t list)) = Obj.obj shallow_jpl in - Specs.Jprop.pp_list pe_default shallow fmt jpl - | (L.PTjprop_short, jp) -> - let (jp: Prop.normal Specs.Jprop.t) = Obj.obj jp in - Specs.Jprop.pp_short pe_default fmt jp - | (L.PTloc, loc) -> - let (loc: Location.t) = Obj.obj loc in - Location.pp fmt loc - | (L.PTnode_instrs, b_n) -> - let (b: bool), (io: Sil.instr option), (n: Procdesc.Node.t) = Obj.obj b_n in - if Config.write_html - then - F.fprintf fmt "%a%a%a" - Io_infer.Html.pp_start_color Pp.Green - (Procdesc.Node.pp_instrs (Pp.html Green) io ~sub_instrs: b) n - Io_infer.Html.pp_end_color () - else - F.fprintf fmt "%a" - (Procdesc.Node.pp_instrs Pp.text io ~sub_instrs: b) n - | (L.PToff, off) -> - let (off: Sil.offset) = Obj.obj off in - Sil.pp_offset pe_default fmt off - | (L.PToff_list, offl) -> - let (offl: Sil.offset list) = Obj.obj offl in - Sil.pp_offset_list pe_default fmt offl - | (L.PTpathset, ps) -> - let (ps: Paths.PathSet.t) = Obj.obj ps in - F.fprintf fmt "%a@\n" (Paths.PathSet.pp pe_default) ps - | (L.PTpi, pi) -> - let (pi: Sil.atom list) = Obj.obj pi in - Prop.pp_pi pe_default fmt pi - | (L.PTpath, path) -> - let (path: Paths.Path.t) = Obj.obj path in - Paths.Path.pp fmt path - | (L.PTprop, p) -> - let (p: Prop.normal Prop.t) = Obj.obj p in - Prop.pp_prop pe_default fmt p - | (L.PTproplist, x) -> - let (p : Prop.normal Prop.t), (pl: Prop.normal Prop.t list) = Obj.obj x in - Propgraph.pp_proplist pe_default "PROP" (p, false) fmt pl - | (L.PTprop_list_with_typ, plist) -> - let (pl: Prop.normal Prop.t list) = Obj.obj plist in - F.fprintf fmt "%a" (Prop.pp_proplist_with_typ pe_default) pl - | (L.PTprop_with_typ, p) -> - let (p: Prop.normal Prop.t) = Obj.obj p in - Prop.pp_prop_with_typ pe_default fmt p - | (L.PTpvar, pvar) -> - let (pvar: Pvar.t) = Obj.obj pvar in - Pvar.pp pe_default fmt pvar - | (L.PTsexp, se) -> - let (se: Sil.strexp) = Obj.obj se in - Sil.pp_sexp pe_default fmt se - | (L.PTsexp_list, sel) -> - let (sel: Sil.strexp list) = Obj.obj sel in - Sil.pp_sexp_list pe_default fmt sel - | (L.PTsigma, sigma) -> - let (sigma: Sil.hpred list) = Obj.obj sigma in - Prop.pp_sigma pe_default fmt sigma - | (L.PTspec, spec) -> - let (spec: Prop.normal Specs.spec) = Obj.obj spec in - Specs.pp_spec - (if Config.write_html then Pp.html Blue else Pp.text) - None fmt spec - | (L.PTstr, s) -> - let (s: string) = Obj.obj s in - F.fprintf fmt "%s" s - | (L.PTstr_color, s) -> - let (s: string), (c: Pp.color) = Obj.obj s in - if Config.write_html - then - F.fprintf fmt "%a%s%a" - Io_infer.Html.pp_start_color c - s - Io_infer.Html.pp_end_color () - else + | L.PTatom, a + -> let a : Sil.atom = Obj.obj a in + Sil.pp_atom pe_default fmt a + | L.PTattribute, a + -> let a : PredSymb.t = Obj.obj a in + F.pp_print_string fmt (PredSymb.to_string pe_default a) + | L.PTdecrease_indent, n + -> let n : int = Obj.obj n in + for _ = 1 to n do F.fprintf fmt "@]" done + | L.PTexp, e + -> let e : Exp.t = Obj.obj e in + Sil.pp_exp_printenv pe_default fmt e + | L.PTexp_list, el + -> let el : Exp.t list = Obj.obj el in + Sil.pp_exp_list pe_default fmt el + | L.PThpred, hpred + -> let hpred : Sil.hpred = Obj.obj hpred in + Sil.pp_hpred pe_default fmt hpred + | L.PTincrease_indent, n + -> let n : int = Obj.obj n in + let s = ref "" in + for _ = 1 to n do s := " " ^ !s done ; + F.fprintf fmt "%s@[" !s + | L.PTinstr, i + -> let i : Sil.instr = Obj.obj i in + if Config.write_html then + F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Pp.Green + (Sil.pp_instr (Pp.html Green)) + i Io_infer.Html.pp_end_color () + else Sil.pp_instr Pp.text fmt i + | L.PTinstr_list, il + -> let il : Sil.instr list = Obj.obj il in + if Config.write_html then + F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Pp.Green + (Sil.pp_instr_list (Pp.html Green)) + il Io_infer.Html.pp_end_color () + else Sil.pp_instr_list Pp.text fmt il + | L.PTjprop_list, shallow_jpl + -> let (shallow: bool), (jpl: Prop.normal Specs.Jprop.t list) = Obj.obj shallow_jpl in + Specs.Jprop.pp_list pe_default shallow fmt jpl + | L.PTjprop_short, jp + -> let jp : Prop.normal Specs.Jprop.t = Obj.obj jp in + Specs.Jprop.pp_short pe_default fmt jp + | L.PTloc, loc + -> let loc : Location.t = Obj.obj loc in + Location.pp fmt loc + | L.PTnode_instrs, b_n + -> let (b: bool), (io: Sil.instr option), (n: Procdesc.Node.t) = Obj.obj b_n in + if Config.write_html then + F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Pp.Green + (Procdesc.Node.pp_instrs (Pp.html Green) io ~sub_instrs:b) + n Io_infer.Html.pp_end_color () + else F.fprintf fmt "%a" (Procdesc.Node.pp_instrs Pp.text io ~sub_instrs:b) n + | L.PToff, off + -> let off : Sil.offset = Obj.obj off in + Sil.pp_offset pe_default fmt off + | L.PToff_list, offl + -> let offl : Sil.offset list = Obj.obj offl in + Sil.pp_offset_list pe_default fmt offl + | L.PTpathset, ps + -> let ps : Paths.PathSet.t = Obj.obj ps in + F.fprintf fmt "%a@\n" (Paths.PathSet.pp pe_default) ps + | L.PTpi, pi + -> let pi : Sil.atom list = Obj.obj pi in + Prop.pp_pi pe_default fmt pi + | L.PTpath, path + -> let path : Paths.Path.t = Obj.obj path in + Paths.Path.pp fmt path + | L.PTprop, p + -> let p : Prop.normal Prop.t = Obj.obj p in + Prop.pp_prop pe_default fmt p + | L.PTproplist, x + -> let (p: Prop.normal Prop.t), (pl: Prop.normal Prop.t list) = Obj.obj x in + Propgraph.pp_proplist pe_default "PROP" (p, false) fmt pl + | L.PTprop_list_with_typ, plist + -> let pl : Prop.normal Prop.t list = Obj.obj plist in + F.fprintf fmt "%a" (Prop.pp_proplist_with_typ pe_default) pl + | L.PTprop_with_typ, p + -> let p : Prop.normal Prop.t = Obj.obj p in + Prop.pp_prop_with_typ pe_default fmt p + | L.PTpvar, pvar + -> let pvar : Pvar.t = Obj.obj pvar in + Pvar.pp pe_default fmt pvar + | L.PTsexp, se + -> let se : Sil.strexp = Obj.obj se in + Sil.pp_sexp pe_default fmt se + | L.PTsexp_list, sel + -> let sel : Sil.strexp list = Obj.obj sel in + Sil.pp_sexp_list pe_default fmt sel + | L.PTsigma, sigma + -> let sigma : Sil.hpred list = Obj.obj sigma in + Prop.pp_sigma pe_default fmt sigma + | L.PTspec, spec + -> let spec : Prop.normal Specs.spec = Obj.obj spec in + Specs.pp_spec (if Config.write_html then Pp.html Blue else Pp.text) None fmt spec + | L.PTstr, s + -> let s : string = Obj.obj s in F.fprintf fmt "%s" s - | (L.PTstrln, s) -> - let (s: string) = Obj.obj s in - F.fprintf fmt "%s@\n" s - | (L.PTstrln_color, s) -> - let (s: string), (c: Pp.color) = Obj.obj s in - if Config.write_html - then - F.fprintf fmt "%a%s%a@\n" - Io_infer.Html.pp_start_color c - s - Io_infer.Html.pp_end_color () - else + | L.PTstr_color, s + -> let (s: string), (c: Pp.color) = Obj.obj s in + if Config.write_html then + F.fprintf fmt "%a%s%a" Io_infer.Html.pp_start_color c s Io_infer.Html.pp_end_color () + else F.fprintf fmt "%s" s + | L.PTstrln, s + -> let s : string = Obj.obj s in F.fprintf fmt "%s@\n" s - | (L.PTsub, sub) -> - let (sub: Sil.subst) = Obj.obj sub in - Prop.pp_sub pe_default fmt sub - | (L.PTtexp_full, te) -> - let (te: Exp.t) = Obj.obj te in - Sil.pp_texp_full pe_default fmt te - | (L.PTtyp_full, t) -> - let (t: Typ.t) = Obj.obj t in - Typ.pp_full pe_default fmt t - | (L.PTtyp_list, tl) -> - let (tl: Typ.t list) = Obj.obj tl in - (Pp.seq (Typ.pp pe_default)) fmt tl - | (L.PTerror, s) -> - let (s: string) = Obj.obj s in - if Config.write_html - then - F.fprintf fmt "%aERROR: %s%a" - Io_infer.Html.pp_start_color Pp.Red - s - Io_infer.Html.pp_end_color () - else - F.fprintf fmt "ERROR: %s" s - | (L.PTwarning, s) -> - let (s: string) = Obj.obj s in - if Config.write_html - then - F.fprintf fmt "%aWARNING: %s%a" - Io_infer.Html.pp_start_color Pp.Orange - s - Io_infer.Html.pp_end_color () - else - F.fprintf fmt "WARNING: %s" s - | (L.PTinfo, s) -> - let (s: string) = Obj.obj s in - if Config.write_html - then - F.fprintf fmt "%aINFO: %s%a" - Io_infer.Html.pp_start_color Pp.Blue - s - Io_infer.Html.pp_end_color () - else - F.fprintf fmt "INFO: %s" s + | L.PTstrln_color, s + -> let (s: string), (c: Pp.color) = Obj.obj s in + if Config.write_html then + F.fprintf fmt "%a%s%a@\n" Io_infer.Html.pp_start_color c s Io_infer.Html.pp_end_color () + else F.fprintf fmt "%s@\n" s + | L.PTsub, sub + -> let sub : Sil.subst = Obj.obj sub in + Prop.pp_sub pe_default fmt sub + | L.PTtexp_full, te + -> let te : Exp.t = Obj.obj te in + Sil.pp_texp_full pe_default fmt te + | L.PTtyp_full, t + -> let t : Typ.t = Obj.obj t in + Typ.pp_full pe_default fmt t + | L.PTtyp_list, tl + -> let tl : Typ.t list = Obj.obj tl in + Pp.seq (Typ.pp pe_default) fmt tl + | L.PTerror, s + -> let s : string = Obj.obj s in + if Config.write_html then + F.fprintf fmt "%aERROR: %s%a" Io_infer.Html.pp_start_color Pp.Red s + Io_infer.Html.pp_end_color () + else F.fprintf fmt "ERROR: %s" s + | L.PTwarning, s + -> let s : string = Obj.obj s in + if Config.write_html then + F.fprintf fmt "%aWARNING: %s%a" Io_infer.Html.pp_start_color Pp.Orange s + Io_infer.Html.pp_end_color () + else F.fprintf fmt "WARNING: %s" s + | L.PTinfo, s + -> let s : string = Obj.obj s in + if Config.write_html then + F.fprintf fmt "%aINFO: %s%a" Io_infer.Html.pp_start_color Pp.Blue s + Io_infer.Html.pp_end_color () + else F.fprintf fmt "INFO: %s" s (** Set printer hook as soon as this module is loaded *) let () = L.printer_hook := force_delayed_print (** Execute the delayed print actions *) let force_delayed_prints () = - Config.forcing_delayed_prints := true; - F.fprintf !curr_html_formatter "@?"; (* flush html stream *) - List.iter - ~f:(force_delayed_print !curr_html_formatter) - (List.rev (L.get_delayed_prints ())); - F.fprintf !curr_html_formatter "@?"; - L.reset_delayed_prints (); + Config.forcing_delayed_prints := true ; + F.fprintf !curr_html_formatter "@?" ; + (* flush html stream *) + List.iter ~f:(force_delayed_print !curr_html_formatter) (List.rev (L.get_delayed_prints ())) ; + F.fprintf !curr_html_formatter "@?" ; + L.reset_delayed_prints () ; Config.forcing_delayed_prints := false (** Start a session, and create a new html fine for the node if it does not exist yet *) let start_session node (loc: Location.t) proc_name session source = let node_id = Procdesc.Node.get_id node in - (if NodesHtml.start_node - (node_id :> int) loc proc_name - (Procdesc.Node.get_preds node) - (Procdesc.Node.get_succs node) - (Procdesc.Node.get_exn node) - source - then - F.fprintf !curr_html_formatter "%a%a%a" - Io_infer.Html.pp_start_color Pp.Green - (Procdesc.Node.pp_instrs (Pp.html Green) None ~sub_instrs: true) node - Io_infer.Html.pp_end_color ()); - F.fprintf !curr_html_formatter "%a%a" - Io_infer.Html.pp_hline () - (Io_infer.Html.pp_session_link source ~with_name: true [".."] ~proc_name) - ((node_id :> int), session, loc.Location.line); - F.fprintf !curr_html_formatter "%a" - Io_infer.Html.pp_start_color Pp.Black + if NodesHtml.start_node + (node_id :> int) + loc proc_name (Procdesc.Node.get_preds node) (Procdesc.Node.get_succs node) + (Procdesc.Node.get_exn node) source + then + F.fprintf !curr_html_formatter "%a%a%a" Io_infer.Html.pp_start_color + Pp.Green + (Procdesc.Node.pp_instrs (Pp.html Green) None ~sub_instrs:true) + node Io_infer.Html.pp_end_color () ; + F.fprintf !curr_html_formatter "%a%a" Io_infer.Html.pp_hline () + (Io_infer.Html.pp_session_link source ~with_name:true [".."] ~proc_name) + ((node_id :> int), session, loc.Location.line) ; + F.fprintf !curr_html_formatter "%a" Io_infer.Html.pp_start_color Pp.Black let node_start_session node session = if Config.write_html then @@ -384,72 +333,61 @@ let node_start_session node session = (** Finish a session, and perform delayed print actions if required *) let node_finish_session node = - if not Config.only_cheap_debug then force_delayed_prints () - else L.reset_delayed_prints (); - if Config.write_html then begin - F.fprintf !curr_html_formatter "%a" - Io_infer.Html.pp_end_color (); + if not Config.only_cheap_debug then force_delayed_prints () else L.reset_delayed_prints () ; + if Config.write_html then ( + F.fprintf !curr_html_formatter "%a" Io_infer.Html.pp_end_color () ; let source = (Procdesc.Node.get_loc node).file in - NodesHtml.finish_node - (Procdesc.Node.get_proc_name node) + NodesHtml.finish_node (Procdesc.Node.get_proc_name node) (Procdesc.Node.get_id node :> int) - source - end + source ) (** Write html file for the procedure. The boolean indicates whether to print whole seconds only *) let write_proc_html pdesc = if Config.write_html then - begin - let pname = Procdesc.get_proc_name pdesc in - let source = (Procdesc.get_loc pdesc).file in - let nodes = List.sort ~cmp:Procdesc.Node.compare (Procdesc.get_nodes pdesc) in - let linenum = (Procdesc.Node.get_loc (List.hd_exn nodes)).Location.line in - let fd, fmt = - Io_infer.Html.create - (DB.Results_dir.Abs_source_dir source) - [Typ.Procname.to_filename pname] in - F.fprintf fmt "

Procedure %a

@\n" - (Io_infer.Html.pp_line_link source - ~text: (Some (Escape.escape_xml (Typ.Procname.to_string pname))) - []) - linenum; - List.iter - ~f:(fun n -> - Io_infer.Html.pp_node_link - [] - (Procdesc.Node.get_proc_name n) - ~description:(Procdesc.Node.get_description (Pp.html Black) n) - ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds n) :> int list) - ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs n) :> int list) - ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn n) :> int list) - ~isvisited:(is_visited n) - ~isproof:false - fmt (Procdesc.Node.get_id n :> int)) - nodes; - (match Specs.get_summary pname with - | None -> - () - | Some summary -> - Specs.pp_summary_html source Black fmt summary; - Io_infer.Html.close (fd, fmt)) - end + let pname = Procdesc.get_proc_name pdesc in + let source = (Procdesc.get_loc pdesc).file in + let nodes = List.sort ~cmp:Procdesc.Node.compare (Procdesc.get_nodes pdesc) in + let linenum = (Procdesc.Node.get_loc (List.hd_exn nodes)).Location.line in + let fd, fmt = + Io_infer.Html.create (DB.Results_dir.Abs_source_dir source) [Typ.Procname.to_filename pname] + in + F.fprintf fmt "

Procedure %a

@\n" + (Io_infer.Html.pp_line_link source + ~text:(Some (Escape.escape_xml (Typ.Procname.to_string pname))) + []) + linenum ; + List.iter + ~f:(fun n -> + Io_infer.Html.pp_node_link [] (Procdesc.Node.get_proc_name n) + ~description:(Procdesc.Node.get_description (Pp.html Black) n) + ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds n) :> int list) + ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs n) :> int list) + ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn n) :> int list) + ~isvisited:(is_visited n) ~isproof:false fmt + (Procdesc.Node.get_id n :> int)) + nodes ; + match Specs.get_summary pname with + | None + -> () + | Some summary + -> Specs.pp_summary_html source Black fmt summary ; + Io_infer.Html.close (fd, fmt) (** Creare a hash table mapping line numbers to the set of errors occurring on that line *) let create_table_err_per_line err_log = let err_per_line = Hashtbl.create 17 in - let add_err (key : Errlog.err_key) (err_data : Errlog.err_data) = + let add_err (key: Errlog.err_key) (err_data: Errlog.err_data) = let err_str = - Localise.to_issue_id key.err_name ^ - " " ^ - (F.asprintf "%a" Localise.pp_error_desc key.err_desc) in + Localise.to_issue_id key.err_name ^ " " ^ F.asprintf "%a" Localise.pp_error_desc key.err_desc + in try let set = Hashtbl.find err_per_line err_data.loc.Location.line in Hashtbl.replace err_per_line err_data.loc.Location.line (String.Set.add set err_str) with Not_found -> - Hashtbl.add err_per_line err_data.loc.Location.line (String.Set.singleton err_str) in - Errlog.iter add_err err_log; - err_per_line + Hashtbl.add err_per_line err_data.loc.Location.line (String.Set.singleton err_str) + in + Errlog.iter add_err err_log ; err_per_line (** Create error message for html file *) let create_err_message err_string = @@ -461,155 +399,147 @@ let write_html_proc source proof_cover table_nodes_at_linenum global_err_log pro let lnum = (Procdesc.Node.get_loc n).Location.line in let curr_nodes = try Hashtbl.find table_nodes_at_linenum lnum - with Not_found -> [] in - Hashtbl.replace table_nodes_at_linenum lnum (n :: curr_nodes) in + with Not_found -> [] + in + Hashtbl.replace table_nodes_at_linenum lnum (n :: curr_nodes) + in let proc_loc = Procdesc.get_loc proc_desc in let process_proc = - Procdesc.is_defined proc_desc && - SourceFile.equal proc_loc.Location.file source && + Procdesc.is_defined proc_desc && SourceFile.equal proc_loc.Location.file source + && match AttributesTable.find_file_capturing_procedure ~cache:true proc_name with - | None -> true - | Some (source_captured, _) -> - SourceFile.equal source_captured (Procdesc.get_loc proc_desc).file in - if process_proc then - begin - List.iter ~f:process_node (Procdesc.get_nodes proc_desc); - match Specs.get_summary proc_name with - | None -> - () - | Some summary -> - List.iter - ~f:(fun sp -> - proof_cover := Specs.Visitedset.union sp.Specs.visited !proof_cover) - (Specs.get_specs_from_payload summary); - Errlog.update global_err_log summary.Specs.attributes.ProcAttributes.err_log - end + | None + -> true + | Some (source_captured, _) + -> SourceFile.equal source_captured (Procdesc.get_loc proc_desc).file + in + if process_proc then ( + List.iter ~f:process_node (Procdesc.get_nodes proc_desc) ; + match Specs.get_summary proc_name with + | None + -> () + | Some summary + -> List.iter + ~f:(fun sp -> proof_cover := Specs.Visitedset.union sp.Specs.visited !proof_cover) + (Specs.get_specs_from_payload summary) ; + Errlog.update global_err_log summary.Specs.attributes.ProcAttributes.err_log ) (** Create filename.ext.html. *) let write_html_file linereader filename procs = let fname_encoding = DB.source_file_encoding filename in - let (fd, fmt) = - Io_infer.Html.create - (DB.Results_dir.Abs_source_dir filename) - [".."; fname_encoding] in + let fd, fmt = + Io_infer.Html.create (DB.Results_dir.Abs_source_dir filename) [".."; fname_encoding] + in let pp_prelude () = - F.fprintf fmt "

File %a

@\n@\n" - SourceFile.pp filename in + F.fprintf fmt "

File %a

@\n
@\n" SourceFile.pp + filename + in let print_one_line proof_cover table_nodes_at_linenum table_err_per_line line_number = let line_html = match LineReader.from_file_linenum linereader filename line_number with - | Some line_raw -> - Escape.escape_xml line_raw - | None -> - raise End_of_file in + | Some line_raw + -> Escape.escape_xml line_raw + | None + -> raise End_of_file + in let nodes_at_linenum = try Hashtbl.find table_nodes_at_linenum line_number - with Not_found -> [] in + with Not_found -> [] + in let errors_at_linenum = try let errset = Hashtbl.find table_err_per_line line_number in String.Set.elements errset - with Not_found -> [] in + with Not_found -> [] + in let linenum_str = string_of_int line_number in let line_str = "LINE" ^ linenum_str in let str = - "@\n" in - - pp_prelude (); + ~f:(fun err_string -> F.fprintf fmt "%s" (create_err_message err_string)) + errors_at_linenum ; + F.fprintf fmt "@\n" + in + pp_prelude () ; let global_err_log = Errlog.empty () in let table_nodes_at_linenum = Hashtbl.create 11 in let proof_cover = ref Specs.Visitedset.empty in - List.iter ~f:(write_html_proc filename proof_cover table_nodes_at_linenum global_err_log) procs; + List.iter ~f:(write_html_proc filename proof_cover table_nodes_at_linenum global_err_log) procs ; let table_err_per_line = create_table_err_per_line global_err_log in let linenum = ref 0 in - try while true do - incr linenum; + incr linenum ; print_one_line proof_cover table_nodes_at_linenum table_err_per_line !linenum done with End_of_file -> - (F.fprintf fmt "
" ^ - linenum_str ^ - "" ^ - line_html in - F.fprintf fmt "%s" str; + "
" ^ linenum_str ^ "" + ^ line_html + in + F.fprintf fmt "%s" str ; List.iter ~f:(fun n -> - let isproof = - Specs.Visitedset.mem (Procdesc.Node.get_id n, []) !proof_cover in - Io_infer.Html.pp_node_link - [fname_encoding] - (Procdesc.Node.get_proc_name n) - ~description:(Procdesc.Node.get_description (Pp.html Black) n) - ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds n) :> int list) - ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs n) :> int list) - ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn n) :> int list) - ~isvisited:(is_visited n) - ~isproof - fmt (Procdesc.Node.get_id n :> int)) - nodes_at_linenum; + let isproof = Specs.Visitedset.mem (Procdesc.Node.get_id n, []) !proof_cover in + Io_infer.Html.pp_node_link [fname_encoding] (Procdesc.Node.get_proc_name n) + ~description:(Procdesc.Node.get_description (Pp.html Black) n) + ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds n) :> int list) + ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs n) :> int list) + ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn n) :> int list) + ~isvisited:(is_visited n) ~isproof fmt + (Procdesc.Node.get_id n :> int)) + nodes_at_linenum ; List.iter ~f:(fun n -> - match Procdesc.Node.get_kind n with - | Procdesc.Node.Start_node proc_name -> - let num_specs = - match Specs.get_summary proc_name with - | None -> 0 - | Some summary -> List.length (Specs.get_specs_from_payload summary) in - let label = - (Escape.escape_xml (Typ.Procname.to_string proc_name)) ^ - ": " ^ - (string_of_int num_specs) ^ - " specs" in - Io_infer.Html.pp_proc_link [fname_encoding] proc_name fmt label - | _ -> - ()) - nodes_at_linenum; + match Procdesc.Node.get_kind n with + | Procdesc.Node.Start_node proc_name + -> let num_specs = + match Specs.get_summary proc_name with + | None + -> 0 + | Some summary + -> List.length (Specs.get_specs_from_payload summary) + in + let label = + Escape.escape_xml (Typ.Procname.to_string proc_name) ^ ": " ^ string_of_int num_specs + ^ " specs" + in + Io_infer.Html.pp_proc_link [fname_encoding] proc_name fmt label + | _ + -> ()) + nodes_at_linenum ; List.iter - ~f:(fun err_string -> - F.fprintf fmt "%s" (create_err_message err_string)) - errors_at_linenum; - F.fprintf fmt "
@\n"; - Errlog.pp_html filename [fname_encoding] fmt global_err_log; - Io_infer.Html.close (fd, fmt)) + F.fprintf fmt "@\n" ; + Errlog.pp_html filename [fname_encoding] fmt global_err_log ; + Io_infer.Html.close (fd, fmt) (** Create filename.ext.html for each file in the cluster. *) let write_all_html_files cluster = let exe_env = Exe_env.from_cluster cluster in let load_proc_desc pname = ignore (Exe_env.get_proc_desc exe_env pname) in let () = List.iter ~f:load_proc_desc (Cg.get_defined_nodes (Exe_env.get_cg exe_env)) in - - let opt_whitelist_regex = match Config.write_html_whitelist_regex with - | [] -> None - | _ as reg_list -> - Some (Str.regexp (String.concat ~sep:"\\|" reg_list)) + let opt_whitelist_regex = + match Config.write_html_whitelist_regex with + | [] + -> None + | _ as reg_list + -> Some (Str.regexp (String.concat ~sep:"\\|" reg_list)) in let is_whitelisted file = Option.value_map opt_whitelist_regex ~default:true ~f:(fun regex -> let fname = SourceFile.to_rel_path file in - Str.string_match regex fname 0 - ) + Str.string_match regex fname 0 ) in - let linereader = LineReader.create () in Exe_env.iter_files (fun _ cfg -> - let source_files_in_cfg = - let files = ref SourceFile.Set.empty in - Cfg.iter_proc_desc cfg - (fun _ proc_desc -> - if Procdesc.is_defined proc_desc - then - let file = (Procdesc.get_loc proc_desc).Location.file in - if is_whitelisted file then - files := SourceFile.Set.add file !files - else ()); - !files in - SourceFile.Set.iter - (fun file -> - write_html_file linereader file (Cfg.get_all_procs cfg)) - source_files_in_cfg) + let source_files_in_cfg = + let files = ref SourceFile.Set.empty in + Cfg.iter_proc_desc cfg (fun _ proc_desc -> + if Procdesc.is_defined proc_desc then + let file = (Procdesc.get_loc proc_desc).Location.file in + if is_whitelisted file then files := SourceFile.Set.add file !files else () ) ; + !files + in + SourceFile.Set.iter + (fun file -> write_html_file linereader file (Cfg.get_all_procs cfg)) + source_files_in_cfg) exe_env diff --git a/infer/src/backend/printer.mli b/infer/src/backend/printer.mli index 8c33b31c3..7dc91c45a 100644 --- a/infer/src/backend/printer.mli +++ b/infer/src/backend/printer.mli @@ -17,36 +17,36 @@ open! IStd module LineReader : sig type t - (** create a line reader *) val create : unit -> t + (** create a line reader *) - (** get the line from a source file and line number *) val from_file_linenum_original : t -> SourceFile.t -> int -> string option + (** get the line from a source file and line number *) - (** get the line from a source file and line number looking for the copy of the file in the results dir *) val from_file_linenum : t -> SourceFile.t -> int -> string option + (** get the line from a source file and line number looking for the copy of the file in the results dir *) - (** get the line from a location looking for the copy of the file in the results dir *) val from_loc : t -> Location.t -> string option + (** get the line from a location looking for the copy of the file in the results dir *) end -(** Current html formatter *) val curr_html_formatter : Format.formatter ref +(** Current html formatter *) -(** Execute the delayed print actions *) val force_delayed_prints : unit -> unit +(** Execute the delayed print actions *) -(** Finish a session, and perform delayed print actions if required *) val node_finish_session : Procdesc.Node.t -> unit +(** Finish a session, and perform delayed print actions if required *) -(** Return true if the node was visited during footprint and during re-execution *) val node_is_visited : Procdesc.Node.t -> bool * bool +(** Return true if the node was visited during footprint and during re-execution *) -(** Start a session, and create a new html fine for the node if it does not exist yet *) val node_start_session : Procdesc.Node.t -> int -> unit +(** Start a session, and create a new html fine for the node if it does not exist yet *) -(** Write html file for the procedure. *) val write_proc_html : Procdesc.t -> unit +(** Write html file for the procedure. *) -(** Create filename.ext.html for each file in the cluster. *) val write_all_html_files : Cluster.t -> unit +(** Create filename.ext.html for each file in the cluster. *) diff --git a/infer/src/backend/prop.ml b/infer/src/backend/prop.ml index 05d3c59b1..dab43dbd3 100644 --- a/infer/src/backend/prop.ml +++ b/infer/src/backend/prop.ml @@ -18,50 +18,45 @@ module F = Format (** type to describe different strategies for initializing fields of a structure. [No_init] does not initialize any fields of the struct. [Fld_init] initializes the fields of the struct with fresh variables (C) or default values (Java). *) -type struct_init_mode = - | No_init - | Fld_init +type struct_init_mode = No_init | Fld_init -let unSome = function - | Some x -> x - | _ -> assert false +let unSome = function Some x -> x | _ -> assert false -type normal (** kind for normal props, i.e. normalized *) +(** kind for normal props, i.e. normalized *) +type normal -type exposed (** kind for exposed props *) +(** kind for exposed props *) +type exposed type pi = Sil.atom list [@@deriving compare] + type sigma = Sil.hpred list [@@deriving compare] let equal_pi = [%compare.equal : pi] let equal_sigma = [%compare.equal : sigma] - module Core : sig - (** the kind 'a should range over [normal] and [exposed] *) type 'a t = private - { - sigma: sigma; (** spatial part *) - sub: Sil.exp_subst; (** substitution *) - pi: pi; (** pure part *) - sigma_fp : sigma; (** abduced spatial part *) - pi_fp: pi; (** abduced pure part *) - } [@@deriving compare] + { sigma: sigma (** spatial part *) + ; sub: Sil.exp_subst (** substitution *) + ; pi: pi (** pure part *) + ; sigma_fp: sigma (** abduced spatial part *) + ; pi_fp: pi (** abduced pure part *) } + [@@deriving compare] - (** Proposition [true /\ emp]. *) val prop_emp : normal t + (** Proposition [true /\ emp]. *) + val set : + ?sub:Sil.exp_subst -> ?pi:pi -> ?sigma:sigma -> ?pi_fp:pi -> ?sigma_fp:sigma -> 'a t + -> exposed t (** Set individual fields of the prop. *) - val set : ?sub:Sil.exp_subst -> ?pi:pi -> ?sigma:sigma -> ?pi_fp:pi -> ?sigma_fp:sigma -> - 'a t -> exposed t - (** Cast an exposed prop to a normalized one by just changing the type *) val unsafe_cast_to_normal : exposed t -> normal t - + (** Cast an exposed prop to a normalized one by just changing the type *) end = struct - (** A proposition. The following invariants are mantained. [sub] is of the form id1 = e1 ... idn = en where: the id's are distinct and do not occur in the e's nor in [pi] or [sigma]; the id's are in sorted @@ -70,108 +65,97 @@ end = struct and normalized, and does not contain x = e. [sigma] is sorted and normalized. *) type 'a t = - { - sigma: sigma; (** spatial part *) - sub: Sil.exp_subst; (** substitution *) - pi: pi; (** pure part *) - sigma_fp : sigma; (** abduced spatial part *) - pi_fp: pi; (** abduced pure part *) - } [@@deriving compare] + { sigma: sigma (** spatial part *) + ; sub: Sil.exp_subst (** substitution *) + ; pi: pi (** pure part *) + ; sigma_fp: sigma (** abduced spatial part *) + ; pi_fp: pi (** abduced pure part *) } + [@@deriving compare] (** Proposition [true /\ emp]. *) - let prop_emp : normal t = - { - sub = Sil.exp_sub_empty; - pi = []; - sigma = []; - pi_fp = []; - sigma_fp = []; - } + let prop_emp : normal t = {sub= Sil.exp_sub_empty; pi= []; sigma= []; pi_fp= []; sigma_fp= []} let set ?sub ?pi ?sigma ?pi_fp ?sigma_fp p = - let set_ p - ?(sub=p.sub) ?(pi=p.pi) ?(sigma=p.sigma) ?(pi_fp=p.pi_fp) ?(sigma_fp=p.sigma_fp) () - = - { sub; pi; sigma; pi_fp; sigma_fp } + let set_ p ?(sub= p.sub) ?(pi= p.pi) ?(sigma= p.sigma) ?(pi_fp= p.pi_fp) + ?(sigma_fp= p.sigma_fp) () = + {sub; pi; sigma; pi_fp; sigma_fp} in set_ p ?sub ?pi ?sigma ?pi_fp ?sigma_fp () - let unsafe_cast_to_normal (p: exposed t) : normal t = - (p :> normal t) - + let unsafe_cast_to_normal (p: exposed t) : normal t = (p :> normal t) end include Core - (** {2 Basic Functions for Propositions} *) (** {1 Functions for Comparison} *) (** Comparison between propositions. Lexicographical order. *) -let compare_prop p1 p2 = - compare (fun _ _ -> 0) p1 p2 +let compare_prop p1 p2 = compare (fun _ _ -> 0) p1 p2 (** Check the equality of two propositions *) -let equal_prop p1 p2 = - Int.equal (compare_prop p1 p2) 0 +let equal_prop p1 p2 = Int.equal (compare_prop p1 p2) 0 (** {1 Functions for Pretty Printing} *) (** Pretty print a footprint. *) let pp_footprint _pe f fp = - let pe = { _pe with Pp.cmap_norm = _pe.Pp.cmap_foot } in + let pe = {_pe with Pp.cmap_norm= _pe.Pp.cmap_foot} in let pp_pi f () = if fp.pi_fp <> [] then - F.fprintf f "%a ;@\n" (Pp.semicolon_seq_oneline pe (Sil.pp_atom pe)) fp.pi_fp in + F.fprintf f "%a ;@\n" (Pp.semicolon_seq_oneline pe (Sil.pp_atom pe)) fp.pi_fp + in if fp.pi_fp <> [] || fp.sigma_fp <> [] then - F.fprintf f "@\n[footprint@\n @[%a%a@] ]" - pp_pi () (Pp.semicolon_seq pe (Sil.pp_hpred pe)) fp.sigma_fp + F.fprintf f "@\n[footprint@\n @[%a%a@] ]" pp_pi () + (Pp.semicolon_seq pe (Sil.pp_hpred pe)) + fp.sigma_fp -let pp_texp_simple pe = match pe.Pp.opt with - | SIM_DEFAULT -> Sil.pp_texp pe - | SIM_WITH_TYP -> Sil.pp_texp_full pe +let pp_texp_simple pe = + match pe.Pp.opt with SIM_DEFAULT -> Sil.pp_texp pe | SIM_WITH_TYP -> Sil.pp_texp_full pe (** Pretty print a pointsto representing a stack variable as an equality *) -let pp_hpred_stackvar pe0 f (hpred : Sil.hpred) = +let pp_hpred_stackvar pe0 f (hpred: Sil.hpred) = let pe, changed = Sil.color_pre_wrapper pe0 f hpred in - begin match hpred with - | Hpointsto (Exp.Lvar pvar, se, te) -> - let pe' = match se with - | Eexp (Exp.Var _, _) when not (Pvar.is_global pvar) -> - { pe with obj_sub = None } (* dont use obj sub on the var defining it *) - | _ -> pe in - (match pe'.kind with - | TEXT | HTML -> - F.fprintf f "%a = %a:%a" - (Pvar.pp_value pe') pvar (Sil.pp_sexp pe') se (pp_texp_simple pe') te - | LATEX -> - F.fprintf f "%a{=}%a" (Pvar.pp_value pe') pvar (Sil.pp_sexp pe') se) - | Hpointsto _ | Hlseg _ | Hdllseg _ -> assert false (* should not happen *) - end; + ( match hpred with + | Hpointsto (Exp.Lvar pvar, se, te) + -> ( + let pe' = + match se with + | Eexp (Exp.Var _, _) when not (Pvar.is_global pvar) + -> {pe with obj_sub= None} (* dont use obj sub on the var defining it *) + | _ + -> pe + in + match pe'.kind with + | TEXT | HTML + -> F.fprintf f "%a = %a:%a" (Pvar.pp_value pe') pvar (Sil.pp_sexp pe') se + (pp_texp_simple pe') te + | LATEX + -> F.fprintf f "%a{=}%a" (Pvar.pp_value pe') pvar (Sil.pp_sexp pe') se ) + | Hpointsto _ | Hlseg _ | Hdllseg _ + -> assert false (* should not happen *) ) ; Sil.color_post_wrapper changed pe0 f (** Pretty print a substitution. *) let pp_sub pe f = function - | `Exp sub -> - let pi_sub = List.map ~f:(fun (id, e) -> Sil.Aeq (Var id, e)) (Sil.sub_to_list sub) in - (Pp.semicolon_seq_oneline pe (Sil.pp_atom pe)) f pi_sub - | `Typ _ -> - F.fprintf f "Printing typ_subst not implemented." + | `Exp sub + -> let pi_sub = List.map ~f:(fun (id, e) -> Sil.Aeq (Var id, e)) (Sil.sub_to_list sub) in + Pp.semicolon_seq_oneline pe (Sil.pp_atom pe) f pi_sub + | `Typ _ + -> F.fprintf f "Printing typ_subst not implemented." (** Dump a substitution. *) let d_sub (sub: Sil.subst) = L.add_print_action (PTsub, Obj.repr sub) let pp_sub_entry pe0 f entry = let pe, changed = Sil.color_pre_wrapper pe0 f entry in - let (x, e) = entry in - begin - match pe.kind with - | TEXT | HTML -> - F.fprintf f "%a = %a" (Ident.pp pe) x (Sil.pp_exp_printenv pe) e - | LATEX -> - F.fprintf f "%a{=}%a" (Ident.pp pe) x (Sil.pp_exp_printenv pe) e - end; + let x, e = entry in + ( match pe.kind with + | TEXT | HTML + -> F.fprintf f "%a = %a" (Ident.pp pe) x (Sil.pp_exp_printenv pe) e + | LATEX + -> F.fprintf f "%a{=}%a" (Ident.pp pe) x (Sil.pp_exp_printenv pe) e ) ; Sil.color_post_wrapper changed pe0 f (** Pretty print a substitution as a list of (ident,exp) pairs *) @@ -188,15 +172,17 @@ let pp_pi pe = let d_pi (pi: pi) = L.add_print_action (PTpi, Obj.repr pi) (** Pretty print a sigma. *) -let pp_sigma pe = - Pp.semicolon_seq pe (Sil.pp_hpred pe) +let pp_sigma pe = Pp.semicolon_seq pe (Sil.pp_hpred pe) (** Split sigma into stack and nonstack parts. The boolean indicates whether the stack should only include local variales. *) let sigma_get_stack_nonstack only_local_vars sigma = let hpred_is_stack_var = function - | Sil.Hpointsto (Lvar pvar, _, _) -> not only_local_vars || Pvar.is_local pvar - | _ -> false in + | Sil.Hpointsto (Lvar pvar, _, _) + -> not only_local_vars || Pvar.is_local pvar + | _ + -> false + in List.partition_tf ~f:hpred_is_stack_var sigma (** Pretty print a sigma in simple mode. *) @@ -204,16 +190,21 @@ let pp_sigma_simple pe env fmt sigma = let sigma_stack, sigma_nonstack = sigma_get_stack_nonstack false sigma in let pp_stack fmt _sg = let sg = List.sort ~cmp:Sil.compare_hpred _sg in - if sg <> [] then Format.fprintf fmt "%a" (Pp.semicolon_seq pe (pp_hpred_stackvar pe)) sg in - let pp_nl fmt doit = if doit then - (match pe.Pp.kind with - | TEXT | HTML -> Format.fprintf fmt " ;@\n" - | LATEX -> Format.fprintf fmt " ; \\\\@\n") in + if sg <> [] then Format.fprintf fmt "%a" (Pp.semicolon_seq pe (pp_hpred_stackvar pe)) sg + in + let pp_nl fmt doit = + if doit then + match pe.Pp.kind with + | TEXT | HTML + -> Format.fprintf fmt " ;@\n" + | LATEX + -> Format.fprintf fmt " ; \\\\@\n" + in let pp_nonstack fmt = Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env)) fmt in if sigma_stack <> [] || sigma_nonstack <> [] then - Format.fprintf fmt "%a%a%a" - pp_stack sigma_stack pp_nl - (sigma_stack <> [] && sigma_nonstack <> []) pp_nonstack sigma_nonstack + Format.fprintf fmt "%a%a%a" pp_stack sigma_stack pp_nl + (sigma_stack <> [] && sigma_nonstack <> []) + pp_nonstack sigma_nonstack (** Dump a sigma. *) let d_sigma (sigma: sigma) = L.add_print_action (PTsigma, Obj.repr sigma) @@ -221,86 +212,83 @@ let d_sigma (sigma: sigma) = L.add_print_action (PTsigma, Obj.repr sigma) (** Dump a pi and a sigma *) let d_pi_sigma pi sigma = let d_separator () = if pi <> [] && sigma <> [] then L.d_strln " *" in - d_pi pi; d_separator (); d_sigma sigma + d_pi pi ; d_separator () ; d_sigma sigma -let pi_of_subst sub = - List.map ~f:(fun (id1, e2) -> Sil.Aeq (Var id1, e2)) (Sil.sub_to_list sub) +let pi_of_subst sub = List.map ~f:(fun (id1, e2) -> Sil.Aeq (Var id1, e2)) (Sil.sub_to_list sub) (** Return the pure part of [prop]. *) -let get_pure (p: 'a t) : pi = - pi_of_subst p.sub @ p.pi +let get_pure (p: 'a t) : pi = pi_of_subst p.sub @ p.pi (** Print existential quantification *) let pp_evars pe f evars = - if evars <> [] - then match pe.Pp.kind with - | TEXT | HTML -> - F.fprintf f "exists [%a]. " (Pp.comma_seq (Ident.pp pe)) evars - | LATEX -> - F.fprintf f "\\exists %a. " (Pp.comma_seq (Ident.pp pe)) evars + if evars <> [] then + match pe.Pp.kind with + | TEXT | HTML + -> F.fprintf f "exists [%a]. " (Pp.comma_seq (Ident.pp pe)) evars + | LATEX + -> F.fprintf f "\\exists %a. " (Pp.comma_seq (Ident.pp pe)) evars (** Print an hpara in simple mode *) let pp_hpara_simple _pe env n f pred = - let pe = Pp.reset_obj_sub _pe in (* no free vars: disable object substitution *) + let pe = Pp.reset_obj_sub _pe in + (* no free vars: disable object substitution *) match pe.kind with - | TEXT | HTML -> - F.fprintf f "P%d = %a%a" - n (pp_evars pe) pred.Sil.evars - (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body - | LATEX -> - F.fprintf f "P_{%d} = %a%a\\\\" - n (pp_evars pe) pred.Sil.evars - (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body + | TEXT | HTML + -> F.fprintf f "P%d = %a%a" n (pp_evars pe) pred.Sil.evars + (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) + pred.Sil.body + | LATEX + -> F.fprintf f "P_{%d} = %a%a\\\\" n (pp_evars pe) pred.Sil.evars + (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) + pred.Sil.body (** Print an hpara_dll in simple mode *) let pp_hpara_dll_simple _pe env n f pred = - let pe = Pp.reset_obj_sub _pe in (* no free vars: disable object substitution *) + let pe = Pp.reset_obj_sub _pe in + (* no free vars: disable object substitution *) match pe.kind with - | TEXT | HTML -> - F.fprintf f "P%d = %a%a" - n (pp_evars pe) pred.Sil.evars_dll - (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body_dll - | LATEX -> - F.fprintf f "P_{%d} = %a%a" - n (pp_evars pe) pred.Sil.evars_dll - (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body_dll + | TEXT | HTML + -> F.fprintf f "P%d = %a%a" n (pp_evars pe) pred.Sil.evars_dll + (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) + pred.Sil.body_dll + | LATEX + -> F.fprintf f "P_{%d} = %a%a" n (pp_evars pe) pred.Sil.evars_dll + (Pp.semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) + pred.Sil.body_dll (** Create an environment mapping (ident) expressions to the program variables containing them *) -let create_pvar_env (sigma: sigma) : (Exp.t -> Exp.t) = +let create_pvar_env (sigma: sigma) : Exp.t -> Exp.t = let env = ref [] in let filter = function - | Sil.Hpointsto (Lvar pvar, Eexp (Var v, _), _) -> - if not (Pvar.is_global pvar) then env := (Exp.Var v, Exp.Lvar pvar) :: !env - | _ -> () in - List.iter ~f:filter sigma; + | Sil.Hpointsto (Lvar pvar, Eexp (Var v, _), _) + -> if not (Pvar.is_global pvar) then env := (Exp.Var v, Exp.Lvar pvar) :: !env + | _ + -> () + in + List.iter ~f:filter sigma ; let find e = - List.find ~f:(fun (e1, _) -> Exp.equal e1 e) !env |> - Option.map ~f:snd |> - Option.value ~default:e in + List.find ~f:(fun (e1, _) -> Exp.equal e1 e) !env |> Option.map ~f:snd + |> Option.value ~default:e + in find (** Update the object substitution given the stack variables in the prop *) let prop_update_obj_sub pe prop = - if !Config.pp_simple - then Pp.set_obj_sub pe (create_pvar_env prop.sigma) - else pe + if !Config.pp_simple then Pp.set_obj_sub pe (create_pvar_env prop.sigma) else pe (** Pretty print a footprint in simple mode. *) let pp_footprint_simple _pe env f fp = - let pe = { _pe with Pp.cmap_norm = _pe.Pp.cmap_foot } in - let pp_pure f pi = - if pi <> [] then - F.fprintf f "%a *@\n" (pp_pi pe) pi in + let pe = {_pe with Pp.cmap_norm= _pe.Pp.cmap_foot} in + let pp_pure f pi = if pi <> [] then F.fprintf f "%a *@\n" (pp_pi pe) pi in if fp.pi_fp <> [] || fp.sigma_fp <> [] then - F.fprintf f "@\n[footprint@\n @[%a%a@] ]" - pp_pure fp.pi_fp - (pp_sigma_simple pe env) fp.sigma_fp + F.fprintf f "@\n[footprint@\n @[%a%a@] ]" pp_pure fp.pi_fp (pp_sigma_simple pe env) + fp.sigma_fp (** Create a predicate environment for a prop *) let prop_pred_env prop = let env = Sil.Predicates.empty_env () in - List.iter ~f:(Sil.Predicates.process_hpred env) prop.sigma; - List.iter ~f:(Sil.Predicates.process_hpred env) prop.sigma_fp; + List.iter ~f:(Sil.Predicates.process_hpred env) prop.sigma ; + List.iter ~f:(Sil.Predicates.process_hpred env) prop.sigma_fp ; env (** Pretty print a proposition. *) @@ -312,39 +300,33 @@ let pp_prop pe0 f prop = (* since prop diff is based on physical equality, we need to extract the sub verbatim *) let pi = prop.pi in let pp_pure f () = - if subl <> [] then F.fprintf f "%a ;@\n" (pp_subl pe) subl; - if pi <> [] then F.fprintf f "%a ;@\n" (pp_pi pe) pi in + if subl <> [] then F.fprintf f "%a ;@\n" (pp_subl pe) subl ; + if pi <> [] then F.fprintf f "%a ;@\n" (pp_pi pe) pi + in if !Config.pp_simple || latex then - begin - let env = prop_pred_env prop in - let iter_f n hpara = F.fprintf f "@,@[%a@]" (pp_hpara_simple pe env n) hpara in - let iter_f_dll n hpara_dll = - F.fprintf f "@,@[%a@]" (pp_hpara_dll_simple pe env n) hpara_dll in - let pp_predicates _ () = - if Sil.Predicates.is_empty env - then () - else if latex then - begin - F.fprintf f "@\n\\\\\\textsf{where }"; - Sil.Predicates.iter env iter_f iter_f_dll - end - else - begin - F.fprintf f "@,where"; - Sil.Predicates.iter env iter_f iter_f_dll - end in - F.fprintf f "%a%a%a%a" - pp_pure () (pp_sigma_simple pe env) prop.sigma - (pp_footprint_simple pe env) prop pp_predicates () - end - else - F.fprintf f "%a%a%a" pp_pure () (pp_sigma pe) prop.sigma (pp_footprint pe) prop in - if !Config.forcing_delayed_prints then (* print in html mode *) - F.fprintf f "%a%a%a" Io_infer.Html.pp_start_color Pp.Blue do_print () Io_infer.Html.pp_end_color () - else - do_print f () (** print in text mode *) - -let pp_prop_with_typ pe f p = pp_prop { pe with opt = SIM_WITH_TYP } f p + let env = prop_pred_env prop in + let iter_f n hpara = F.fprintf f "@,@[%a@]" (pp_hpara_simple pe env n) hpara in + let iter_f_dll n hpara_dll = + F.fprintf f "@,@[%a@]" (pp_hpara_dll_simple pe env n) hpara_dll + in + let pp_predicates _ () = + if Sil.Predicates.is_empty env then () + else if latex then ( + F.fprintf f "@\n\\\\\\textsf{where }" ; Sil.Predicates.iter env iter_f iter_f_dll ) + else ( F.fprintf f "@,where" ; Sil.Predicates.iter env iter_f iter_f_dll ) + in + F.fprintf f "%a%a%a%a" pp_pure () (pp_sigma_simple pe env) prop.sigma + (pp_footprint_simple pe env) prop pp_predicates () + else F.fprintf f "%a%a%a" pp_pure () (pp_sigma pe) prop.sigma (pp_footprint pe) prop + in + if !Config.forcing_delayed_prints then + (* print in html mode *) + F.fprintf f "%a%a%a" Io_infer.Html.pp_start_color Pp.Blue do_print () + Io_infer.Html.pp_end_color () + else do_print f () + (** print in text mode *) + +let pp_prop_with_typ pe f p = pp_prop {pe with opt= SIM_WITH_TYP} f p (** Dump a proposition. *) let d_prop (prop: 'a t) = L.add_print_action (PTprop, Obj.repr prop) @@ -355,64 +337,56 @@ let d_prop_with_typ (prop: 'a t) = L.add_print_action (PTprop_with_typ, Obj.repr (** Print a list of propositions, prepending each one with the given string *) let pp_proplist_with_typ pe f plist = let rec pp_seq_newline f = function - | [] -> () - | [x] -> F.fprintf f "@[%a@]" (pp_prop_with_typ pe) x - | x:: l -> F.fprintf f "@[%a@]@\n(||)@\n%a" (pp_prop_with_typ pe) x pp_seq_newline l in + | [] + -> () + | [x] + -> F.fprintf f "@[%a@]" (pp_prop_with_typ pe) x + | x :: l + -> F.fprintf f "@[%a@]@\n(||)@\n%a" (pp_prop_with_typ pe) x pp_seq_newline l + in F.fprintf f "@[%a@]" pp_seq_newline plist (** dump a proplist *) -let d_proplist_with_typ (pl: 'a t list) = - L.add_print_action (PTprop_list_with_typ, Obj.repr pl) +let d_proplist_with_typ (pl: 'a t list) = L.add_print_action (PTprop_list_with_typ, Obj.repr pl) (** {1 Functions for computing free non-program variables} *) -let pi_fav_add fav pi = - List.iter ~f:(Sil.atom_fav_add fav) pi +let pi_fav_add fav pi = List.iter ~f:(Sil.atom_fav_add fav) pi -let pi_fav = - Sil.fav_imperative_to_functional pi_fav_add +let pi_fav = Sil.fav_imperative_to_functional pi_fav_add -let sigma_fav_add fav sigma = - List.iter ~f:(Sil.hpred_fav_add fav) sigma +let sigma_fav_add fav sigma = List.iter ~f:(Sil.hpred_fav_add fav) sigma -let sigma_fav = - Sil.fav_imperative_to_functional sigma_fav_add +let sigma_fav = Sil.fav_imperative_to_functional sigma_fav_add -let prop_footprint_fav_add fav prop = - sigma_fav_add fav prop.sigma_fp; - pi_fav_add fav prop.pi_fp +let prop_footprint_fav_add fav prop = sigma_fav_add fav prop.sigma_fp ; pi_fav_add fav prop.pi_fp (** Find fav of the footprint part of the prop *) -let prop_footprint_fav prop = - Sil.fav_imperative_to_functional prop_footprint_fav_add prop +let prop_footprint_fav prop = Sil.fav_imperative_to_functional prop_footprint_fav_add prop let prop_fav_add fav prop = - sigma_fav_add fav prop.sigma; - sigma_fav_add fav prop.sigma_fp; - Sil.sub_fav_add fav prop.sub; - pi_fav_add fav prop.pi; + sigma_fav_add fav prop.sigma ; + sigma_fav_add fav prop.sigma_fp ; + Sil.sub_fav_add fav prop.sub ; + pi_fav_add fav prop.pi ; pi_fav_add fav prop.pi_fp -let prop_fav p = - Sil.fav_imperative_to_functional prop_fav_add p +let prop_fav p = Sil.fav_imperative_to_functional prop_fav_add p (** free vars of the prop, excluding the pure part *) -let prop_fav_nonpure_add fav prop = - sigma_fav_add fav prop.sigma; - sigma_fav_add fav prop.sigma_fp +let prop_fav_nonpure_add fav prop = sigma_fav_add fav prop.sigma ; sigma_fav_add fav prop.sigma_fp (** free vars, except pi and sub, of current and footprint parts *) -let prop_fav_nonpure = - Sil.fav_imperative_to_functional prop_fav_nonpure_add +let prop_fav_nonpure = Sil.fav_imperative_to_functional prop_fav_nonpure_add -let hpred_fav_in_pvars_add fav (hpred : Sil.hpred) = match hpred with - | Hpointsto (Lvar _, sexp, _) -> - Sil.strexp_fav_add fav sexp - | Hpointsto _ | Hlseg _ | Hdllseg _ -> - () +let hpred_fav_in_pvars_add fav (hpred: Sil.hpred) = + match hpred with + | Hpointsto (Lvar _, sexp, _) + -> Sil.strexp_fav_add fav sexp + | Hpointsto _ | Hlseg _ | Hdllseg _ + -> () -let sigma_fav_in_pvars_add fav sigma = - List.iter ~f:(hpred_fav_in_pvars_add fav) sigma +let sigma_fav_in_pvars_add fav sigma = List.iter ~f:(hpred_fav_in_pvars_add fav) sigma (** {2 Functions for Subsitition} *) @@ -425,203 +399,199 @@ let sigma_sub subst sigma = List.map ~f sigma (** Return [true] if the atom is an inequality *) -let atom_is_inequality (atom : Sil.atom) = match atom with - | Aeq (BinOp ((Le | Lt), _, _), Const (Cint i)) - when IntLit.isone i -> true - | _ -> false +let atom_is_inequality (atom: Sil.atom) = + match atom with + | Aeq (BinOp ((Le | Lt), _, _), Const Cint i) when IntLit.isone i + -> true + | _ + -> false (** If the atom is [e<=n] return [e,n] *) -let atom_exp_le_const (atom : Sil.atom) = match atom with - | Aeq(BinOp (Le, e1, Const (Cint n)), Const (Cint i)) - when IntLit.isone i -> - Some (e1, n) - | _ -> None +let atom_exp_le_const (atom: Sil.atom) = + match atom with + | Aeq (BinOp (Le, e1, Const Cint n), Const Cint i) when IntLit.isone i + -> Some (e1, n) + | _ + -> None (** If the atom is [n - Some (n, e1) - | _ -> None +let atom_const_lt_exp (atom: Sil.atom) = + match atom with + | Aeq (BinOp (Lt, Const Cint n, e1), Const Cint i) when IntLit.isone i + -> Some (n, e1) + | _ + -> None let exp_reorder e1 e2 = if Exp.compare e1 e2 <= 0 then (e1, e2) else (e2, e1) (** create a strexp of the given type, populating the structures if [expand_structs] is true *) -let rec create_strexp_of_type tenv struct_init_mode (typ : Typ.t) len inst : Sil.strexp = +let rec create_strexp_of_type tenv struct_init_mode (typ: Typ.t) len inst : Sil.strexp = let init_value () = let create_fresh_var () = let fresh_id = - (Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed)) in - Exp.Var fresh_id in - if Config.curr_language_is Config.Java && - Sil.equal_inst inst Sil.Ialloc - then - match typ.desc with - | Tfloat _ -> Exp.Const (Cfloat 0.0) - | _ -> Exp.zero - else - create_fresh_var () in - match typ.desc, len with - | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), None -> - Eexp (init_value (), inst) + Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed) + in + Exp.Var fresh_id + in + if Config.curr_language_is Config.Java && Sil.equal_inst inst Sil.Ialloc then + match typ.desc with Tfloat _ -> Exp.Const (Cfloat 0.0) | _ -> Exp.zero + else create_fresh_var () + in + match (typ.desc, len) with + | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), None + -> Eexp (init_value (), inst) | Tstruct name, _ -> ( - match struct_init_mode, Tenv.lookup tenv name with - | Fld_init, Some { fields } -> - (* pass len as an accumulator, so that it is passed to create_strexp_of_type for the last + match (struct_init_mode, Tenv.lookup tenv name) with + | Fld_init, Some {fields} + -> (* pass len as an accumulator, so that it is passed to create_strexp_of_type for the last field, but always return None so that only the last field receives len *) - let f (fld, t, a) (flds, len) = - if Typ.Struct.is_objc_ref_counter_field (fld, t, a) then - ((fld, Sil.Eexp (Exp.one, inst)) :: flds, None) - else - ((fld, create_strexp_of_type tenv struct_init_mode t len inst) :: flds, None) in - let flds, _ = List.fold_right ~f fields ~init:([], len) in - Estruct (flds, inst) - | _ -> - Estruct ([], inst) - ) - | Tarray (_, len_opt, _), None -> - let len = match len_opt with - | None -> Exp.get_undefined false - | Some len -> Exp.Const (Cint len) in - Earray (len, [], inst) - | Tarray _, Some len -> + let f (fld, t, a) (flds, len) = + if Typ.Struct.is_objc_ref_counter_field (fld, t, a) then + ((fld, Sil.Eexp (Exp.one, inst)) :: flds, None) + else ((fld, create_strexp_of_type tenv struct_init_mode t len inst) :: flds, None) + in + let flds, _ = List.fold_right ~f fields ~init:([], len) in + Estruct (flds, inst) + | _ + -> Estruct ([], inst) ) + | Tarray (_, len_opt, _), None + -> let len = + match len_opt with None -> Exp.get_undefined false | Some len -> Exp.Const (Cint len) + in Earray (len, [], inst) - | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), Some _ -> - assert false - -let replace_array_contents (hpred : Sil.hpred) esel : Sil.hpred = match hpred with - | Hpointsto (root, Sil.Earray (len, [], inst), te) -> - Hpointsto (root, Earray (len, esel, inst), te) - | _ -> assert false + | Tarray _, Some len + -> Earray (len, [], inst) + | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), Some _ + -> assert false + +let replace_array_contents (hpred: Sil.hpred) esel : Sil.hpred = + match hpred with + | Hpointsto (root, Sil.Earray (len, [], inst), te) + -> Hpointsto (root, Earray (len, esel, inst), te) + | _ + -> assert false (** remove duplicate atoms and redundant inequalities from a sorted pi *) -let rec pi_sorted_remove_redundant (pi : pi) = match pi with - | (Aeq (BinOp (Le, e1, Const (Cint n1)), - Const (Cint i1)) as a1) :: - Aeq (BinOp (Le, e2, Const (Cint n2)), - Const (Cint i2)) :: rest - when IntLit.isone i1 && IntLit.isone i2 && Exp.equal e1 e2 && IntLit.lt n1 n2 -> - (* second inequality redundant *) +let rec pi_sorted_remove_redundant (pi: pi) = + match pi with + | (Aeq (BinOp (Le, e1, Const Cint n1), Const Cint i1) as a1) + :: (Aeq (BinOp (Le, e2, Const Cint n2), Const Cint i2)) :: rest + when IntLit.isone i1 && IntLit.isone i2 && Exp.equal e1 e2 && IntLit.lt n1 n2 + -> (* second inequality redundant *) pi_sorted_remove_redundant (a1 :: rest) - | Aeq (BinOp (Lt, Const (Cint n1), e1), Const (Cint i1)) :: - (Aeq (BinOp (Lt, Const (Cint n2), e2), Const (Cint i2)) as a2) - :: rest - when IntLit.isone i1 && IntLit.isone i2 && Exp.equal e1 e2 && IntLit.lt n1 n2 -> - (* first inequality redundant *) + | (Aeq (BinOp (Lt, Const Cint n1, e1), Const Cint i1)) + :: (Aeq (BinOp (Lt, Const Cint n2, e2), Const Cint i2) as a2) :: rest + when IntLit.isone i1 && IntLit.isone i2 && Exp.equal e1 e2 && IntLit.lt n1 n2 + -> (* first inequality redundant *) pi_sorted_remove_redundant (a2 :: rest) - | a1:: a2:: rest -> - if Sil.equal_atom a1 a2 then pi_sorted_remove_redundant (a2 :: rest) + | a1 :: a2 :: rest + -> if Sil.equal_atom a1 a2 then pi_sorted_remove_redundant (a2 :: rest) else a1 :: pi_sorted_remove_redundant (a2 :: rest) - | [a] -> [a] - | [] -> [] + | [a] + -> [a] + | [] + -> [] (** find the unsigned expressions in sigma (immediately inside a pointsto, for now) *) let sigma_get_unsigned_exps sigma = let uexps = ref [] in - let do_hpred (hpred : Sil.hpred) = match hpred with - | Hpointsto (_, Eexp (e, _), Sizeof {typ={desc=Tint ik}}) - when Typ.ikind_is_unsigned ik -> - uexps := e :: !uexps - | _ -> () in - List.iter ~f:do_hpred sigma; - !uexps + let do_hpred (hpred: Sil.hpred) = + match hpred with + | Hpointsto (_, Eexp (e, _), Sizeof {typ= {desc= Tint ik}}) when Typ.ikind_is_unsigned ik + -> uexps := e :: !uexps + | _ + -> () + in + List.iter ~f:do_hpred sigma ; !uexps (** Collapse consecutive indices that should be added. For instance, this function reduces x[1][1] to x[2]. The [typ] argument is used to ensure the soundness of this collapsing. *) -let exp_collapse_consecutive_indices_prop (typ : Typ.t) exp = - let typ_is_base (typ1 : Typ.t) = - match typ1.desc with - | Tint _ | Tfloat _ | Tstruct _ | Tvoid | Tfun _ -> - true - | _ -> - false in +let exp_collapse_consecutive_indices_prop (typ: Typ.t) exp = + let typ_is_base (typ1: Typ.t) = + match typ1.desc with Tint _ | Tfloat _ | Tstruct _ | Tvoid | Tfun _ -> true | _ -> false + in let typ_is_one_step_from_base = - match typ.desc with - | Tptr (t, _) | Tarray (t, _, _) -> - typ_is_base t - | _ -> - false in - let rec exp_remove (e0 : Exp.t) = + match typ.desc with Tptr (t, _) | Tarray (t, _, _) -> typ_is_base t | _ -> false + in + let rec exp_remove (e0: Exp.t) = match e0 with - | Lindex(Lindex(base, e1), e2) -> - let e0' : Exp.t = Lindex(base, BinOp(PlusA, e1, e2)) in + | Lindex (Lindex (base, e1), e2) + -> let e0' : Exp.t = Lindex (base, BinOp (PlusA, e1, e2)) in exp_remove e0' - | _ -> e0 in - begin - if typ_is_one_step_from_base then exp_remove exp else exp - end + | _ + -> e0 + in + if typ_is_one_step_from_base then exp_remove exp else exp (** {2 Compaction} *) (** Return a compact representation of the prop *) -let prop_compact sh (prop : normal t) : normal t = +let prop_compact sh (prop: normal t) : normal t = let sigma' = List.map ~f:(Sil.hpred_compact sh) prop.sigma in unsafe_cast_to_normal (set prop ~sigma:sigma') (** {2 Query about Proposition} *) (** Check if the sigma part of the proposition is emp *) -let prop_is_emp p = match p.sigma with - | [] -> true - | _ -> false +let prop_is_emp p = match p.sigma with [] -> true | _ -> false (** {2 Functions for changing and generating propositions} *) (** Conjoin a heap predicate by separating conjunction. *) -let prop_hpred_star (p : 'a t) (h : Sil.hpred) : exposed t = - let sigma' = h:: p.sigma in +let prop_hpred_star (p: 'a t) (h: Sil.hpred) : exposed t = + let sigma' = h :: p.sigma in set p ~sigma:sigma' -let prop_sigma_star (p : 'a t) (sigma : sigma) : exposed t = +let prop_sigma_star (p: 'a t) (sigma: sigma) : exposed t = let sigma' = sigma @ p.sigma in set p ~sigma:sigma' (** return the set of subexpressions of [strexp] *) let strexp_get_exps strexp = - let rec strexp_get_exps_rec exps (se : Sil.strexp) = match se with - | Eexp (Exn e, _) -> Exp.Set.add e exps - | Eexp (e, _) -> Exp.Set.add e exps - | Estruct (flds, _) -> - List.fold - ~f:(fun exps (_, strexp) -> strexp_get_exps_rec exps strexp) - ~init:exps - flds - | Earray (_, elems, _) -> - List.fold - ~f:(fun exps (_, strexp) -> strexp_get_exps_rec exps strexp) - ~init:exps - elems in + let rec strexp_get_exps_rec exps (se: Sil.strexp) = + match se with + | Eexp (Exn e, _) + -> Exp.Set.add e exps + | Eexp (e, _) + -> Exp.Set.add e exps + | Estruct (flds, _) + -> List.fold ~f:(fun exps (_, strexp) -> strexp_get_exps_rec exps strexp) ~init:exps flds + | Earray (_, elems, _) + -> List.fold ~f:(fun exps (_, strexp) -> strexp_get_exps_rec exps strexp) ~init:exps elems + in strexp_get_exps_rec Exp.Set.empty strexp (** get the set of expressions on the righthand side of [hpred] *) -let hpred_get_targets (hpred : Sil.hpred) = match hpred with - | Hpointsto (_, rhs, _) -> strexp_get_exps rhs - | Hlseg (_, _, _, e, el) -> - List.fold ~f:(fun exps e -> Exp.Set.add e exps) ~init:Exp.Set.empty (e :: el) - | Hdllseg (_, _, _, oB, oF, iB, el) -> - (* only one direction supported for now *) - List.fold - ~f:(fun exps e -> Exp.Set.add e exps) - ~init:Exp.Set.empty - (oB :: oF :: iB :: el) +let hpred_get_targets (hpred: Sil.hpred) = + match hpred with + | Hpointsto (_, rhs, _) + -> strexp_get_exps rhs + | Hlseg (_, _, _, e, el) + -> List.fold ~f:(fun exps e -> Exp.Set.add e exps) ~init:Exp.Set.empty (e :: el) + | Hdllseg (_, _, _, oB, oF, iB, el) + -> (* only one direction supported for now *) + List.fold ~f:(fun exps e -> Exp.Set.add e exps) ~init:Exp.Set.empty (oB :: oF :: iB :: el) (** return the set of hpred's and exp's in [sigma] that are reachable from an expression in [exps] *) let compute_reachable_hpreds sigma exps = let rec compute_reachable_hpreds_rec sigma (reach, exps) = - let add_hpred_if_reachable (reach, exps) (hpred : Sil.hpred) = match hpred with - | Hpointsto (lhs, _, _) as hpred when Exp.Set.mem lhs exps-> - let reach' = Sil.HpredSet.add hpred reach in + let add_hpred_if_reachable (reach, exps) (hpred: Sil.hpred) = + match hpred with + | Hpointsto (lhs, _, _) as hpred when Exp.Set.mem lhs exps + -> let reach' = Sil.HpredSet.add hpred reach in let reach_exps = hpred_get_targets hpred in (reach', Exp.Set.union exps reach_exps) - | _ -> reach, exps in + | _ + -> (reach, exps) + in let reach', exps' = List.fold ~f:add_hpred_if_reachable ~init:(reach, exps) sigma in if Int.equal (Sil.HpredSet.cardinal reach) (Sil.HpredSet.cardinal reach') then (reach, exps) - else compute_reachable_hpreds_rec sigma (reach', exps') in + else compute_reachable_hpreds_rec sigma (reach', exps') + in compute_reachable_hpreds_rec sigma (Sil.HpredSet.empty, exps) - (* Module for normalization *) module Normalize = struct (** Eliminates all empty lsegs from sigma, and collect equalities @@ -633,770 +603,781 @@ module Normalize = struct cell iF or iB. *) let sigma_remove_emptylseg sigma = let alloc_set = - let rec f_alloc set (sigma1 : sigma) = match sigma1 with - | [] -> - set - | Hpointsto (e, _, _) :: sigma' | Hlseg (Sil.Lseg_NE, _, e, _, _) :: sigma' -> - f_alloc (Exp.Set.add e set) sigma' - | Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) :: sigma' -> - f_alloc (Exp.Set.add iF (Exp.Set.add iB set)) sigma' - | _ :: sigma' -> - f_alloc set sigma' in + let rec f_alloc set (sigma1: sigma) = + match sigma1 with + | [] + -> set + | (Hpointsto (e, _, _)) :: sigma' | (Hlseg (Sil.Lseg_NE, _, e, _, _)) :: sigma' + -> f_alloc (Exp.Set.add e set) sigma' + | (Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _)) :: sigma' + -> f_alloc (Exp.Set.add iF (Exp.Set.add iB set)) sigma' + | _ :: sigma' + -> f_alloc set sigma' + in f_alloc Exp.Set.empty sigma in - let rec f eqs_zero sigma_passed (sigma1: sigma) = match sigma1 with - | [] -> - (List.rev eqs_zero, List.rev sigma_passed) - | Hpointsto _ as hpred :: sigma' -> - f eqs_zero (hpred :: sigma_passed) sigma' - | Hlseg (Lseg_PE, _, e1, e2, _) :: sigma' - when (Exp.equal e1 Exp.zero) || (Exp.Set.mem e1 alloc_set) -> - f (Sil.Aeq(e1, e2) :: eqs_zero) sigma_passed sigma' - | Hlseg _ as hpred :: sigma' -> - f eqs_zero (hpred :: sigma_passed) sigma' - | Hdllseg (Lseg_PE, _, iF, oB, oF, iB, _) :: sigma' - when (Exp.equal iF Exp.zero) || (Exp.Set.mem iF alloc_set) - || (Exp.equal iB Exp.zero) || (Exp.Set.mem iB alloc_set) -> - f (Sil.Aeq(iF, oF):: Sil.Aeq(iB, oB):: eqs_zero) sigma_passed sigma' - | Hdllseg _ as hpred :: sigma' -> - f eqs_zero (hpred :: sigma_passed) sigma' + let rec f eqs_zero sigma_passed (sigma1: sigma) = + match sigma1 with + | [] + -> (List.rev eqs_zero, List.rev sigma_passed) + | (Hpointsto _ as hpred) :: sigma' + -> f eqs_zero (hpred :: sigma_passed) sigma' + | (Hlseg (Lseg_PE, _, e1, e2, _)) :: sigma' + when Exp.equal e1 Exp.zero || Exp.Set.mem e1 alloc_set + -> f (Sil.Aeq (e1, e2) :: eqs_zero) sigma_passed sigma' + | (Hlseg _ as hpred) :: sigma' + -> f eqs_zero (hpred :: sigma_passed) sigma' + | (Hdllseg (Lseg_PE, _, iF, oB, oF, iB, _)) :: sigma' + when Exp.equal iF Exp.zero || Exp.Set.mem iF alloc_set || Exp.equal iB Exp.zero + || Exp.Set.mem iB alloc_set + -> f (Sil.Aeq (iF, oF) :: Sil.Aeq (iB, oB) :: eqs_zero) sigma_passed sigma' + | (Hdllseg _ as hpred) :: sigma' + -> f eqs_zero (hpred :: sigma_passed) sigma' in f [] [] sigma let sigma_intro_nonemptylseg e1 e2 sigma = - let rec f sigma_passed (sigma1 : sigma) = match sigma1 with - | [] -> - List.rev sigma_passed - | Hpointsto _ as hpred :: sigma' -> - f (hpred :: sigma_passed) sigma' - | Hlseg (Lseg_PE, para, f1, f2, shared) :: sigma' - when (Exp.equal e1 f1 && Exp.equal e2 f2) - || (Exp.equal e2 f1 && Exp.equal e1 f2) -> - f (Sil.Hlseg (Lseg_NE, para, f1, f2, shared) :: sigma_passed) sigma' - | Hlseg _ as hpred :: sigma' -> - f (hpred :: sigma_passed) sigma' - | Hdllseg (Lseg_PE, para, iF, oB, oF, iB, shared) :: sigma' - when (Exp.equal e1 iF && Exp.equal e2 oF) - || (Exp.equal e2 iF && Exp.equal e1 oF) - || (Exp.equal e1 iB && Exp.equal e2 oB) - || (Exp.equal e2 iB && Exp.equal e1 oB) -> - f (Sil.Hdllseg (Lseg_NE, para, iF, oB, oF, iB, shared) :: sigma_passed) sigma' - | Hdllseg _ as hpred :: sigma' -> - f (hpred :: sigma_passed) sigma' + let rec f sigma_passed (sigma1: sigma) = + match sigma1 with + | [] + -> List.rev sigma_passed + | (Hpointsto _ as hpred) :: sigma' + -> f (hpred :: sigma_passed) sigma' + | (Hlseg (Lseg_PE, para, f1, f2, shared)) :: sigma' + when Exp.equal e1 f1 && Exp.equal e2 f2 || Exp.equal e2 f1 && Exp.equal e1 f2 + -> f (Sil.Hlseg (Lseg_NE, para, f1, f2, shared) :: sigma_passed) sigma' + | (Hlseg _ as hpred) :: sigma' + -> f (hpred :: sigma_passed) sigma' + | (Hdllseg (Lseg_PE, para, iF, oB, oF, iB, shared)) :: sigma' + when Exp.equal e1 iF && Exp.equal e2 oF || Exp.equal e2 iF && Exp.equal e1 oF + || Exp.equal e1 iB && Exp.equal e2 oB || Exp.equal e2 iB && Exp.equal e1 oB + -> f (Sil.Hdllseg (Lseg_NE, para, iF, oB, oF, iB, shared) :: sigma_passed) sigma' + | (Hdllseg _ as hpred) :: sigma' + -> f (hpred :: sigma_passed) sigma' in f [] sigma - let (--) = IntLit.sub - let (++) = IntLit.add + let ( -- ) = IntLit.sub + + let ( ++ ) = IntLit.add let sym_eval tenv abs e = let lookup = Tenv.lookup tenv in - let rec eval (e : Exp.t) : Exp.t = + let rec eval (e: Exp.t) : Exp.t = (* L.d_str " ["; Sil.d_exp e; L.d_str"] "; *) match e with - | Var _ -> - e - | Closure c -> - let captured_vars = - List.map ~f:(fun (exp, pvar, typ) -> (eval exp, pvar, typ)) c.captured_vars in - Closure { c with captured_vars; } - | Const _ -> - e - | Sizeof {typ={desc=Tarray ({desc=Tint ik}, _, _)}; dynamic_length=Some l} - when Typ.ikind_is_char ik && Config.curr_language_is Config.Clang -> - eval l - | Sizeof {typ={desc=Tarray ({desc=Tint ik}, Some l, _)}} - when Typ.ikind_is_char ik && Config.curr_language_is Config.Clang -> - Const (Cint l) - | Sizeof _ -> - e - | Cast (_, e1) -> - eval e1 - | UnOp (Unop.LNot, e1, topt) -> - begin - match eval e1 with - | Const (Cint i) when IntLit.iszero i -> - Exp.one - | Const (Cint _) -> - Exp.zero - | UnOp(LNot, e1', _) -> - e1' - | e1' -> - if abs then Exp.get_undefined false else UnOp(LNot, e1', topt) - end - | UnOp (Neg, e1, topt) -> - begin - match eval e1 with - | UnOp (Neg, e2', _) -> - e2' - | Const (Cint i) -> - Exp.int (IntLit.neg i) - | Const (Cfloat v) -> - Exp.float (-. v) - | Var id -> - UnOp (Neg, Var id, topt) - | e1' -> - if abs then Exp.get_undefined false else UnOp (Neg, e1', topt) - end - | UnOp (BNot, e1, topt) -> - begin - match eval e1 with - | UnOp(BNot, e2', _) -> - e2' - | Const (Cint i) -> - Exp.int (IntLit.lognot i) - | e1' -> - if abs then Exp.get_undefined false else UnOp (BNot, e1', topt) - end - | BinOp (Le, e1, e2) -> - begin - match eval e1, eval e2 with - | Const (Cint n), Const (Cint m) -> - Exp.bool (IntLit.leq n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.bool (v <= w) - | BinOp (PlusA, e3, Const (Cint n)), Const (Cint m) -> - BinOp (Le, e3, Exp.int (m -- n)) - | e1', e2' -> - Exp.le e1' e2' - end - | BinOp (Lt, e1, e2) -> - begin - match eval e1, eval e2 with - | Const (Cint n), Const (Cint m) -> - Exp.bool (IntLit.lt n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.bool (v < w) - | Const (Cint n), BinOp (MinusA, f1, f2) -> - BinOp - (Le, BinOp (MinusA, f2, f1), Exp.int (IntLit.minus_one -- n)) - | BinOp(MinusA, f1 , f2), Const(Cint n) -> - Exp.le (BinOp(MinusA, f1 , f2)) (Exp.int (n -- IntLit.one)) - | BinOp (PlusA, e3, Const (Cint n)), Const (Cint m) -> - BinOp (Lt, e3, Exp.int (m -- n)) - | e1', e2' -> - Exp.lt e1' e2' - end - | BinOp (Ge, e1, e2) -> - eval (Exp.le e2 e1) - | BinOp (Gt, e1, e2) -> - eval (Exp.lt e2 e1) - | BinOp (Eq, e1, e2) -> - begin - match eval e1, eval e2 with - | Const (Cint n), Const (Cint m) -> - Exp.bool (IntLit.eq n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.bool (Float.equal v w) - | e1', e2' -> - Exp.eq e1' e2' - end - | BinOp (Ne, e1, e2) -> - begin - match eval e1, eval e2 with - | Const (Cint n), Const (Cint m) -> - Exp.bool (IntLit.neq n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.bool (v <> w) - | e1', e2' -> - Exp.ne e1' e2' - end - | BinOp (LAnd, e1, e2) -> + | Var _ + -> e + | Closure c + -> let captured_vars = + List.map ~f:(fun (exp, pvar, typ) -> (eval exp, pvar, typ)) c.captured_vars + in + Closure {c with captured_vars} + | Const _ + -> e + | Sizeof {typ= {desc= Tarray ({desc= Tint ik}, _, _)}; dynamic_length= Some l} + when Typ.ikind_is_char ik && Config.curr_language_is Config.Clang + -> eval l + | Sizeof {typ= {desc= Tarray ({desc= Tint ik}, Some l, _)}} + when Typ.ikind_is_char ik && Config.curr_language_is Config.Clang + -> Const (Cint l) + | Sizeof _ + -> e + | Cast (_, e1) + -> eval e1 + | UnOp (Unop.LNot, e1, topt) -> ( + match eval e1 with + | Const Cint i when IntLit.iszero i + -> Exp.one + | Const Cint _ + -> Exp.zero + | UnOp (LNot, e1', _) + -> e1' + | e1' + -> if abs then Exp.get_undefined false else UnOp (LNot, e1', topt) ) + | UnOp (Neg, e1, topt) -> ( + match eval e1 with + | UnOp (Neg, e2', _) + -> e2' + | Const Cint i + -> Exp.int (IntLit.neg i) + | Const Cfloat v + -> Exp.float ~-.v + | Var id + -> UnOp (Neg, Var id, topt) + | e1' + -> if abs then Exp.get_undefined false else UnOp (Neg, e1', topt) ) + | UnOp (BNot, e1, topt) -> ( + match eval e1 with + | UnOp (BNot, e2', _) + -> e2' + | Const Cint i + -> Exp.int (IntLit.lognot i) + | e1' + -> if abs then Exp.get_undefined false else UnOp (BNot, e1', topt) ) + | BinOp (Le, e1, e2) -> ( + match (eval e1, eval e2) with + | Const Cint n, Const Cint m + -> Exp.bool (IntLit.leq n m) + | Const Cfloat v, Const Cfloat w + -> Exp.bool (v <= w) + | BinOp (PlusA, e3, Const Cint n), Const Cint m + -> BinOp (Le, e3, Exp.int (m -- n)) + | e1', e2' + -> Exp.le e1' e2' ) + | BinOp (Lt, e1, e2) -> ( + match (eval e1, eval e2) with + | Const Cint n, Const Cint m + -> Exp.bool (IntLit.lt n m) + | Const Cfloat v, Const Cfloat w + -> Exp.bool (v < w) + | Const Cint n, BinOp (MinusA, f1, f2) + -> BinOp (Le, BinOp (MinusA, f2, f1), Exp.int (IntLit.minus_one -- n)) + | BinOp (MinusA, f1, f2), Const Cint n + -> Exp.le (BinOp (MinusA, f1, f2)) (Exp.int (n -- IntLit.one)) + | BinOp (PlusA, e3, Const Cint n), Const Cint m + -> BinOp (Lt, e3, Exp.int (m -- n)) + | e1', e2' + -> Exp.lt e1' e2' ) + | BinOp (Ge, e1, e2) + -> eval (Exp.le e2 e1) + | BinOp (Gt, e1, e2) + -> eval (Exp.lt e2 e1) + | BinOp (Eq, e1, e2) -> ( + match (eval e1, eval e2) with + | Const Cint n, Const Cint m + -> Exp.bool (IntLit.eq n m) + | Const Cfloat v, Const Cfloat w + -> Exp.bool (Float.equal v w) + | e1', e2' + -> Exp.eq e1' e2' ) + | BinOp (Ne, e1, e2) -> ( + match (eval e1, eval e2) with + | Const Cint n, Const Cint m + -> Exp.bool (IntLit.neq n m) + | Const Cfloat v, Const Cfloat w + -> Exp.bool (v <> w) + | e1', e2' + -> Exp.ne e1' e2' ) + | BinOp (LAnd, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin match e1', e2' with - | Const (Cint i), _ when IntLit.iszero i -> - e1' - | Const (Cint _), _ -> - e2' - | _, Const (Cint i) when IntLit.iszero i -> - e2' - | _, Const (Cint _) -> - e1' - | _ -> - BinOp (LAnd, e1', e2') - end - | BinOp (LOr, e1, e2) -> + match (e1', e2') with + | Const Cint i, _ when IntLit.iszero i + -> e1' + | Const Cint _, _ + -> e2' + | _, Const Cint i when IntLit.iszero i + -> e2' + | _, Const Cint _ + -> e1' + | _ + -> BinOp (LAnd, e1', e2') ) + | BinOp (LOr, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin - match e1', e2' with - | Const (Cint i), _ when IntLit.iszero i -> - e2' - | Const (Cint _), _ -> - e1' - | _, Const (Cint i) when IntLit.iszero i -> - e1' - | _, Const (Cint _) -> - e2' - | _ -> - BinOp (LOr, e1', e2') - end - | BinOp(PlusPI, Lindex (ep, e1), e2) -> - (* array access with pointer arithmetic *) + match (e1', e2') with + | Const Cint i, _ when IntLit.iszero i + -> e2' + | Const Cint _, _ + -> e1' + | _, Const Cint i when IntLit.iszero i + -> e1' + | _, Const Cint _ + -> e2' + | _ + -> BinOp (LOr, e1', e2') ) + | BinOp (PlusPI, Lindex (ep, e1), e2) + -> (* array access with pointer arithmetic *) let e' : Exp.t = BinOp (PlusA, e1, e2) in eval (Exp.Lindex (ep, e')) - | BinOp (PlusPI, (BinOp (PlusPI, e11, e12)), e2) -> - (* take care of pattern ((ptr + off1) + off2) *) + | BinOp (PlusPI, BinOp (PlusPI, e11, e12), e2) + -> (* take care of pattern ((ptr + off1) + off2) *) (* progress: convert inner +I to +A *) let e2' : Exp.t = BinOp (PlusA, e12, e2) in eval (Exp.BinOp (PlusPI, e11, e2')) - | BinOp (PlusA as oplus, e1, e2) - | BinOp (PlusPI as oplus, e1, e2) -> + | BinOp ((PlusA as oplus), e1, e2) | BinOp ((PlusPI as oplus), e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in let isPlusA = Binop.equal oplus Binop.PlusA in let ominus = if isPlusA then Binop.MinusA else Binop.MinusPI in - let (+++) (x : Exp.t) (y : Exp.t) : Exp.t = match x, y with - | _, Const (Cint i) when IntLit.iszero i -> x - | Const (Cint i), Const (Cint j) -> - Const (Cint (IntLit.add i j)) - | _ -> - BinOp (oplus, x, y) in - let (---) (x : Exp.t) (y : Exp.t) : Exp.t = match x, y with - | _, Const (Cint i) when IntLit.iszero i -> x - | Const (Cint i), Const (Cint j) -> - Const (Cint (IntLit.sub i j)) - | _ -> BinOp (ominus, x, y) in + let ( +++ ) (x: Exp.t) (y: Exp.t) : Exp.t = + match (x, y) with + | _, Const Cint i when IntLit.iszero i + -> x + | Const Cint i, Const Cint j + -> Const (Cint (IntLit.add i j)) + | _ + -> BinOp (oplus, x, y) + in + let ( --- ) (x: Exp.t) (y: Exp.t) : Exp.t = + match (x, y) with + | _, Const Cint i when IntLit.iszero i + -> x + | Const Cint i, Const Cint j + -> Const (Cint (IntLit.sub i j)) + | _ + -> BinOp (ominus, x, y) + in (* test if the extensible array at the end of [typ] has elements of type [elt] *) let extensible_array_element_typ_equal elt typ = Option.value_map ~f:(Typ.equal elt) ~default:false - (Typ.Struct.get_extensible_array_element_typ ~lookup typ) in - begin - match e1', e2' with - (* pattern for arrays and extensible structs: + (Typ.Struct.get_extensible_array_element_typ ~lookup typ) + in + match (e1', e2') with + (* pattern for arrays and extensible structs: sizeof(struct s {... t[l]}) + k * sizeof(t)) = sizeof(struct s {... t[l + k]}) *) - | Sizeof ({typ; dynamic_length=len1_opt} as sizeof_data), - BinOp (Mult, len2, Sizeof {typ=elt; dynamic_length=None}) - when isPlusA && (extensible_array_element_typ_equal elt typ) -> - let len = match len1_opt with Some len1 -> len1 +++ len2 | None -> len2 in - Sizeof {sizeof_data with dynamic_length=Some len} - | Const c, _ when Const.iszero_int_float c -> - e2' - | _, Const c when Const.iszero_int_float c -> - e1' - | Const (Cint n), Const (Cint m) -> - Exp.int (n ++ m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.float (v +. w) - | UnOp(Neg, f1, _), f2 - | f2, UnOp(Neg, f1, _) -> - BinOp (ominus, f2, f1) - | BinOp (PlusA, e, Const (Cint n1)), Const (Cint n2) - | BinOp (PlusPI, e, Const (Cint n1)), Const (Cint n2) - | Const (Cint n2), BinOp (PlusA, e, Const (Cint n1)) - | Const (Cint n2), BinOp (PlusPI, e, Const (Cint n1)) -> - e +++ (Exp.int (n1 ++ n2)) - | BinOp (MinusA, Const (Cint n1), e), Const (Cint n2) - | Const (Cint n2), BinOp (MinusA, Const (Cint n1), e) -> - Exp.int (n1 ++ n2) --- e - | BinOp (MinusA, e1, e2), e3 -> (* (e1-e2)+e3 --> e1 + (e3-e2) *) - (* progress: brings + to the outside *) - eval (e1 +++ (e3 --- e2)) - | _, Const _ -> - e1' +++ e2' - | Const _, _ -> - if isPlusA then e2' +++ e1' else e1' +++ e2' - | Var _, Var _ -> - e1' +++ e2' - | _ -> - if abs && isPlusA then Exp.get_undefined false else - if abs && not isPlusA then e1' +++ (Exp.get_undefined false) - else e1' +++ e2' - end - | BinOp (MinusA as ominus, e1, e2) - | BinOp (MinusPI as ominus, e1, e2) -> + | ( Sizeof ({typ; dynamic_length= len1_opt} as sizeof_data) + , BinOp (Mult, len2, Sizeof {typ= elt; dynamic_length= None}) ) + when isPlusA && extensible_array_element_typ_equal elt typ + -> let len = match len1_opt with Some len1 -> len1 +++ len2 | None -> len2 in + Sizeof {sizeof_data with dynamic_length= Some len} + | Const c, _ when Const.iszero_int_float c + -> e2' + | _, Const c when Const.iszero_int_float c + -> e1' + | Const Cint n, Const Cint m + -> Exp.int (n ++ m) + | Const Cfloat v, Const Cfloat w + -> Exp.float (v +. w) + | UnOp (Neg, f1, _), f2 | f2, UnOp (Neg, f1, _) + -> BinOp (ominus, f2, f1) + | BinOp (PlusA, e, Const Cint n1), Const Cint n2 + | BinOp (PlusPI, e, Const Cint n1), Const Cint n2 + | Const Cint n2, BinOp (PlusA, e, Const Cint n1) + | Const Cint n2, BinOp (PlusPI, e, Const Cint n1) + -> e +++ Exp.int (n1 ++ n2) + | BinOp (MinusA, Const Cint n1, e), Const Cint n2 + | Const Cint n2, BinOp (MinusA, Const Cint n1, e) + -> Exp.int (n1 ++ n2) --- e + | BinOp (MinusA, e1, e2), e3 + -> (* (e1-e2)+e3 --> e1 + (e3-e2) *) + (* progress: brings + to the outside *) + eval (e1 +++ (e3 --- e2)) + | _, Const _ + -> e1' +++ e2' + | Const _, _ + -> if isPlusA then e2' +++ e1' else e1' +++ e2' + | Var _, Var _ + -> e1' +++ e2' + | _ + -> if abs && isPlusA then Exp.get_undefined false + else if abs && not isPlusA then e1' +++ Exp.get_undefined false + else e1' +++ e2' ) + | BinOp ((MinusA as ominus), e1, e2) | BinOp ((MinusPI as ominus), e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in let isMinusA = Binop.equal ominus Binop.MinusA in let oplus = if isMinusA then Binop.PlusA else Binop.PlusPI in - let (+++) x y : Exp.t = BinOp (oplus, x, y) in - let (---) x y : Exp.t = BinOp (ominus, x, y) in + let ( +++ ) x y : Exp.t = BinOp (oplus, x, y) in + let ( --- ) x y : Exp.t = BinOp (ominus, x, y) in if Exp.equal e1' e2' then Exp.zero - else begin - match e1', e2' with - | Const c, _ when Const.iszero_int_float c -> - eval (Exp.UnOp(Neg, e2', None)) - | _, Const c when Const.iszero_int_float c -> - e1' - | Const (Cint n), Const (Cint m) -> - Exp.int (n -- m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.float (v -. w) - | _, UnOp (Neg, f2, _) -> - eval (e1 +++ f2) - | _ , Const(Cint n) -> - eval (e1' +++ (Exp.int (IntLit.neg n))) - | Const _, _ -> - e1' --- e2' - | Var _, Var _ -> - e1' --- e2' - | _, _ -> - if abs then Exp.get_undefined false else e1' --- e2' - end - | BinOp (MinusPP, e1, e2) -> - if abs then Exp.get_undefined false - else BinOp (MinusPP, eval e1, eval e2) - | BinOp (Mult, e1, e2) -> + else + match (e1', e2') with + | Const c, _ when Const.iszero_int_float c + -> eval (Exp.UnOp (Neg, e2', None)) + | _, Const c when Const.iszero_int_float c + -> e1' + | Const Cint n, Const Cint m + -> Exp.int (n -- m) + | Const Cfloat v, Const Cfloat w + -> Exp.float (v -. w) + | _, UnOp (Neg, f2, _) + -> eval (e1 +++ f2) + | _, Const Cint n + -> eval (e1' +++ Exp.int (IntLit.neg n)) + | Const _, _ + -> e1' --- e2' + | Var _, Var _ + -> e1' --- e2' + | _, _ + -> if abs then Exp.get_undefined false else e1' --- e2' ) + | BinOp (MinusPP, e1, e2) + -> if abs then Exp.get_undefined false else BinOp (MinusPP, eval e1, eval e2) + | BinOp (Mult, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin - match e1', e2' with - | Const c, _ when Const.iszero_int_float c -> - Exp.zero - | Const c, _ when Const.isone_int_float c -> - e2' - | Const c, _ when Const.isminusone_int_float c -> - eval (Exp.UnOp (Neg, e2', None)) - | _, Const c when Const.iszero_int_float c -> - Exp.zero - | _, Const c when Const.isone_int_float c -> - e1' - | _, Const c when Const.isminusone_int_float c -> - eval (Exp.UnOp (Neg, e1', None)) - | Const (Cint n), Const (Cint m) -> - Exp.int (IntLit.mul n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.float (v *. w) - | Var _, Var _ -> - BinOp(Mult, e1', e2') - | _, Sizeof _ - | Sizeof _, _ -> - BinOp(Mult, e1', e2') - | _, _ -> - if abs then Exp.get_undefined false else BinOp(Mult, e1', e2') - end - | BinOp (Div, e1, e2) -> + match (e1', e2') with + | Const c, _ when Const.iszero_int_float c + -> Exp.zero + | Const c, _ when Const.isone_int_float c + -> e2' + | Const c, _ when Const.isminusone_int_float c + -> eval (Exp.UnOp (Neg, e2', None)) + | _, Const c when Const.iszero_int_float c + -> Exp.zero + | _, Const c when Const.isone_int_float c + -> e1' + | _, Const c when Const.isminusone_int_float c + -> eval (Exp.UnOp (Neg, e1', None)) + | Const Cint n, Const Cint m + -> Exp.int (IntLit.mul n m) + | Const Cfloat v, Const Cfloat w + -> Exp.float (v *. w) + | Var _, Var _ + -> BinOp (Mult, e1', e2') + | _, Sizeof _ | Sizeof _, _ + -> BinOp (Mult, e1', e2') + | _, _ + -> if abs then Exp.get_undefined false else BinOp (Mult, e1', e2') ) + | BinOp (Div, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin - match e1', e2' with - | _, Const c when Const.iszero_int_float c -> - Exp.get_undefined false - | Const c, _ when Const.iszero_int_float c -> - e1' - | _, Const c when Const.isone_int_float c -> - e1' - | Const (Cint n), Const (Cint m) -> - Exp.int (IntLit.div n m) - | Const (Cfloat v), Const (Cfloat w) -> - Exp.float (v /.w) - | Sizeof {typ={desc=Tarray (elt, _, _)}; dynamic_length=Some len}, - Sizeof {typ=elt2; dynamic_length=None} - (* pattern: sizeof(elt[len]) / sizeof(elt) = len *) - when Typ.equal elt elt2 -> - len - | Sizeof {typ={desc=Tarray (elt, Some len, _)}; dynamic_length=None}, - Sizeof {typ=elt2; dynamic_length=None} - (* pattern: sizeof(elt[len]) / sizeof(elt) = len *) - when Typ.equal elt elt2 -> - Const (Cint len) - | _ -> - if abs then Exp.get_undefined false else BinOp (Div, e1', e2') - end - | BinOp (Mod, e1, e2) -> + match (e1', e2') with + | _, Const c when Const.iszero_int_float c + -> Exp.get_undefined false + | Const c, _ when Const.iszero_int_float c + -> e1' + | _, Const c when Const.isone_int_float c + -> e1' + | Const Cint n, Const Cint m + -> Exp.int (IntLit.div n m) + | Const Cfloat v, Const Cfloat w + -> Exp.float (v /. w) + | ( Sizeof {typ= {desc= Tarray (elt, _, _)}; dynamic_length= Some len} + , Sizeof {typ= elt2; dynamic_length= None} ) + (* pattern: sizeof(elt[len]) / sizeof(elt) = len *) + when Typ.equal elt elt2 + -> len + | ( Sizeof {typ= {desc= Tarray (elt, Some len, _)}; dynamic_length= None} + , Sizeof {typ= elt2; dynamic_length= None} ) + (* pattern: sizeof(elt[len]) / sizeof(elt) = len *) + when Typ.equal elt elt2 + -> Const (Cint len) + | _ + -> if abs then Exp.get_undefined false else BinOp (Div, e1', e2') ) + | BinOp (Mod, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin - match e1', e2' with - | _, Const (Cint i) when IntLit.iszero i -> - Exp.get_undefined false - | Const (Cint i), _ when IntLit.iszero i -> - e1' - | _, Const (Cint i) when IntLit.isone i -> - Exp.zero - | Const (Cint n), Const (Cint m) -> - Exp.int (IntLit.rem n m) - | _ -> - if abs then Exp.get_undefined false else BinOp (Mod, e1', e2') - end - | BinOp (Shiftlt, e1, e2) -> - if abs then Exp.get_undefined false else BinOp (Shiftlt, eval e1, eval e2) - | BinOp (Shiftrt, e1, e2) -> - if abs then Exp.get_undefined false else BinOp (Shiftrt, eval e1, eval e2) - | BinOp (BAnd, e1, e2) -> + match (e1', e2') with + | _, Const Cint i when IntLit.iszero i + -> Exp.get_undefined false + | Const Cint i, _ when IntLit.iszero i + -> e1' + | _, Const Cint i when IntLit.isone i + -> Exp.zero + | Const Cint n, Const Cint m + -> Exp.int (IntLit.rem n m) + | _ + -> if abs then Exp.get_undefined false else BinOp (Mod, e1', e2') ) + | BinOp (Shiftlt, e1, e2) + -> if abs then Exp.get_undefined false else BinOp (Shiftlt, eval e1, eval e2) + | BinOp (Shiftrt, e1, e2) + -> if abs then Exp.get_undefined false else BinOp (Shiftrt, eval e1, eval e2) + | BinOp (BAnd, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin match e1', e2' with - | Const (Cint i), _ when IntLit.iszero i -> - e1' - | _, Const (Cint i) when IntLit.iszero i -> - e2' - | Const (Cint i1), Const(Cint i2) -> - Exp.int (IntLit.logand i1 i2) - | _ -> - if abs then Exp.get_undefined false else BinOp (BAnd, e1', e2') - end - | BinOp (BOr, e1, e2) -> + match (e1', e2') with + | Const Cint i, _ when IntLit.iszero i + -> e1' + | _, Const Cint i when IntLit.iszero i + -> e2' + | Const Cint i1, Const Cint i2 + -> Exp.int (IntLit.logand i1 i2) + | _ + -> if abs then Exp.get_undefined false else BinOp (BAnd, e1', e2') ) + | BinOp (BOr, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin match e1', e2' with - | Const (Cint i), _ when IntLit.iszero i -> - e2' - | _, Const (Cint i) when IntLit.iszero i -> - e1' - | Const (Cint i1), Const(Cint i2) -> - Exp.int (IntLit.logor i1 i2) - | _ -> - if abs then Exp.get_undefined false else BinOp (BOr, e1', e2') - end - | BinOp (BXor, e1, e2) -> + match (e1', e2') with + | Const Cint i, _ when IntLit.iszero i + -> e2' + | _, Const Cint i when IntLit.iszero i + -> e1' + | Const Cint i1, Const Cint i2 + -> Exp.int (IntLit.logor i1 i2) + | _ + -> if abs then Exp.get_undefined false else BinOp (BOr, e1', e2') ) + | BinOp (BXor, e1, e2) + -> ( let e1' = eval e1 in let e2' = eval e2 in - begin match e1', e2' with - | Const (Cint i), _ when IntLit.iszero i -> - e2' - | _, Const (Cint i) when IntLit.iszero i -> - e1' - | Const (Cint i1), Const(Cint i2) -> - Exp.int (IntLit.logxor i1 i2) - | _ -> - if abs then Exp.get_undefined false else BinOp (BXor, e1', e2') - end - | Exn _ -> - e - | Lvar _ -> - e - | Lfield (e1, fld, typ) -> - let e1' = eval e1 in + match (e1', e2') with + | Const Cint i, _ when IntLit.iszero i + -> e2' + | _, Const Cint i when IntLit.iszero i + -> e1' + | Const Cint i1, Const Cint i2 + -> Exp.int (IntLit.logxor i1 i2) + | _ + -> if abs then Exp.get_undefined false else BinOp (BXor, e1', e2') ) + | Exn _ + -> e + | Lvar _ + -> e + | Lfield (e1, fld, typ) + -> let e1' = eval e1 in Lfield (e1', fld, typ) - | Lindex(Lvar pv, e2) when false - (* removed: it interferes with re-arrangement and error messages *) - -> (* &x[n] --> &x + n *) + | Lindex (Lvar pv, e2) + when false (* removed: it interferes with re-arrangement and error messages *) + -> (* &x[n] --> &x + n *) eval (Exp.BinOp (PlusPI, Lvar pv, e2)) - | Lindex (BinOp(PlusPI, ep, e1), e2) -> - (* array access with pointer arithmetic *) + | Lindex (BinOp (PlusPI, ep, e1), e2) + -> (* array access with pointer arithmetic *) let e' : Exp.t = BinOp (PlusA, e1, e2) in eval (Exp.Lindex (ep, e')) - | Lindex (e1, e2) -> - let e1' = eval e1 in + | Lindex (e1, e2) + -> let e1' = eval e1 in let e2' = eval e2 in - Lindex(e1', e2') in + Lindex (e1', e2') + in let e' = eval e in (* L.d_str "sym_eval "; Sil.d_exp e; L.d_str" --> "; Sil.d_exp e'; L.d_ln (); *) e' let exp_normalize tenv sub exp = let exp' = Sil.exp_sub sub exp in - if !Config.abs_val >= 1 then sym_eval tenv true exp' - else sym_eval tenv false exp' + if !Config.abs_val >= 1 then sym_eval tenv true exp' else sym_eval tenv false exp' - let texp_normalize tenv sub (exp : Exp.t) : Exp.t = match exp with - | Sizeof ({dynamic_length} as sizeof_data) -> - Sizeof {sizeof_data with - dynamic_length=Option.map ~f:(exp_normalize tenv sub) dynamic_length} - | _ -> - exp_normalize tenv sub exp + let texp_normalize tenv sub (exp: Exp.t) : Exp.t = + match exp with + | Sizeof ({dynamic_length} as sizeof_data) + -> Sizeof + {sizeof_data with dynamic_length= Option.map ~f:(exp_normalize tenv sub) dynamic_length} + | _ + -> exp_normalize tenv sub exp let exp_normalize_noabs tenv sub exp = Config.run_with_abs_val_equal_zero (exp_normalize tenv sub) exp (** Turn an inequality expression into an atom *) - let mk_inequality tenv (e : Exp.t) : Sil.atom = + let mk_inequality tenv (e: Exp.t) : Sil.atom = match e with - | BinOp (Le, base, Const (Cint n)) -> + | BinOp (Le, base, Const Cint n) + -> ( (* base <= n case *) let nbase = exp_normalize_noabs tenv Sil.sub_empty base in - (match nbase with - | BinOp(PlusA, base', Const (Cint n')) -> - let new_offset = Exp.int (n -- n') in - let new_e : Exp.t = BinOp (Le, base', new_offset) in - Aeq (new_e, Exp.one) - | BinOp(PlusA, Const (Cint n'), base') -> - let new_offset = Exp.int (n -- n') in - let new_e : Exp.t = BinOp (Le, base', new_offset) in - Aeq (new_e, Exp.one) - | BinOp(MinusA, base', Const (Cint n')) -> - let new_offset = Exp.int (n ++ n') in - let new_e : Exp.t = BinOp (Le, base', new_offset) in - Aeq (new_e, Exp.one) - | BinOp(MinusA, Const (Cint n'), base') -> - let new_offset = Exp.int (n' -- n -- IntLit.one) in - let new_e : Exp.t = BinOp (Lt, new_offset, base') in - Aeq (new_e, Exp.one) - | UnOp(Neg, new_base, _) -> - (* In this case, base = -new_base. Construct -n-1 < new_base. *) - let new_offset = Exp.int (IntLit.zero -- n -- IntLit.one) in - let new_e : Exp.t = BinOp (Lt, new_offset, new_base) in - Aeq (new_e, Exp.one) - | _ -> - Aeq (e, Exp.one)) - | BinOp (Lt, Const (Cint n), base) -> + match nbase with + | BinOp (PlusA, base', Const Cint n') + -> let new_offset = Exp.int (n -- n') in + let new_e : Exp.t = BinOp (Le, base', new_offset) in + Aeq (new_e, Exp.one) + | BinOp (PlusA, Const Cint n', base') + -> let new_offset = Exp.int (n -- n') in + let new_e : Exp.t = BinOp (Le, base', new_offset) in + Aeq (new_e, Exp.one) + | BinOp (MinusA, base', Const Cint n') + -> let new_offset = Exp.int (n ++ n') in + let new_e : Exp.t = BinOp (Le, base', new_offset) in + Aeq (new_e, Exp.one) + | BinOp (MinusA, Const Cint n', base') + -> let new_offset = Exp.int (n' -- n -- IntLit.one) in + let new_e : Exp.t = BinOp (Lt, new_offset, base') in + Aeq (new_e, Exp.one) + | UnOp (Neg, new_base, _) + -> (* In this case, base = -new_base. Construct -n-1 < new_base. *) + let new_offset = Exp.int (IntLit.zero -- n -- IntLit.one) in + let new_e : Exp.t = BinOp (Lt, new_offset, new_base) in + Aeq (new_e, Exp.one) + | _ + -> Aeq (e, Exp.one) ) + | BinOp (Lt, Const Cint n, base) + -> ( (* n < base case *) let nbase = exp_normalize_noabs tenv Sil.sub_empty base in - (match nbase with - | BinOp(PlusA, base', Const (Cint n')) -> - let new_offset = Exp.int (n -- n') in - let new_e : Exp.t = BinOp (Lt, new_offset, base') in - Aeq (new_e, Exp.one) - | BinOp(PlusA, Const (Const.Cint n'), base') -> - let new_offset = Exp.int (n -- n') in - let new_e : Exp.t = BinOp (Lt, new_offset, base') in - Aeq (new_e, Exp.one) - | BinOp(MinusA, base', Const (Cint n')) -> - let new_offset = Exp.int (n ++ n') in - let new_e : Exp.t = BinOp (Lt, new_offset, base') in - Aeq (new_e, Exp.one) - | BinOp(MinusA, Const (Cint n'), base') -> - let new_offset = Exp.int (n' -- n -- IntLit.one) in - let new_e : Exp.t = BinOp (Le, base', new_offset) in - Aeq (new_e, Exp.one) - | UnOp(Neg, new_base, _) -> - (* In this case, base = -new_base. Construct new_base <= -n-1 *) - let new_offset = Exp.int (IntLit.zero -- n -- IntLit.one) in - let new_e : Exp.t = BinOp (Le, new_base, new_offset) in - Aeq (new_e, Exp.one) - | _ -> - Aeq (e, Exp.one)) - | _ -> - Aeq (e, Exp.one) + match nbase with + | BinOp (PlusA, base', Const Cint n') + -> let new_offset = Exp.int (n -- n') in + let new_e : Exp.t = BinOp (Lt, new_offset, base') in + Aeq (new_e, Exp.one) + | BinOp (PlusA, Const Const.Cint n', base') + -> let new_offset = Exp.int (n -- n') in + let new_e : Exp.t = BinOp (Lt, new_offset, base') in + Aeq (new_e, Exp.one) + | BinOp (MinusA, base', Const Cint n') + -> let new_offset = Exp.int (n ++ n') in + let new_e : Exp.t = BinOp (Lt, new_offset, base') in + Aeq (new_e, Exp.one) + | BinOp (MinusA, Const Cint n', base') + -> let new_offset = Exp.int (n' -- n -- IntLit.one) in + let new_e : Exp.t = BinOp (Le, base', new_offset) in + Aeq (new_e, Exp.one) + | UnOp (Neg, new_base, _) + -> (* In this case, base = -new_base. Construct new_base <= -n-1 *) + let new_offset = Exp.int (IntLit.zero -- n -- IntLit.one) in + let new_e : Exp.t = BinOp (Le, new_base, new_offset) in + Aeq (new_e, Exp.one) + | _ + -> Aeq (e, Exp.one) ) + | _ + -> Aeq (e, Exp.one) (** Normalize an inequality *) - let inequality_normalize tenv (a : Sil.atom) = + let inequality_normalize tenv (a: Sil.atom) = (* turn an expression into a triple (pos,neg,off) of positive and negative occurrences, and integer offset representing inequality [sum(pos) - sum(neg) + off <= 0] *) - let rec exp_to_posnegoff (e : Exp.t) = match e with - | Const (Cint n) -> - [],[], n - | BinOp(PlusA, e1, e2) | BinOp(PlusPI, e1, e2) -> - let pos1, neg1, n1 = exp_to_posnegoff e1 in + let rec exp_to_posnegoff (e: Exp.t) = + match e with + | Const Cint n + -> ([], [], n) + | BinOp (PlusA, e1, e2) | BinOp (PlusPI, e1, e2) + -> let pos1, neg1, n1 = exp_to_posnegoff e1 in let pos2, neg2, n2 = exp_to_posnegoff e2 in - (pos1@pos2, neg1@neg2, n1 ++ n2) - | BinOp(MinusA, e1, e2) - | BinOp(MinusPI, e1, e2) - | BinOp(MinusPP, e1, e2) -> - let pos1, neg1, n1 = exp_to_posnegoff e1 in + (pos1 @ pos2, neg1 @ neg2, n1 ++ n2) + | BinOp (MinusA, e1, e2) | BinOp (MinusPI, e1, e2) | BinOp (MinusPP, e1, e2) + -> let pos1, neg1, n1 = exp_to_posnegoff e1 in let pos2, neg2, n2 = exp_to_posnegoff e2 in - (pos1@neg2, neg1@pos2, n1 -- n2) - | UnOp(Neg, e1, _) -> - let pos1, neg1, n1 = exp_to_posnegoff e1 in + (pos1 @ neg2, neg1 @ pos2, n1 -- n2) + | UnOp (Neg, e1, _) + -> let pos1, neg1, n1 = exp_to_posnegoff e1 in (neg1, pos1, IntLit.zero -- n1) - | _ -> [e],[], IntLit.zero in + | _ + -> ([e], [], IntLit.zero) + in (* sort and filter out expressions appearing in both the positive and negative part *) let normalize_posnegoff (pos, neg, off) = let pos' = List.sort ~cmp:Exp.compare pos in let neg' = List.sort ~cmp:Exp.compare neg in let rec combine pacc nacc = function - | x:: ps, y:: ng -> - (match Exp.compare x y with - | n when n < 0 -> combine (x:: pacc) nacc (ps, y :: ng) - | 0 -> combine pacc nacc (ps, ng) - | _ -> combine pacc (y:: nacc) (x :: ps, ng)) - | ps, ng -> List.rev_append pacc ps, List.rev_append nacc ng in + | x :: ps, y :: ng -> ( + match Exp.compare x y with + | n when n < 0 + -> combine (x :: pacc) nacc (ps, y :: ng) + | 0 + -> combine pacc nacc (ps, ng) + | _ + -> combine pacc (y :: nacc) (x :: ps, ng) ) + | ps, ng + -> (List.rev_append pacc ps, List.rev_append nacc ng) + in let pos'', neg'' = combine [] [] (pos', neg') in - (pos'', neg'', off) in + (pos'', neg'', off) + in (* turn a non-empty list of expressions into a sum expression *) let rec exp_list_to_sum : Exp.t list -> Exp.t = function - | [] -> assert false - | [e] -> e - | e:: el -> BinOp(PlusA, e, exp_list_to_sum el) in + | [] + -> assert false + | [e] + -> e + | e :: el + -> BinOp (PlusA, e, exp_list_to_sum el) + in let norm_from_exp e : Exp.t = match normalize_posnegoff (exp_to_posnegoff e) with - | [],[], n -> - BinOp(Le, Exp.int n, Exp.zero) - | [], neg, n -> - BinOp(Lt, Exp.int (n -- IntLit.one), exp_list_to_sum neg) - | pos, [], n -> - BinOp(Le, exp_list_to_sum pos, Exp.int (IntLit.zero -- n)) - | pos, neg, n -> - let lhs_e : Exp.t = BinOp(MinusA, exp_list_to_sum pos, exp_list_to_sum neg) in - BinOp(Le, lhs_e, Exp.int (IntLit.zero -- n)) in - let ineq = match a with - | Aeq (ineq, Const (Cint i)) when IntLit.isone i -> - ineq - | _ -> assert false in + | [], [], n + -> BinOp (Le, Exp.int n, Exp.zero) + | [], neg, n + -> BinOp (Lt, Exp.int (n -- IntLit.one), exp_list_to_sum neg) + | pos, [], n + -> BinOp (Le, exp_list_to_sum pos, Exp.int (IntLit.zero -- n)) + | pos, neg, n + -> let lhs_e : Exp.t = BinOp (MinusA, exp_list_to_sum pos, exp_list_to_sum neg) in + BinOp (Le, lhs_e, Exp.int (IntLit.zero -- n)) + in + let ineq = + match a with Aeq (ineq, Const Cint i) when IntLit.isone i -> ineq | _ -> assert false + in match ineq with - | BinOp(Le, e1, e2) -> - let e : Exp.t = BinOp(MinusA, e1, e2) in + | BinOp (Le, e1, e2) + -> let e : Exp.t = BinOp (MinusA, e1, e2) in mk_inequality tenv (norm_from_exp e) - | BinOp(Lt, e1, e2) -> - let e : Exp.t = BinOp(MinusA, BinOp(MinusA, e1, e2), Exp.minus_one) in + | BinOp (Lt, e1, e2) + -> let e : Exp.t = BinOp (MinusA, BinOp (MinusA, e1, e2), Exp.minus_one) in mk_inequality tenv (norm_from_exp e) - | _ -> a + | _ + -> a (** Normalize an atom. We keep the convention that inequalities with constants are only of the form [e <= n] and [n < e]. *) let atom_normalize tenv sub a0 = let a = Sil.atom_sub sub a0 in - let rec normalize_eq (eq : Exp.t * Exp.t) = match eq with - | BinOp(PlusA, e1, Const (Cint n1)), Const (Cint n2) + let rec normalize_eq (eq: Exp.t * Exp.t) = + match eq with + | BinOp (PlusA, e1, Const Cint n1), Const Cint n2 (* e1+n1==n2 ---> e1==n2-n1 *) - | BinOp(PlusPI, e1, Const (Cint n1)), Const (Cint n2) -> - (e1, Exp.int (n2 -- n1)) - | BinOp(MinusA, e1, Const (Cint n1)), Const (Cint n2) + | BinOp (PlusPI, e1, Const Cint n1), Const Cint n2 + -> (e1, Exp.int (n2 -- n1)) + | BinOp (MinusA, e1, Const Cint n1), Const Cint n2 (* e1-n1==n2 ---> e1==n1+n2 *) - | BinOp(MinusPI, e1, Const (Cint n1)), Const (Cint n2) -> - (e1, Exp.int (n1 ++ n2)) - | BinOp(MinusA, Const (Cint n1), e1), Const (Cint n2) -> - (* n1-e1 == n2 -> e1==n1-n2 *) + | BinOp (MinusPI, e1, Const Cint n1), Const Cint n2 + -> (e1, Exp.int (n1 ++ n2)) + | BinOp (MinusA, Const Cint n1, e1), Const Cint n2 + -> (* n1-e1 == n2 -> e1==n1-n2 *) (e1, Exp.int (n1 -- n2)) - | Lfield (e1', fld1, _), Lfield (e2', fld2, _) -> - if Typ.Fieldname.equal fld1 fld2 - then normalize_eq (e1', e2') - else eq - | Lindex (e1', idx1), Lindex (e2', idx2) -> - if Exp.equal idx1 idx2 then normalize_eq (e1', e2') + | Lfield (e1', fld1, _), Lfield (e2', fld2, _) + -> if Typ.Fieldname.equal fld1 fld2 then normalize_eq (e1', e2') else eq + | Lindex (e1', idx1), Lindex (e2', idx2) + -> if Exp.equal idx1 idx2 then normalize_eq (e1', e2') else if Exp.equal e1' e2' then normalize_eq (idx1, idx2) else eq - | _ -> eq in - let handle_unary_negation (e1 : Exp.t) (e2 : Exp.t) = - match e1, e2 with - | UnOp (LNot, e1', _), Const (Cint i) - | Const (Cint i), UnOp (LNot, e1', _) when IntLit.iszero i -> - (e1', Exp.zero, true) - | _ -> (e1, e2, false) in + | _ + -> eq + in + let handle_unary_negation (e1: Exp.t) (e2: Exp.t) = + match (e1, e2) with + | UnOp (LNot, e1', _), Const Cint i + | Const Cint i, UnOp (LNot, e1', _) + when IntLit.iszero i + -> (e1', Exp.zero, true) + | _ + -> (e1, e2, false) + in let handle_boolean_operation from_equality e1 e2 : Sil.atom = let ne1 = exp_normalize tenv sub e1 in let ne2 = exp_normalize tenv sub e2 in let ne1', ne2', op_negated = handle_unary_negation ne1 ne2 in - let (e1', e2') = normalize_eq (ne1', ne2') in - let (e1'', e2'') = exp_reorder e1' e2' in - let use_equality = - if op_negated then not from_equality else from_equality in - if use_equality then - Aeq (e1'', e2'') - else - Aneq (e1'', e2'') in - let a' : Sil.atom = match a with - | Aeq (e1, e2) -> - handle_boolean_operation true e1 e2 - | Aneq (e1, e2) -> - handle_boolean_operation false e1 e2 - | Apred (a, es) -> - Apred (a, List.map ~f:(fun e -> exp_normalize tenv sub e) es) - | Anpred (a, es) -> - Anpred (a, List.map ~f:(fun e -> exp_normalize tenv sub e) es) in + let e1', e2' = normalize_eq (ne1', ne2') in + let e1'', e2'' = exp_reorder e1' e2' in + let use_equality = if op_negated then not from_equality else from_equality in + if use_equality then Aeq (e1'', e2'') else Aneq (e1'', e2'') + in + let a' : Sil.atom = + match a with + | Aeq (e1, e2) + -> handle_boolean_operation true e1 e2 + | Aneq (e1, e2) + -> handle_boolean_operation false e1 e2 + | Apred (a, es) + -> Apred (a, List.map ~f:(fun e -> exp_normalize tenv sub e) es) + | Anpred (a, es) + -> Anpred (a, List.map ~f:(fun e -> exp_normalize tenv sub e) es) + in if atom_is_inequality a' then inequality_normalize tenv a' else a' - let normalize_and_strengthen_atom tenv (p : normal t) (a : Sil.atom) : Sil.atom = + let normalize_and_strengthen_atom tenv (p: normal t) (a: Sil.atom) : Sil.atom = let a' = atom_normalize tenv (`Exp p.sub) a in match a' with - | Aeq (BinOp (Le, Var id, Const (Cint n)), Const (Cint i)) - when IntLit.isone i -> - let lower = Exp.int (n -- IntLit.one) in + | Aeq (BinOp (Le, Var id, Const Cint n), Const Cint i) when IntLit.isone i + -> let lower = Exp.int (n -- IntLit.one) in let a_lower : Sil.atom = Aeq (BinOp (Lt, lower, Var id), Exp.one) in - if not (List.mem ~equal:Sil.equal_atom p.pi a_lower) then a' - else Aeq (Var id, Exp.int n) - | Aeq (BinOp (Lt, Const (Cint n), Var id), Const (Cint i)) - when IntLit.isone i -> - let upper = Exp.int (n ++ IntLit.one) in + if not (List.mem ~equal:Sil.equal_atom p.pi a_lower) then a' else Aeq (Var id, Exp.int n) + | Aeq (BinOp (Lt, Const Cint n, Var id), Const Cint i) when IntLit.isone i + -> let upper = Exp.int (n ++ IntLit.one) in let a_upper : Sil.atom = Aeq (BinOp (Le, Var id, upper), Exp.one) in - if not (List.mem ~equal:Sil.equal_atom p.pi a_upper) then a' - else Aeq (Var id, upper) - | Aeq (BinOp (Ne, e1, e2), Const (Cint i)) when IntLit.isone i -> - Aneq (e1, e2) - | _ -> a' + if not (List.mem ~equal:Sil.equal_atom p.pi a_upper) then a' else Aeq (Var id, upper) + | Aeq (BinOp (Ne, e1, e2), Const Cint i) when IntLit.isone i + -> Aneq (e1, e2) + | _ + -> a' - let rec strexp_normalize tenv sub (se : Sil.strexp) : Sil.strexp = + let rec strexp_normalize tenv sub (se: Sil.strexp) : Sil.strexp = match se with - | Eexp (e, inst) -> - Eexp (exp_normalize tenv sub e, inst) - | Estruct (fld_cnts, inst) -> - begin - match fld_cnts with - | [] -> se - | _ -> - let fld_cnts' = - List.map ~f:(fun (fld, cnt) -> - fld, strexp_normalize tenv sub cnt) fld_cnts in - let fld_cnts'' = List.sort ~cmp:[%compare: Typ.Fieldname.t * Sil.strexp] fld_cnts' in - Estruct (fld_cnts'', inst) - end - | Earray (len, idx_cnts, inst) -> - begin - let len' = exp_normalize_noabs tenv sub len in - match idx_cnts with - | [] -> - if Exp.equal len len' then se else Earray (len', idx_cnts, inst) - | _ -> - let idx_cnts' = - List.map ~f:(fun (idx, cnt) -> - let idx' = exp_normalize tenv sub idx in - idx', strexp_normalize tenv sub cnt) idx_cnts in - let idx_cnts'' = - List.sort ~cmp:[%compare: Exp.t * Sil.strexp] idx_cnts' in - Earray (len', idx_cnts'', inst) - end + | Eexp (e, inst) + -> Eexp (exp_normalize tenv sub e, inst) + | Estruct (fld_cnts, inst) -> ( + match fld_cnts with + | [] + -> se + | _ + -> let fld_cnts' = + List.map ~f:(fun (fld, cnt) -> (fld, strexp_normalize tenv sub cnt)) fld_cnts + in + let fld_cnts'' = List.sort ~cmp:[%compare : Typ.Fieldname.t * Sil.strexp] fld_cnts' in + Estruct (fld_cnts'', inst) ) + | Earray (len, idx_cnts, inst) + -> let len' = exp_normalize_noabs tenv sub len in + match idx_cnts with + | [] + -> if Exp.equal len len' then se else Earray (len', idx_cnts, inst) + | _ + -> let idx_cnts' = + List.map + ~f:(fun (idx, cnt) -> + let idx' = exp_normalize tenv sub idx in + (idx', strexp_normalize tenv sub cnt)) + idx_cnts + in + let idx_cnts'' = List.sort ~cmp:[%compare : Exp.t * Sil.strexp] idx_cnts' in + Earray (len', idx_cnts'', inst) (** Exp.Construct a pointsto. *) let mk_ptsto tenv lexp sexp te : Sil.hpred = let nsexp = strexp_normalize tenv Sil.sub_empty sexp in - Hpointsto(lexp, nsexp, te) + Hpointsto (lexp, nsexp, te) (** Construct a points-to predicate for an expression using either the provided expression [name] as base for fresh identifiers. If [expand_structs] is true, initialize the fields of structs with fresh variables. *) - let mk_ptsto_exp tenv struct_init_mode (exp, (te : Exp.t), expo) inst : Sil.hpred = - let default_strexp () : Sil.strexp = match te with - | Sizeof {typ; dynamic_length} -> - create_strexp_of_type tenv struct_init_mode typ dynamic_length inst - | Var _ -> - Estruct ([], inst) - | te -> - L.internal_error "trying to create ptsto with type: %a@." (Sil.pp_texp_full Pp.text) te; - assert false in - let strexp : Sil.strexp = match expo with - | Some e -> Eexp (e, inst) - | None -> default_strexp () in + let mk_ptsto_exp tenv struct_init_mode (exp, (te: Exp.t), expo) inst : Sil.hpred = + let default_strexp () : Sil.strexp = + match te with + | Sizeof {typ; dynamic_length} + -> create_strexp_of_type tenv struct_init_mode typ dynamic_length inst + | Var _ + -> Estruct ([], inst) + | te + -> L.internal_error "trying to create ptsto with type: %a@." (Sil.pp_texp_full Pp.text) te ; + assert false + in + let strexp : Sil.strexp = + match expo with Some e -> Eexp (e, inst) | None -> default_strexp () + in mk_ptsto tenv exp strexp te - let rec hpred_normalize tenv sub (hpred : Sil.hpred) : Sil.hpred = + let rec hpred_normalize tenv sub (hpred: Sil.hpred) : Sil.hpred = let replace_hpred hpred' = - L.d_strln "found array with sizeof(..) size"; - L.d_str "converting original hpred: "; Sil.d_hpred hpred; L.d_ln (); - L.d_str "into the following: "; Sil.d_hpred hpred'; L.d_ln (); - hpred' in + L.d_strln "found array with sizeof(..) size" ; + L.d_str "converting original hpred: " ; + Sil.d_hpred hpred ; + L.d_ln () ; + L.d_str "into the following: " ; + Sil.d_hpred hpred' ; + L.d_ln () ; + hpred' + in match hpred with - | Hpointsto (root, cnt, te) -> + | Hpointsto (root, cnt, te) + -> ( let normalized_root = exp_normalize tenv sub root in let normalized_cnt = strexp_normalize tenv sub cnt in let normalized_te = texp_normalize tenv sub te in - begin match normalized_cnt, normalized_te with - | Earray (Exp.Sizeof _ as size, [], inst), Sizeof {typ={desc=Tarray _}} -> - (* check for an empty array whose size expression is (Sizeof type), and turn the array + match (normalized_cnt, normalized_te) with + | Earray ((Exp.Sizeof _ as size), [], inst), Sizeof {typ= {desc= Tarray _}} + -> (* check for an empty array whose size expression is (Sizeof type), and turn the array into a strexp of the given type *) - let hpred' = mk_ptsto_exp tenv Fld_init (root, size, None) inst in - replace_hpred hpred' - | Earray (BinOp (Mult, Sizeof {typ=t; dynamic_length=None; subtype=st1}, x), esel, inst), - Sizeof {typ={desc=Tarray (elt, _, _)} as arr} when Typ.equal t elt -> - let dynamic_length = Some x in - let sizeof_data = {Exp.typ=arr; nbytes=None; dynamic_length; subtype=st1} in - let hpred' = - mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in - replace_hpred (replace_array_contents hpred' esel) - | Earray (BinOp (Mult, x, Sizeof {typ; dynamic_length=None; subtype}), esel, inst), - Sizeof {typ={desc=Tarray (elt, _, _)} as arr} when Typ.equal typ elt -> - let sizeof_data = {Exp.typ=arr; nbytes=None; dynamic_length=Some x; subtype} in - let hpred' = - mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in - replace_hpred (replace_array_contents hpred' esel) - | Earray (BinOp (Mult, Sizeof {typ; dynamic_length=Some len; subtype}, x), esel, inst), - Sizeof {typ={desc=Tarray (elt, _, _)} as arr} when Typ.equal typ elt -> - let sizeof_data = {Exp.typ=arr; nbytes=None; - dynamic_length=Some (Exp.BinOp(Mult, x, len)); subtype} in - let hpred' = - mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in - replace_hpred (replace_array_contents hpred' esel) - | Earray (BinOp (Mult, x, Sizeof {typ; dynamic_length=Some len; subtype}), esel, inst), - Sizeof {typ={desc=Tarray (elt, _, _)} as arr} when Typ.equal typ elt -> - let sizeof_data = {Exp.typ=arr; nbytes=None; - dynamic_length=Some (Exp.BinOp(Mult, x, len)); subtype} in - let hpred' = - mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in - replace_hpred (replace_array_contents hpred' esel) - | _ -> - Hpointsto (normalized_root, normalized_cnt, normalized_te) - end - | Hlseg (k, para, e1, e2, elist) -> - let normalized_e1 = exp_normalize tenv sub e1 in + let hpred' = mk_ptsto_exp tenv Fld_init (root, size, None) inst in + replace_hpred hpred' + | ( Earray + (BinOp (Mult, Sizeof {typ= t; dynamic_length= None; subtype= st1}, x), esel, inst) + , Sizeof {typ= {desc= Tarray (elt, _, _)} as arr} ) + when Typ.equal t elt + -> let dynamic_length = Some x in + let sizeof_data = {Exp.typ= arr; nbytes= None; dynamic_length; subtype= st1} in + let hpred' = mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in + replace_hpred (replace_array_contents hpred' esel) + | ( Earray (BinOp (Mult, x, Sizeof {typ; dynamic_length= None; subtype}), esel, inst) + , Sizeof {typ= {desc= Tarray (elt, _, _)} as arr} ) + when Typ.equal typ elt + -> let sizeof_data = {Exp.typ= arr; nbytes= None; dynamic_length= Some x; subtype} in + let hpred' = mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in + replace_hpred (replace_array_contents hpred' esel) + | ( Earray (BinOp (Mult, Sizeof {typ; dynamic_length= Some len; subtype}, x), esel, inst) + , Sizeof {typ= {desc= Tarray (elt, _, _)} as arr} ) + when Typ.equal typ elt + -> let sizeof_data = + {Exp.typ= arr; nbytes= None; dynamic_length= Some (Exp.BinOp (Mult, x, len)); subtype} + in + let hpred' = mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in + replace_hpred (replace_array_contents hpred' esel) + | ( Earray (BinOp (Mult, x, Sizeof {typ; dynamic_length= Some len; subtype}), esel, inst) + , Sizeof {typ= {desc= Tarray (elt, _, _)} as arr} ) + when Typ.equal typ elt + -> let sizeof_data = + {Exp.typ= arr; nbytes= None; dynamic_length= Some (Exp.BinOp (Mult, x, len)); subtype} + in + let hpred' = mk_ptsto_exp tenv Fld_init (root, Sizeof sizeof_data, None) inst in + replace_hpred (replace_array_contents hpred' esel) + | _ + -> Hpointsto (normalized_root, normalized_cnt, normalized_te) ) + | Hlseg (k, para, e1, e2, elist) + -> let normalized_e1 = exp_normalize tenv sub e1 in let normalized_e2 = exp_normalize tenv sub e2 in let normalized_elist = List.map ~f:(exp_normalize tenv sub) elist in let normalized_para = hpara_normalize tenv para in Hlseg (k, normalized_para, normalized_e1, normalized_e2, normalized_elist) - | Hdllseg (k, para, e1, e2, e3, e4, elist) -> - let norm_e1 = exp_normalize tenv sub e1 in + | Hdllseg (k, para, e1, e2, e3, e4, elist) + -> let norm_e1 = exp_normalize tenv sub e1 in let norm_e2 = exp_normalize tenv sub e2 in let norm_e3 = exp_normalize tenv sub e3 in let norm_e4 = exp_normalize tenv sub e4 in @@ -1404,81 +1385,91 @@ module Normalize = struct let norm_para = hpara_dll_normalize tenv para in Hdllseg (k, norm_para, norm_e1, norm_e2, norm_e3, norm_e4, norm_elist) - and hpara_normalize tenv (para : Sil.hpara) = - let normalized_body = List.map ~f:(hpred_normalize tenv Sil.sub_empty) (para.body) in + and hpara_normalize tenv (para: Sil.hpara) = + let normalized_body = List.map ~f:(hpred_normalize tenv Sil.sub_empty) para.body in let sorted_body = List.sort ~cmp:Sil.compare_hpred normalized_body in - { para with body = sorted_body } + {para with body= sorted_body} - and hpara_dll_normalize tenv (para : Sil.hpara_dll) = - let normalized_body = List.map ~f:(hpred_normalize tenv Sil.sub_empty) (para.body_dll) in + and hpara_dll_normalize tenv (para: Sil.hpara_dll) = + let normalized_body = List.map ~f:(hpred_normalize tenv Sil.sub_empty) para.body_dll in let sorted_body = List.sort ~cmp:Sil.compare_hpred normalized_body in - { para with body_dll = sorted_body } - + {para with body_dll= sorted_body} let sigma_normalize tenv sub sigma = let sigma' = - List.stable_sort ~cmp:Sil.compare_hpred (List.map ~f:(hpred_normalize tenv sub) sigma) in + List.stable_sort ~cmp:Sil.compare_hpred (List.map ~f:(hpred_normalize tenv sub) sigma) + in if equal_sigma sigma sigma' then sigma else sigma' let pi_tighten_ineq tenv pi = let ineq_list, nonineq_list = List.partition_tf ~f:atom_is_inequality pi in let diseq_list = - let get_disequality_info acc (a : Sil.atom) = match a with - | Aneq (Const (Cint n), e) - | Aneq(e, Const (Cint n)) -> (e, n) :: acc - | _ -> acc in - List.fold ~f:get_disequality_info ~init:[] nonineq_list in + let get_disequality_info acc (a: Sil.atom) = + match a with Aneq (Const Cint n, e) | Aneq (e, Const Cint n) -> (e, n) :: acc | _ -> acc + in + List.fold ~f:get_disequality_info ~init:[] nonineq_list + in let is_neq e n = - List.exists ~f:(fun (e', n') -> Exp.equal e e' && IntLit.eq n n') diseq_list in + List.exists ~f:(fun (e', n') -> Exp.equal e e' && IntLit.eq n n') diseq_list + in let le_list_tightened = let get_le_inequality_info acc a = - match atom_exp_le_const a with - | Some (e, n) -> (e, n):: acc - | _ -> acc in + match atom_exp_le_const a with Some (e, n) -> (e, n) :: acc | _ -> acc + in let rec le_tighten le_list_done = function - | [] -> List.rev le_list_done - | (e, n):: le_list_todo -> (* e <= n *) - if is_neq e n then le_tighten le_list_done ((e, n -- IntLit.one):: le_list_todo) - else le_tighten ((e, n):: le_list_done) (le_list_todo) in + | [] + -> List.rev le_list_done + | (e, n) :: le_list_todo + -> (* e <= n *) + if is_neq e n then le_tighten le_list_done ((e, n -- IntLit.one) :: le_list_todo) + else le_tighten ((e, n) :: le_list_done) le_list_todo + in let le_list = List.rev (List.fold ~f:get_le_inequality_info ~init:[] ineq_list) in - le_tighten [] le_list in + le_tighten [] le_list + in let lt_list_tightened = let get_lt_inequality_info acc a = - match atom_const_lt_exp a with - | Some (n, e) -> (n, e):: acc - | _ -> acc in + match atom_const_lt_exp a with Some (n, e) -> (n, e) :: acc | _ -> acc + in let rec lt_tighten lt_list_done = function - | [] -> List.rev lt_list_done - | (n, e):: lt_list_todo -> (* n < e *) + | [] + -> List.rev lt_list_done + | (n, e) :: lt_list_todo + -> (* n < e *) let n_plus_one = n ++ IntLit.one in - if is_neq e n_plus_one - then lt_tighten lt_list_done ((n ++ IntLit.one, e):: lt_list_todo) - else lt_tighten ((n, e):: lt_list_done) (lt_list_todo) in + if is_neq e n_plus_one then + lt_tighten lt_list_done ((n ++ IntLit.one, e) :: lt_list_todo) + else lt_tighten ((n, e) :: lt_list_done) lt_list_todo + in let lt_list = List.rev (List.fold ~f:get_lt_inequality_info ~init:[] ineq_list) in - lt_tighten [] lt_list in + lt_tighten [] lt_list + in let ineq_list' = let le_ineq_list = - List.map - ~f:(fun (e, n) -> mk_inequality tenv (BinOp(Le, e, Exp.int n))) - le_list_tightened in + List.map ~f:(fun (e, n) -> mk_inequality tenv (BinOp (Le, e, Exp.int n))) le_list_tightened + in let lt_ineq_list = - List.map - ~f:(fun (n, e) -> mk_inequality tenv (BinOp(Lt, Exp.int n, e))) - lt_list_tightened in - le_ineq_list @ lt_ineq_list in + List.map ~f:(fun (n, e) -> mk_inequality tenv (BinOp (Lt, Exp.int n, e))) lt_list_tightened + in + le_ineq_list @ lt_ineq_list + in let nonineq_list' = List.filter - ~f:(fun (a : Sil.atom) -> match a with - | Aneq (Const (Cint n), e) - | Aneq (e, Const (Cint n)) -> - (not (List.exists - ~f:(fun (e', n') -> Exp.equal e e' && IntLit.lt n' n) - le_list_tightened)) && - (not (List.exists - ~f:(fun (n', e') -> Exp.equal e e' && IntLit.leq n n') - lt_list_tightened)) - | _ -> true) - nonineq_list in + ~f:(fun (a: Sil.atom) -> + match a with + | Aneq (Const Cint n, e) | Aneq (e, Const Cint n) + -> not + (List.exists + ~f:(fun (e', n') -> Exp.equal e e' && IntLit.lt n' n) + le_list_tightened) + && not + (List.exists + ~f:(fun (n', e') -> Exp.equal e e' && IntLit.leq n n') + lt_list_tightened) + | _ + -> true) + nonineq_list + in (ineq_list', nonineq_list') (** Normalization of pi. @@ -1487,34 +1478,31 @@ module Normalize = struct let pi = List.map ~f:(atom_normalize tenv sub) pi0 in let ineq_list, nonineq_list = pi_tighten_ineq tenv pi in let syntactically_different : Exp.t * Exp.t -> bool = function - | BinOp(op1, e1, Const c1), BinOp(op2, e2, Const c2) - when Exp.equal e1 e2 -> - Binop.equal op1 op2 && Binop.injective op1 && not (Const.equal c1 c2) - | e1, BinOp(op2, e2, Const c2) - when Exp.equal e1 e2 -> - Binop.injective op2 && - Binop.is_zero_runit op2 && - not (Const.equal (Cint IntLit.zero) c2) - | BinOp(op1, e1, Const c1), e2 - when Exp.equal e1 e2 -> - Binop.injective op1 && - Binop.is_zero_runit op1 && - not (Const.equal (Cint IntLit.zero) c1) - | _ -> false in + | BinOp (op1, e1, Const c1), BinOp (op2, e2, Const c2) when Exp.equal e1 e2 + -> Binop.equal op1 op2 && Binop.injective op1 && not (Const.equal c1 c2) + | e1, BinOp (op2, e2, Const c2) when Exp.equal e1 e2 + -> Binop.injective op2 && Binop.is_zero_runit op2 && not (Const.equal (Cint IntLit.zero) c2) + | BinOp (op1, e1, Const c1), e2 when Exp.equal e1 e2 + -> Binop.injective op1 && Binop.is_zero_runit op1 && not (Const.equal (Cint IntLit.zero) c1) + | _ + -> false + in let filter_useful_atom : Sil.atom -> bool = - let unsigned_exps = lazy (sigma_get_unsigned_exps sigma) in + let unsigned_exps = (lazy (sigma_get_unsigned_exps sigma)) in function - | Aneq ((Var _) as e, Const (Cint n)) when IntLit.isnegative n -> - not (List.exists ~f:(Exp.equal e) (Lazy.force unsigned_exps)) - | Aneq (e1, e2) -> - not (syntactically_different (e1, e2)) - | Aeq (Const c1, Const c2) -> - not (Const.equal c1 c2) - | _ -> true in + | Aneq ((Var _ as e), Const Cint n) when IntLit.isnegative n + -> not (List.exists ~f:(Exp.equal e) (Lazy.force unsigned_exps)) + | Aneq (e1, e2) + -> not (syntactically_different (e1, e2)) + | Aeq (Const c1, Const c2) + -> not (Const.equal c1 c2) + | _ + -> true + in let pi' = - List.stable_sort - ~cmp:Sil.compare_atom - ((List.filter ~f:filter_useful_atom nonineq_list) @ ineq_list) in + List.stable_sort ~cmp:Sil.compare_atom + (List.filter ~f:filter_useful_atom nonineq_list @ ineq_list) + in let pi'' = pi_sorted_remove_redundant pi' in if equal_pi pi0 pi'' then pi0 else pi'' @@ -1525,104 +1513,108 @@ module Normalize = struct let npi = pi_normalize tenv Sil.sub_empty nsigma prop.pi_fp in let fp_vars = let fav = pi_fav npi in - sigma_fav_add fav nsigma; - fav in + sigma_fav_add fav nsigma ; fav + in (* TODO (t4893479): make this check less angelic *) - if Sil.fav_exists fp_vars Ident.is_normal && not Config.angelic_execution then - begin - L.d_strln "footprint part contains normal variables"; - d_pi npi; L.d_ln (); - d_sigma nsigma; L.d_ln (); - assert false - end; - Sil.fav_filter_ident fp_vars Ident.is_primed; (* only keep primed vars *) + if Sil.fav_exists fp_vars Ident.is_normal && not Config.angelic_execution then ( + L.d_strln "footprint part contains normal variables" ; + d_pi npi ; + L.d_ln () ; + d_sigma nsigma ; + L.d_ln () ; + assert false ) ; + Sil.fav_filter_ident fp_vars Ident.is_primed ; + (* only keep primed vars *) let npi', nsigma' = - if Sil.fav_is_empty fp_vars then npi, nsigma - else (* replace primed vars by fresh footprint vars *) + if Sil.fav_is_empty fp_vars then (npi, nsigma) + else + (* replace primed vars by fresh footprint vars *) let ids_primed = Sil.fav_to_list fp_vars in let ids_footprint = - List.map ~f:(fun id -> (id, Ident.create_fresh Ident.kfootprint)) ids_primed in + List.map ~f:(fun id -> (id, Ident.create_fresh Ident.kfootprint)) ids_primed + in let ren_sub = - Sil.subst_of_list (List.map ~f:(fun (id1, id2) -> (id1, Exp.Var id2)) ids_footprint) in + Sil.subst_of_list (List.map ~f:(fun (id1, id2) -> (id1, Exp.Var id2)) ids_footprint) + in let nsigma' = sigma_normalize tenv Sil.sub_empty (sigma_sub ren_sub nsigma) in let npi' = pi_normalize tenv Sil.sub_empty nsigma' (pi_sub ren_sub npi) in - (npi', nsigma') in + (npi', nsigma') + in set prop ~pi_fp:npi' ~sigma_fp:nsigma' (** This function assumes that if (x,Exp.Var(y)) in sub, then compare x y = 1 *) let sub_normalize sub = - let f (id, e) = (not (Ident.is_primed id)) && (not (Sil.ident_in_exp id e)) in + let f (id, e) = not (Ident.is_primed id) && not (Sil.ident_in_exp id e) in let sub' = Sil.sub_filter_pair ~f sub in if Sil.equal_exp_subst sub sub' then sub else sub' (** Conjoin a pure atomic predicate by normal conjunction. *) - let rec prop_atom_and tenv ?(footprint=false) (p : normal t) a : normal t = + let rec prop_atom_and tenv ?(footprint= false) (p: normal t) a : normal t = let a' = normalize_and_strengthen_atom tenv p a in if List.mem ~equal:Sil.equal_atom p.pi a' then p - else begin + else let p' = match a' with - | Aeq (Var i, e) when Sil.ident_in_exp i e -> p - | Aeq (Var i, e) -> - let sub_list = [(i, e)] in + | Aeq (Var i, e) when Sil.ident_in_exp i e + -> p + | Aeq (Var i, e) + -> let sub_list = [(i, e)] in let mysub = Sil.exp_subst_of_list sub_list in let p_sub = Sil.sub_filter (fun i' -> not (Ident.equal i i')) p.sub in - let exp_sub' = Sil.sub_join mysub (Sil.sub_range_map (Sil.exp_sub (`Exp mysub)) p_sub) in + let exp_sub' = + Sil.sub_join mysub (Sil.sub_range_map (Sil.exp_sub (`Exp mysub)) p_sub) + in let sub' = `Exp exp_sub' in - let (nsub', npi', nsigma') = + let nsub', npi', nsigma' = let nsigma' = sigma_normalize tenv sub' p.sigma in - (sub_normalize exp_sub', pi_normalize tenv sub' nsigma' p.pi, nsigma') in - let (eqs_zero, nsigma'') = sigma_remove_emptylseg nsigma' in - let p' = - unsafe_cast_to_normal - (set p ~sub:nsub' ~pi:npi' ~sigma:nsigma'') in + (sub_normalize exp_sub', pi_normalize tenv sub' nsigma' p.pi, nsigma') + in + let eqs_zero, nsigma'' = sigma_remove_emptylseg nsigma' in + let p' = unsafe_cast_to_normal (set p ~sub:nsub' ~pi:npi' ~sigma:nsigma'') in List.fold ~f:(prop_atom_and tenv ~footprint) ~init:p' eqs_zero - | Aeq (e1, e2) when Exp.equal e1 e2 -> - p - | Aneq (e1, e2) -> - let sigma' = sigma_intro_nonemptylseg e1 e2 p.sigma in - let pi' = pi_normalize tenv (`Exp p.sub) sigma' (a':: p.pi) in - unsafe_cast_to_normal - (set p ~pi:pi' ~sigma:sigma') - | _ -> - let pi' = pi_normalize tenv (`Exp p.sub) p.sigma (a':: p.pi) in - unsafe_cast_to_normal - (set p ~pi:pi') in + | Aeq (e1, e2) when Exp.equal e1 e2 + -> p + | Aneq (e1, e2) + -> let sigma' = sigma_intro_nonemptylseg e1 e2 p.sigma in + let pi' = pi_normalize tenv (`Exp p.sub) sigma' (a' :: p.pi) in + unsafe_cast_to_normal (set p ~pi:pi' ~sigma:sigma') + | _ + -> let pi' = pi_normalize tenv (`Exp p.sub) p.sigma (a' :: p.pi) in + unsafe_cast_to_normal (set p ~pi:pi') + in if not footprint then p' - else begin + else let fav_a' = Sil.atom_fav a' in let fav_nofootprint_a' = - Sil.fav_copy_filter_ident fav_a' (fun id -> not (Ident.is_footprint id)) in - let predicate_warning = - not (Sil.fav_is_empty fav_nofootprint_a') in + Sil.fav_copy_filter_ident fav_a' (fun id -> not (Ident.is_footprint id)) + in + let predicate_warning = not (Sil.fav_is_empty fav_nofootprint_a') in let p'' = if predicate_warning then footprint_normalize tenv p' else match a' with - | Aeq (Exp.Var i, e) when not (Sil.ident_in_exp i e) -> - let mysub = Sil.subst_of_list [(i, e)] in + | Aeq (Exp.Var i, e) when not (Sil.ident_in_exp i e) + -> let mysub = Sil.subst_of_list [(i, e)] in let sigma_fp' = sigma_normalize tenv mysub p'.sigma_fp in let pi_fp' = a' :: pi_normalize tenv mysub sigma_fp' p'.pi_fp in - footprint_normalize tenv - (set p' ~pi_fp:pi_fp' ~sigma_fp:sigma_fp') - | _ -> - footprint_normalize tenv - (set p' ~pi_fp:(a' :: p'.pi_fp)) in - if predicate_warning then (L.d_warning "dropping non-footprint "; Sil.d_atom a'; L.d_ln ()); + footprint_normalize tenv (set p' ~pi_fp:pi_fp' ~sigma_fp:sigma_fp') + | _ + -> footprint_normalize tenv (set p' ~pi_fp:(a' :: p'.pi_fp)) + in + if predicate_warning then ( + L.d_warning "dropping non-footprint " ; Sil.d_atom a' ; L.d_ln () ) ; unsafe_cast_to_normal p'' - end - end (** normalize a prop *) - let normalize tenv (eprop : 'a t) : normal t = + let normalize tenv (eprop: 'a t) : normal t = let p0 = - unsafe_cast_to_normal - (set prop_emp ~sigma: (sigma_normalize tenv Sil.sub_empty eprop.sigma)) in + unsafe_cast_to_normal (set prop_emp ~sigma:(sigma_normalize tenv Sil.sub_empty eprop.sigma)) + in let nprop = List.fold ~f:(prop_atom_and tenv) ~init:p0 (get_pure eprop) in unsafe_cast_to_normal (footprint_normalize tenv (set nprop ~pi_fp:eprop.pi_fp ~sigma_fp:eprop.sigma_fp)) - end + (* End of module Normalize *) let exp_normalize_prop tenv prop exp = @@ -1633,12 +1625,11 @@ let lexp_normalize_prop tenv p lexp = let offsets = Sil.exp_get_offsets lexp in let nroot = exp_normalize_prop tenv p root in let noffsets = - List.map ~f:(fun (n : Sil.offset) -> match n with - | Off_fld _ -> - n - | Off_index e -> - Sil.Off_index (exp_normalize_prop tenv p e) - ) offsets in + List.map + ~f:(fun (n: Sil.offset) -> + match n with Off_fld _ -> n | Off_index e -> Sil.Off_index (exp_normalize_prop tenv p e)) + offsets + in Sil.exp_add_offsets nroot noffsets let atom_normalize_prop tenv prop atom = @@ -1662,7 +1653,9 @@ let sigma_replace_exp tenv epairs sigma = (** Construct an atom. *) let mk_atom tenv atom = - Config.run_with_abs_val_equal_zero (fun () -> Normalize.atom_normalize tenv Sil.sub_empty atom) () + Config.run_with_abs_val_equal_zero + (fun () -> Normalize.atom_normalize tenv Sil.sub_empty atom) + () (** Exp.Construct a disequality. *) let mk_neq tenv e1 e2 = mk_atom tenv (Aneq (e1, e2)) @@ -1684,27 +1677,18 @@ let mk_lseg tenv k para e_start e_end es_shared : Sil.hpred = (** Exp.Construct a dllseg predicate *) let mk_dllseg tenv k para exp_iF exp_oB exp_oF exp_iB exps_shared : Sil.hpred = let npara = Normalize.hpara_dll_normalize tenv para in - Hdllseg (k, npara, exp_iF, exp_oB , exp_oF, exp_iB, exps_shared) + Hdllseg (k, npara, exp_iF, exp_oB, exp_oF, exp_iB, exps_shared) (** Exp.Construct a hpara *) let mk_hpara tenv root next svars evars body = - let para = - { Sil.root = root; - next = next; - svars = svars; - evars = evars; - body = body } in + let para = {Sil.root= root; next; svars; evars; body} in Normalize.hpara_normalize tenv para (** Exp.Construct a dll_hpara *) let mk_dll_hpara tenv iF oB oF svars evars body = let para = - { Sil.cell = iF; - blink = oB; - flink = oF; - svars_dll = svars; - evars_dll = evars; - body_dll = body } in + {Sil.cell= iF; blink= oB; flink= oF; svars_dll= svars; evars_dll= evars; body_dll= body} + in Normalize.hpara_dll_normalize tenv para (** Construct a points-to predicate for a single program variable. @@ -1713,11 +1697,11 @@ let mk_ptsto_lvar tenv expand_structs inst ((pvar: Pvar.t), texp, expo) : Sil.hp Normalize.mk_ptsto_exp tenv expand_structs (Lvar pvar, texp, expo) inst (** Conjoin [exp1]=[exp2] with a symbolic heap [prop]. *) -let conjoin_eq tenv ?(footprint = false) exp1 exp2 prop = - Normalize.prop_atom_and tenv ~footprint prop (Aeq(exp1, exp2)) +let conjoin_eq tenv ?(footprint= false) exp1 exp2 prop = + Normalize.prop_atom_and tenv ~footprint prop (Aeq (exp1, exp2)) (** Conjoin [exp1!=exp2] with a symbolic heap [prop]. *) -let conjoin_neq tenv ?(footprint = false) exp1 exp2 prop = +let conjoin_neq tenv ?(footprint= false) exp1 exp2 prop = Normalize.prop_atom_and tenv ~footprint prop (Aneq (exp1, exp2)) (** Reset every inst in the prop using the given map *) @@ -1726,7 +1710,6 @@ let prop_reset_inst inst_map prop = let sigma_fp' = List.map ~f:(Sil.hpred_instmap inst_map) prop.sigma_fp in set prop ~sigma:sigma' ~sigma_fp:sigma_fp' - (** {1 Functions for transforming footprints into propositions.} *) (** The ones used for abstraction add/remove local stacks in order to @@ -1734,11 +1717,10 @@ let prop_reset_inst inst_map prop = transforation functions do not use this hack. *) (** Extract the footprint and return it as a prop *) -let extract_footprint p = - set prop_emp ~pi:p.pi_fp ~sigma:p.sigma_fp +let extract_footprint p = set prop_emp ~pi:p.pi_fp ~sigma:p.sigma_fp (** Extract the (footprint,current) pair *) -let extract_spec (p : normal t) : normal t * normal t = +let extract_spec (p: normal t) : normal t * normal t = let pre = extract_footprint p in let post = set p ~pi_fp:[] ~sigma_fp:[] in (unsafe_cast_to_normal pre, unsafe_cast_to_normal post) @@ -1746,86 +1728,91 @@ let extract_spec (p : normal t) : normal t * normal t = (** [prop_set_fooprint p p_foot] sets proposition [p_foot] as footprint of [p]. *) let prop_set_footprint p p_foot = let pi = - (List.map - ~f:(fun (i, e) -> Sil.Aeq(Var i, e)) - (Sil.sub_to_list p_foot.sub)) @ p_foot.pi in + List.map ~f:(fun (i, e) -> Sil.Aeq (Var i, e)) (Sil.sub_to_list p_foot.sub) @ p_foot.pi + in set p ~pi_fp:pi ~sigma_fp:p_foot.sigma (** {2 Functions for renaming primed variables by "canonical names"} *) module ExpStack : sig val init : Exp.t list -> unit + val final : unit -> unit + val is_empty : unit -> bool + val push : Exp.t -> unit + val pop : unit -> Exp.t end = struct let stack = Stack.create () + let init es = - Stack.clear stack; + Stack.clear stack ; List.iter ~f:(fun e -> Stack.push stack e) (List.rev es) + let final () = Stack.clear stack + let is_empty () = Stack.is_empty stack + let push e = Stack.push stack e + let pop () = Stack.pop_exn stack end let sigma_get_start_lexps_sort sigma = - let exp_compare_neg e1 e2 = - (Exp.compare e1 e2) in + let exp_compare_neg e1 e2 = -Exp.compare e1 e2 in let filter e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in let lexps = Sil.hpred_list_get_lexps filter sigma in List.sort ~cmp:exp_compare_neg lexps let sigma_dfs_sort tenv sigma = - let init () = let start_lexps = sigma_get_start_lexps_sort sigma in - ExpStack.init start_lexps in - + ExpStack.init start_lexps + in let final () = ExpStack.final () in - - let rec handle_strexp (se : Sil.strexp) = match se with - | Eexp (e, _) -> - ExpStack.push e - | Estruct (fld_se_list, _) -> - List.iter ~f:(fun (_, se) -> handle_strexp se) fld_se_list - | Earray (_, idx_se_list, _) -> - List.iter ~f:(fun (_, se) -> handle_strexp se) idx_se_list in - - let rec handle_e visited seen e (sigma : sigma) = match sigma with - | [] -> (visited, List.rev seen) + let rec handle_strexp (se: Sil.strexp) = + match se with + | Eexp (e, _) + -> ExpStack.push e + | Estruct (fld_se_list, _) + -> List.iter ~f:(fun (_, se) -> handle_strexp se) fld_se_list + | Earray (_, idx_se_list, _) + -> List.iter ~f:(fun (_, se) -> handle_strexp se) idx_se_list + in + let rec handle_e visited seen e (sigma: sigma) = + match sigma with + | [] + -> (visited, List.rev seen) | hpred :: cur -> - begin - match hpred with - | Hpointsto (e', se, _) when Exp.equal e e' -> - handle_strexp se; - (hpred:: visited, List.rev_append cur seen) - | Hlseg (_, _, root, next, shared) when Exp.equal e root -> - List.iter ~f:ExpStack.push (next:: shared); - (hpred:: visited, List.rev_append cur seen) - | Hdllseg (_, _, iF, oB, oF, iB, shared) - when Exp.equal e iF || Exp.equal e iB -> - List.iter ~f:ExpStack.push (oB:: oF:: shared); - (hpred:: visited, List.rev_append cur seen) - | _ -> - handle_e visited (hpred:: seen) e cur - end in - + match hpred with + | Hpointsto (e', se, _) when Exp.equal e e' + -> handle_strexp se ; (hpred :: visited, List.rev_append cur seen) + | Hlseg (_, _, root, next, shared) when Exp.equal e root + -> List.iter ~f:ExpStack.push (next :: shared) ; + (hpred :: visited, List.rev_append cur seen) + | Hdllseg (_, _, iF, oB, oF, iB, shared) when Exp.equal e iF || Exp.equal e iB + -> List.iter ~f:ExpStack.push (oB :: oF :: shared) ; + (hpred :: visited, List.rev_append cur seen) + | _ + -> handle_e visited (hpred :: seen) e cur + in let rec handle_sigma visited = function - | [] -> List.rev visited - | cur -> - if ExpStack.is_empty () then + | [] + -> List.rev visited + | cur + -> if ExpStack.is_empty () then let cur' = Normalize.sigma_normalize tenv Sil.sub_empty cur in List.rev_append cur' visited else let e = ExpStack.pop () in - let (visited', cur') = handle_e visited [] e cur in - handle_sigma visited' cur' in - - init (); + let visited', cur' = handle_e visited [] e cur in + handle_sigma visited' cur' + in + init () ; let sigma' = handle_sigma [] sigma in - final (); - sigma' + final () ; sigma' let prop_dfs_sort tenv p = let sigma = p.sigma in @@ -1836,25 +1823,26 @@ let prop_dfs_sort tenv p = (* L.out "@[<2>P SORTED:@\n%a@\n@." pp_prop p'; *) p' -let prop_fav_add_dfs tenv fav prop = - prop_fav_add fav (prop_dfs_sort tenv prop) +let prop_fav_add_dfs tenv fav prop = prop_fav_add fav (prop_dfs_sort tenv prop) -let rec strexp_get_array_indices acc (se : Sil.strexp) = match se with - | Eexp _ -> - acc - | Estruct (fsel, _) -> - let se_list = List.map ~f:snd fsel in +let rec strexp_get_array_indices acc (se: Sil.strexp) = + match se with + | Eexp _ + -> acc + | Estruct (fsel, _) + -> let se_list = List.map ~f:snd fsel in List.fold ~f:strexp_get_array_indices ~init:acc se_list - | Earray (_, isel, _) -> - let acc_new = List.fold ~f:(fun acc' (idx, _) -> idx:: acc') ~init:acc isel in + | Earray (_, isel, _) + -> let acc_new = List.fold ~f:(fun acc' (idx, _) -> idx :: acc') ~init:acc isel in let se_list = List.map ~f:snd isel in List.fold ~f:strexp_get_array_indices ~init:acc_new se_list -let hpred_get_array_indices acc (hpred : Sil.hpred) = match hpred with - | Hpointsto (_, se, _) -> - strexp_get_array_indices acc se - | Hlseg _ | Hdllseg _ -> - acc +let hpred_get_array_indices acc (hpred: Sil.hpred) = + match hpred with + | Hpointsto (_, se, _) + -> strexp_get_array_indices acc se + | Hlseg _ | Hdllseg _ + -> acc let sigma_get_array_indices sigma = let indices = List.fold ~f:hpred_get_array_indices ~init:[] sigma in @@ -1862,40 +1850,47 @@ let sigma_get_array_indices sigma = let compute_reindexing fav_add get_id_offset list = let rec select list_passed list_seen = function - | [] -> list_passed - | x :: list_rest -> - let id_offset_opt = get_id_offset x in - let list_passed_new = match id_offset_opt with - | None -> list_passed - | Some (id, _) -> - let fav = Sil.fav_new () in - List.iter ~f:(fav_add fav) list_seen; - List.iter ~f:(fav_add fav) list_passed; - if (Sil.fav_exists fav (Ident.equal id)) - then list_passed - else (x:: list_passed) in - let list_seen_new = x:: list_seen in - select list_passed_new list_seen_new list_rest in + | [] + -> list_passed + | x :: list_rest + -> let id_offset_opt = get_id_offset x in + let list_passed_new = + match id_offset_opt with + | None + -> list_passed + | Some (id, _) + -> let fav = Sil.fav_new () in + List.iter ~f:(fav_add fav) list_seen ; + List.iter ~f:(fav_add fav) list_passed ; + if Sil.fav_exists fav (Ident.equal id) then list_passed else x :: list_passed + in + let list_seen_new = x :: list_seen in + select list_passed_new list_seen_new list_rest + in let list_passed = select [] [] list in let transform x = let id, offset = match get_id_offset x with None -> assert false | Some io -> io in let base_new : Exp.t = Var (Ident.create_fresh Ident.kprimed) in let offset_new = Exp.int (IntLit.neg offset) in let exp_new : Exp.t = BinOp (PlusA, base_new, offset_new) in - (id, exp_new) in + (id, exp_new) + in let reindexing = List.map ~f:transform list_passed in Sil.exp_subst_of_list reindexing let compute_reindexing_from_indices indices = - let get_id_offset (e : Exp.t) = match e with - | BinOp (PlusA, Var id, Const(Cint offset)) -> - if Ident.is_primed id then Some (id, offset) else None - | _ -> None in + let get_id_offset (e: Exp.t) = + match e with + | BinOp (PlusA, Var id, Const Cint offset) + -> if Ident.is_primed id then Some (id, offset) else None + | _ + -> None + in let fav_add = Sil.exp_fav_add in compute_reindexing fav_add get_id_offset indices -let apply_reindexing tenv (exp_subst : Sil.exp_subst) prop = - let subst = (`Exp exp_subst) in +let apply_reindexing tenv (exp_subst: Sil.exp_subst) prop = + let subst = `Exp exp_subst in let nsigma = Normalize.sigma_normalize tenv subst prop.sigma in let npi = Normalize.pi_normalize tenv subst nsigma prop.pi in let nsub, atoms = @@ -1906,133 +1901,137 @@ let apply_reindexing tenv (exp_subst : Sil.exp_subst) prop = let sub_eqs, sub_keep = Sil.sub_range_partition contains_substituted_id sub' in let eqs = Sil.sub_to_list sub_eqs in let atoms = - List.map - ~f:(fun (id, e) -> Sil.Aeq (Var id, Normalize.exp_normalize tenv subst e)) - eqs in - (sub_keep, atoms) in - let p' = - unsafe_cast_to_normal - (set prop ~sub:nsub ~pi:npi ~sigma:nsigma) in + List.map ~f:(fun (id, e) -> Sil.Aeq (Var id, Normalize.exp_normalize tenv subst e)) eqs + in + (sub_keep, atoms) + in + let p' = unsafe_cast_to_normal (set prop ~sub:nsub ~pi:npi ~sigma:nsigma) in List.fold ~f:(Normalize.prop_atom_and tenv) ~init:p' atoms let prop_rename_array_indices tenv prop = if !Config.footprint then prop - else begin + else let indices = sigma_get_array_indices prop.sigma in - let not_same_base_lt_offsets (e1 : Exp.t) (e2 : Exp.t) = - match e1, e2 with - | BinOp (PlusA, e1', Const (Cint n1')), - BinOp(PlusA, e2', Const (Cint n2')) -> - not (Exp.equal e1' e2' && IntLit.lt n1' n2') - | _ -> true in + let not_same_base_lt_offsets (e1: Exp.t) (e2: Exp.t) = + match (e1, e2) with + | BinOp (PlusA, e1', Const Cint n1'), BinOp (PlusA, e2', Const Cint n2') + -> not (Exp.equal e1' e2' && IntLit.lt n1' n2') + | _ + -> true + in let rec select_minimal_indices indices_seen = function - | [] -> List.rev indices_seen - | index:: indices_rest -> - let indices_seen' = List.filter ~f:(not_same_base_lt_offsets index) indices_seen in - let indices_seen_new = index:: indices_seen' in + | [] + -> List.rev indices_seen + | index :: indices_rest + -> let indices_seen' = List.filter ~f:(not_same_base_lt_offsets index) indices_seen in + let indices_seen_new = index :: indices_seen' in let indices_rest_new = List.filter ~f:(not_same_base_lt_offsets index) indices_rest in - select_minimal_indices indices_seen_new indices_rest_new in + select_minimal_indices indices_seen_new indices_rest_new + in let minimal_indices = select_minimal_indices [] indices in let subst = compute_reindexing_from_indices minimal_indices in apply_reindexing tenv subst prop - end let compute_renaming fav = let ids = Sil.fav_to_list fav in let ids_primed, ids_nonprimed = List.partition_tf ~f:Ident.is_primed ids in let ids_footprint = List.filter ~f:Ident.is_footprint ids_nonprimed in - let id_base_primed = Ident.create Ident.kprimed 0 in let id_base_footprint = Ident.create Ident.kfootprint 0 in - let rec f id_base index ren_subst = function - | [] -> ren_subst - | id:: ids -> - let new_id = Ident.set_stamp id_base index in - if Ident.equal id new_id then - f id_base (index + 1) ren_subst ids - else - f id_base (index + 1) ((id, new_id):: ren_subst) ids in - + | [] + -> ren_subst + | id :: ids + -> let new_id = Ident.set_stamp id_base index in + if Ident.equal id new_id then f id_base (index + 1) ren_subst ids + else f id_base (index + 1) ((id, new_id) :: ren_subst) ids + in let ren_primed = f id_base_primed 0 [] ids_primed in let ren_footprint = f id_base_footprint 0 [] ids_footprint in - ren_primed @ ren_footprint let rec idlist_assoc id = function - | [] -> raise Not_found - | (i, x):: l -> if Ident.equal i id then x else idlist_assoc id l + | [] + -> raise Not_found + | (i, x) :: l + -> if Ident.equal i id then x else idlist_assoc id l let ident_captured_ren ren id = - try (idlist_assoc id ren) + try idlist_assoc id ren with Not_found -> id + (* If not defined in ren, id should be mapped to itself *) -let rec exp_captured_ren ren (e : Exp.t) : Exp.t = match e with - | Var id -> - Var (ident_captured_ren ren id) - | Exn e -> - Exn (exp_captured_ren ren e) - | Closure _ -> - e (* TODO: why captured vars not renamed? *) - | Const _ -> - e - | Sizeof ({dynamic_length} as sizeof_data) -> - Sizeof {sizeof_data with dynamic_length=Option.map ~f:(exp_captured_ren ren) dynamic_length} - | Cast (t, e) -> - Cast (t, exp_captured_ren ren e) - | UnOp (op, e, topt) -> - UnOp (op, exp_captured_ren ren e, topt) - | BinOp (op, e1, e2) -> - let e1' = exp_captured_ren ren e1 in +let rec exp_captured_ren ren (e: Exp.t) : Exp.t = + match e with + | Var id + -> Var (ident_captured_ren ren id) + | Exn e + -> Exn (exp_captured_ren ren e) + | Closure _ + -> e (* TODO: why captured vars not renamed? *) + | Const _ + -> e + | Sizeof ({dynamic_length} as sizeof_data) + -> Sizeof {sizeof_data with dynamic_length= Option.map ~f:(exp_captured_ren ren) dynamic_length} + | Cast (t, e) + -> Cast (t, exp_captured_ren ren e) + | UnOp (op, e, topt) + -> UnOp (op, exp_captured_ren ren e, topt) + | BinOp (op, e1, e2) + -> let e1' = exp_captured_ren ren e1 in let e2' = exp_captured_ren ren e2 in BinOp (op, e1', e2') - | Lvar id -> - Lvar id - | Lfield (e, fld, typ) -> - Lfield (exp_captured_ren ren e, fld, typ) - | Lindex (e1, e2) -> - let e1' = exp_captured_ren ren e1 in + | Lvar id + -> Lvar id + | Lfield (e, fld, typ) + -> Lfield (exp_captured_ren ren e, fld, typ) + | Lindex (e1, e2) + -> let e1' = exp_captured_ren ren e1 in let e2' = exp_captured_ren ren e2 in Lindex (e1', e2') -let atom_captured_ren ren (a : Sil.atom) : Sil.atom = match a with - | Aeq (e1, e2) -> - Aeq (exp_captured_ren ren e1, exp_captured_ren ren e2) - | Aneq (e1, e2) -> - Aneq (exp_captured_ren ren e1, exp_captured_ren ren e2) - | Apred (a, es) -> - Apred (a, List.map ~f:(fun e -> exp_captured_ren ren e) es) - | Anpred (a, es) -> - Anpred (a, List.map ~f:(fun e -> exp_captured_ren ren e) es) - -let rec strexp_captured_ren ren (se : Sil.strexp) : Sil.strexp = match se with - | Eexp (e, inst) -> - Eexp (exp_captured_ren ren e, inst) - | Estruct (fld_se_list, inst) -> - let f (fld, se) = (fld, strexp_captured_ren ren se) in +let atom_captured_ren ren (a: Sil.atom) : Sil.atom = + match a with + | Aeq (e1, e2) + -> Aeq (exp_captured_ren ren e1, exp_captured_ren ren e2) + | Aneq (e1, e2) + -> Aneq (exp_captured_ren ren e1, exp_captured_ren ren e2) + | Apred (a, es) + -> Apred (a, List.map ~f:(fun e -> exp_captured_ren ren e) es) + | Anpred (a, es) + -> Anpred (a, List.map ~f:(fun e -> exp_captured_ren ren e) es) + +let rec strexp_captured_ren ren (se: Sil.strexp) : Sil.strexp = + match se with + | Eexp (e, inst) + -> Eexp (exp_captured_ren ren e, inst) + | Estruct (fld_se_list, inst) + -> let f (fld, se) = (fld, strexp_captured_ren ren se) in Estruct (List.map ~f fld_se_list, inst) - | Earray (len, idx_se_list, inst) -> - let f (idx, se) = + | Earray (len, idx_se_list, inst) + -> let f (idx, se) = let idx' = exp_captured_ren ren idx in - (idx', strexp_captured_ren ren se) in + (idx', strexp_captured_ren ren se) + in let len' = exp_captured_ren ren len in Earray (len', List.map ~f idx_se_list, inst) -and hpred_captured_ren ren (hpred : Sil.hpred) : Sil.hpred = match hpred with - | Hpointsto (base, se, te) -> - let base' = exp_captured_ren ren base in +and hpred_captured_ren ren (hpred: Sil.hpred) : Sil.hpred = + match hpred with + | Hpointsto (base, se, te) + -> let base' = exp_captured_ren ren base in let se' = strexp_captured_ren ren se in let te' = exp_captured_ren ren te in Hpointsto (base', se', te') - | Hlseg (k, para, e1, e2, elist) -> - let para' = hpara_ren para in + | Hlseg (k, para, e1, e2, elist) + -> let para' = hpara_ren para in let e1' = exp_captured_ren ren e1 in let e2' = exp_captured_ren ren e2 in let elist' = List.map ~f:(exp_captured_ren ren) elist in Hlseg (k, para', e1', e2', elist') - | Hdllseg (k, para, e1, e2, e3, e4, elist) -> - let para' = hpara_dll_ren para in + | Hdllseg (k, para, e1, e2, e3, e4, elist) + -> let para' = hpara_dll_ren para in let e1' = exp_captured_ren ren e1 in let e2' = exp_captured_ren ren e2 in let e3' = exp_captured_ren ren e3 in @@ -2040,7 +2039,7 @@ and hpred_captured_ren ren (hpred : Sil.hpred) : Sil.hpred = match hpred with let elist' = List.map ~f:(exp_captured_ren ren) elist in Hdllseg (k, para', e1', e2', e3', e4', elist') -and hpara_ren (para : Sil.hpara) : Sil.hpara = +and hpara_ren (para: Sil.hpara) : Sil.hpara = let av = Sil.hpara_shallow_av para in let ren = compute_renaming av in let root = ident_captured_ren ren para.root in @@ -2048,9 +2047,9 @@ and hpara_ren (para : Sil.hpara) : Sil.hpara = let svars = List.map ~f:(ident_captured_ren ren) para.svars in let evars = List.map ~f:(ident_captured_ren ren) para.evars in let body = List.map ~f:(hpred_captured_ren ren) para.body in - { root; next; svars; evars; body} + {root; next; svars; evars; body} -and hpara_dll_ren (para : Sil.hpara_dll) : Sil.hpara_dll = +and hpara_dll_ren (para: Sil.hpara_dll) : Sil.hpara_dll = let av = Sil.hpara_dll_shallow_av para in let ren = compute_renaming av in let iF = ident_captured_ren ren para.cell in @@ -2059,49 +2058,42 @@ and hpara_dll_ren (para : Sil.hpara_dll) : Sil.hpara_dll = let svars' = List.map ~f:(ident_captured_ren ren) para.svars_dll in let evars' = List.map ~f:(ident_captured_ren ren) para.evars_dll in let body' = List.map ~f:(hpred_captured_ren ren) para.body_dll in - { cell = iF; - flink = oF; - blink = oB; - svars_dll = svars'; - evars_dll = evars'; - body_dll = body'} + {cell= iF; flink= oF; blink= oB; svars_dll= svars'; evars_dll= evars'; body_dll= body'} -let pi_captured_ren ren pi = - List.map ~f:(atom_captured_ren ren) pi +let pi_captured_ren ren pi = List.map ~f:(atom_captured_ren ren) pi -let sigma_captured_ren ren sigma = - List.map ~f:(hpred_captured_ren ren) sigma +let sigma_captured_ren ren sigma = List.map ~f:(hpred_captured_ren ren) sigma -let sub_captured_ren ren sub = - Sil.sub_map (ident_captured_ren ren) (exp_captured_ren ren) sub +let sub_captured_ren ren sub = Sil.sub_map (ident_captured_ren ren) (exp_captured_ren ren) sub (** Canonicalize the names of primed variables and footprint vars. *) -let prop_rename_primed_footprint_vars tenv (p : normal t) : normal t = +let prop_rename_primed_footprint_vars tenv (p: normal t) : normal t = let p = prop_rename_array_indices tenv p in let bound_vars = let filter id = Ident.is_footprint id || Ident.is_primed id in let p_dfs = prop_dfs_sort tenv p in let fvars_in_p = prop_fav p_dfs in - Sil.fav_filter_ident fvars_in_p filter; - fvars_in_p in + Sil.fav_filter_ident fvars_in_p filter ; fvars_in_p + in let ren = compute_renaming bound_vars in let sub' = sub_captured_ren ren p.sub in let pi' = pi_captured_ren ren p.pi in let sigma' = sigma_captured_ren ren p.sigma in let pi_fp' = pi_captured_ren ren p.pi_fp in let sigma_fp' = sigma_captured_ren ren p.sigma_fp in - let sub_for_normalize = Sil.sub_empty in (* It is fine to use the empty substituion during normalization because the renaming maintains that a substitution is normalized *) let nsub' = Normalize.sub_normalize sub' in let nsigma' = Normalize.sigma_normalize tenv sub_for_normalize sigma' in let npi' = Normalize.pi_normalize tenv sub_for_normalize nsigma' pi' in - let p' = Normalize.footprint_normalize tenv - (set prop_emp ~sub:nsub' ~pi:npi' ~sigma:nsigma' ~pi_fp:pi_fp' ~sigma_fp:sigma_fp') in + let p' = + Normalize.footprint_normalize tenv + (set prop_emp ~sub:nsub' ~pi:npi' ~sigma:nsigma' ~pi_fp:pi_fp' ~sigma_fp:sigma_fp') + in unsafe_cast_to_normal p' -let expose (p : normal t) : exposed t = Obj.magic p +let expose (p: normal t) : exposed t = Obj.magic p (** Apply subsitution to prop. *) let prop_sub subst (prop: 'a t) : exposed t = @@ -2117,18 +2109,20 @@ let prop_ren_sub tenv (ren_sub: Sil.exp_subst) (prop: normal t) : normal t = (** Existentially quantify the [fav] in [prop]. [fav] should not contain any primed variables. *) -let exist_quantify tenv fav (prop : normal t) : normal t = +let exist_quantify tenv fav (prop: normal t) : normal t = let ids = Sil.fav_to_list fav in - if List.exists ~f:Ident.is_primed ids then assert false; (* sanity check *) - if List.is_empty ids then prop else + if List.exists ~f:Ident.is_primed ids then assert false ; + (* sanity check *) + if List.is_empty ids then prop + else let gen_fresh_id_sub id = (id, Exp.Var (Ident.create_fresh Ident.kprimed)) in let ren_sub = Sil.exp_subst_of_list (List.map ~f:gen_fresh_id_sub ids) in let prop' = (* throw away x=E if x becomes _x *) let mem_idlist i = List.exists ~f:(fun id -> Ident.equal i id) in let sub = Sil.sub_filter (fun i -> not (mem_idlist i ids)) prop.sub in - if Sil.equal_exp_subst sub prop.sub then prop - else unsafe_cast_to_normal (set prop ~sub) in + if Sil.equal_exp_subst sub prop.sub then prop else unsafe_cast_to_normal (set prop ~sub) + in (* L.out "@[<2>.... Existential Quantification ....@\n"; L.out "SUB:%a@\n" pp_sub prop'.sub; @@ -2150,33 +2144,29 @@ let prop_expmap (fe: Exp.t -> Exp.t) prop = let vars_make_unprimed tenv fav prop = let ids = Sil.fav_to_list fav in let ren_sub = - Sil.exp_subst_of_list (List.map - ~f:(fun i -> (i, Exp.Var (Ident.create_fresh Ident.knormal))) - ids) in + Sil.exp_subst_of_list + (List.map ~f:(fun i -> (i, Exp.Var (Ident.create_fresh Ident.knormal))) ids) + in prop_ren_sub tenv ren_sub prop (** convert the normal vars to primed vars. *) let prop_normal_vars_to_primed_vars tenv p = let fav = prop_fav p in - Sil.fav_filter_ident fav Ident.is_normal; - exist_quantify tenv fav p + Sil.fav_filter_ident fav Ident.is_normal ; exist_quantify tenv fav p (** convert the primed vars to normal vars. *) -let prop_primed_vars_to_normal_vars tenv (p : normal t) : normal t = +let prop_primed_vars_to_normal_vars tenv (p: normal t) : normal t = let fav = prop_fav p in - Sil.fav_filter_ident fav Ident.is_primed; - vars_make_unprimed tenv fav p + Sil.fav_filter_ident fav Ident.is_primed ; vars_make_unprimed tenv fav p -let from_pi pi = - set prop_emp ~pi +let from_pi pi = set prop_emp ~pi -let from_sigma sigma = - set prop_emp ~sigma +let from_sigma sigma = set prop_emp ~sigma (** Rename free variables in a prop replacing them with existentially quantified vars *) -let prop_rename_fav_with_existentials tenv (p : normal t) : normal t = +let prop_rename_fav_with_existentials tenv (p: normal t) : normal t = let fav = Sil.fav_new () in - prop_fav_add fav p; + prop_fav_add fav p ; let ids = Sil.fav_to_list fav in let ids' = List.map ~f:(fun i -> (i, Ident.create_fresh Ident.kprimed)) ids in let ren_sub = Sil.subst_of_list (List.map ~f:(fun (i, i') -> (i, Exp.Var i')) ids') in @@ -2186,68 +2176,65 @@ let prop_rename_fav_with_existentials tenv (p : normal t) : normal t = (** Removes seeds variables from a prop corresponding to captured variables in an objc block *) let remove_seed_captured_vars_block tenv captured_vars prop = - let hpred_seed_captured = - function - | Sil.Hpointsto (Exp.Lvar pv, _, _) -> - let pname = Pvar.get_name pv in - (Pvar.is_seed pv) && (List.mem ~equal:Mangled.equal captured_vars pname) - | _ -> false in + let hpred_seed_captured = function + | Sil.Hpointsto (Exp.Lvar pv, _, _) + -> let pname = Pvar.get_name pv in + Pvar.is_seed pv && List.mem ~equal:Mangled.equal captured_vars pname + | _ + -> false + in let sigma = prop.sigma in - let sigma' = - List.filter ~f:(fun hpred -> not (hpred_seed_captured hpred)) sigma in + let sigma' = List.filter ~f:(fun hpred -> not (hpred_seed_captured hpred)) sigma in Normalize.normalize tenv (set prop ~sigma:sigma') (** {2 Prop iterators} *) (** Iterator state over sigma. *) type 'a prop_iter = - { pit_sub : Sil.exp_subst; (** substitution for equalities *) - pit_pi : pi; (** pure part *) - pit_newpi : (bool * Sil.atom) list; (** newly added atoms. *) - (* The first records !Config.footprint. *) - pit_old : sigma; (** sigma already visited *) - pit_curr : Sil.hpred; (** current element *) - pit_state : 'a; (** state of current element *) - pit_new : sigma; (** sigma not yet visited *) - pit_pi_fp : pi; (** pure part of the footprint *) - pit_sigma_fp : sigma; (** sigma part of the footprint *) - } + { pit_sub: Sil.exp_subst (** substitution for equalities *) + ; pit_pi: pi (** pure part *) + ; pit_newpi: (bool * Sil.atom) list (** newly added atoms. *) + ; (* The first records !Config.footprint. *) + pit_old: sigma (** sigma already visited *) + ; pit_curr: Sil.hpred (** current element *) + ; pit_state: 'a (** state of current element *) + ; pit_new: sigma (** sigma not yet visited *) + ; pit_pi_fp: pi (** pure part of the footprint *) + ; pit_sigma_fp: sigma (** sigma part of the footprint *) } let prop_iter_create prop = match prop.sigma with - | hpred:: sigma' -> Some - { pit_sub = prop.sub; - pit_pi = prop.pi; - pit_newpi = []; - pit_old = []; - pit_curr = hpred; - pit_state = (); - pit_new = sigma'; - pit_pi_fp = prop.pi_fp; - pit_sigma_fp = prop.sigma_fp } - | _ -> None + | hpred :: sigma' + -> Some + { pit_sub= prop.sub + ; pit_pi= prop.pi + ; pit_newpi= [] + ; pit_old= [] + ; pit_curr= hpred + ; pit_state= () + ; pit_new= sigma' + ; pit_pi_fp= prop.pi_fp + ; pit_sigma_fp= prop.sigma_fp } + | _ + -> None (** Return the prop associated to the iterator. *) let prop_iter_to_prop tenv iter = - let sigma = List.rev_append iter.pit_old (iter.pit_curr:: iter.pit_new) in + let sigma = List.rev_append iter.pit_old (iter.pit_curr :: iter.pit_new) in let prop = Normalize.normalize tenv - (set prop_emp - ~sub:iter.pit_sub - ~pi:iter.pit_pi - ~sigma:sigma - ~pi_fp:iter.pit_pi_fp - ~sigma_fp:iter.pit_sigma_fp) in + (set prop_emp ~sub:iter.pit_sub ~pi:iter.pit_pi ~sigma ~pi_fp:iter.pit_pi_fp + ~sigma_fp:iter.pit_sigma_fp) + in List.fold - ~f:(fun p (footprint, atom) -> Normalize.prop_atom_and tenv ~footprint: footprint p atom) - ~init:prop - iter.pit_newpi + ~f:(fun p (footprint, atom) -> Normalize.prop_atom_and tenv ~footprint p atom) + ~init:prop iter.pit_newpi (** Add an atom to the pi part of prop iter. The first parameter records whether it is done during footprint or during re - execution. *) let prop_iter_add_atom footprint iter atom = - { iter with pit_newpi = (footprint, atom):: iter.pit_newpi } + {iter with pit_newpi= (footprint, atom) :: iter.pit_newpi} (** Remove the current element of the iterator, and return the prop associated to the resulting iterator *) @@ -2255,223 +2242,203 @@ let prop_iter_remove_curr_then_to_prop tenv iter : normal t = let sigma = List.rev_append iter.pit_old iter.pit_new in let normalized_sigma = Normalize.sigma_normalize tenv (`Exp iter.pit_sub) sigma in let prop = - set prop_emp - ~sub:iter.pit_sub - ~pi:iter.pit_pi - ~sigma:normalized_sigma - ~pi_fp:iter.pit_pi_fp - ~sigma_fp:iter.pit_sigma_fp in + set prop_emp ~sub:iter.pit_sub ~pi:iter.pit_pi ~sigma:normalized_sigma ~pi_fp:iter.pit_pi_fp + ~sigma_fp:iter.pit_sigma_fp + in unsafe_cast_to_normal prop (** Return the current hpred and state. *) let prop_iter_current tenv iter = let curr = Normalize.hpred_normalize tenv (`Exp iter.pit_sub) iter.pit_curr in - let prop = - unsafe_cast_to_normal - (set prop_emp ~sigma:[curr]) in + let prop = unsafe_cast_to_normal (set prop_emp ~sigma:[curr]) in let prop' = List.fold - ~f:(fun p (footprint, atom) -> Normalize.prop_atom_and tenv ~footprint: footprint p atom) - ~init:prop - iter.pit_newpi in - match prop'.sigma with - | [curr'] -> (curr', iter.pit_state) - | _ -> assert false + ~f:(fun p (footprint, atom) -> Normalize.prop_atom_and tenv ~footprint p atom) + ~init:prop iter.pit_newpi + in + match prop'.sigma with [curr'] -> (curr', iter.pit_state) | _ -> assert false (** Update the current element of the iterator. *) -let prop_iter_update_current iter hpred = - { iter with pit_curr = hpred } +let prop_iter_update_current iter hpred = {iter with pit_curr= hpred} (** Update the current element of the iterator by a nonempty list of elements. *) let prop_iter_update_current_by_list iter = function - | [] -> assert false (* the list should be nonempty *) - | hpred:: hpred_list -> - let pit_new' = hpred_list@iter.pit_new in - { iter with pit_curr = hpred; pit_state = (); pit_new = pit_new'} + | [] + -> assert false (* the list should be nonempty *) + | hpred :: hpred_list + -> let pit_new' = hpred_list @ iter.pit_new in + {iter with pit_curr= hpred; pit_state= (); pit_new= pit_new'} let prop_iter_next iter = match iter.pit_new with - | [] -> None - | hpred':: new' -> Some - { iter with - pit_old = iter.pit_curr:: iter.pit_old; - pit_curr = hpred'; - pit_state = (); - pit_new = new'} + | [] + -> None + | hpred' :: new' + -> Some + { iter with + pit_old= iter.pit_curr :: iter.pit_old; pit_curr= hpred'; pit_state= (); pit_new= new' } let prop_iter_remove_curr_then_next iter = match iter.pit_new with - | [] -> None - | hpred':: new' -> Some - { iter with - pit_old = iter.pit_old; - pit_curr = hpred'; - pit_state = (); - pit_new = new'} + | [] + -> None + | hpred' :: new' + -> Some {iter with pit_old= iter.pit_old; pit_curr= hpred'; pit_state= (); pit_new= new'} (** Insert before the current element of the iterator. *) let prop_iter_prev_then_insert iter hpred = - { iter with - pit_new = iter.pit_curr:: iter.pit_new; - pit_curr = hpred } + {iter with pit_new= iter.pit_curr :: iter.pit_new; pit_curr= hpred} (** Scan sigma to find an [hpred] satisfying the filter function. *) let rec prop_iter_find iter filter = match filter iter.pit_curr with - | Some st -> Some { iter with pit_state = st } + | Some st + -> Some {iter with pit_state= st} | None -> - (match prop_iter_next iter with - | None -> None - | Some iter' -> prop_iter_find iter' filter) + match prop_iter_next iter with None -> None | Some iter' -> prop_iter_find iter' filter (** Set the state of the iterator *) -let prop_iter_set_state iter state = - { iter with pit_state = state } +let prop_iter_set_state iter state = {iter with pit_state= state} let prop_iter_make_id_primed tenv id iter = let pid = Ident.create_fresh Ident.kprimed in let sub_id = Sil.subst_of_list [(id, Exp.Var pid)] in - let normalize (id, e) = let eq' : Sil.atom = Aeq (Sil.exp_sub sub_id (Var id), Sil.exp_sub sub_id e) in - Normalize.atom_normalize tenv Sil.sub_empty eq' in - + Normalize.atom_normalize tenv Sil.sub_empty eq' + in let rec split pairs_unpid pairs_pid = function - | [] -> (List.rev pairs_unpid, List.rev pairs_pid) - | (eq:: eqs_cur : pi) -> - begin - match eq with - | Aeq (Var id1, e1) when Sil.ident_in_exp id1 e1 -> - L.internal_error "@[<2>#### ERROR: an assumption of the analyzer broken ####@\n"; - L.internal_error "Broken Assumption: id notin e for all (id,e) in sub@\n"; - L.internal_error "(id,e) : (%a,%a)@\n" (Ident.pp Pp.text) id1 Exp.pp e1; - L.internal_error "PROP : %a@\n@." (pp_prop Pp.text) (prop_iter_to_prop tenv iter); - assert false - | Aeq (Var id1, e1) when Ident.equal pid id1 -> - split pairs_unpid ((id1, e1):: pairs_pid) eqs_cur - | Aeq (Var id1, e1) -> - split ((id1, e1):: pairs_unpid) pairs_pid eqs_cur - | _ -> - assert false - end in - + | [] + -> (List.rev pairs_unpid, List.rev pairs_pid) + | (eq :: eqs_cur: pi) -> + match eq with + | Aeq (Var id1, e1) when Sil.ident_in_exp id1 e1 + -> L.internal_error "@[<2>#### ERROR: an assumption of the analyzer broken ####@\n" ; + L.internal_error "Broken Assumption: id notin e for all (id,e) in sub@\n" ; + L.internal_error "(id,e) : (%a,%a)@\n" (Ident.pp Pp.text) id1 Exp.pp e1 ; + L.internal_error "PROP : %a@\n@." (pp_prop Pp.text) (prop_iter_to_prop tenv iter) ; + assert false + | Aeq (Var id1, e1) when Ident.equal pid id1 + -> split pairs_unpid ((id1, e1) :: pairs_pid) eqs_cur + | Aeq (Var id1, e1) + -> split ((id1, e1) :: pairs_unpid) pairs_pid eqs_cur + | _ + -> assert false + in let rec get_eqs acc = function - | [] | [_] -> - List.rev acc - | (_, e1) :: (((_, e2) :: _) as pairs) -> - get_eqs (Sil.Aeq(e1, e2):: acc) pairs in - + | [] | [_] + -> List.rev acc + | (_, e1) :: ((_, e2) :: _ as pairs) + -> get_eqs (Sil.Aeq (e1, e2) :: acc) pairs + in let sub_new, sub_use, eqs_add = let eqs = List.map ~f:normalize (Sil.sub_to_list iter.pit_sub) in let pairs_unpid, pairs_pid = split [] [] eqs in match pairs_pid with - | [] -> - let sub_unpid = Sil.exp_subst_of_list pairs_unpid in + | [] + -> let sub_unpid = Sil.exp_subst_of_list pairs_unpid in let pairs = (id, Exp.Var pid) :: pairs_unpid in - sub_unpid, Sil.subst_of_list pairs, [] - | (id1, e1):: _ -> - let sub_id1 = Sil.subst_of_list [(id1, e1)] in + (sub_unpid, Sil.subst_of_list pairs, []) + | (id1, e1) :: _ + -> let sub_id1 = Sil.subst_of_list [(id1, e1)] in let pairs_unpid' = - List.map ~f:(fun (id', e') -> (id', Sil.exp_sub sub_id1 e')) pairs_unpid in + List.map ~f:(fun (id', e') -> (id', Sil.exp_sub sub_id1 e')) pairs_unpid + in let sub_unpid = Sil.exp_subst_of_list pairs_unpid' in let pairs = (id, e1) :: pairs_unpid' in - sub_unpid, Sil.subst_of_list pairs, get_eqs [] pairs_pid in + (sub_unpid, Sil.subst_of_list pairs, get_eqs [] pairs_pid) + in let nsub_new = Normalize.sub_normalize sub_new in - { iter with - pit_sub = nsub_new; - pit_pi = pi_sub sub_use (iter.pit_pi @ eqs_add); - pit_old = sigma_sub sub_use iter.pit_old; - pit_curr = Sil.hpred_sub sub_use iter.pit_curr; - pit_new = sigma_sub sub_use iter.pit_new } + pit_sub= nsub_new + ; pit_pi= pi_sub sub_use (iter.pit_pi @ eqs_add) + ; pit_old= sigma_sub sub_use iter.pit_old + ; pit_curr= Sil.hpred_sub sub_use iter.pit_curr + ; pit_new= sigma_sub sub_use iter.pit_new } let prop_iter_footprint_fav_add fav iter = - sigma_fav_add fav iter.pit_sigma_fp; - pi_fav_add fav iter.pit_pi_fp + sigma_fav_add fav iter.pit_sigma_fp ; pi_fav_add fav iter.pit_pi_fp (** Find fav of the footprint part of the iterator *) let prop_iter_footprint_fav iter = Sil.fav_imperative_to_functional prop_iter_footprint_fav_add iter let prop_iter_fav_add fav iter = - Sil.sub_fav_add fav iter.pit_sub; - pi_fav_add fav iter.pit_pi; - pi_fav_add fav (List.map ~f:snd iter.pit_newpi); - sigma_fav_add fav iter.pit_old; - sigma_fav_add fav iter.pit_new; - Sil.hpred_fav_add fav iter.pit_curr; + Sil.sub_fav_add fav iter.pit_sub ; + pi_fav_add fav iter.pit_pi ; + pi_fav_add fav (List.map ~f:snd iter.pit_newpi) ; + sigma_fav_add fav iter.pit_old ; + sigma_fav_add fav iter.pit_new ; + Sil.hpred_fav_add fav iter.pit_curr ; prop_iter_footprint_fav_add fav iter (** Find fav of the iterator *) -let prop_iter_fav iter = - Sil.fav_imperative_to_functional prop_iter_fav_add iter +let prop_iter_fav iter = Sil.fav_imperative_to_functional prop_iter_fav_add iter (** Free vars of the iterator except the current hpred (and footprint). *) let prop_iter_noncurr_fav_add fav iter = - sigma_fav_add fav iter.pit_old; - sigma_fav_add fav iter.pit_new; - Sil.sub_fav_add fav iter.pit_sub; + sigma_fav_add fav iter.pit_old ; + sigma_fav_add fav iter.pit_new ; + Sil.sub_fav_add fav iter.pit_sub ; pi_fav_add fav iter.pit_pi (** Extract the sigma part of the footprint *) -let prop_iter_get_footprint_sigma iter = - iter.pit_sigma_fp +let prop_iter_get_footprint_sigma iter = iter.pit_sigma_fp (** Replace the sigma part of the footprint *) -let prop_iter_replace_footprint_sigma iter sigma = - { iter with pit_sigma_fp = sigma } +let prop_iter_replace_footprint_sigma iter sigma = {iter with pit_sigma_fp= sigma} -let prop_iter_noncurr_fav iter = - Sil.fav_imperative_to_functional prop_iter_noncurr_fav_add iter +let prop_iter_noncurr_fav iter = Sil.fav_imperative_to_functional prop_iter_noncurr_fav_add iter -let rec strexp_gc_fields (fav: Sil.fav) (se : Sil.strexp) = +let rec strexp_gc_fields (fav: Sil.fav) (se: Sil.strexp) = match se with - | Eexp _ -> - Some se - | Estruct (fsel, inst) -> - let fselo = List.map ~f:(fun (f, se) -> (f, strexp_gc_fields fav se)) fsel in + | Eexp _ + -> Some se + | Estruct (fsel, inst) + -> let fselo = List.map ~f:(fun (f, se) -> (f, strexp_gc_fields fav se)) fsel in let fsel' = - let fselo' = List.filter ~f:(function | (_, Some _) -> true | _ -> false) fselo in - List.map ~f:(function (f, seo) -> (f, unSome seo)) fselo' in - if [%compare.equal: (Typ.Fieldname.t * Sil.strexp) list] fsel fsel' then Some se + let fselo' = List.filter ~f:(function _, Some _ -> true | _ -> false) fselo in + List.map ~f:(function f, seo -> (f, unSome seo)) fselo' + in + if [%compare.equal : (Typ.Fieldname.t * Sil.strexp) list] fsel fsel' then Some se else Some (Sil.Estruct (fsel', inst)) - | Earray _ -> - Some se - -let hpred_gc_fields (fav: Sil.fav) (hpred : Sil.hpred) : Sil.hpred = match hpred with - | Hpointsto (e, se, te) -> - Sil.exp_fav_add fav e; - Sil.exp_fav_add fav te; - (match strexp_gc_fields fav se with - | None -> hpred - | Some se' -> - if Sil.equal_strexp se se' then hpred - else Hpointsto (e, se', te)) - | Hlseg _ | Hdllseg _ -> - hpred + | Earray _ + -> Some se + +let hpred_gc_fields (fav: Sil.fav) (hpred: Sil.hpred) : Sil.hpred = + match hpred with + | Hpointsto (e, se, te) + -> ( + Sil.exp_fav_add fav e ; + Sil.exp_fav_add fav te ; + match strexp_gc_fields fav se with + | None + -> hpred + | Some se' + -> if Sil.equal_strexp se se' then hpred else Hpointsto (e, se', te) ) + | Hlseg _ | Hdllseg _ + -> hpred let rec prop_iter_map f iter = let hpred_curr = f iter in - let iter' = { iter with pit_curr = hpred_curr } in - match prop_iter_next iter' with - | None -> iter' - | Some iter'' -> prop_iter_map f iter'' + let iter' = {iter with pit_curr= hpred_curr} in + match prop_iter_next iter' with None -> iter' | Some iter'' -> prop_iter_map f iter'' (** Collect garbage fields. *) let prop_iter_gc_fields iter = let f iter' = let fav = prop_iter_noncurr_fav iter' in - hpred_gc_fields fav iter'.pit_curr in + hpred_gc_fields fav iter'.pit_curr + in prop_iter_map f iter let prop_case_split tenv prop = let pi_sigma_list = Sil.sigma_to_sigma_ne prop.sigma in let f props_acc (pi, sigma) = let sigma' = sigma_normalize_prop tenv prop sigma in - let prop' = - unsafe_cast_to_normal - (set prop ~sigma:sigma') in - (List.fold ~f:(Normalize.prop_atom_and tenv) ~init:prop' pi):: props_acc in + let prop' = unsafe_cast_to_normal (set prop ~sigma:sigma') in + List.fold ~f:(Normalize.prop_atom_and tenv) ~init:prop' pi :: props_acc + in List.fold ~f ~init:[] pi_sigma_list let prop_expand prop = @@ -2483,27 +2450,32 @@ let prop_expand prop = (*** START of module Metrics ***) module Metrics : sig val prop_size : 'a t -> int + val prop_chain_size : 'a t -> int end = struct let ptsto_weight = 1 + and lseg_weight = 3 + and pi_weight = 1 let rec hpara_size hpara = sigma_size hpara.Sil.body and hpara_dll_size hpara_dll = sigma_size hpara_dll.Sil.body_dll - and hpred_size (hpred : Sil.hpred) = match hpred with - | Hpointsto _ -> - ptsto_weight - | Hlseg (_, hpara, _, _, _) -> - lseg_weight * hpara_size hpara - | Hdllseg (_, hpara_dll, _, _, _, _, _) -> - lseg_weight * hpara_dll_size hpara_dll + and hpred_size (hpred: Sil.hpred) = + match hpred with + | Hpointsto _ + -> ptsto_weight + | Hlseg (_, hpara, _, _, _) + -> lseg_weight * hpara_size hpara + | Hdllseg (_, hpara_dll, _, _, _, _, _) + -> lseg_weight * hpara_dll_size hpara_dll and sigma_size sigma = let size = ref 0 in - List.iter ~f:(fun hpred -> size := hpred_size hpred + !size) sigma; !size + List.iter ~f:(fun hpred -> size := hpred_size hpred + !size) sigma ; + !size let pi_size pi = pi_weight * List.length pi @@ -2521,75 +2493,76 @@ end = struct let fp_size = pi_size p.pi_fp + sigma_size p.sigma_fp in pi_size p.pi + sigma_size p.sigma + fp_size end + (*** END of module Metrics ***) module CategorizePreconditions = struct type pre_category = (* no preconditions *) | NoPres - (* the preconditions impose no restrictions *) | Empty (* the preconditions only demand that some pointers are allocated *) | OnlyAllocation - (* the preconditions impose constraints on the values of variables and/or memory *) | DataConstraints (** categorize a list of preconditions *) let categorize preconditions = - let lhs_is_lvar : Exp.t -> bool = function - | Lvar _ -> true - | _ -> false in - let lhs_is_var_lvar : Exp.t -> bool = function - | Var _ -> true - | Lvar _ -> true - | _ -> false in - let rhs_is_var : Sil.strexp -> bool = function - | Eexp (Var _, _) -> true - | _ -> false in + let lhs_is_lvar : Exp.t -> bool = function Lvar _ -> true | _ -> false in + let lhs_is_var_lvar : Exp.t -> bool = function Var _ -> true | Lvar _ -> true | _ -> false in + let rhs_is_var : Sil.strexp -> bool = function Eexp (Var _, _) -> true | _ -> false in let rec rhs_only_vars : Sil.strexp -> bool = function - | Eexp (Var _, _) -> - true - | Estruct (fsel, _) -> - List.for_all ~f:(fun (_, se) -> rhs_only_vars se) fsel - | Earray _ -> - true - | _ -> - false in - let hpred_is_var : Sil.hpred -> bool = function (* stack variable with no constraints *) - | Hpointsto (e, se, _) -> - lhs_is_lvar e && rhs_is_var se - | _ -> - false in - let hpred_only_allocation : Sil.hpred -> bool = function (* only constraint is allocation *) - | Hpointsto (e, se, _) -> - lhs_is_var_lvar e && rhs_only_vars se - | _ -> - false in + | Eexp (Var _, _) + -> true + | Estruct (fsel, _) + -> List.for_all ~f:(fun (_, se) -> rhs_only_vars se) fsel + | Earray _ + -> true + | _ + -> false + in + let hpred_is_var : Sil.hpred -> bool = function + (* stack variable with no constraints *) + | Hpointsto (e, se, _) + -> lhs_is_lvar e && rhs_is_var se + | _ + -> false + in + let hpred_only_allocation : Sil.hpred -> bool = function + (* only constraint is allocation *) + | Hpointsto (e, se, _) + -> lhs_is_var_lvar e && rhs_only_vars se + | _ + -> false + in let check_pre hpred_filter pre = - let check_pi pi = - List.is_empty pi in - let check_sigma sigma = - List.for_all ~f:hpred_filter sigma in - check_pi pre.pi && check_sigma pre.sigma in + let check_pi pi = List.is_empty pi in + let check_sigma sigma = List.for_all ~f:hpred_filter sigma in + check_pi pre.pi && check_sigma pre.sigma + in let pres_no_constraints = List.filter ~f:(check_pre hpred_is_var) preconditions in let pres_only_allocation = List.filter ~f:(check_pre hpred_only_allocation) preconditions in - match preconditions, pres_no_constraints, pres_only_allocation with - | [], _, _ -> - NoPres - | _:: _, _:: _, _ -> - Empty - | _:: _, [], _:: _ -> - OnlyAllocation - | _:: _, [], [] -> - DataConstraints + match (preconditions, pres_no_constraints, pres_only_allocation) with + | [], _, _ + -> NoPres + | _ :: _, _ :: _, _ + -> Empty + | _ :: _, [], _ :: _ + -> OnlyAllocation + | _ :: _, [], [] + -> DataConstraints end (* Export for interface *) let exp_normalize_noabs = Normalize.exp_normalize_noabs + let mk_inequality = Normalize.mk_inequality + let mk_ptsto_exp = Normalize.mk_ptsto_exp + let mk_ptsto = Normalize.mk_ptsto + let normalize = Normalize.normalize + let prop_atom_and = Normalize.prop_atom_and diff --git a/infer/src/backend/prop.mli b/infer/src/backend/prop.mli index a48fe902c..f6c238612 100644 --- a/infer/src/backend/prop.mli +++ b/infer/src/backend/prop.mli @@ -14,167 +14,166 @@ open! IStd open Sil -type normal (** kind for normal props, i.e. normalized *) +(** kind for normal props, i.e. normalized *) +type normal -type exposed (** kind for exposed props *) +(** kind for exposed props *) +type exposed (** Proposition. *) type pi = Sil.atom list + type sigma = Sil.hpred list (** the kind 'a should range over [normal] and [exposed] *) type 'a t = private - { - sigma: sigma; (** spatial part *) - sub: Sil.exp_subst; (** substitution *) - pi: pi; (** pure part *) - sigma_fp : sigma; (** abduced spatial part *) - pi_fp: pi; (** abduced pure part *) - } [@@deriving compare] + { sigma: sigma (** spatial part *) + ; sub: Sil.exp_subst (** substitution *) + ; pi: pi (** pure part *) + ; sigma_fp: sigma (** abduced spatial part *) + ; pi_fp: pi (** abduced pure part *) } + [@@deriving compare] (** type to describe different strategies for initializing fields of a structure. [No_init] does not initialize any fields of the struct. [Fld_init] initializes the fields of the struct with fresh variables (C) or default values (Java). *) -type struct_init_mode = - | No_init - | Fld_init - +type struct_init_mode = No_init | Fld_init (** {2 Basic Functions for propositions} *) -(** Compare propositions *) val compare_prop : 'a t -> 'a t -> int +(** Compare propositions *) -(** Check the equality of two sigma's *) val equal_sigma : sigma -> sigma -> bool +(** Check the equality of two sigma's *) -(** Check the equality of two propositions *) val equal_prop : 'a t -> 'a t -> bool +(** Check the equality of two propositions *) -(** Pretty print a substitution. *) val pp_sub : Pp.env -> Format.formatter -> subst -> unit +(** Pretty print a substitution. *) -(** Dump a substitution. *) val d_sub : subst -> unit +(** Dump a substitution. *) -(** Pretty print a pi. *) val pp_pi : Pp.env -> Format.formatter -> pi -> unit +(** Pretty print a pi. *) -(** Dump a pi. *) val d_pi : pi -> unit +(** Dump a pi. *) -(** Pretty print a sigma. *) val pp_sigma : Pp.env -> Format.formatter -> sigma -> unit +(** Pretty print a sigma. *) -(** Dump a sigma. *) val d_sigma : sigma -> unit +(** Dump a sigma. *) +val d_pi_sigma : pi -> sigma -> unit (** Dump a pi and a sigma *) -val d_pi_sigma: pi -> sigma -> unit +val sigma_get_stack_nonstack : bool -> sigma -> sigma * sigma (** Split sigma into stack and nonstack parts. The boolean indicates whether the stack should only include local variales. *) -val sigma_get_stack_nonstack : bool -> sigma -> sigma * sigma -(** Update the object substitution given the stack variables in the prop *) val prop_update_obj_sub : Pp.env -> 'a t -> Pp.env +(** Update the object substitution given the stack variables in the prop *) -(** Pretty print a proposition. *) val pp_prop : Pp.env -> Format.formatter -> 'a t -> unit +(** Pretty print a proposition. *) -(** Pretty print a proposition with type information *) val pp_prop_with_typ : Pp.env -> Format.formatter -> normal t -> unit +(** Pretty print a proposition with type information *) -(** Create a predicate environment for a prop *) val prop_pred_env : 'a t -> Sil.Predicates.env +(** Create a predicate environment for a prop *) -(** Dump a proposition. *) val d_prop : 'a t -> unit +(** Dump a proposition. *) -(** Dump a proposition with type information *) val d_prop_with_typ : 'a t -> unit +(** Dump a proposition with type information *) -(** Pretty print a list propositions with type information *) val pp_proplist_with_typ : Pp.env -> Format.formatter -> normal t list -> unit +(** Pretty print a list propositions with type information *) val d_proplist_with_typ : 'a t list -> unit -(** Compute free non-program variables of pi *) val pi_fav : atom list -> fav +(** Compute free non-program variables of pi *) val pi_fav_add : fav -> atom list -> unit -(** Compute free non-program variables of sigma *) val sigma_fav_add : fav -> hpred list -> unit +(** Compute free non-program variables of sigma *) val sigma_fav : hpred list -> fav +val sigma_fav_in_pvars_add : fav -> hpred list -> unit (** returns free non-program variables that are used to express the contents of stack variables *) -val sigma_fav_in_pvars_add : fav -> hpred list -> unit -(** Compute free non-program variables of prop *) val prop_fav_add : fav -> 'a t -> unit +(** Compute free non-program variables of prop *) -(** Compute free non-program variables of prop, visited in depth first order *) val prop_fav_add_dfs : Tenv.t -> fav -> 'a t -> unit +(** Compute free non-program variables of prop, visited in depth first order *) -val prop_fav: normal t -> fav +val prop_fav : normal t -> fav -(** free vars, except pi and sub, of current and footprint parts *) val prop_fav_nonpure : normal t -> fav +(** free vars, except pi and sub, of current and footprint parts *) -(** Find fav of the footprint part of the prop *) val prop_footprint_fav : 'a t -> fav +(** Find fav of the footprint part of the prop *) -(** Apply substitution for pi *) val pi_sub : subst -> atom list -> atom list +(** Apply substitution for pi *) -(** Apply subsitution for sigma *) val sigma_sub : subst -> hpred list -> hpred list +(** Apply subsitution for sigma *) -(** Apply subsitution to prop. Result is not normalized. *) val prop_sub : subst -> 'a t -> exposed t +(** Apply subsitution to prop. Result is not normalized. *) -(** Apply the substitution to all the expressions in the prop. *) val prop_expmap : (Exp.t -> Exp.t) -> 'a t -> exposed t +(** Apply the substitution to all the expressions in the prop. *) +val sigma_replace_exp : Tenv.t -> (Exp.t * Exp.t) list -> hpred list -> hpred list (** Relaces all expressions in the [hpred list] using the first argument. Assume that the first parameter defines a partial function. No expressions inside hpara are replaced. *) -val sigma_replace_exp : Tenv.t -> (Exp.t * Exp.t) list -> hpred list -> hpred list (** {2 Normalization} *) -(** Turn an inequality expression into an atom *) val mk_inequality : Tenv.t -> Exp.t -> Sil.atom +(** Turn an inequality expression into an atom *) -(** Return [true] if the atom is an inequality *) val atom_is_inequality : Sil.atom -> bool +(** Return [true] if the atom is an inequality *) -(** If the atom is [e<=n] return [e,n] *) val atom_exp_le_const : Sil.atom -> (Exp.t * IntLit.t) option +(** If the atom is [e<=n] return [e,n] *) -(** If the atom is [n (IntLit.t * Exp.t) option +(** If the atom is [n 'a t -> Exp.t -> Exp.t (** Normalize [exp] using the pure part of [prop]. Later, we should change this such that the normalization exposes offsets of [exp] as much as possible. *) -val exp_normalize_prop : Tenv.t -> 'a t -> Exp.t -> Exp.t -(** Normalize the expression without abstracting complex subexpressions *) val exp_normalize_noabs : Tenv.t -> Sil.subst -> Exp.t -> Exp.t +(** Normalize the expression without abstracting complex subexpressions *) +val exp_collapse_consecutive_indices_prop : Typ.t -> Exp.t -> Exp.t (** Collapse consecutive indices that should be added. For instance, this function reduces x[1][1] to x[2]. The [typ] argument is used to ensure the soundness of this collapsing. *) -val exp_collapse_consecutive_indices_prop : Typ.t -> Exp.t -> Exp.t +val lexp_normalize_prop : Tenv.t -> 'a t -> Exp.t -> Exp.t (** Normalize [exp] used for the address of a heap cell. This normalization does not combine two offsets inside [exp]. *) -val lexp_normalize_prop : Tenv.t -> 'a t -> Exp.t -> Exp.t val atom_normalize_prop : Tenv.t -> 'a t -> atom -> atom @@ -186,228 +185,228 @@ val sigma_normalize_prop : Tenv.t -> 'a t -> hpred list -> hpred list val pi_normalize_prop : Tenv.t -> 'a t -> atom list -> atom list -(** normalize a prop *) val normalize : Tenv.t -> exposed t -> normal t +(** normalize a prop *) -(** expose a prop, no-op used to instantiate the sub-type relation *) val expose : normal t -> exposed t +(** expose a prop, no-op used to instantiate the sub-type relation *) (** {2 Compaction} *) -(** Return a compact representation of the prop *) val prop_compact : sharing_env -> normal t -> normal t +(** Return a compact representation of the prop *) (** {2 Queries about propositions} *) -(** Check if the sigma part of the proposition is emp *) val prop_is_emp : 'a t -> bool +(** Check if the sigma part of the proposition is emp *) (** {2 Functions for changing and generating propositions} *) -(** Construct a disequality. *) val mk_neq : Tenv.t -> Exp.t -> Exp.t -> atom +(** Construct a disequality. *) -(** Construct an equality. *) val mk_eq : Tenv.t -> Exp.t -> Exp.t -> atom +(** Construct an equality. *) -(** Construct a positive pred. *) val mk_pred : Tenv.t -> PredSymb.t -> Exp.t list -> atom +(** Construct a positive pred. *) -(** Construct a negative pred. *) val mk_npred : Tenv.t -> PredSymb.t -> Exp.t list -> atom +(** Construct a negative pred. *) -(** create a strexp of the given type, populating the structures if [expand_structs] is true *) val create_strexp_of_type : Tenv.t -> struct_init_mode -> Typ.t -> Exp.t option -> Sil.inst -> Sil.strexp +(** create a strexp of the given type, populating the structures if [expand_structs] is true *) -(** Construct a pointsto. *) val mk_ptsto : Tenv.t -> Exp.t -> strexp -> Exp.t -> hpred +(** Construct a pointsto. *) +val mk_ptsto_exp : Tenv.t -> struct_init_mode -> Exp.t * Exp.t * Exp.t option -> Sil.inst -> hpred (** Construct a points-to predicate for an expression using either the provided expression [name] as base for fresh identifiers. *) -val mk_ptsto_exp : Tenv.t -> struct_init_mode -> Exp.t * Exp.t * Exp.t option -> Sil.inst -> hpred +val mk_ptsto_lvar : + Tenv.t -> struct_init_mode -> Sil.inst -> Pvar.t * Exp.t * Exp.t option -> hpred (** Construct a points-to predicate for a single program variable. If [expand_structs] is true, initialize the fields of structs with fresh variables. *) -val mk_ptsto_lvar : Tenv.t -> struct_init_mode -> Sil.inst -> Pvar.t * Exp.t * Exp.t option -> hpred -(** Construct a lseg predicate *) val mk_lseg : Tenv.t -> lseg_kind -> hpara -> Exp.t -> Exp.t -> Exp.t list -> hpred +(** Construct a lseg predicate *) +val mk_dllseg : + Tenv.t -> lseg_kind -> hpara_dll -> Exp.t -> Exp.t -> Exp.t -> Exp.t -> Exp.t list -> hpred (** Construct a dllseg predicate *) -val mk_dllseg : Tenv.t -> lseg_kind -> hpara_dll -> Exp.t -> Exp.t -> Exp.t -> Exp.t -> Exp.t list -> hpred -(** Construct a hpara *) val mk_hpara : Tenv.t -> Ident.t -> Ident.t -> Ident.t list -> Ident.t list -> hpred list -> hpara +(** Construct a hpara *) -(** Construct a dll_hpara *) val mk_dll_hpara : - Tenv.t -> Ident.t -> Ident.t -> Ident.t -> Ident.t list -> Ident.t list -> hpred list -> hpara_dll + Tenv.t -> Ident.t -> Ident.t -> Ident.t -> Ident.t list -> Ident.t list -> hpred list + -> hpara_dll +(** Construct a dll_hpara *) -(** Proposition [true /\ emp]. *) val prop_emp : normal t +(** Proposition [true /\ emp]. *) -(** Reset every inst in the prop using the given map *) val prop_reset_inst : (Sil.inst -> Sil.inst) -> 'a t -> exposed t +(** Reset every inst in the prop using the given map *) -(** Conjoin a heap predicate by separating conjunction. *) val prop_hpred_star : 'a t -> hpred -> exposed t +(** Conjoin a heap predicate by separating conjunction. *) -(** Conjoin a list of heap predicates by separating conjunction *) val prop_sigma_star : 'a t -> hpred list -> exposed t +(** Conjoin a list of heap predicates by separating conjunction *) +val prop_atom_and : Tenv.t -> ?footprint:bool -> normal t -> atom -> normal t (** Conjoin a pure atomic predicate by normal conjunction. *) -val prop_atom_and : Tenv.t -> ?footprint: bool -> normal t -> atom -> normal t +val conjoin_eq : Tenv.t -> ?footprint:bool -> Exp.t -> Exp.t -> normal t -> normal t (** Conjoin [exp1]=[exp2] with a symbolic heap [prop]. *) -val conjoin_eq : Tenv.t -> ?footprint: bool -> Exp.t -> Exp.t -> normal t -> normal t +val conjoin_neq : Tenv.t -> ?footprint:bool -> Exp.t -> Exp.t -> normal t -> normal t (** Conjoin [exp1]!=[exp2] with a symbolic heap [prop]. *) -val conjoin_neq : Tenv.t -> ?footprint: bool -> Exp.t -> Exp.t -> normal t -> normal t -(** Return the pure part of [prop]. *) val get_pure : 'a t -> atom list +(** Return the pure part of [prop]. *) -(** Canonicalize the names of primed variables. *) val prop_rename_primed_footprint_vars : Tenv.t -> normal t -> normal t +(** Canonicalize the names of primed variables. *) -(** Extract the footprint and return it as a prop *) val extract_footprint : 'a t -> exposed t +(** Extract the footprint and return it as a prop *) +val extract_spec : normal t -> normal t * normal t (** Extract the (footprint,current) pair *) -val extract_spec : normal t -> (normal t * normal t) -(** [prop_set_fooprint p p_foot] sets proposition [p_foot] as footprint of [p]. *) val prop_set_footprint : 'a t -> 'b t -> exposed t +(** [prop_set_fooprint p p_foot] sets proposition [p_foot] as footprint of [p]. *) -(** Expand PE listsegs if the flag is on. *) val prop_expand : Tenv.t -> normal t -> normal t list +(** Expand PE listsegs if the flag is on. *) (** {2 Functions for existentially quantifying and unquantifying variables} *) -(** Existentially quantify the [ids] in [prop]. *) val exist_quantify : Tenv.t -> fav -> normal t -> normal t +(** Existentially quantify the [ids] in [prop]. *) -(** convert the footprint vars to primed vars. *) val prop_normal_vars_to_primed_vars : Tenv.t -> normal t -> normal t +(** convert the footprint vars to primed vars. *) -(** convert the primed vars to normal vars. *) val prop_primed_vars_to_normal_vars : Tenv.t -> normal t -> normal t +(** convert the primed vars to normal vars. *) -(** Build an exposed prop from pi *) val from_pi : pi -> exposed t +(** Build an exposed prop from pi *) -(** Build an exposed prop from sigma *) val from_sigma : sigma -> exposed t +(** Build an exposed prop from sigma *) +val set : + ?sub:Sil.exp_subst -> ?pi:pi -> ?sigma:sigma -> ?pi_fp:pi -> ?sigma_fp:sigma -> 'a t -> exposed t (** Set individual fields of the prop. *) -val set : ?sub:Sil.exp_subst -> ?pi:pi -> ?sigma:sigma -> ?pi_fp:pi -> ?sigma_fp:sigma -> - 'a t -> exposed t -(** Rename free variables in a prop replacing them with existentially quantified vars *) val prop_rename_fav_with_existentials : Tenv.t -> normal t -> normal t +(** Rename free variables in a prop replacing them with existentially quantified vars *) +val remove_seed_captured_vars_block : Tenv.t -> Mangled.t list -> normal t -> normal t (** Removes seeds variables from a prop corresponding to captured variables in an objc block *) -val remove_seed_captured_vars_block: Tenv.t -> Mangled.t list -> normal t -> normal t (** {2 Prop iterators} *) (** Iterator over the sigma part. Each iterator has a current [hpred]. *) type 'a prop_iter -(** Create an iterator, return None if sigma part is empty. *) val prop_iter_create : normal t -> unit prop_iter option +(** Create an iterator, return None if sigma part is empty. *) -(** Return the prop associated to the iterator. *) val prop_iter_to_prop : Tenv.t -> 'a prop_iter -> normal t +(** Return the prop associated to the iterator. *) +val prop_iter_add_atom : bool -> 'a prop_iter -> atom -> 'a prop_iter (** Add an atom to the pi part of prop iter. The first parameter records whether it is done during footprint or during re - execution. *) -val prop_iter_add_atom : bool -> 'a prop_iter -> atom -> 'a prop_iter +val prop_iter_remove_curr_then_to_prop : Tenv.t -> 'a prop_iter -> normal t (** Remove the current element from the iterator, and return the prop associated to the resulting iterator. *) -val prop_iter_remove_curr_then_to_prop : Tenv.t -> 'a prop_iter -> normal t +val prop_iter_current : Tenv.t -> 'a prop_iter -> hpred * 'a (** Return the current hpred and state. *) -val prop_iter_current : Tenv.t -> 'a prop_iter -> (hpred * 'a) -(** Return the next iterator. *) val prop_iter_next : 'a prop_iter -> unit prop_iter option +(** Return the next iterator. *) -(** Remove the current hpred and return the next iterator. *) val prop_iter_remove_curr_then_next : 'a prop_iter -> unit prop_iter option +(** Remove the current hpred and return the next iterator. *) -(** Update the current element of the iterator. *) val prop_iter_update_current : 'a prop_iter -> hpred -> 'a prop_iter +(** Update the current element of the iterator. *) -(** Insert before the current element of the iterator. *) val prop_iter_prev_then_insert : 'a prop_iter -> hpred -> 'a prop_iter +(** Insert before the current element of the iterator. *) -(** Find fav of the footprint part of the iterator *) val prop_iter_footprint_fav : 'a prop_iter -> fav +(** Find fav of the footprint part of the iterator *) -(** Find fav of the iterator *) val prop_iter_fav : 'a prop_iter -> fav +(** Find fav of the iterator *) -(** Extract the sigma part of the footprint *) val prop_iter_get_footprint_sigma : 'a prop_iter -> hpred list +(** Extract the sigma part of the footprint *) -(** Replace the sigma part of the footprint *) val prop_iter_replace_footprint_sigma : 'a prop_iter -> hpred list -> 'a prop_iter +(** Replace the sigma part of the footprint *) -(** Scan sigma to find an [hpred] satisfying the filter function. *) val prop_iter_find : unit prop_iter -> (hpred -> 'a option) -> 'a prop_iter option +(** Scan sigma to find an [hpred] satisfying the filter function. *) -(** Update the current element of the iterator by a nonempty list of elements. *) val prop_iter_update_current_by_list : 'a prop_iter -> hpred list -> unit prop_iter +(** Update the current element of the iterator by a nonempty list of elements. *) -(** Set the state of an iterator *) val prop_iter_set_state : 'a prop_iter -> 'b -> 'b prop_iter +(** Set the state of an iterator *) -(** Rename [ident] in [iter] by a fresh primed identifier *) val prop_iter_make_id_primed : Tenv.t -> Ident.t -> 'a prop_iter -> 'a prop_iter +(** Rename [ident] in [iter] by a fresh primed identifier *) -(** Collect garbage fields. *) val prop_iter_gc_fields : unit prop_iter -> unit prop_iter +(** Collect garbage fields. *) -(** return the set of subexpressions of [strexp] *) val strexp_get_exps : Sil.strexp -> Exp.Set.t +(** return the set of subexpressions of [strexp] *) -(** get the set of expressions on the righthand side of [hpred] *) val hpred_get_targets : Sil.hpred -> Exp.Set.t +(** get the set of expressions on the righthand side of [hpred] *) +val compute_reachable_hpreds : hpred list -> Exp.Set.t -> Sil.HpredSet.t * Exp.Set.t (** return the set of hpred's and exp's in [sigma] that are reachable from an expression in [exps] *) -val compute_reachable_hpreds : hpred list -> Exp.Set.t -> Sil.HpredSet.t * Exp.Set.t (** {2 Internal modules} *) module Metrics : sig - (** Compute a size value for the prop, which indicates its complexity *) val prop_size : 'a t -> int + (** Compute a size value for the prop, which indicates its complexity *) + val prop_chain_size : 'a t -> int (** Approximate the size of the longest chain by counting the max number of |-> with the same type and whose lhs is primed or footprint *) - val prop_chain_size : 'a t -> int end module CategorizePreconditions : sig type pre_category = (* no preconditions *) | NoPres - (* the preconditions impose no restrictions *) | Empty - (* the preconditions only demand that some pointers are allocated *) | OnlyAllocation - (* the preconditions impose constraints on the values of variables and/or memory *) | DataConstraints - (** categorize a list of preconditions *) val categorize : 'a t list -> pre_category + (** categorize a list of preconditions *) end diff --git a/infer/src/backend/propgraph.ml b/infer/src/backend/propgraph.ml index e94079a2a..cb472af7d 100644 --- a/infer/src/backend/propgraph.ml +++ b/infer/src/backend/propgraph.ml @@ -27,87 +27,97 @@ let from_prop p = p (** Return [true] if root node *) let rec is_root = function - | Exp.Var id -> Ident.is_normal id - | Exp.Exn _ | Exp.Closure _ | Exp.Const _ | Exp.Lvar _ -> true - | Exp.Cast (_, e) -> is_root e - | Exp.UnOp _ | Exp.BinOp _ | Exp.Lfield _ | Exp.Lindex _ | Exp.Sizeof _ -> false + | Exp.Var id + -> Ident.is_normal id + | Exp.Exn _ | Exp.Closure _ | Exp.Const _ | Exp.Lvar _ + -> true + | Exp.Cast (_, e) + -> is_root e + | Exp.UnOp _ | Exp.BinOp _ | Exp.Lfield _ | Exp.Lindex _ | Exp.Sizeof _ + -> false (** Return [true] if the nodes are connected. Used to compute reachability. *) -let nodes_connected n1 n2 = - Exp.equal n1 n2 (* Implemented as equality for now, later it might contain offset by a constant *) +let nodes_connected n1 n2 = Exp.equal n1 n2 + +(* Implemented as equality for now, later it might contain offset by a constant *) (** Return [true] if the edge is an hpred, and [false] if it is an atom *) -let edge_is_hpred = function - | Ehpred _ -> true - | Eatom _ -> false - | Esub_entry _ -> false +let edge_is_hpred = function Ehpred _ -> true | Eatom _ -> false | Esub_entry _ -> false (** Return the source of the edge *) let edge_get_source = function - | Ehpred (Sil.Hpointsto(e, _, _)) -> Some e - | Ehpred (Sil.Hlseg(_, _, e, _, _)) -> Some e - | Ehpred (Sil.Hdllseg(_, _, e1, _, _, _, _)) -> Some e1 (* only one direction supported for now *) - | Eatom (Sil.Aeq (e1, _)) -> Some e1 - | Eatom (Sil.Aneq (e1, _)) -> Some e1 - | Eatom (Sil.Apred (_, e :: _) | Anpred (_, e :: _)) -> Some e - | Eatom (Sil.Apred (_, []) | Anpred (_, [])) -> None - | Esub_entry (x, _) -> Some (Exp.Var x) + | Ehpred Sil.Hpointsto (e, _, _) + -> Some e + | Ehpred Sil.Hlseg (_, _, e, _, _) + -> Some e + | Ehpred Sil.Hdllseg (_, _, e1, _, _, _, _) + -> Some e1 (* only one direction supported for now *) + | Eatom Sil.Aeq (e1, _) + -> Some e1 + | Eatom Sil.Aneq (e1, _) + -> Some e1 + | Eatom (Sil.Apred (_, e :: _) | Anpred (_, e :: _)) + -> Some e + | Eatom (Sil.Apred (_, []) | Anpred (_, [])) + -> None + | Esub_entry (x, _) + -> Some (Exp.Var x) (** Return the successor nodes of the edge *) let edge_get_succs = function - | Ehpred hpred -> Exp.Set.elements (Prop.hpred_get_targets hpred) - | Eatom (Sil.Aeq (_, e2)) -> [e2] - | Eatom (Sil.Aneq (_, e2)) -> [e2] - | Eatom (Sil.Apred _ | Anpred _) -> [] - | Esub_entry (_, e) -> [e] - -let get_sigma footprint_part g = - if footprint_part then g.Prop.sigma_fp else g.Prop.sigma -let get_pi footprint_part g = - if footprint_part then g.Prop.pi_fp else g.Prop.pi -let get_subl footprint_part g = - if footprint_part then [] else Sil.sub_to_list g.Prop.sub + | Ehpred hpred + -> Exp.Set.elements (Prop.hpred_get_targets hpred) + | Eatom Sil.Aeq (_, e2) + -> [e2] + | Eatom Sil.Aneq (_, e2) + -> [e2] + | Eatom (Sil.Apred _ | Anpred _) + -> [] + | Esub_entry (_, e) + -> [e] + +let get_sigma footprint_part g = if footprint_part then g.Prop.sigma_fp else g.Prop.sigma + +let get_pi footprint_part g = if footprint_part then g.Prop.pi_fp else g.Prop.pi + +let get_subl footprint_part g = if footprint_part then [] else Sil.sub_to_list g.Prop.sub (** [edge_from_source g n footprint_part is_hpred] finds and edge with the given source [n] in prop [g]. [footprint_part] indicates whether to search the edge in the footprint part, and [is_pred] whether it is an hpred edge. *) let edge_from_source g n footprint_part is_hpred = let edges = - if is_hpred - then - List.map ~f:(fun hpred -> Ehpred hpred ) (get_sigma footprint_part g) - else - List.map - ~f:(fun a -> Eatom a) (get_pi footprint_part g) @ - List.map ~f:(fun entry -> Esub_entry entry) (get_subl footprint_part g) in + if is_hpred then List.map ~f:(fun hpred -> Ehpred hpred) (get_sigma footprint_part g) + else List.map ~f:(fun a -> Eatom a) (get_pi footprint_part g) + @ List.map ~f:(fun entry -> Esub_entry entry) (get_subl footprint_part g) + in let starts_from hpred = - match edge_get_source hpred with - | Some e -> Exp.equal n e - | None -> false in - match List.filter ~f:starts_from edges with - | [] -> None - | edge:: _ -> Some edge + match edge_get_source hpred with Some e -> Exp.equal n e | None -> false + in + match List.filter ~f:starts_from edges with [] -> None | edge :: _ -> Some edge (** [get_succs g n footprint_part is_hpred] returns the successor nodes of [n] in [g]. [footprint_part] indicates whether to search the successors in the footprint part, and [is_pred] whether to follow hpred edges. *) let get_succs g n footprint_part is_hpred = - match edge_from_source g n footprint_part is_hpred with - | None -> [] - | Some e -> edge_get_succs e + match edge_from_source g n footprint_part is_hpred with None -> [] | Some e -> edge_get_succs e (** [get_edges footprint_part g] returns the list of edges in [g], in the footprint part if [fotprint_part] is true *) let get_edges footprint_part g = let hpreds = get_sigma footprint_part g in let atoms = get_pi footprint_part g in let subst_entries = get_subl footprint_part g in - List.map ~f:(fun hpred -> Ehpred hpred) hpreds @ - List.map ~f:(fun a -> Eatom a) atoms @ - List.map ~f:(fun entry -> Esub_entry entry) subst_entries + List.map ~f:(fun hpred -> Ehpred hpred) hpreds @ List.map ~f:(fun a -> Eatom a) atoms + @ List.map ~f:(fun entry -> Esub_entry entry) subst_entries -let edge_equal e1 e2 = match e1, e2 with - | Ehpred hp1, Ehpred hp2 -> Sil.equal_hpred hp1 hp2 - | Eatom a1, Eatom a2 -> Sil.equal_atom a1 a2 - | Esub_entry (x1, e1), Esub_entry (x2, e2) -> Ident.equal x1 x2 && Exp.equal e1 e2 - | _ -> false +let edge_equal e1 e2 = + match (e1, e2) with + | Ehpred hp1, Ehpred hp2 + -> Sil.equal_hpred hp1 hp2 + | Eatom a1, Eatom a2 + -> Sil.equal_atom a1 a2 + | Esub_entry (x1, e1), Esub_entry (x2, e2) + -> Ident.equal x1 x2 && Exp.equal e1 e2 + | _ + -> false (** [contains_edge footprint_part g e] returns true if the graph [g] contains edge [e], searching the footprint part if [footprint_part] is true. *) @@ -116,65 +126,80 @@ let contains_edge (footprint_part: bool) (g: t) (e: edge) = (** [iter_edges footprint_part f g] iterates function [f] on the edges in [g] in the same order as returned by [get_edges]; if [footprint_part] is true the edges are taken from the footprint part. *) -let iter_edges footprint_part f g = - List.iter ~f (get_edges footprint_part g) +let iter_edges footprint_part f g = List.iter ~f (get_edges footprint_part g) (** Graph annotated with the differences w.r.t. a previous graph *) type diff = - { diff_newgraph : t; (** the new graph *) - diff_changed_norm : Obj.t list; (** objects changed in the normal part *) - diff_cmap_norm : Pp.colormap; (** colormap for the normal part *) - diff_changed_foot : Obj.t list; (** objects changed in the footprint part *) - diff_cmap_foot : Pp.colormap (** colormap for the footprint part *) } + { diff_newgraph: t (** the new graph *) + ; diff_changed_norm: Obj.t list (** objects changed in the normal part *) + ; diff_cmap_norm: Pp.colormap (** colormap for the normal part *) + ; diff_changed_foot: Obj.t list (** objects changed in the footprint part *) + ; diff_cmap_foot: Pp.colormap (** colormap for the footprint part *) } (** Compute the subobjects in [e2] which are different from those in [e1] *) let compute_exp_diff (e1: Exp.t) (e2: Exp.t) : Obj.t list = if Exp.equal e1 e2 then [] else [Obj.repr e2] - (** Compute the subobjects in [se2] which are different from those in [se1] *) -let rec compute_sexp_diff (se1: Sil.strexp) (se2: Sil.strexp) : Obj.t list = match se1, se2 with - | Sil.Eexp (e1, _), Sil.Eexp (e2, _) -> if Exp.equal e1 e2 then [] else [Obj.repr se2] - | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) -> - compute_fsel_diff fsel1 fsel2 - | Sil.Earray (e1, esel1, _), Sil.Earray (e2, esel2, _) -> - compute_exp_diff e1 e2 @ compute_esel_diff esel1 esel2 - | _ -> [Obj.repr se2] - -and compute_fsel_diff fsel1 fsel2 : Obj.t list = match fsel1, fsel2 with - | ((f1, se1):: fsel1'), (((f2, se2) as x):: fsel2') -> - (match Typ.Fieldname.compare f1 f2 with - | n when n < 0 -> compute_fsel_diff fsel1' fsel2 - | 0 -> compute_sexp_diff se1 se2 @ compute_fsel_diff fsel1' fsel2' - | _ -> (Obj.repr x) :: compute_fsel_diff fsel1 fsel2') - | _, [] -> [] - | [], x:: fsel2' -> - (Obj.repr x) :: compute_fsel_diff [] fsel2' - -and compute_esel_diff esel1 esel2 : Obj.t list = match esel1, esel2 with - | ((e1, se1):: esel1'), (((e2, se2) as x):: esel2') -> - (match Exp.compare e1 e2 with - | n when n < 0 -> compute_esel_diff esel1' esel2 - | 0 -> compute_sexp_diff se1 se2 @ compute_esel_diff esel1' esel2' - | _ -> (Obj.repr x) :: compute_esel_diff esel1 esel2') - | _, [] -> [] - | [], x:: esel2' -> - (Obj.repr x) :: compute_esel_diff [] esel2' +let rec compute_sexp_diff (se1: Sil.strexp) (se2: Sil.strexp) : Obj.t list = + match (se1, se2) with + | Sil.Eexp (e1, _), Sil.Eexp (e2, _) + -> if Exp.equal e1 e2 then [] else [Obj.repr se2] + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) + -> compute_fsel_diff fsel1 fsel2 + | Sil.Earray (e1, esel1, _), Sil.Earray (e2, esel2, _) + -> compute_exp_diff e1 e2 @ compute_esel_diff esel1 esel2 + | _ + -> [Obj.repr se2] + +and compute_fsel_diff fsel1 fsel2 : Obj.t list = + match (fsel1, fsel2) with + | (f1, se1) :: fsel1', (f2, se2 as x) :: fsel2' -> ( + match Typ.Fieldname.compare f1 f2 with + | n when n < 0 + -> compute_fsel_diff fsel1' fsel2 + | 0 + -> compute_sexp_diff se1 se2 @ compute_fsel_diff fsel1' fsel2' + | _ + -> Obj.repr x :: compute_fsel_diff fsel1 fsel2' ) + | _, [] + -> [] + | [], x :: fsel2' + -> Obj.repr x :: compute_fsel_diff [] fsel2' + +and compute_esel_diff esel1 esel2 : Obj.t list = + match (esel1, esel2) with + | (e1, se1) :: esel1', (e2, se2 as x) :: esel2' -> ( + match Exp.compare e1 e2 with + | n when n < 0 + -> compute_esel_diff esel1' esel2 + | 0 + -> compute_sexp_diff se1 se2 @ compute_esel_diff esel1' esel2' + | _ + -> Obj.repr x :: compute_esel_diff esel1 esel2' ) + | _, [] + -> [] + | [], x :: esel2' + -> Obj.repr x :: compute_esel_diff [] esel2' (** Compute the subobjects in [newedge] which are different from those in [oldedge] *) -let compute_edge_diff (oldedge: edge) (newedge: edge) : Obj.t list = match oldedge, newedge with - | Ehpred (Sil.Hpointsto(_, se1, e1)), Ehpred (Sil.Hpointsto(_, se2, e2)) -> - compute_sexp_diff se1 se2 @ compute_exp_diff e1 e2 - | Eatom (Sil.Aeq (_, e1)), Eatom (Sil.Aeq (_, e2)) -> - compute_exp_diff e1 e2 - | Eatom (Sil.Aneq (_, e1)), Eatom (Sil.Aneq (_, e2)) -> - compute_exp_diff e1 e2 - | Eatom (Sil.Apred (_, es1)), Eatom (Sil.Apred (_, es2)) - | Eatom (Sil.Anpred (_, es1)), Eatom (Sil.Anpred (_, es2)) -> - List.concat (try List.map2_exn ~f:compute_exp_diff es1 es2 with Invalid_argument _ -> []) - | Esub_entry (_, e1), Esub_entry (_, e2) -> - compute_exp_diff e1 e2 - | _ -> [Obj.repr newedge] +let compute_edge_diff (oldedge: edge) (newedge: edge) : Obj.t list = + match (oldedge, newedge) with + | Ehpred Sil.Hpointsto (_, se1, e1), Ehpred Sil.Hpointsto (_, se2, e2) + -> compute_sexp_diff se1 se2 @ compute_exp_diff e1 e2 + | Eatom Sil.Aeq (_, e1), Eatom Sil.Aeq (_, e2) + -> compute_exp_diff e1 e2 + | Eatom Sil.Aneq (_, e1), Eatom Sil.Aneq (_, e2) + -> compute_exp_diff e1 e2 + | Eatom Sil.Apred (_, es1), Eatom Sil.Apred (_, es2) + | Eatom Sil.Anpred (_, es1), Eatom Sil.Anpred (_, es2) + -> List.concat + ( try List.map2_exn ~f:compute_exp_diff es1 es2 + with Invalid_argument _ -> [] ) + | Esub_entry (_, e1), Esub_entry (_, e2) + -> compute_exp_diff e1 e2 + | _ + -> [Obj.repr newedge] (** [compute_diff oldgraph newgraph] returns the list of edges which are only in [newgraph] *) let compute_diff default_color oldgraph newgraph : diff = @@ -185,29 +210,36 @@ let compute_diff default_color oldgraph newgraph : diff = if not (contains_edge footprint_part oldgraph edge) then match edge_get_source edge with | Some source -> ( - match edge_from_source oldgraph source footprint_part (edge_is_hpred edge) with - | None -> - let changed_obj = match edge with - | Ehpred hpred -> Obj.repr hpred - | Eatom a -> Obj.repr a - | Esub_entry entry -> Obj.repr entry in - changed := changed_obj :: !changed - | Some oldedge -> changed := compute_edge_diff oldedge edge @ !changed - ) - | None -> - () in - List.iter ~f:build_changed newedges; + match edge_from_source oldgraph source footprint_part (edge_is_hpred edge) with + | None + -> let changed_obj = + match edge with + | Ehpred hpred + -> Obj.repr hpred + | Eatom a + -> Obj.repr a + | Esub_entry entry + -> Obj.repr entry + in + changed := changed_obj :: !changed + | Some oldedge + -> changed := compute_edge_diff oldedge edge @ !changed ) + | None + -> () + in + List.iter ~f:build_changed newedges ; let colormap (o: Obj.t) = - if List.exists ~f:(fun x -> phys_equal x o) !changed then Pp.Red - else default_color in - !changed, colormap in + if List.exists ~f:(fun x -> phys_equal x o) !changed then Pp.Red else default_color + in + (!changed, colormap) + in let changed_norm, colormap_norm = compute_changed false in let changed_foot, colormap_foot = compute_changed true in - { diff_newgraph = newgraph; - diff_changed_norm = changed_norm; - diff_cmap_norm = colormap_norm; - diff_changed_foot = changed_foot; - diff_cmap_foot = colormap_foot } + { diff_newgraph= newgraph + ; diff_changed_norm= changed_norm + ; diff_cmap_norm= colormap_norm + ; diff_changed_foot= changed_foot + ; diff_cmap_foot= colormap_foot } (** [diff_get_colormap footprint_part diff] returns the colormap of a computed diff, selecting the footprint colormap if [footprint_part] is true. *) @@ -221,32 +253,48 @@ let pp_proplist pe0 s (base_prop, extract_stack) f plist = let num = List.length plist in let base_stack = fst (Prop.sigma_get_stack_nonstack true base_prop.Prop.sigma) in let add_base_stack prop = - if extract_stack then Prop.set prop ~sigma:(base_stack @ prop.Prop.sigma) - else Prop.expose prop in + if extract_stack then Prop.set prop ~sigma:(base_stack @ prop.Prop.sigma) else Prop.expose prop + in let update_pe_diff (prop: Prop.normal Prop.t) : Pp.env = if Config.print_using_diff then let diff = compute_diff Blue (from_prop base_prop) (from_prop prop) in let cmap_norm = diff_get_colormap false diff in let cmap_foot = diff_get_colormap true diff in - { pe0 with cmap_norm; cmap_foot } - else pe0 in + {pe0 with cmap_norm; cmap_foot} + else pe0 + in let rec pp_seq_newline n f = function - | [] -> () - | [_x] -> + | [] + -> () + | [_x] + -> ( let pe = update_pe_diff _x in let x = add_base_stack _x in - (match pe.kind with - | TEXT -> F.fprintf f "%s %d of %d:@\n%a" s n num (Prop.pp_prop pe) x - | HTML -> F.fprintf f "%s %d of %d:@\n%a@\n" s n num (Prop.pp_prop pe) x - | LATEX -> F.fprintf f "@[%a@]@\n" (Prop.pp_prop pe) x) - | _x:: l -> - let pe = update_pe_diff _x in + match pe.kind with + | TEXT + -> F.fprintf f "%s %d of %d:@\n%a" s n num (Prop.pp_prop pe) x + | HTML + -> F.fprintf f "%s %d of %d:@\n%a@\n" s n num (Prop.pp_prop pe) x + | LATEX + -> F.fprintf f "@[%a@]@\n" (Prop.pp_prop pe) x ) + | _x :: l + -> let pe = update_pe_diff _x in let x = add_base_stack _x in - (match pe.kind with - | TEXT -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l - | HTML -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l - | LATEX -> F.fprintf f "@[%a@]\\\\@\n\\bigvee\\\\@\n%a" (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l) - in pp_seq_newline 1 f plist + match pe.kind with + | TEXT + -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x + (pp_seq_newline (n + 1)) + l + | HTML + -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x + (pp_seq_newline (n + 1)) + l + | LATEX + -> F.fprintf f "@[%a@]\\\\@\n\\bigvee\\\\@\n%a" (Prop.pp_prop pe) x + (pp_seq_newline (n + 1)) + l + in + pp_seq_newline 1 f plist (** dump a propset *) let d_proplist (p: 'a Prop.t) (pl: 'b Prop.t list) = diff --git a/infer/src/backend/propgraph.mli b/infer/src/backend/propgraph.mli index d10224473..9eb164e49 100644 --- a/infer/src/backend/propgraph.mli +++ b/infer/src/backend/propgraph.mli @@ -12,68 +12,71 @@ open! IStd (** Propositions seen as graphs *) -type t (** prop considered as a graph *) +(** prop considered as a graph *) +type t -type node (** node of the graph *) +(** node of the graph *) +type node -type edge (** multi-edge: one source and many destinations *) +(** multi-edge: one source and many destinations *) +type edge -(** create a graph from a prop *) val from_prop : Prop.normal Prop.t -> t +(** create a graph from a prop *) -(** Return [true] if root node *) val is_root : node -> bool +(** Return [true] if root node *) -(** Return [true] if the nodes are connected. Used to compute reachability. *) val nodes_connected : node -> node -> bool +(** Return [true] if the nodes are connected. Used to compute reachability. *) -(** Return the source of the edge *) val edge_get_source : edge -> node option +(** Return the source of the edge *) -(** Return the successor nodes of the edge *) val edge_get_succs : edge -> node list +(** Return the successor nodes of the edge *) +val edge_from_source : t -> node -> bool -> bool -> edge option (** [edge_from_source g n footprint_part is_hpred] finds and edge with the given source [n] in prop [g]. [footprint_part] indicates whether to search the edge in the footprint part, and [is_pred] whether it is an hpred edge. *) -val edge_from_source : t -> node -> bool -> bool -> edge option +val get_succs : t -> node -> bool -> bool -> node list (** [get_succs g n footprint_part is_hpred] returns the successor nodes of [n] in [g]. [footprint_part] indicates whether to search the successors in the footprint part, and [is_pred] whether to follow hpred edges. *) -val get_succs : t -> node -> bool -> bool -> node list +val get_edges : bool -> t -> edge list (** [get_edges footprint_part g] returns the list of edges in [g], in the footprint part if [fotprint_part] is true *) -val get_edges : bool -> t -> edge list +val contains_edge : bool -> t -> edge -> bool (** [contains_edge footprint_part g e] returns true if the graph [g] contains edge [e], searching the footprint part if [footprint_part] is true. *) -val contains_edge : bool -> t -> edge -> bool +val iter_edges : bool -> (edge -> unit) -> t -> unit (** [iter_edges footprint_part f g] iterates function [f] on the edges in [g] in the same order as returned by [get_edges]; if [footprint_part] is true the edges are taken from the footprint part. *) -val iter_edges : bool -> (edge -> unit) -> t -> unit (** Graph annotated with the differences w.r.t. a previous graph *) type diff +val compute_diff : Pp.color -> t -> t -> diff (** [compute_diff default_color oldgraph newgraph] returns the list of edges which are only in [newgraph] *) -val compute_diff : Pp.color -> t -> t -> diff +val diff_get_colormap : bool -> diff -> Pp.colormap (** [diff_get_colormap footprint_part diff] returns the colormap of a computed diff, selecting the footprint colormap if [footprint_part] is true. *) -val diff_get_colormap : bool -> diff -> Pp.colormap +val pp_proplist : + Pp.env -> string -> Prop.normal Prop.t * bool -> Format.formatter -> Prop.normal Prop.t list + -> unit (** Print a list of propositions, prepending each one with the given string, If !Config.pring_using_diff is true, print the diff w.r.t. the given prop, extracting its local stack vars if the boolean is true. *) -val pp_proplist : - Pp.env -> string -> (Prop.normal Prop.t * bool) -> - Format.formatter -> Prop.normal Prop.t list -> unit -(** dump a prop list coming form the given initial prop *) val d_proplist : 'a Prop.t -> 'b Prop.t list -> unit +(** dump a prop list coming form the given initial prop *) diff --git a/infer/src/backend/propset.ml b/infer/src/backend/propset.ml index f984f4a16..5482f4d16 100644 --- a/infer/src/backend/propset.ml +++ b/infer/src/backend/propset.ml @@ -17,11 +17,11 @@ module F = Format (** {2 Sets of Propositions} *) -module PropSet = - Caml.Set.Make(struct - type t = Prop.normal Prop.t - let compare = Prop.compare_prop - end) +module PropSet = Caml.Set.Make (struct + type t = Prop.normal Prop.t + + let compare = Prop.compare_prop +end) let compare = PropSet.compare @@ -32,28 +32,23 @@ type t = PropSet.t let add tenv p pset = let ps = Prop.prop_expand tenv p in List.fold - ~f:(fun pset' p' -> - PropSet.add (Prop.prop_rename_primed_footprint_vars tenv p') pset') - ~init:pset - ps + ~f:(fun pset' p' -> PropSet.add (Prop.prop_rename_primed_footprint_vars tenv p') pset') + ~init:pset ps (** Singleton set. *) -let singleton tenv p = - add tenv p PropSet.empty +let singleton tenv p = add tenv p PropSet.empty (** Set union. *) let union = PropSet.union (** Set membership *) -let mem p = - PropSet.mem p +let mem p = PropSet.mem p (** Set intersection *) let inter = PropSet.inter (** Set difference. *) -let diff = - PropSet.diff +let diff = PropSet.diff let empty = PropSet.empty @@ -65,22 +60,19 @@ let size = PropSet.cardinal let filter = PropSet.filter -let from_proplist tenv plist = - List.fold ~f:(fun pset p -> add tenv p pset) ~init:empty plist +let from_proplist tenv plist = List.fold ~f:(fun pset p -> add tenv p pset) ~init:empty plist -let to_proplist pset = - PropSet.elements pset +let to_proplist pset = PropSet.elements pset (** Apply function to all the elements of [propset], removing those where it returns [None]. *) let map_option tenv f pset = let plisto = List.map ~f (to_proplist pset) in - let plisto = List.filter ~f:(function | Some _ -> true | None -> false) plisto in + let plisto = List.filter ~f:(function Some _ -> true | None -> false) plisto in let plist = List.map ~f:(function Some p -> p | None -> assert false) plisto in from_proplist tenv plist (** Apply function to all the elements of [propset]. *) -let map tenv f pset = - from_proplist tenv (List.map ~f (to_proplist pset)) +let map tenv f pset = from_proplist tenv (List.map ~f (to_proplist pset)) (** [fold f pset a] computes [f (... (f (f a p1) p2) ...) pn] where [p1 ... pN] are the elements of pset, in increasing order. *) @@ -90,21 +82,18 @@ let fold f a pset = (** [iter f pset] computes (f p1;f p2;..;f pN) where [p1 ... pN] are the elements of pset, in increasing order. *) -let iter = - PropSet.iter +let iter = PropSet.iter -let subseteq = - PropSet.subset +let subseteq = PropSet.subset -let partition = - PropSet.partition +let partition = PropSet.partition (** {2 Pretty print} *) (** Pretty print a set of propositions, obtained from the given prop. *) let pp pe prop f pset = let plist = to_proplist pset in - (Propgraph.pp_proplist pe "PROP" (prop, false)) f plist + Propgraph.pp_proplist pe "PROP" (prop, false) f plist let d p ps = let plist = to_proplist ps in diff --git a/infer/src/backend/propset.mli b/infer/src/backend/propset.mli index 07709c267..c4ab77886 100644 --- a/infer/src/backend/propset.mli +++ b/infer/src/backend/propset.mli @@ -18,65 +18,65 @@ open! IStd The invariant is maintaned that Prop.prop_rename_primed_footprint_vars is called on any prop added to the set. *) type t -(** Compare propsets *) val compare : t -> t -> int +(** Compare propsets *) -(** Singleton set. *) val singleton : Tenv.t -> Prop.normal Prop.t -> t +(** Singleton set. *) -(** Set membership. *) val mem : Prop.normal Prop.t -> t -> bool +(** Set membership. *) -(** Set union. *) val union : t -> t -> t +(** Set union. *) -(** Set intersection *) val inter : t -> t -> t +(** Set intersection *) -(** Add [prop] to propset. *) val add : Tenv.t -> Prop.normal Prop.t -> t -> t +(** Add [prop] to propset. *) -(** Set difference. *) val diff : t -> t -> t +(** Set difference. *) -(** The empty set of propositions. *) val empty : t +(** The empty set of propositions. *) -(** Size of the set *) val size : t -> int +(** Size of the set *) val from_proplist : Tenv.t -> Prop.normal Prop.t list -> t val to_proplist : t -> Prop.normal Prop.t list -(** Apply function to all the elements of the propset. *) val map : Tenv.t -> (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t +(** Apply function to all the elements of the propset. *) -(** Apply function to all the elements of the propset, removing those where it returns [None]. *) val map_option : Tenv.t -> (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t +(** Apply function to all the elements of the propset, removing those where it returns [None]. *) +val fold : ('a -> Prop.normal Prop.t -> 'a) -> 'a -> t -> 'a (** [fold f pset a] computes [(f pN ... (f p2 (f p1 a))...)], where [p1 ... pN] are the elements of pset, in increasing order. *) -val fold : ('a -> Prop.normal Prop.t -> 'a) -> 'a -> t -> 'a +val iter : (Prop.normal Prop.t -> unit) -> t -> unit (** [iter f pset] computes (f p1;f p2;..;f pN) where [p1 ... pN] are the elements of pset, in increasing order. *) -val iter : (Prop.normal Prop.t -> unit) -> t -> unit val partition : (Prop.normal Prop.t -> bool) -> t -> t * t val subseteq : t -> t -> bool -(** Set emptiness check. *) val is_empty : t -> bool +(** Set emptiness check. *) val filter : (Prop.normal Prop.t -> bool) -> t -> t (** {2 Pretty print} *) -(** Pretty print a set of propositions, obtained from the given prop. *) val pp : Pp.env -> Prop.normal Prop.t -> Format.formatter -> t -> unit +(** Pretty print a set of propositions, obtained from the given prop. *) -(** dump a propset coming form the given initial prop *) val d : Prop.normal Prop.t -> t -> unit +(** dump a propset coming form the given initial prop *) diff --git a/infer/src/backend/prover.ml b/infer/src/backend/prover.ml index aa9f1b59b..0848add75 100644 --- a/infer/src/backend/prover.ml +++ b/infer/src/backend/prover.ml @@ -16,115 +16,130 @@ module L = Logging module F = Format let decrease_indent_when_exception thunk = - try (thunk ()) - with exn when SymOp.exn_not_failure exn -> (L.d_decrease_indent 1; raise exn) + try thunk () + with exn when SymOp.exn_not_failure exn -> L.d_decrease_indent 1 ; raise exn -let compute_max_from_nonempty_int_list l = - uw (List.max_elt ~cmp:IntLit.compare_value l) +let compute_max_from_nonempty_int_list l = uw (List.max_elt ~cmp:IntLit.compare_value l) -let compute_min_from_nonempty_int_list l = - uw (List.min_elt ~cmp:IntLit.compare_value l) +let compute_min_from_nonempty_int_list l = uw (List.min_elt ~cmp:IntLit.compare_value l) -let rec list_rev_acc acc = function - | [] -> acc - | x:: l -> list_rev_acc (x:: acc) l +let rec list_rev_acc acc = function [] -> acc | x :: l -> list_rev_acc (x :: acc) l let rec remove_redundancy have_same_key acc = function - | [] -> List.rev acc - | [x] -> List.rev (x:: acc) - | x:: ((y:: l') as l) -> - if have_same_key x y then remove_redundancy have_same_key acc (x:: l') - else remove_redundancy have_same_key (x:: acc) l + | [] + -> List.rev acc + | [x] + -> List.rev (x :: acc) + | x :: (y :: l' as l) + -> if have_same_key x y then remove_redundancy have_same_key acc (x :: l') + else remove_redundancy have_same_key (x :: acc) l let rec is_java_class tenv (typ: Typ.t) = match typ.desc with - | Tstruct name -> Typ.Name.Java.is_class name - | Tarray (inner_typ, _, _) | Tptr (inner_typ, _) -> is_java_class tenv inner_typ - | _ -> false + | Tstruct name + -> Typ.Name.Java.is_class name + | Tarray (inner_typ, _, _) | Tptr (inner_typ, _) + -> is_java_class tenv inner_typ + | _ + -> false (** Negate an atom *) let atom_negate tenv = function - | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const (Const.Cint i)) when IntLit.isone i -> - Prop.mk_inequality tenv (Exp.lt e2 e1) - | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const (Const.Cint i)) when IntLit.isone i -> - Prop.mk_inequality tenv (Exp.le e2 e1) - | Sil.Aeq (e1, e2) -> Sil.Aneq (e1, e2) - | Sil.Aneq (e1, e2) -> Sil.Aeq (e1, e2) - | Sil.Apred (a, es) -> Sil.Anpred (a, es) - | Sil.Anpred (a, es) -> Sil.Apred (a, es) + | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> Prop.mk_inequality tenv (Exp.lt e2 e1) + | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> Prop.mk_inequality tenv (Exp.le e2 e1) + | Sil.Aeq (e1, e2) + -> Sil.Aneq (e1, e2) + | Sil.Aneq (e1, e2) + -> Sil.Aeq (e1, e2) + | Sil.Apred (a, es) + -> Sil.Anpred (a, es) + | Sil.Anpred (a, es) + -> Sil.Apred (a, es) (** {2 Ordinary Theorem Proving} *) -let (++) = IntLit.add -let (--) = IntLit.sub +let ( ++ ) = IntLit.add + +let ( -- ) = IntLit.sub (** Reasoning about constraints of the form x-y <= n *) module DiffConstr : sig - type t + val to_leq : t -> Exp.t * Exp.t + val to_lt : t -> Exp.t * Exp.t + val to_triple : t -> Exp.t * Exp.t * IntLit.t + val from_leq : t list -> Exp.t * Exp.t -> t list + val from_lt : t list -> Exp.t * Exp.t -> t list - val saturate : t list -> bool * t list + val saturate : t list -> bool * t list end = struct - type t = Exp.t * Exp.t * IntLit.t [@@deriving compare] let equal = [%compare.equal : t] - let to_leq (e1, e2, n) = - Exp.BinOp(Binop.MinusA, e1, e2), Exp.int n + let to_leq (e1, e2, n) = (Exp.BinOp (Binop.MinusA, e1, e2), Exp.int n) + let to_lt (e1, e2, n) = - Exp.int (IntLit.zero -- n -- IntLit.one), Exp.BinOp(Binop.MinusA, e2, e1) + (Exp.int (IntLit.zero -- n -- IntLit.one), Exp.BinOp (Binop.MinusA, e2, e1)) + let to_triple entry = entry let from_leq acc (e1, e2) = - match e1, e2 with - | Exp.BinOp (Binop.MinusA, (Exp.Var id11 as e11), (Exp.Var id12 as e12)), - Exp.Const (Const.Cint n) - when not (Ident.equal id11 id12) -> - (match IntLit.to_signed n with - | None -> acc (* ignore: constraint algorithm only terminates on signed integers *) - | Some n' -> - (e11, e12, n') :: acc) - | _ -> acc + match (e1, e2) with + | ( Exp.BinOp (Binop.MinusA, (Exp.Var id11 as e11), (Exp.Var id12 as e12)) + , Exp.Const Const.Cint n ) + when not (Ident.equal id11 id12) -> ( + match IntLit.to_signed n with + | None + -> acc (* ignore: constraint algorithm only terminates on signed integers *) + | Some n' + -> (e11, e12, n') :: acc ) + | _ + -> acc + let from_lt acc (e1, e2) = - match e1, e2 with - | Exp.Const (Const.Cint n), - Exp.BinOp (Binop.MinusA, (Exp.Var id21 as e21), (Exp.Var id22 as e22)) - when not (Ident.equal id21 id22) -> - (match IntLit.to_signed n with - | None -> acc (* ignore: constraint algorithm only terminates on signed integers *) - | Some n' -> - let m = IntLit.zero -- n' -- IntLit.one in - (e22, e21, m) :: acc) - | _ -> acc - - let rec generate ((e1, e2, n) as constr) acc = function - | [] -> false, acc - | (f1, f2, m):: rest -> - let equal_e2_f1 = Exp.equal e2 f1 in + match (e1, e2) with + | Exp.Const Const.Cint n, Exp.BinOp (Binop.MinusA, (Exp.Var id21 as e21), (Exp.Var id22 as e22)) + when not (Ident.equal id21 id22) -> ( + match IntLit.to_signed n with + | None + -> acc (* ignore: constraint algorithm only terminates on signed integers *) + | Some n' + -> let m = IntLit.zero -- n' -- IntLit.one in + (e22, e21, m) :: acc ) + | _ + -> acc + + let rec generate (e1, e2, n as constr) acc = function + | [] + -> (false, acc) + | (f1, f2, m) :: rest + -> let equal_e2_f1 = Exp.equal e2 f1 in let equal_e1_f2 = Exp.equal e1 f2 in - if equal_e2_f1 && equal_e1_f2 && IntLit.lt (n ++ m) IntLit.zero then - true, [] (* constraints are inconsistent *) - else if equal_e2_f1 && equal_e1_f2 then - generate constr acc rest + if equal_e2_f1 && equal_e1_f2 && IntLit.lt (n ++ m) IntLit.zero then (true, []) + (* constraints are inconsistent *) + else if equal_e2_f1 && equal_e1_f2 then generate constr acc rest else if equal_e2_f1 then let constr_new = (e1, f2, n ++ m) in - generate constr (constr_new:: acc) rest + generate constr (constr_new :: acc) rest else if equal_e1_f2 then let constr_new = (f1, e2, m ++ n) in - generate constr (constr_new:: acc) rest - else - generate constr acc rest + generate constr (constr_new :: acc) rest + else generate constr acc rest let sort_then_remove_redundancy constraints = let constraints_sorted = List.sort ~cmp:compare constraints in - let have_same_key (e1, e2, _) (f1, f2, _) = [%compare.equal: Exp.t * Exp.t] (e1, e2) (f1, f2) in + let have_same_key (e1, e2, _) (f1, f2, _) = + [%compare.equal : Exp.t * Exp.t] (e1, e2) (f1, f2) + in remove_redundancy have_same_key [] constraints_sorted let remove_redundancy constraints = @@ -132,30 +147,30 @@ end = struct List.filter ~f:(fun entry -> List.exists ~f:(equal entry) constraints') constraints let rec combine acc_todos acc_seen constraints_new constraints_old = - match constraints_new, constraints_old with - | [], [] -> List.rev acc_todos, List.rev acc_seen - | [], _ -> List.rev acc_todos, list_rev_acc constraints_old acc_seen - | _, [] -> list_rev_acc constraints_new acc_todos, list_rev_acc constraints_new acc_seen - | constr:: rest, constr':: rest' -> - let e1, e2, n = constr in + match (constraints_new, constraints_old) with + | [], [] + -> (List.rev acc_todos, List.rev acc_seen) + | [], _ + -> (List.rev acc_todos, list_rev_acc constraints_old acc_seen) + | _, [] + -> (list_rev_acc constraints_new acc_todos, list_rev_acc constraints_new acc_seen) + | constr :: rest, constr' :: rest' + -> let e1, e2, n = constr in let f1, f2, m = constr' in - let c1 = [%compare: Exp.t * Exp.t] (e1, e2) (f1, f2) in - if Int.equal c1 0 && IntLit.lt n m then - combine acc_todos acc_seen constraints_new rest' - else if Int.equal c1 0 then - combine acc_todos acc_seen rest constraints_old - else if c1 < 0 then - combine (constr:: acc_todos) (constr:: acc_seen) rest constraints_old - else - combine acc_todos (constr':: acc_seen) constraints_new rest' + let c1 = [%compare : Exp.t * Exp.t] (e1, e2) (f1, f2) in + if Int.equal c1 0 && IntLit.lt n m then combine acc_todos acc_seen constraints_new rest' + else if Int.equal c1 0 then combine acc_todos acc_seen rest constraints_old + else if c1 < 0 then combine (constr :: acc_todos) (constr :: acc_seen) rest constraints_old + else combine acc_todos (constr' :: acc_seen) constraints_new rest' let rec _saturate seen todos = (* seen is a superset of todos. "seen" is sorted and doesn't have redundancy. *) match todos with - | [] -> false, seen - | constr:: rest -> - let inconsistent, constraints_new = generate constr [] seen in - if inconsistent then true, [] + | [] + -> (false, seen) + | constr :: rest + -> let inconsistent, constraints_new = generate constr [] seen in + if inconsistent then (true, []) else let constraints_new' = sort_then_remove_redundancy constraints_new in let todos_new, seen_new = combine [] [] constraints_new' seen in @@ -170,65 +185,69 @@ end = struct end (** Return true if the two types have sizes which can be compared *) -let type_size_comparable t1 t2 = match t1.Typ.desc, t2.Typ.desc with - | Typ.Tint _, Typ.Tint _ -> true - | _ -> false +let type_size_comparable t1 t2 = + match (t1.Typ.desc, t2.Typ.desc) with Typ.Tint _, Typ.Tint _ -> true | _ -> false (** Compare the size of comparable types *) let type_size_compare t1 t2 = let ik_compare ik1 ik2 = let ik_size = function - | Typ.IChar | Typ.ISChar | Typ.IUChar | Typ.IBool -> 1 - | Typ.IShort | Typ.IUShort -> 2 - | Typ.IInt | Typ.IUInt -> 3 - | Typ.ILong | Typ.IULong -> 4 - | Typ.ILongLong | Typ.IULongLong -> 5 - | Typ.I128 | Typ.IU128 -> 6 in + | Typ.IChar | Typ.ISChar | Typ.IUChar | Typ.IBool + -> 1 + | Typ.IShort | Typ.IUShort + -> 2 + | Typ.IInt | Typ.IUInt + -> 3 + | Typ.ILong | Typ.IULong + -> 4 + | Typ.ILongLong | Typ.IULongLong + -> 5 + | Typ.I128 | Typ.IU128 + -> 6 + in let n1 = ik_size ik1 in let n2 = ik_size ik2 in - n1 - n2 in - match t1.Typ.desc, t2.Typ.desc with - | Typ.Tint ik1, Typ.Tint ik2 -> - Some (ik_compare ik1 ik2) - | _ -> None + n1 - n2 + in + match (t1.Typ.desc, t2.Typ.desc) with + | Typ.Tint ik1, Typ.Tint ik2 + -> Some (ik_compare ik1 ik2) + | _ + -> None (** Check <= on the size of comparable types *) -let check_type_size_leq t1 t2 = match type_size_compare t1 t2 with - | None -> false - | Some n -> n <= 0 +let check_type_size_leq t1 t2 = + match type_size_compare t1 t2 with None -> false | Some n -> n <= 0 (** Check < on the size of comparable types *) -let check_type_size_lt t1 t2 = match type_size_compare t1 t2 with - | None -> false - | Some n -> n < 0 +let check_type_size_lt t1 t2 = match type_size_compare t1 t2 with None -> false | Some n -> n < 0 (** Reasoning about inequalities *) module Inequalities : sig (** type for inequalities (and implied disequalities) *) type t - (** Extract inequalities and disequalities from [prop] *) val from_prop : Tenv.t -> Prop.normal Prop.t -> t + (** Extract inequalities and disequalities from [prop] *) - (** Check [t |- e1!=e2]. Result [false] means "don't know". *) val check_ne : t -> Exp.t -> Exp.t -> bool + (** Check [t |- e1!=e2]. Result [false] means "don't know". *) - (** Check [t |- e1<=e2]. Result [false] means "don't know". *) val check_le : t -> Exp.t -> Exp.t -> bool + (** Check [t |- e1<=e2]. Result [false] means "don't know". *) - (** Check [t |- e1 Exp.t -> Exp.t -> bool + (** Check [t |- e1 Exp.t -> IntLit.t option + (** Find a IntLit.t n such that [t |- e<=n] if possible. *) - (** Find a IntLit.t n such that [t |- n Exp.t -> IntLit.t option + (** Find a IntLit.t n such that [t |- n bool - -(* + (** Return [true] if a simple inconsistency is detected *) + (* (** Extract inequalities and disequalities from [pi] *) val from_pi : Sil.atom list -> t @@ -251,174 +270,192 @@ module Inequalities : sig val d_neqs : t -> unit *) end = struct + type t = + { mutable leqs: (Exp.t * Exp.t) list (** le fasts [e1 <= e2] *) + ; mutable lts: (Exp.t * Exp.t) list (** lt facts [e1 < e2] *) + ; mutable neqs: (Exp.t * Exp.t) list (** ne facts [e1 != e2] *) } - type t = { - mutable leqs: (Exp.t * Exp.t) list; (** le fasts [e1 <= e2] *) - mutable lts: (Exp.t * Exp.t) list; (** lt facts [e1 < e2] *) - mutable neqs: (Exp.t * Exp.t) list; (** ne facts [e1 != e2] *) - } - - let inconsistent_ineq = { leqs = [(Exp.one, Exp.zero)]; lts = []; neqs = [] } + let inconsistent_ineq = {leqs= [(Exp.one, Exp.zero)]; lts= []; neqs= []} let leq_compare (e1, e2) (f1, f2) = let c1 = Exp.compare e1 f1 in if c1 <> 0 then c1 else Exp.compare e2 f2 + let lt_compare (e1, e2) (f1, f2) = let c2 = Exp.compare e2 f2 in - if c2 <> 0 then c2 else - (Exp.compare e1 f1) + if c2 <> 0 then c2 else -Exp.compare e1 f1 let leqs_sort_then_remove_redundancy leqs = let leqs_sorted = List.sort ~cmp:leq_compare leqs in let have_same_key leq1 leq2 = - match leq1, leq2 with - | (e1, Exp.Const (Const.Cint n1)), (e2, Exp.Const (Const.Cint n2)) -> - Exp.equal e1 e2 && IntLit.leq n1 n2 - | _, _ -> false in + match (leq1, leq2) with + | (e1, Exp.Const Const.Cint n1), (e2, Exp.Const Const.Cint n2) + -> Exp.equal e1 e2 && IntLit.leq n1 n2 + | _, _ + -> false + in remove_redundancy have_same_key [] leqs_sorted + let lts_sort_then_remove_redundancy lts = let lts_sorted = List.sort ~cmp:lt_compare lts in let have_same_key lt1 lt2 = - match lt1, lt2 with - | (Exp.Const (Const.Cint n1), e1), (Exp.Const (Const.Cint n2), e2) -> - Exp.equal e1 e2 && IntLit.geq n1 n2 - | _, _ -> false in + match (lt1, lt2) with + | (Exp.Const Const.Cint n1, e1), (Exp.Const Const.Cint n2, e2) + -> Exp.equal e1 e2 && IntLit.geq n1 n2 + | _, _ + -> false + in remove_redundancy have_same_key [] lts_sorted - let saturate { leqs = leqs; lts = lts; neqs = neqs } = + let saturate {leqs; lts; neqs} = let diff_constraints1 = - List.fold - ~f:DiffConstr.from_lt - ~init:(List.fold ~f:DiffConstr.from_leq ~init:[] leqs) - lts in + List.fold ~f:DiffConstr.from_lt ~init:(List.fold ~f:DiffConstr.from_leq ~init:[] leqs) lts + in let inconsistent, diff_constraints2 = DiffConstr.saturate diff_constraints1 in if inconsistent then inconsistent_ineq - else begin + else let umap_add umap e new_upper = try let old_upper = Exp.Map.find e umap in if IntLit.leq old_upper new_upper then umap else Exp.Map.add e new_upper umap - with Not_found -> Exp.Map.add e new_upper umap in + with Not_found -> Exp.Map.add e new_upper umap + in let lmap_add lmap e new_lower = try let old_lower = Exp.Map.find e lmap in if IntLit.geq old_lower new_lower then lmap else Exp.Map.add e new_lower lmap - with Not_found -> Exp.Map.add e new_lower lmap in + with Not_found -> Exp.Map.add e new_lower lmap + in let rec umap_create_from_leqs umap = function - | [] -> umap - | (e1, Exp.Const (Const.Cint upper1)):: leqs_rest -> - let umap' = umap_add umap e1 upper1 in + | [] + -> umap + | (e1, Exp.Const Const.Cint upper1) :: leqs_rest + -> let umap' = umap_add umap e1 upper1 in umap_create_from_leqs umap' leqs_rest - | _:: leqs_rest -> umap_create_from_leqs umap leqs_rest in + | _ :: leqs_rest + -> umap_create_from_leqs umap leqs_rest + in let rec lmap_create_from_lts lmap = function - | [] -> lmap - | (Exp.Const (Const.Cint lower1), e1):: lts_rest -> - let lmap' = lmap_add lmap e1 lower1 in + | [] + -> lmap + | (Exp.Const Const.Cint lower1, e1) :: lts_rest + -> let lmap' = lmap_add lmap e1 lower1 in lmap_create_from_lts lmap' lts_rest - | _:: lts_rest -> lmap_create_from_lts lmap lts_rest in + | _ :: lts_rest + -> lmap_create_from_lts lmap lts_rest + in let rec umap_improve_by_difference_constraints umap = function - | [] -> umap - | constr:: constrs_rest -> - try - let e1, e2, n = DiffConstr.to_triple constr (* e1 - e2 <= n *) in - let upper2 = Exp.Map.find e2 umap in - let new_upper1 = upper2 ++ n in - let new_umap = umap_add umap e1 new_upper1 in - umap_improve_by_difference_constraints new_umap constrs_rest - with Not_found -> - umap_improve_by_difference_constraints umap constrs_rest in + | [] + -> umap + | constr :: constrs_rest -> + try + let e1, e2, n = DiffConstr.to_triple constr (* e1 - e2 <= n *) in + let upper2 = Exp.Map.find e2 umap in + let new_upper1 = upper2 ++ n in + let new_umap = umap_add umap e1 new_upper1 in + umap_improve_by_difference_constraints new_umap constrs_rest + with Not_found -> umap_improve_by_difference_constraints umap constrs_rest + in let rec lmap_improve_by_difference_constraints lmap = function - | [] -> lmap - | constr:: constrs_rest -> (* e2 - e1 > -n-1 *) - try - let e1, e2, n = DiffConstr.to_triple constr (* e2 - e1 > -n-1 *) in - let lower1 = Exp.Map.find e1 lmap in - let new_lower2 = lower1 -- n -- IntLit.one in - let new_lmap = lmap_add lmap e2 new_lower2 in - lmap_improve_by_difference_constraints new_lmap constrs_rest - with Not_found -> - lmap_improve_by_difference_constraints lmap constrs_rest in + | [] + -> lmap + | constr :: constrs_rest -> + (* e2 - e1 > -n-1 *) + try + let e1, e2, n = DiffConstr.to_triple constr (* e2 - e1 > -n-1 *) in + let lower1 = Exp.Map.find e1 lmap in + let new_lower2 = lower1 -- n -- IntLit.one in + let new_lmap = lmap_add lmap e2 new_lower2 in + lmap_improve_by_difference_constraints new_lmap constrs_rest + with Not_found -> lmap_improve_by_difference_constraints lmap constrs_rest + in let leqs_res = let umap = umap_create_from_leqs Exp.Map.empty leqs in let umap' = umap_improve_by_difference_constraints umap diff_constraints2 in - let leqs' = Exp.Map.fold - (fun e upper acc_leqs -> (e, Exp.int upper):: acc_leqs) - umap' [] in - let leqs'' = (List.map ~f:DiffConstr.to_leq diff_constraints2) @ leqs' in - leqs_sort_then_remove_redundancy leqs'' in + let leqs' = + Exp.Map.fold (fun e upper acc_leqs -> (e, Exp.int upper) :: acc_leqs) umap' [] + in + let leqs'' = List.map ~f:DiffConstr.to_leq diff_constraints2 @ leqs' in + leqs_sort_then_remove_redundancy leqs'' + in let lts_res = let lmap = lmap_create_from_lts Exp.Map.empty lts in let lmap' = lmap_improve_by_difference_constraints lmap diff_constraints2 in - let lts' = Exp.Map.fold - (fun e lower acc_lts -> (Exp.int lower, e):: acc_lts) - lmap' [] in - let lts'' = (List.map ~f:DiffConstr.to_lt diff_constraints2) @ lts' in - lts_sort_then_remove_redundancy lts'' in - { leqs = leqs_res; lts = lts_res; neqs = neqs } - end + let lts' = Exp.Map.fold (fun e lower acc_lts -> (Exp.int lower, e) :: acc_lts) lmap' [] in + let lts'' = List.map ~f:DiffConstr.to_lt diff_constraints2 @ lts' in + lts_sort_then_remove_redundancy lts'' + in + {leqs= leqs_res; lts= lts_res; neqs} (** Extract inequalities and disequalities from [pi] *) let from_pi pi = - let leqs = ref [] in (* <= facts *) - let lts = ref [] in (* < facts *) - let neqs = ref [] in (* != facts *) + let leqs = ref [] in + (* <= facts *) + let lts = ref [] in + (* < facts *) + let neqs = ref [] in + (* != facts *) let process_atom = function - | Sil.Aneq (e1, e2) -> (* != *) + | Sil.Aneq (e1, e2) + -> (* != *) neqs := (e1, e2) :: !neqs - | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const (Const.Cint i)) when IntLit.isone i -> - leqs := (e1, e2) :: !leqs (* <= *) - | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const (Const.Cint i)) when IntLit.isone i -> - lts := (e1, e2) :: !lts (* < *) - | Sil.Aeq _ - | Sil.Apred _ | Anpred _ -> () in - List.iter ~f:process_atom pi; - saturate { leqs = !leqs; lts = !lts; neqs = !neqs } + | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> leqs := (e1, e2) :: !leqs (* <= *) + | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> lts := (e1, e2) :: !lts (* < *) + | Sil.Aeq _ | Sil.Apred _ | Anpred _ + -> () + in + List.iter ~f:process_atom pi ; + saturate {leqs= !leqs; lts= !lts; neqs= !neqs} let from_sigma tenv sigma = let lookup = Tenv.lookup tenv in let leqs = ref [] in let lts = ref [] in - let add_lt_minus1_e e = - lts := (Exp.minus_one, e)::!lts in + let add_lt_minus1_e e = lts := (Exp.minus_one, e) :: !lts in let type_opt_is_unsigned = function - | Some {Typ.desc=Tint ik} -> Typ.ikind_is_unsigned ik - | _ -> false in - let type_of_texp = function - | Exp.Sizeof {typ} -> Some typ - | _ -> None in + | Some {Typ.desc= Tint ik} + -> Typ.ikind_is_unsigned ik + | _ + -> false + in + let type_of_texp = function Exp.Sizeof {typ} -> Some typ | _ -> None in let texp_is_unsigned texp = type_opt_is_unsigned @@ type_of_texp texp in - let strexp_lt_minus1 = function - | Sil.Eexp (e, _) -> add_lt_minus1_e e - | _ -> () in + let strexp_lt_minus1 = function Sil.Eexp (e, _) -> add_lt_minus1_e e | _ -> () in let rec strexp_extract = function - | Sil.Eexp (e, _), t -> - if type_opt_is_unsigned t then add_lt_minus1_e e - | Sil.Estruct (fsel, _), t -> - let get_field_type f = + | Sil.Eexp (e, _), t + -> if type_opt_is_unsigned t then add_lt_minus1_e e + | Sil.Estruct (fsel, _), t + -> let get_field_type f = Option.bind t ~f:(fun t' -> - Option.map ~f:fst @@ Typ.Struct.get_field_type_and_annotation ~lookup f t' - ) in + Option.map ~f:fst @@ Typ.Struct.get_field_type_and_annotation ~lookup f t' ) + in List.iter ~f:(fun (f, se) -> strexp_extract (se, get_field_type f)) fsel - | Sil.Earray (len, isel, _), t -> - let elt_t = match t with - | Some {Typ.desc=Tarray (t, _, _)} -> Some t - | _ -> None in - add_lt_minus1_e len; - List.iter ~f:(fun (idx, se) -> - add_lt_minus1_e idx; - strexp_extract (se, elt_t)) isel in + | Sil.Earray (len, isel, _), t + -> let elt_t = match t with Some {Typ.desc= Tarray (t, _, _)} -> Some t | _ -> None in + add_lt_minus1_e len ; + List.iter + ~f:(fun (idx, se) -> + add_lt_minus1_e idx ; + strexp_extract (se, elt_t)) + isel + in let hpred_extract = function - | Sil.Hpointsto(_, se, texp) -> - if texp_is_unsigned texp then strexp_lt_minus1 se; + | Sil.Hpointsto (_, se, texp) + -> if texp_is_unsigned texp then strexp_lt_minus1 se ; strexp_extract (se, type_of_texp texp) - | Sil.Hlseg _ | Sil.Hdllseg _ -> () in - List.iter ~f:hpred_extract sigma; - saturate { leqs = !leqs; lts = !lts; neqs = [] } + | Sil.Hlseg _ | Sil.Hdllseg _ + -> () + in + List.iter ~f:hpred_extract sigma ; + saturate {leqs= !leqs; lts= !lts; neqs= []} let join ineq1 ineq2 = let leqs_new = ineq1.leqs @ ineq2.leqs in let lts_new = ineq1.lts @ ineq2.lts in let neqs_new = ineq1.neqs @ ineq2.neqs in - saturate { leqs = leqs_new; lts = lts_new; neqs = neqs_new } + saturate {leqs= leqs_new; lts= lts_new; neqs= neqs_new} let from_prop tenv prop = let sigma = prop.Prop.sigma in @@ -428,95 +465,114 @@ end = struct saturate (join ineq_sigma ineq_pi) (** Return true if the two pairs of expressions are equal *) - let exp_pair_eq (e1, e2) (f1, f2) = - Exp.equal e1 f1 && Exp.equal e2 f2 + let exp_pair_eq (e1, e2) (f1, f2) = Exp.equal e1 f1 && Exp.equal e2 f2 (** Check [t |- e1<=e2]. Result [false] means "don't know". *) - let check_le { leqs = leqs; lts = lts; neqs = _ } e1 e2 = + let check_le {leqs; lts; neqs= _} e1 e2 = (* L.d_str "check_le "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) - match e1, e2 with - | Exp.Const (Const.Cint n1), Exp.Const (Const.Cint n2) -> IntLit.leq n1 n2 - | Exp.BinOp (Binop.MinusA, - Exp.Sizeof {typ=t1; dynamic_length=None}, - Exp.Sizeof {typ=t2; dynamic_length=None}), - Exp.Const(Const.Cint n2) - when IntLit.isminusone n2 && type_size_comparable t1 t2 -> - (* [ sizeof(t1) - sizeof(t2) <= -1 ] *) + match (e1, e2) with + | Exp.Const Const.Cint n1, Exp.Const Const.Cint n2 + -> IntLit.leq n1 n2 + | ( Exp.BinOp + ( Binop.MinusA + , Exp.Sizeof {typ= t1; dynamic_length= None} + , Exp.Sizeof {typ= t2; dynamic_length= None} ) + , Exp.Const Const.Cint n2 ) + when IntLit.isminusone n2 && type_size_comparable t1 t2 + -> (* [ sizeof(t1) - sizeof(t2) <= -1 ] *) check_type_size_lt t1 t2 - | e, Exp.Const (Const.Cint n) -> (* [e <= n' <= n |- e <= n] *) - List.exists ~f:(function - | e', Exp.Const (Const.Cint n') -> Exp.equal e e' && IntLit.leq n' n - | _, _ -> false) leqs - | Exp.Const (Const.Cint n), e -> (* [ n-1 <= n' < e |- n <= e] *) - List.exists ~f:(function - | Exp.Const (Const.Cint n'), e' -> Exp.equal e e' && IntLit.leq (n -- IntLit.one) n' - | _, _ -> false) lts - | _ -> Exp.equal e1 e2 + | e, Exp.Const Const.Cint n + -> (* [e <= n' <= n |- e <= n] *) + List.exists + ~f:(function + | e', Exp.Const Const.Cint n' -> Exp.equal e e' && IntLit.leq n' n | _, _ -> false) + leqs + | Exp.Const Const.Cint n, e + -> (* [ n-1 <= n' < e |- n <= e] *) + List.exists + ~f:(function + | Exp.Const Const.Cint n', e' + -> Exp.equal e e' && IntLit.leq (n -- IntLit.one) n' + | _, _ + -> false) + lts + | _ + -> Exp.equal e1 e2 (** Check [prop |- e1 IntLit.lt n1 n2 - | Exp.Const (Const.Cint n), e -> (* [n <= n' < e |- n < e] *) - List.exists ~f:(function - | Exp.Const (Const.Cint n'), e' -> Exp.equal e e' && IntLit.leq n n' - | _, _ -> false) lts - | e, Exp.Const (Const.Cint n) -> (* [e <= n' <= n-1 |- e < n] *) - List.exists ~f:(function - | e', Exp.Const (Const.Cint n') -> Exp.equal e e' && IntLit.leq n' (n -- IntLit.one) - | _, _ -> false) leqs - | _ -> false + match (e1, e2) with + | Exp.Const Const.Cint n1, Exp.Const Const.Cint n2 + -> IntLit.lt n1 n2 + | Exp.Const Const.Cint n, e + -> (* [n <= n' < e |- n < e] *) + List.exists + ~f:(function + | Exp.Const Const.Cint n', e' -> Exp.equal e e' && IntLit.leq n n' | _, _ -> false) + lts + | e, Exp.Const Const.Cint n + -> (* [e <= n' <= n-1 |- e < n] *) + List.exists + ~f:(function + | e', Exp.Const Const.Cint n' + -> Exp.equal e e' && IntLit.leq n' (n -- IntLit.one) + | _, _ + -> false) + leqs + | _ + -> false (** Check [prop |- e1!=e2]. Result [false] means "don't know". *) let check_ne ineq _e1 _e2 = - let e1, e2 = if Exp.compare _e1 _e2 <= 0 then _e1, _e2 else _e2, _e1 in + let e1, e2 = if Exp.compare _e1 _e2 <= 0 then (_e1, _e2) else (_e2, _e1) in List.exists ~f:(exp_pair_eq (e1, e2)) ineq.neqs || check_lt ineq e1 e2 || check_lt ineq e2 e1 (** Find a IntLit.t n such that [t |- e<=n] if possible. *) - let compute_upper_bound { leqs = leqs; lts = _; neqs = _ } e1 = + let compute_upper_bound {leqs; lts= _; neqs= _} e1 = match e1 with - | Exp.Const (Const.Cint n1) -> Some n1 - | _ -> - let e_upper_list = - List.filter ~f:(function - | e', Exp.Const (Const.Cint _) -> Exp.equal e1 e' - | _, _ -> false) leqs in + | Exp.Const Const.Cint n1 + -> Some n1 + | _ + -> let e_upper_list = + List.filter + ~f:(function e', Exp.Const Const.Cint _ -> Exp.equal e1 e' | _, _ -> false) + leqs + in let upper_list = - List.map ~f:(function - | _, Exp.Const (Const.Cint n) -> n - | _ -> assert false) e_upper_list in + List.map ~f:(function _, Exp.Const Const.Cint n -> n | _ -> assert false) e_upper_list + in if List.is_empty upper_list then None else Some (compute_min_from_nonempty_int_list upper_list) (** Find a IntLit.t n such that [t |- n < e] if possible. *) - let compute_lower_bound { leqs = _; lts = lts; neqs = _ } e1 = + let compute_lower_bound {leqs= _; lts; neqs= _} e1 = match e1 with - | Exp.Const (Const.Cint n1) -> Some (n1 -- IntLit.one) - | Exp.Sizeof _ -> Some IntLit.zero - | _ -> - let e_lower_list = - List.filter ~f:(function - | Exp.Const (Const.Cint _), e' -> Exp.equal e1 e' - | _, _ -> false) lts in + | Exp.Const Const.Cint n1 + -> Some (n1 -- IntLit.one) + | Exp.Sizeof _ + -> Some IntLit.zero + | _ + -> let e_lower_list = + List.filter + ~f:(function Exp.Const Const.Cint _, e' -> Exp.equal e1 e' | _, _ -> false) + lts + in let lower_list = - List.map ~f:(function - | Exp.Const (Const.Cint n), _ -> n - | _ -> assert false) e_lower_list in + List.map ~f:(function Exp.Const Const.Cint n, _ -> n | _ -> assert false) e_lower_list + in if List.is_empty lower_list then None else Some (compute_max_from_nonempty_int_list lower_list) (** Return [true] if a simple inconsistency is detected *) - let inconsistent ({ leqs = leqs; lts = lts; neqs = neqs } as ineq) = - let inconsistent_neq (e1, e2) = - check_le ineq e1 e2 && check_le ineq e2 e1 in + let inconsistent ({leqs; lts; neqs} as ineq) = + let inconsistent_neq (e1, e2) = check_le ineq e1 e2 && check_le ineq e2 e1 in let inconsistent_leq (e1, e2) = check_lt ineq e2 e1 in let inconsistent_lt (e1, e2) = check_le ineq e2 e1 in - List.exists ~f:inconsistent_neq neqs || - List.exists ~f:inconsistent_leq leqs || - List.exists ~f:inconsistent_lt lts + List.exists ~f:inconsistent_neq neqs || List.exists ~f:inconsistent_leq leqs + || List.exists ~f:inconsistent_lt lts -(* + (* (** Pretty print inequalities and disequalities *) let pp pe fmt { leqs = leqs; lts = lts; neqs = neqs } = let pp_leq fmt (e1, e2) = F.fprintf fmt "%a<=%a" (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 in @@ -537,35 +593,36 @@ end = struct Sil.d_exp_list elist *) end + (* End of module Inequalities *) (** Check [prop |- e1=e2]. Result [false] means "don't know". *) let check_equal tenv prop e1 e2 = let n_e1 = Prop.exp_normalize_prop tenv prop e1 in let n_e2 = Prop.exp_normalize_prop tenv prop e2 in - let check_equal () = - Exp.equal n_e1 n_e2 in + let check_equal () = Exp.equal n_e1 n_e2 in let check_equal_const () = - match n_e1, n_e2 with - | Exp.BinOp (Binop.PlusA, e1, Exp.Const (Const.Cint d)), e2 - | e2, Exp.BinOp (Binop.PlusA, e1, Exp.Const (Const.Cint d)) -> - if Exp.equal e1 e2 then IntLit.iszero d - else false - | Exp.Const c1, Exp.Lindex(Exp.Const c2, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - Const.equal c1 c2 - | Exp.Lindex(Exp.Const c1, Exp.Const (Const.Cint i)), Exp.Const c2 when IntLit.iszero i -> - Const.equal c1 c2 - | _, _ -> false in + match (n_e1, n_e2) with + | Exp.BinOp (Binop.PlusA, e1, Exp.Const Const.Cint d), e2 + | e2, Exp.BinOp (Binop.PlusA, e1, Exp.Const Const.Cint d) + -> if Exp.equal e1 e2 then IntLit.iszero d else false + | Exp.Const c1, Exp.Lindex (Exp.Const c2, Exp.Const Const.Cint i) when IntLit.iszero i + -> Const.equal c1 c2 + | Exp.Lindex (Exp.Const c1, Exp.Const Const.Cint i), Exp.Const c2 when IntLit.iszero i + -> Const.equal c1 c2 + | _, _ + -> false + in let check_equal_pi () = - let eq = Sil.Aeq(n_e1, n_e2) in + let eq = Sil.Aeq (n_e1, n_e2) in let n_eq = Prop.atom_normalize_prop tenv prop eq in let pi = prop.Prop.pi in - List.exists ~f:(Sil.equal_atom n_eq) pi in + List.exists ~f:(Sil.equal_atom n_eq) pi + in check_equal () || check_equal_const () || check_equal_pi () (** Check [ |- e=0]. Result [false] means "don't know". *) -let check_zero tenv e = - check_equal tenv Prop.prop_emp e Exp.zero +let check_zero tenv e = check_equal tenv Prop.prop_emp e Exp.zero (** [is_root prop base_exp exp] checks whether [base_exp = exp.offlist] for some list of offsets [offlist]. If so, it returns @@ -573,34 +630,43 @@ let check_zero tenv e = [base_exp] points to the beginning of a structure, not the middle. *) let is_root tenv prop base_exp exp = - let rec f offlist_past e = match e with - | Exp.Var _ | Exp.Const _ | Exp.UnOp _ | Exp.BinOp _ | Exp.Exn _ | Exp.Closure _ | Exp.Lvar _ - | Exp.Sizeof _ -> - if check_equal tenv prop base_exp e - then Some offlist_past - else None - | Exp.Cast(_, sub_exp) -> f offlist_past sub_exp - | Exp.Lfield(sub_exp, fldname, typ) -> f (Sil.Off_fld (fldname, typ) :: offlist_past) sub_exp - | Exp.Lindex(sub_exp, e) -> f (Sil.Off_index e :: offlist_past) sub_exp - in f [] exp + let rec f offlist_past e = + match e with + | Exp.Var _ + | Exp.Const _ + | Exp.UnOp _ + | Exp.BinOp _ + | Exp.Exn _ + | Exp.Closure _ + | Exp.Lvar _ + | Exp.Sizeof _ + -> if check_equal tenv prop base_exp e then Some offlist_past else None + | Exp.Cast (_, sub_exp) + -> f offlist_past sub_exp + | Exp.Lfield (sub_exp, fldname, typ) + -> f (Sil.Off_fld (fldname, typ) :: offlist_past) sub_exp + | Exp.Lindex (sub_exp, e) + -> f (Sil.Off_index e :: offlist_past) sub_exp + in + f [] exp (** Get upper and lower bounds of an expression, if any *) let get_bounds tenv prop _e = let e_norm = Prop.exp_normalize_prop tenv prop _e in - let e_root, off = match e_norm with - | Exp.BinOp (Binop.PlusA, e, Exp.Const (Const.Cint n1)) -> - e, IntLit.neg n1 - | Exp.BinOp (Binop.MinusA, e, Exp.Const (Const.Cint n1)) -> - e, n1 - | _ -> - e_norm, IntLit.zero in + let e_root, off = + match e_norm with + | Exp.BinOp (Binop.PlusA, e, Exp.Const Const.Cint n1) + -> (e, IntLit.neg n1) + | Exp.BinOp (Binop.MinusA, e, Exp.Const Const.Cint n1) + -> (e, n1) + | _ + -> (e_norm, IntLit.zero) + in let ineq = Inequalities.from_prop tenv prop in let upper_opt = Inequalities.compute_upper_bound ineq e_root in let lower_opt = Inequalities.compute_lower_bound ineq e_root in - let (+++) n_opt k = match n_opt with - | None -> None - | Some n -> Some (n ++ k) in - upper_opt +++ off, lower_opt +++ off + let ( +++ ) n_opt k = match n_opt with None -> None | Some n -> Some (n ++ k) in + (upper_opt +++ off, lower_opt +++ off) (** Check whether [prop |- e1!=e2]. *) let check_disequal tenv prop e1 e2 = @@ -608,133 +674,136 @@ let check_disequal tenv prop e1 e2 = let n_e1 = Prop.exp_normalize_prop tenv prop e1 in let n_e2 = Prop.exp_normalize_prop tenv prop e2 in let rec check_expr_disequal ce1 ce2 = - match ce1, ce2 with - | Exp.Const c1, Exp.Const c2 -> - (Const.kind_equal c1 c2) && not (Const.equal c1 c2) - | Exp.Const c1, Exp.Lindex(Exp.Const c2, Exp.Const (Const.Cint d)) -> - if IntLit.iszero d - then not (Const.equal c1 c2) (* offset=0 is no offset *) - else Const.equal c1 c2 (* same base, different offsets *) - | Exp.BinOp (Binop.PlusA, e1, Exp.Const (Const.Cint d1)), - Exp.BinOp (Binop.PlusA, e2, Exp.Const (Const.Cint d2)) -> - if Exp.equal e1 e2 then IntLit.neq d1 d2 - else false - | Exp.BinOp (Binop.PlusA, e1, Exp.Const (Const.Cint d)), e2 - | e2, Exp.BinOp (Binop.PlusA, e1, Exp.Const (Const.Cint d)) -> - if Exp.equal e1 e2 then not (IntLit.iszero d) - else false - | Exp.Lindex(Exp.Const c1, Exp.Const (Const.Cint d)), Exp.Const c2 -> - if IntLit.iszero d then not (Const.equal c1 c2) else Const.equal c1 c2 - | Exp.Lindex(Exp.Const c1, Exp.Const d1), Exp.Lindex (Exp.Const c2, Exp.Const d2) -> - Const.equal c1 c2 && not (Const.equal d1 d2) - | Exp.Const (Const.Cint n), Exp.BinOp (Binop.Mult, Exp.Sizeof _, e21) - | Exp.Const (Const.Cint n), Exp.BinOp (Binop.Mult, e21, Sizeof _) - | Exp.BinOp (Binop.Mult, Exp.Sizeof _, e21), Exp.Const (Const.Cint n) - | Exp.BinOp (Binop.Mult, e21, Exp.Sizeof _), Exp.Const (Const.Cint n) -> - IntLit.iszero n && not (Exp.is_zero e21) - | Exp.Lvar pv0, Exp.Lvar pv1 -> - (* Addresses of any two local vars must be different *) + match (ce1, ce2) with + | Exp.Const c1, Exp.Const c2 + -> Const.kind_equal c1 c2 && not (Const.equal c1 c2) + | Exp.Const c1, Exp.Lindex (Exp.Const c2, Exp.Const Const.Cint d) + -> if IntLit.iszero d then not (Const.equal c1 c2) (* offset=0 is no offset *) + else Const.equal c1 c2 + (* same base, different offsets *) + | ( Exp.BinOp (Binop.PlusA, e1, Exp.Const Const.Cint d1) + , Exp.BinOp (Binop.PlusA, e2, Exp.Const Const.Cint d2) ) + -> if Exp.equal e1 e2 then IntLit.neq d1 d2 else false + | Exp.BinOp (Binop.PlusA, e1, Exp.Const Const.Cint d), e2 + | e2, Exp.BinOp (Binop.PlusA, e1, Exp.Const Const.Cint d) + -> if Exp.equal e1 e2 then not (IntLit.iszero d) else false + | Exp.Lindex (Exp.Const c1, Exp.Const Const.Cint d), Exp.Const c2 + -> if IntLit.iszero d then not (Const.equal c1 c2) else Const.equal c1 c2 + | Exp.Lindex (Exp.Const c1, Exp.Const d1), Exp.Lindex (Exp.Const c2, Exp.Const d2) + -> Const.equal c1 c2 && not (Const.equal d1 d2) + | Exp.Const Const.Cint n, Exp.BinOp (Binop.Mult, Exp.Sizeof _, e21) + | Exp.Const Const.Cint n, Exp.BinOp (Binop.Mult, e21, Sizeof _) + | Exp.BinOp (Binop.Mult, Exp.Sizeof _, e21), Exp.Const Const.Cint n + | Exp.BinOp (Binop.Mult, e21, Exp.Sizeof _), Exp.Const Const.Cint n + -> IntLit.iszero n && not (Exp.is_zero e21) + | Exp.Lvar pv0, Exp.Lvar pv1 + -> (* Addresses of any two local vars must be different *) not (Pvar.equal pv0 pv1) - | Exp.Lvar pv, Exp.Var id | Exp.Var id, Exp.Lvar pv -> - (* Address of any non-global var must be different from the value of any footprint var *) - (not (Pvar.is_global pv)) && Ident.is_footprint id - | Exp.UnOp (op1, e1, _), Exp.UnOp (op2, e2, _) -> - if Unop.equal op1 op2 then check_expr_disequal e1 e2 - else false - | Exp.Lfield (e1, f1, _), Exp.Lfield (e2, f2, _) -> - if Typ.Fieldname.equal f1 f2 then check_expr_disequal e1 e2 - else false - | Exp.Exn e1, Exp.Exn e2 -> - check_expr_disequal e1 e2 - | _, _ -> false in - let ineq = lazy (Inequalities.from_prop tenv prop) in - let check_pi_implies_disequal e1 e2 = - Inequalities.check_ne (Lazy.force ineq) e1 e2 in + | Exp.Lvar pv, Exp.Var id | Exp.Var id, Exp.Lvar pv + -> (* Address of any non-global var must be different from the value of any footprint var *) + not (Pvar.is_global pv) && Ident.is_footprint id + | Exp.UnOp (op1, e1, _), Exp.UnOp (op2, e2, _) + -> if Unop.equal op1 op2 then check_expr_disequal e1 e2 else false + | Exp.Lfield (e1, f1, _), Exp.Lfield (e2, f2, _) + -> if Typ.Fieldname.equal f1 f2 then check_expr_disequal e1 e2 else false + | Exp.Exn e1, Exp.Exn e2 + -> check_expr_disequal e1 e2 + | _, _ + -> false + in + let ineq = (lazy (Inequalities.from_prop tenv prop)) in + let check_pi_implies_disequal e1 e2 = Inequalities.check_ne (Lazy.force ineq) e1 e2 in let neq_spatial_part () = let rec f sigma_irrelevant e = function - | [] -> None - | Sil.Hpointsto (base, _, _) as hpred :: sigma_rest -> - (match is_root tenv prop base e with - | None -> - let sigma_irrelevant' = hpred :: sigma_irrelevant - in f sigma_irrelevant' e sigma_rest - | Some _ -> - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (true, sigma_irrelevant')) - | Sil.Hlseg (k, _, e1, e2, _) as hpred :: sigma_rest -> - (match is_root tenv prop e1 e with - | None -> - let sigma_irrelevant' = hpred :: sigma_irrelevant - in f sigma_irrelevant' e sigma_rest - | Some _ -> - if (Sil.equal_lseg_kind k Sil.Lseg_NE || check_pi_implies_disequal e1 e2) then - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (true, sigma_irrelevant') - else if (Exp.equal e2 Exp.zero) then - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (false, sigma_irrelevant') - else - let sigma_rest' = List.rev_append sigma_irrelevant sigma_rest - in f [] e2 sigma_rest') - | Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) :: sigma_rest -> - if is_root tenv prop iF e <> None || is_root tenv prop iB e <> None then - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (true, sigma_irrelevant') + | [] + -> None + | (Sil.Hpointsto (base, _, _) as hpred) :: sigma_rest -> ( + match is_root tenv prop base e with + | None + -> let sigma_irrelevant' = hpred :: sigma_irrelevant in + f sigma_irrelevant' e sigma_rest + | Some _ + -> let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (true, sigma_irrelevant') ) + | (Sil.Hlseg (k, _, e1, e2, _) as hpred) :: sigma_rest -> ( + match is_root tenv prop e1 e with + | None + -> let sigma_irrelevant' = hpred :: sigma_irrelevant in + f sigma_irrelevant' e sigma_rest + | Some _ + -> if Sil.equal_lseg_kind k Sil.Lseg_NE || check_pi_implies_disequal e1 e2 then + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (true, sigma_irrelevant') + else if Exp.equal e2 Exp.zero then + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (false, sigma_irrelevant') + else + let sigma_rest' = List.rev_append sigma_irrelevant sigma_rest in + f [] e2 sigma_rest' ) + | (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _)) :: sigma_rest + -> if is_root tenv prop iF e <> None || is_root tenv prop iB e <> None then + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (true, sigma_irrelevant') else - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (false, sigma_irrelevant') - | Sil.Hdllseg (Sil.Lseg_PE, _, iF, _, oF, _, _) as hpred :: sigma_rest -> - (match is_root tenv prop iF e with - | None -> - let sigma_irrelevant' = hpred :: sigma_irrelevant - in f sigma_irrelevant' e sigma_rest - | Some _ -> - if (check_pi_implies_disequal iF oF) then - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (true, sigma_irrelevant') - else if (Exp.equal oF Exp.zero) then - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (false, sigma_irrelevant') - else - let sigma_rest' = List.rev_append sigma_irrelevant sigma_rest - in f [] oF sigma_rest') in + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (false, sigma_irrelevant') + | (Sil.Hdllseg (Sil.Lseg_PE, _, iF, _, oF, _, _) as hpred) :: sigma_rest -> + match is_root tenv prop iF e with + | None + -> let sigma_irrelevant' = hpred :: sigma_irrelevant in + f sigma_irrelevant' e sigma_rest + | Some _ + -> if check_pi_implies_disequal iF oF then + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (true, sigma_irrelevant') + else if Exp.equal oF Exp.zero then + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (false, sigma_irrelevant') + else + let sigma_rest' = List.rev_append sigma_irrelevant sigma_rest in + f [] oF sigma_rest' + in let f_null_check sigma_irrelevant e sigma_rest = if not (Exp.equal e Exp.zero) then f sigma_irrelevant e sigma_rest else - let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest - in Some (false, sigma_irrelevant') - in match f_null_check [] n_e1 spatial_part with - | None -> false + let sigma_irrelevant' = List.rev_append sigma_irrelevant sigma_rest in + Some (false, sigma_irrelevant') + in + match f_null_check [] n_e1 spatial_part with + | None + -> false | Some (e1_allocated, spatial_part_leftover) -> - (match f_null_check [] n_e2 spatial_part_leftover with - | None -> false - | Some ((e2_allocated : bool), _) -> e1_allocated || e2_allocated) in - let check_disequal_expr () = - check_expr_disequal n_e1 n_e2 in - let neq_pure_part () = - check_pi_implies_disequal n_e1 n_e2 in + match f_null_check [] n_e2 spatial_part_leftover with + | None + -> false + | Some ((e2_allocated: bool), _) + -> e1_allocated || e2_allocated + in + let check_disequal_expr () = check_expr_disequal n_e1 n_e2 in + let neq_pure_part () = check_pi_implies_disequal n_e1 n_e2 in check_disequal_expr () || neq_pure_part () || neq_spatial_part () (** Check [prop |- e1<=e2], to be called from normalized atom *) let check_le_normalized tenv prop e1 e2 = (* L.d_str "check_le_normalized "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) - let eL, eR, off = match e1, e2 with - | Exp.BinOp(Binop.MinusA, f1, f2), Exp.Const (Const.Cint n) -> - if Exp.equal f1 f2 - then Exp.zero, Exp.zero, n - else f1, f2, n - | _ -> - e1, e2, IntLit.zero in + let eL, eR, off = + match (e1, e2) with + | Exp.BinOp (Binop.MinusA, f1, f2), Exp.Const Const.Cint n + -> if Exp.equal f1 f2 then (Exp.zero, Exp.zero, n) else (f1, f2, n) + | _ + -> (e1, e2, IntLit.zero) + in let ineq = Inequalities.from_prop tenv prop in let upper_lower_check () = let upperL_opt = Inequalities.compute_upper_bound ineq eL in let lowerR_opt = Inequalities.compute_lower_bound ineq eR in - match upperL_opt, lowerR_opt with - | None, _ | _, None -> false - | Some upper1, Some lower2 -> IntLit.leq upper1 (lower2 ++ IntLit.one ++ off) in - (upper_lower_check ()) - || (Inequalities.check_le ineq e1 e2) - || (check_equal tenv prop e1 e2) + match (upperL_opt, lowerR_opt) with + | None, _ | _, None + -> false + | Some upper1, Some lower2 + -> IntLit.leq upper1 (lower2 ++ IntLit.one ++ off) + in + upper_lower_check () || Inequalities.check_le ineq e1 e2 || check_equal tenv prop e1 e2 (** Check [prop |- e1 false - | Some upper1, Some lower2 -> IntLit.leq upper1 lower2 in - (upper_lower_check ()) || (Inequalities.check_lt ineq e1 e2) + match (upper1_opt, lower2_opt) with + | None, _ | _, None + -> false + | Some upper1, Some lower2 + -> IntLit.leq upper1 lower2 + in + upper_lower_check () || Inequalities.check_lt ineq e1 e2 (** Given an atom and a proposition returns a unique identifier. We use this to distinguish among different queries. *) @@ -755,37 +827,45 @@ let get_smt_key a p = let outc_tmp = Out_channel.create tmp_filename in let fmt_tmp = F.formatter_of_out_channel outc_tmp in let () = F.fprintf fmt_tmp "%a%a" (Sil.pp_atom Pp.text) a (Prop.pp_prop Pp.text) p in - Out_channel.close outc_tmp; + Out_channel.close outc_tmp ; Digest.to_hex (Digest.file tmp_filename) (** Check whether [prop |- a]. False means dont know. *) let check_atom tenv prop a0 = let a = Prop.atom_normalize_prop tenv prop a0 in let prop_no_fp = Prop.set prop ~pi_fp:[] ~sigma_fp:[] in - if Config.smt_output then begin - let key = get_smt_key a prop_no_fp in - let key_filename = - let source = (State.get_loc ()).file in - DB.Results_dir.path_to_filename - (DB.Results_dir.Abs_source_dir source) - [(key ^ ".cns")] in - let outc = Out_channel.create (DB.filename_to_string key_filename) in - let fmt = F.formatter_of_out_channel outc in - L.d_str ("ID: "^key); L.d_ln (); - L.d_str "CHECK_ATOM_BOUND: "; Sil.d_atom a; L.d_ln (); - L.d_str "WHERE:"; L.d_ln(); Prop.d_prop prop_no_fp; L.d_ln (); L.d_ln (); - F.fprintf fmt "ID: %s @\nCHECK_ATOM_BOUND: %a@\nWHERE:@\n%a" - key (Sil.pp_atom Pp.text) a (Prop.pp_prop Pp.text) prop_no_fp; - Out_channel.close outc; - end; + ( if Config.smt_output then + let key = get_smt_key a prop_no_fp in + let key_filename = + let source = (State.get_loc ()).file in + DB.Results_dir.path_to_filename (DB.Results_dir.Abs_source_dir source) [(key ^ ".cns")] + in + let outc = Out_channel.create (DB.filename_to_string key_filename) in + let fmt = F.formatter_of_out_channel outc in + L.d_str ("ID: " ^ key) ; + L.d_ln () ; + L.d_str "CHECK_ATOM_BOUND: " ; + Sil.d_atom a ; + L.d_ln () ; + L.d_str "WHERE:" ; + L.d_ln () ; + Prop.d_prop prop_no_fp ; + L.d_ln () ; + L.d_ln () ; + F.fprintf fmt "ID: %s @\nCHECK_ATOM_BOUND: %a@\nWHERE:@\n%a" key (Sil.pp_atom Pp.text) a + (Prop.pp_prop Pp.text) prop_no_fp ; + Out_channel.close outc ) ; match a with - | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const (Const.Cint i)) - when IntLit.isone i -> check_le_normalized tenv prop e1 e2 - | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const (Const.Cint i)) - when IntLit.isone i -> check_lt_normalized tenv prop e1 e2 - | Sil.Aeq (e1, e2) -> check_equal tenv prop e1 e2 - | Sil.Aneq (e1, e2) -> check_disequal tenv prop e1 e2 - | Sil.Apred _ | Anpred _ -> List.exists ~f:(Sil.equal_atom a) prop.Prop.pi + | Sil.Aeq (Exp.BinOp (Binop.Le, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> check_le_normalized tenv prop e1 e2 + | Sil.Aeq (Exp.BinOp (Binop.Lt, e1, e2), Exp.Const Const.Cint i) when IntLit.isone i + -> check_lt_normalized tenv prop e1 e2 + | Sil.Aeq (e1, e2) + -> check_equal tenv prop e1 e2 + | Sil.Aneq (e1, e2) + -> check_disequal tenv prop e1 e2 + | Sil.Apred _ | Anpred _ + -> List.exists ~f:(Sil.equal_atom a) prop.Prop.pi (** Check [prop |- e1<=e2]. Result [false] means "don't know". *) let check_le tenv prop e1 e2 = @@ -797,22 +877,19 @@ let check_allocatedness tenv prop e = let n_e = Prop.exp_normalize_prop tenv prop e in let spatial_part = prop.Prop.sigma in let f = function - | Sil.Hpointsto (base, _, _) -> - is_root tenv prop base n_e <> None - | Sil.Hlseg (k, _, e1, e2, _) -> - if Sil.equal_lseg_kind k Sil.Lseg_NE || check_disequal tenv prop e1 e2 then + | Sil.Hpointsto (base, _, _) + -> is_root tenv prop base n_e <> None + | Sil.Hlseg (k, _, e1, e2, _) + -> if Sil.equal_lseg_kind k Sil.Lseg_NE || check_disequal tenv prop e1 e2 then is_root tenv prop e1 n_e <> None - else - false - | Sil.Hdllseg (k, _, iF, oB, oF, iB, _) -> - if Sil.equal_lseg_kind k Sil.Lseg_NE || - check_disequal tenv prop iF oF || - check_disequal tenv prop iB oB - then - is_root tenv prop iF n_e <> None || is_root tenv prop iB n_e <> None - else - false - in List.exists ~f spatial_part + else false + | Sil.Hdllseg (k, _, iF, oB, oF, iB, _) + -> if Sil.equal_lseg_kind k Sil.Lseg_NE || check_disequal tenv prop iF oF + || check_disequal tenv prop iB oB + then is_root tenv prop iF n_e <> None || is_root tenv prop iB n_e <> None + else false + in + List.exists ~f spatial_part (** Compute an upper bound of an expression *) let compute_upper_bound_of_exp tenv p e = @@ -823,94 +900,96 @@ let compute_upper_bound_of_exp tenv p e = let check_inconsistency_two_hpreds tenv prop = let sigma = prop.Prop.sigma in let rec f e sigma_seen = function - | [] -> false + | [] + -> false | (Sil.Hpointsto (e1, _, _) as hpred) :: sigma_rest - | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest -> - if Exp.equal e1 e then true - else f e (hpred:: sigma_seen) sigma_rest - | (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred) :: sigma_rest -> - if Exp.equal iF e || Exp.equal iB e then true - else f e (hpred:: sigma_seen) sigma_rest - | Sil.Hlseg (Sil.Lseg_PE, _, e1, Exp.Const (Const.Cint i), _) as hpred :: sigma_rest - when IntLit.iszero i -> - if Exp.equal e1 e then true - else f e (hpred:: sigma_seen) sigma_rest - | Sil.Hlseg (Sil.Lseg_PE, _, e1, e2, _) as hpred :: sigma_rest -> - if Exp.equal e1 e - then - let prop' = Prop.normalize tenv (Prop.from_sigma (sigma_seen@sigma_rest)) in + | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest + -> if Exp.equal e1 e then true else f e (hpred :: sigma_seen) sigma_rest + | (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred) :: sigma_rest + -> if Exp.equal iF e || Exp.equal iB e then true else f e (hpred :: sigma_seen) sigma_rest + | (Sil.Hlseg (Sil.Lseg_PE, _, e1, Exp.Const Const.Cint i, _) as hpred) :: sigma_rest + when IntLit.iszero i + -> if Exp.equal e1 e then true else f e (hpred :: sigma_seen) sigma_rest + | (Sil.Hlseg (Sil.Lseg_PE, _, e1, e2, _) as hpred) :: sigma_rest + -> if Exp.equal e1 e then + let prop' = Prop.normalize tenv (Prop.from_sigma (sigma_seen @ sigma_rest)) in let prop_new = Prop.conjoin_eq tenv e1 e2 prop' in let sigma_new = prop_new.Prop.sigma in - let e_new = Prop.exp_normalize_prop tenv prop_new e - in f e_new [] sigma_new - else f e (hpred:: sigma_seen) sigma_rest - | Sil.Hdllseg (Sil.Lseg_PE, _, e1, _, Exp.Const (Const.Cint i), _, _) as hpred :: sigma_rest - when IntLit.iszero i -> - if Exp.equal e1 e then true - else f e (hpred:: sigma_seen) sigma_rest - | Sil.Hdllseg (Sil.Lseg_PE, _, e1, _, e3, _, _) as hpred :: sigma_rest -> - if Exp.equal e1 e - then - let prop' = Prop.normalize tenv (Prop.from_sigma (sigma_seen@sigma_rest)) in + let e_new = Prop.exp_normalize_prop tenv prop_new e in + f e_new [] sigma_new + else f e (hpred :: sigma_seen) sigma_rest + | (Sil.Hdllseg (Sil.Lseg_PE, _, e1, _, Exp.Const Const.Cint i, _, _) as hpred) :: sigma_rest + when IntLit.iszero i + -> if Exp.equal e1 e then true else f e (hpred :: sigma_seen) sigma_rest + | (Sil.Hdllseg (Sil.Lseg_PE, _, e1, _, e3, _, _) as hpred) :: sigma_rest + -> if Exp.equal e1 e then + let prop' = Prop.normalize tenv (Prop.from_sigma (sigma_seen @ sigma_rest)) in let prop_new = Prop.conjoin_eq tenv e1 e3 prop' in let sigma_new = prop_new.Prop.sigma in - let e_new = Prop.exp_normalize_prop tenv prop_new e - in f e_new [] sigma_new - else f e (hpred:: sigma_seen) sigma_rest in + let e_new = Prop.exp_normalize_prop tenv prop_new e in + f e_new [] sigma_new + else f e (hpred :: sigma_seen) sigma_rest + in let rec check sigma_seen = function - | [] -> false + | [] + -> false | (Sil.Hpointsto (e1, _, _) as hpred) :: sigma_rest - | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest -> - if (f e1 [] (sigma_seen@sigma_rest)) then true - else check (hpred:: sigma_seen) sigma_rest - | Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred :: sigma_rest -> - if f iF [] (sigma_seen@sigma_rest) || f iB [] (sigma_seen@sigma_rest) then true - else check (hpred:: sigma_seen) sigma_rest + | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest + -> if f e1 [] (sigma_seen @ sigma_rest) then true else check (hpred :: sigma_seen) sigma_rest + | (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred) :: sigma_rest + -> if f iF [] (sigma_seen @ sigma_rest) || f iB [] (sigma_seen @ sigma_rest) then true + else check (hpred :: sigma_seen) sigma_rest | (Sil.Hlseg (Sil.Lseg_PE, _, _, _, _) as hpred) :: sigma_rest - | (Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) as hpred) :: sigma_rest -> - check (hpred:: sigma_seen) sigma_rest in + | (Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) as hpred) :: sigma_rest + -> check (hpred :: sigma_seen) sigma_rest + in check [] sigma (** Inconsistency checking ignoring footprint. *) let check_inconsistency_base tenv prop = let pi = prop.Prop.pi in let sigma = prop.Prop.sigma in - let inconsistent_ptsto _ = - check_allocatedness tenv prop Exp.zero in + let inconsistent_ptsto _ = check_allocatedness tenv prop Exp.zero in let inconsistent_this_self_var () = match State.get_prop_tenv_pdesc () with - | None -> false - | Some (_, _, pdesc) -> - let procedure_attr = - Procdesc.get_attributes pdesc in + | None + -> false + | Some (_, _, pdesc) + -> let procedure_attr = Procdesc.get_attributes pdesc in let is_java_this pvar = - Config.equal_language procedure_attr.ProcAttributes.language Config.Java && - Pvar.is_this pvar in + Config.equal_language procedure_attr.ProcAttributes.language Config.Java + && Pvar.is_this pvar + in let is_objc_instance_self pvar = - Config.equal_language procedure_attr.ProcAttributes.language Config.Clang && - Mangled.equal (Pvar.get_name pvar) (Mangled.from_string "self") && - procedure_attr.ProcAttributes.is_objc_instance_method in + Config.equal_language procedure_attr.ProcAttributes.language Config.Clang + && Mangled.equal (Pvar.get_name pvar) (Mangled.from_string "self") + && procedure_attr.ProcAttributes.is_objc_instance_method + in let is_cpp_this pvar = - Config.equal_language procedure_attr.ProcAttributes.language Config.Clang && - Pvar.is_this pvar && - procedure_attr.ProcAttributes.is_cpp_instance_method in + Config.equal_language procedure_attr.ProcAttributes.language Config.Clang + && Pvar.is_this pvar && procedure_attr.ProcAttributes.is_cpp_instance_method + in let do_hpred = function - | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (e, _), _) -> - Exp.equal e Exp.zero && - Pvar.is_seed pv && - (is_java_this pv || is_cpp_this pv || is_objc_instance_self pv) - | _ -> false in - List.exists ~f:do_hpred sigma in + | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (e, _), _) + -> Exp.equal e Exp.zero && Pvar.is_seed pv + && (is_java_this pv || is_cpp_this pv || is_objc_instance_self pv) + | _ + -> false + in + List.exists ~f:do_hpred sigma + in let inconsistent_atom = function - | Sil.Aeq (e1, e2) -> - (match e1, e2 with - | Exp.Const c1, Exp.Const c2 -> not (Const.equal c1 c2) - | _ -> check_disequal tenv prop e1 e2) - | Sil.Aneq (e1, e2) -> - (match e1, e2 with - | Exp.Const c1, Exp.Const c2 -> Const.equal c1 c2 - | _ -> Exp.equal e1 e2) - | Sil.Apred _ | Anpred _ -> false in + | Sil.Aeq (e1, e2) -> ( + match (e1, e2) with + | Exp.Const c1, Exp.Const c2 + -> not (Const.equal c1 c2) + | _ + -> check_disequal tenv prop e1 e2 ) + | Sil.Aneq (e1, e2) -> ( + match (e1, e2) with Exp.Const c1, Exp.Const c2 -> Const.equal c1 c2 | _ -> Exp.equal e1 e2 ) + | Sil.Apred _ | Anpred _ + -> false + in let inconsistent_inequalities () = let ineq = Inequalities.from_prop tenv prop in (* @@ -920,18 +999,16 @@ let check_inconsistency_base tenv prop = L.d_str "lts: "; Inequalities.d_lts ineq; L.d_ln (); L.d_str "neqs: "; Inequalities.d_neqs ineq; L.d_ln (); *) - Inequalities.inconsistent ineq in - inconsistent_ptsto () - || check_inconsistency_two_hpreds tenv prop - || List.exists ~f:inconsistent_atom pi - || inconsistent_inequalities () + Inequalities.inconsistent ineq + in + inconsistent_ptsto () || check_inconsistency_two_hpreds tenv prop + || List.exists ~f:inconsistent_atom pi || inconsistent_inequalities () || inconsistent_this_self_var () (** Inconsistency checking. *) let check_inconsistency tenv prop = - (check_inconsistency_base tenv prop) - || - (check_inconsistency_base tenv (Prop.normalize tenv (Prop.extract_footprint prop))) + check_inconsistency_base tenv prop + || check_inconsistency_base tenv (Prop.normalize tenv (Prop.extract_footprint prop)) (** Inconsistency checking for the pi part ignoring footprint. *) let check_inconsistency_pi tenv pi = @@ -953,319 +1030,365 @@ exception IMPL_EXC of string * subst2 * exc_body exception MISSING_EXC of string -type check = - | Bounds_check - | Class_cast_check of Exp.t * Exp.t * Exp.t +type check = Bounds_check | Class_cast_check of Exp.t * Exp.t * Exp.t let d_typings typings = - let d_elem (exp, texp) = - Sil.d_exp exp; L.d_str ": "; Sil.d_texp_full texp; L.d_str " " in + let d_elem (exp, texp) = Sil.d_exp exp ; L.d_str ": " ; Sil.d_texp_full texp ; L.d_str " " in List.iter ~f:d_elem typings (** Module to encapsulate operations on the internal state of the prover *) module ProverState : sig val reset : Prop.normal Prop.t -> Prop.exposed Prop.t -> unit + val checks : check list ref (** type for array bounds checks *) type bounds_check = - | BClen_imply of Exp.t * Exp.t * Exp.t list (** coming from array_len_imply *) - | BCfrom_pre of Sil.atom (** coming implicitly from preconditions *) + | BClen_imply of Exp.t * Exp.t * Exp.t list (** coming from array_len_imply *) + | BCfrom_pre of Sil.atom (** coming implicitly from preconditions *) val add_bounds_check : bounds_check -> unit + val add_frame_fld : Sil.hpred -> unit + val add_frame_typ : Exp.t * Exp.t -> unit + val add_missing_fld : Sil.hpred -> unit + val add_missing_pi : Sil.atom -> unit + val add_missing_sigma : Sil.hpred list -> unit + val add_missing_typ : Exp.t * Exp.t -> unit - val atom_is_array_bounds_check : Sil.atom -> bool (** check if atom in pre is a bounds check *) + val atom_is_array_bounds_check : Sil.atom -> bool + (** check if atom in pre is a bounds check *) val get_bounds_checks : unit -> bounds_check list + val get_frame_fld : unit -> Sil.hpred list + val get_frame_typ : unit -> (Exp.t * Exp.t) list + val get_missing_fld : unit -> Sil.hpred list + val get_missing_pi : unit -> Sil.atom list + val get_missing_sigma : unit -> Sil.hpred list + val get_missing_typ : unit -> (Exp.t * Exp.t) list val d_implication : Sil.subst * Sil.subst -> 'a Prop.t * 'b Prop.t -> unit + val d_implication_error : string * (Sil.subst * Sil.subst) * exc_body -> unit end = struct - type bounds_check = - | BClen_imply of Exp.t * Exp.t * Exp.t list - | BCfrom_pre of Sil.atom + type bounds_check = BClen_imply of Exp.t * Exp.t * Exp.t list | BCfrom_pre of Sil.atom let implication_lhs = ref Prop.prop_emp + let implication_rhs = ref (Prop.expose Prop.prop_emp) - let fav_in_array_len = ref (Sil.fav_new ()) (* free variables in array len position *) - let bounds_checks = ref [] (* delayed bounds check for arrays *) + + let fav_in_array_len = ref (Sil.fav_new ()) + + (* free variables in array len position *) + let bounds_checks = ref [] + + (* delayed bounds check for arrays *) let frame_fld = ref [] + let missing_fld = ref [] + let missing_pi = ref [] + let missing_sigma = ref [] + let frame_typ = ref [] + let missing_typ = ref [] + let checks = ref [] (** free vars in array len position in current strexp part of prop *) let prop_fav_len prop = let fav = Sil.fav_new () in let do_hpred = function - | Sil.Hpointsto (_, Sil.Earray (Exp.Var _ as len, _, _), _) -> - Sil.exp_fav_add fav len - | _ -> () in - List.iter ~f:do_hpred prop.Prop.sigma; - fav + | Sil.Hpointsto (_, Sil.Earray ((Exp.Var _ as len), _, _), _) + -> Sil.exp_fav_add fav len + | _ + -> () + in + List.iter ~f:do_hpred prop.Prop.sigma ; fav let reset lhs rhs = - checks := []; - implication_lhs := lhs; - implication_rhs := rhs; - fav_in_array_len := prop_fav_len rhs; - bounds_checks := []; - frame_fld := []; - frame_typ := []; - missing_fld := []; - missing_pi := []; - missing_sigma := []; + checks := [] ; + implication_lhs := lhs ; + implication_rhs := rhs ; + fav_in_array_len := prop_fav_len rhs ; + bounds_checks := [] ; + frame_fld := [] ; + frame_typ := [] ; + missing_fld := [] ; + missing_pi := [] ; + missing_sigma := [] ; missing_typ := [] - let add_bounds_check bounds_check = - bounds_checks := bounds_check :: !bounds_checks + let add_bounds_check bounds_check = bounds_checks := bounds_check :: !bounds_checks - let add_frame_fld hpred = - frame_fld := hpred :: !frame_fld + let add_frame_fld hpred = frame_fld := hpred :: !frame_fld - let add_missing_fld hpred = - missing_fld := hpred :: !missing_fld + let add_missing_fld hpred = missing_fld := hpred :: !missing_fld - let add_frame_typ typing = - frame_typ := typing :: !frame_typ + let add_frame_typ typing = frame_typ := typing :: !frame_typ - let add_missing_typ typing = - missing_typ := typing :: !missing_typ + let add_missing_typ typing = missing_typ := typing :: !missing_typ - let add_missing_pi a = - missing_pi := a :: !missing_pi + let add_missing_pi a = missing_pi := a :: !missing_pi - let add_missing_sigma sigma = - missing_sigma := sigma @ !missing_sigma + let add_missing_sigma sigma = missing_sigma := sigma @ !missing_sigma (** atom considered array bounds check if it contains vars present in array length position in the pre *) let atom_is_array_bounds_check atom = let fav_a = Sil.atom_fav atom in - Prop.atom_is_inequality atom && - Sil.fav_exists fav_a (fun a -> Sil.fav_mem !fav_in_array_len a) + Prop.atom_is_inequality atom && Sil.fav_exists fav_a (fun a -> Sil.fav_mem !fav_in_array_len a) let get_bounds_checks () = !bounds_checks + let get_frame_fld () = !frame_fld + let get_frame_typ () = !frame_typ + let get_missing_fld () = !missing_fld + let get_missing_pi () = !missing_pi + let get_missing_sigma () = !missing_sigma + let get_missing_typ () = !missing_typ let _d_missing sub = - L.d_strln "SUB: "; - L.d_increase_indent 1; Prop.d_sub sub; L.d_decrease_indent 1; - if !missing_pi <> [] && !missing_sigma <> [] - then (L.d_ln (); Prop.d_pi !missing_pi; L.d_str "*"; L.d_ln (); Prop.d_sigma !missing_sigma) - else if !missing_pi <> [] - then (L.d_ln (); Prop.d_pi !missing_pi) - else if !missing_sigma <> [] - then (L.d_ln (); Prop.d_sigma !missing_sigma); - if !missing_fld <> [] then - begin - L.d_ln (); - L.d_strln "MISSING FLD: "; L.d_increase_indent 1; Prop.d_sigma !missing_fld; L.d_decrease_indent 1 - end; - if !missing_typ <> [] then - begin - L.d_ln (); - L.d_strln "MISSING TYPING: "; L.d_increase_indent 1; d_typings !missing_typ; L.d_decrease_indent 1 - end - - let d_missing sub = (* optional print of missing: if print something, prepend with newline *) - if !missing_pi <> [] || !missing_sigma <> [] || !missing_fld <> [] || !missing_typ <> [] || not (Sil.is_sub_empty sub) then - begin - L.d_ln (); - L.d_str "["; - _d_missing sub; - L.d_str "]" - end - - let d_frame_fld () = (* optional print of frame fld: if print something, prepend with newline *) - if !frame_fld <> [] then - begin - L.d_ln (); - L.d_strln "[FRAME FLD:"; - L.d_increase_indent 1; Prop.d_sigma !frame_fld; L.d_str "]"; L.d_decrease_indent 1 - end - - let d_frame_typ () = (* optional print of frame typ: if print something, prepend with newline *) - if !frame_typ <> [] then - begin - L.d_ln (); - L.d_strln "[FRAME TYPING:"; - L.d_increase_indent 1; d_typings !frame_typ; L.d_str "]"; L.d_decrease_indent 1 - end + L.d_strln "SUB: " ; + L.d_increase_indent 1 ; + Prop.d_sub sub ; + L.d_decrease_indent 1 ; + if !missing_pi <> [] && !missing_sigma <> [] then ( + L.d_ln () ; + Prop.d_pi !missing_pi ; + L.d_str "*" ; + L.d_ln () ; + Prop.d_sigma !missing_sigma ) + else if !missing_pi <> [] then ( + L.d_ln () ; + Prop.d_pi !missing_pi ) + else if !missing_sigma <> [] then ( + L.d_ln () ; + Prop.d_sigma !missing_sigma ) ; + if !missing_fld <> [] then ( + L.d_ln () ; + L.d_strln "MISSING FLD: " ; + L.d_increase_indent 1 ; + Prop.d_sigma !missing_fld ; + L.d_decrease_indent 1 ) ; + if !missing_typ <> [] then ( + L.d_ln () ; + L.d_strln "MISSING TYPING: " ; + L.d_increase_indent 1 ; + d_typings !missing_typ ; + L.d_decrease_indent 1 ) + + let d_missing sub = + (* optional print of missing: if print something, prepend with newline *) + if !missing_pi <> [] || !missing_sigma <> [] || !missing_fld <> [] || !missing_typ <> [] + || not (Sil.is_sub_empty sub) + then ( L.d_ln () ; L.d_str "[" ; _d_missing sub ; L.d_str "]" ) + + let d_frame_fld () = + (* optional print of frame fld: if print something, prepend with newline *) + if !frame_fld <> [] then ( + L.d_ln () ; + L.d_strln "[FRAME FLD:" ; + L.d_increase_indent 1 ; + Prop.d_sigma !frame_fld ; + L.d_str "]" ; + L.d_decrease_indent 1 ) + + let d_frame_typ () = + (* optional print of frame typ: if print something, prepend with newline *) + if !frame_typ <> [] then ( + L.d_ln () ; + L.d_strln "[FRAME TYPING:" ; + L.d_increase_indent 1 ; + d_typings !frame_typ ; + L.d_str "]" ; + L.d_decrease_indent 1 ) (** Dump an implication *) let d_implication (sub1, sub2) (p1, p2) = - let p1, p2 = Prop.prop_sub sub1 p1, Prop.prop_sub sub2 p2 in - L.d_strln "SUB:"; - L.d_increase_indent 1; Prop.d_sub sub1; L.d_decrease_indent 1; L.d_ln (); - Prop.d_prop p1; - d_missing sub2; L.d_ln (); - L.d_strln "|-"; - Prop.d_prop p2; - d_frame_fld (); + let p1, p2 = (Prop.prop_sub sub1 p1, Prop.prop_sub sub2 p2) in + L.d_strln "SUB:" ; + L.d_increase_indent 1 ; + Prop.d_sub sub1 ; + L.d_decrease_indent 1 ; + L.d_ln () ; + Prop.d_prop p1 ; + d_missing sub2 ; + L.d_ln () ; + L.d_strln "|-" ; + Prop.d_prop p2 ; + d_frame_fld () ; d_frame_typ () let d_implication_error (s, subs, body) = - let p1, p2 = !implication_lhs,!implication_rhs in - let d_inner () = match body with - | EXC_FALSE -> - () - | EXC_FALSE_HPRED hpred -> - L.d_str " on "; - Sil.d_hpred hpred; - | EXC_FALSE_EXPS (e1, e2) -> - L.d_str " on "; - Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; - | EXC_FALSE_SEXPS (se1, se2) -> - L.d_str " on "; - Sil.d_sexp se1; L.d_str ","; Sil.d_sexp se2; - | EXC_FALSE_ATOM a -> - L.d_str " on "; - Sil.d_atom a; - | EXC_FALSE_SIGMA sigma -> - L.d_str " on "; - Prop.d_sigma sigma in - L.d_ln (); - L.d_strln "$$$$$$$ Implication"; - d_implication subs (p1, p2); L.d_ln (); - L.d_str ("$$$$$$ error: " ^ s); d_inner (); - L.d_strln " returning FALSE"; + let p1, p2 = (!implication_lhs, !implication_rhs) in + let d_inner () = + match body with + | EXC_FALSE + -> () + | EXC_FALSE_HPRED hpred + -> L.d_str " on " ; Sil.d_hpred hpred + | EXC_FALSE_EXPS (e1, e2) + -> L.d_str " on " ; Sil.d_exp e1 ; L.d_str "," ; Sil.d_exp e2 + | EXC_FALSE_SEXPS (se1, se2) + -> L.d_str " on " ; Sil.d_sexp se1 ; L.d_str "," ; Sil.d_sexp se2 + | EXC_FALSE_ATOM a + -> L.d_str " on " ; Sil.d_atom a + | EXC_FALSE_SIGMA sigma + -> L.d_str " on " ; Prop.d_sigma sigma + in + L.d_ln () ; + L.d_strln "$$$$$$$ Implication" ; + d_implication subs (p1, p2) ; + L.d_ln () ; + L.d_str ("$$$$$$ error: " ^ s) ; + d_inner () ; + L.d_strln " returning FALSE" ; L.d_ln () end let d_impl (s1, s2) = ProverState.d_implication (`Exp s1, `Exp s2) + let d_impl_err (arg1, (s1, s2), arg3) = ProverState.d_implication_error (arg1, (`Exp s1, `Exp s2), arg3) (** extend a substitution *) let extend_sub sub v e = - let new_exp_sub = Sil.exp_subst_of_list [v, e] in + let new_exp_sub = Sil.exp_subst_of_list [(v, e)] in let new_sub = `Exp new_exp_sub in Sil.sub_join new_exp_sub (Sil.sub_range_map (Sil.exp_sub new_sub) sub) (** Extend [sub1] and [sub2] to witnesses that each instance of [e1[sub1]] is an instance of [e2[sub2]]. Raise IMPL_FALSE if not possible. *) -let exp_imply tenv calc_missing (subs : subst2) e1_in e2_in : subst2 = +let exp_imply tenv calc_missing (subs: subst2) e1_in e2_in : subst2 = let e1 = Prop.exp_normalize_noabs tenv (`Exp (fst subs)) e1_in in let e2 = Prop.exp_normalize_noabs tenv (`Exp (snd subs)) e2_in in - let var_imply (subs : subst2) v1 v2 : subst2 = - match Ident.is_primed v1, Ident.is_primed v2 with - | false, false -> - if Ident.equal v1 v2 then subs - else if calc_missing && Ident.is_footprint v1 && Ident.is_footprint v2 - then + let var_imply (subs: subst2) v1 v2 : subst2 = + match (Ident.is_primed v1, Ident.is_primed v2) with + | false, false + -> if Ident.equal v1 v2 then subs + else if calc_missing && Ident.is_footprint v1 && Ident.is_footprint v2 then let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in subs - else raise (IMPL_EXC ("exps", subs, (EXC_FALSE_EXPS (e1, e2)))) - | true, false -> raise (IMPL_EXC ("exps", subs, (EXC_FALSE_EXPS (e1, e2)))) - | false, true -> - let sub2' = extend_sub (snd subs) v2 (Sil.exp_sub (`Exp (fst subs)) (Exp.Var v1)) in + else raise (IMPL_EXC ("exps", subs, EXC_FALSE_EXPS (e1, e2))) + | true, false + -> raise (IMPL_EXC ("exps", subs, EXC_FALSE_EXPS (e1, e2))) + | false, true + -> let sub2' = extend_sub (snd subs) v2 (Sil.exp_sub (`Exp (fst subs)) (Exp.Var v1)) in (fst subs, sub2') - | true, true -> - let v1' = Ident.create_fresh Ident.knormal in + | true, true + -> let v1' = Ident.create_fresh Ident.knormal in let sub1' = extend_sub (fst subs) v1 (Exp.Var v1') in let sub2' = extend_sub (snd subs) v2 (Exp.Var v1') in - (sub1', sub2') in + (sub1', sub2') + in let rec do_imply subs e1 e2 : subst2 = - L.d_str "do_imply "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); - match e1, e2 with - | Exp.Var v1, Exp.Var v2 -> - var_imply subs v1 v2 - | e1, Exp.Var v2 -> - let occurs_check v e = (* check whether [v] occurs in normalized [e] *) + L.d_str "do_imply " ; + Sil.d_exp e1 ; + L.d_str " " ; + Sil.d_exp e2 ; + L.d_ln () ; + match (e1, e2) with + | Exp.Var v1, Exp.Var v2 + -> var_imply subs v1 v2 + | e1, Exp.Var v2 + -> let occurs_check v e = + (* check whether [v] occurs in normalized [e] *) if Sil.fav_mem (Sil.exp_fav e) v - && Sil.fav_mem (Sil.exp_fav (Prop.exp_normalize_prop tenv Prop.prop_emp e)) v - then raise (IMPL_EXC ("occurs check", subs, (EXC_FALSE_EXPS (e1, e2)))) in + && Sil.fav_mem (Sil.exp_fav (Prop.exp_normalize_prop tenv Prop.prop_emp e)) v + then raise (IMPL_EXC ("occurs check", subs, EXC_FALSE_EXPS (e1, e2))) + in if Ident.is_primed v2 then let () = occurs_check v2 e1 in let sub2' = extend_sub (snd subs) v2 e1 in (fst subs, sub2') - else - raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + else raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) | e1, Exp.BinOp (Binop.PlusA, (Exp.Var v2 as e2), e2') - when Ident.is_primed v2 || Ident.is_footprint v2 -> - (* here e2' could also be a variable that we could try to substitute (as in the next match + when Ident.is_primed v2 || Ident.is_footprint v2 + -> (* here e2' could also be a variable that we could try to substitute (as in the next match case), but we ignore that to avoid backtracking *) let e' = Exp.BinOp (Binop.MinusA, e1, e2') in do_imply subs (Prop.exp_normalize_noabs tenv Sil.sub_empty e') e2 | e1, Exp.BinOp (Binop.PlusA, e2, (Exp.Var v2 as e2')) - when Ident.is_primed v2 || Ident.is_footprint v2 -> - (* symmetric of above case *) + when Ident.is_primed v2 || Ident.is_footprint v2 + -> (* symmetric of above case *) let e' = Exp.BinOp (Binop.MinusA, e1, e2') in do_imply subs (Prop.exp_normalize_noabs tenv Sil.sub_empty e') e2 - | Exp.Var id, Exp.Lvar pv when Ident.is_footprint id && Pvar.is_local pv -> - (* Footprint var could never be the same as local address *) - raise (IMPL_EXC ("expression not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Var _, e2 -> - if calc_missing then + | Exp.Var id, Exp.Lvar pv when Ident.is_footprint id && Pvar.is_local pv + -> (* Footprint var could never be the same as local address *) + raise (IMPL_EXC ("expression not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Var _, e2 + -> if calc_missing then let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in subs - else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Lvar pv1, Exp.Const _ when Pvar.is_global pv1 -> - if calc_missing then + else raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Lvar pv1, Exp.Const _ when Pvar.is_global pv1 + -> if calc_missing then let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in subs - else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Lvar v1, Exp.Lvar v2 -> - if Pvar.equal v1 v2 then subs - else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Const c1, Exp.Const c2 -> - if (Const.equal c1 c2) then subs - else raise (IMPL_EXC ("constants not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Const (Const.Cint _), Exp.BinOp (Binop.PlusPI, _, _) -> - raise (IMPL_EXC ("pointer+index cannot evaluate to a constant", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Const (Const.Cint n1), Exp.BinOp (Binop.PlusA, f1, Exp.Const (Const.Cint n2)) -> - do_imply subs (Exp.int (n1 -- n2)) f1 - | Exp.BinOp(op1, e1, f1), Exp.BinOp(op2, e2, f2) when Binop.equal op1 op2 -> - do_imply (do_imply subs e1 e2) f1 f2 - | Exp.BinOp (Binop.PlusA, Exp.Var v1, e1), e2 -> - do_imply subs (Exp.Var v1) (Exp.BinOp (Binop.MinusA, e2, e1)) - | Exp.BinOp (Binop.PlusPI, Exp.Lvar pv1, e1), e2 -> - do_imply subs (Exp.Lvar pv1) (Exp.BinOp (Binop.MinusA, e2, e1)) - | Exp.Sizeof {typ=t1; dynamic_length=None; subtype=st1}, - Exp.Sizeof {typ=t2; dynamic_length=None; subtype=st2} - when Typ.equal t1 t2 && Subtype.equal_modulo_flag st1 st2 -> subs - | Exp.Sizeof {typ=t1; dynamic_length=Some d1; subtype=st1}, - Exp.Sizeof {typ=t2; dynamic_length=Some d2; subtype=st2} - when Typ.equal t1 t2 && Exp.equal d1 d2 && Subtype.equal_modulo_flag st1 st2 -> subs - | e', Exp.Const (Const.Cint n) - when IntLit.iszero n && check_disequal tenv Prop.prop_emp e' Exp.zero -> - raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Const (Const.Cint n), e' - when IntLit.iszero n && check_disequal tenv Prop.prop_emp e' Exp.zero -> - raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) - | e1, Exp.Const _ -> - raise (IMPL_EXC ("lhs not constant", subs, (EXC_FALSE_EXPS (e1, e2)))) - | Exp.Lfield(e1, fd1, _), Exp.Lfield(e2, fd2, _) when Typ.Fieldname.equal fd1 fd2 -> - do_imply subs e1 e2 - | Exp.Lindex(e1, f1), Exp.Lindex(e2, f2) -> - do_imply (do_imply subs e1 e2) f1 f2 - | Exp.Exn e1, Exp.Exn e2 -> - do_imply subs e1 e2 - | _ -> - d_impl_err ("exp_imply not implemented", subs, (EXC_FALSE_EXPS (e1, e2))); - raise (Exceptions.Abduction_case_not_implemented __POS__) in + else raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Lvar v1, Exp.Lvar v2 + -> if Pvar.equal v1 v2 then subs + else raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Const c1, Exp.Const c2 + -> if Const.equal c1 c2 then subs + else raise (IMPL_EXC ("constants not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Const Const.Cint _, Exp.BinOp (Binop.PlusPI, _, _) + -> raise + (IMPL_EXC ("pointer+index cannot evaluate to a constant", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Const Const.Cint n1, Exp.BinOp (Binop.PlusA, f1, Exp.Const Const.Cint n2) + -> do_imply subs (Exp.int (n1 -- n2)) f1 + | Exp.BinOp (op1, e1, f1), Exp.BinOp (op2, e2, f2) when Binop.equal op1 op2 + -> do_imply (do_imply subs e1 e2) f1 f2 + | Exp.BinOp (Binop.PlusA, Exp.Var v1, e1), e2 + -> do_imply subs (Exp.Var v1) (Exp.BinOp (Binop.MinusA, e2, e1)) + | Exp.BinOp (Binop.PlusPI, Exp.Lvar pv1, e1), e2 + -> do_imply subs (Exp.Lvar pv1) (Exp.BinOp (Binop.MinusA, e2, e1)) + | ( Exp.Sizeof {typ= t1; dynamic_length= None; subtype= st1} + , Exp.Sizeof {typ= t2; dynamic_length= None; subtype= st2} ) + when Typ.equal t1 t2 && Subtype.equal_modulo_flag st1 st2 + -> subs + | ( Exp.Sizeof {typ= t1; dynamic_length= Some d1; subtype= st1} + , Exp.Sizeof {typ= t2; dynamic_length= Some d2; subtype= st2} ) + when Typ.equal t1 t2 && Exp.equal d1 d2 && Subtype.equal_modulo_flag st1 st2 + -> subs + | e', Exp.Const Const.Cint n + when IntLit.iszero n && check_disequal tenv Prop.prop_emp e' Exp.zero + -> raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Const Const.Cint n, e' + when IntLit.iszero n && check_disequal tenv Prop.prop_emp e' Exp.zero + -> raise (IMPL_EXC ("expressions not equal", subs, EXC_FALSE_EXPS (e1, e2))) + | e1, Exp.Const _ + -> raise (IMPL_EXC ("lhs not constant", subs, EXC_FALSE_EXPS (e1, e2))) + | Exp.Lfield (e1, fd1, _), Exp.Lfield (e2, fd2, _) when Typ.Fieldname.equal fd1 fd2 + -> do_imply subs e1 e2 + | Exp.Lindex (e1, f1), Exp.Lindex (e2, f2) + -> do_imply (do_imply subs e1 e2) f1 f2 + | Exp.Exn e1, Exp.Exn e2 + -> do_imply subs e1 e2 + | _ + -> d_impl_err ("exp_imply not implemented", subs, EXC_FALSE_EXPS (e1, e2)) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) + in do_imply subs e1 e2 (** Convert a path (from lhs of a |-> to a field name present only in @@ -1274,247 +1397,291 @@ let exp_imply tenv calc_missing (subs : subst2) e1_in e2_in : subst2 = and stamp - 1 *) let path_to_id path = let rec f = function - | Exp.Var id -> - if Ident.is_footprint id then None - else Some (Ident.name_to_string (Ident.get_name id) ^ (string_of_int (Ident.get_stamp id))) - | Exp.Lfield (e, fld, _) -> - (match f e with - | None -> None - | Some s -> Some (s ^ "_" ^ (Typ.Fieldname.to_string fld))) - | Exp.Lindex (e, ind) -> - (match f e with - | None -> None - | Some s -> Some (s ^ "_" ^ (Exp.to_string ind))) - | Exp.Lvar _ -> - Some (Exp.to_string path) - | Exp.Const (Const.Cstr s) -> - Some ("_const_str_" ^ s) - | Exp.Const (Const.Cclass c) -> - Some ("_const_class_" ^ Ident.name_to_string c) - | Exp.Const _ -> None - | _ -> - L.d_str "path_to_id undefined on "; Sil.d_exp path; L.d_ln (); - assert false (* None *) in + | Exp.Var id + -> if Ident.is_footprint id then None + else Some (Ident.name_to_string (Ident.get_name id) ^ string_of_int (Ident.get_stamp id)) + | Exp.Lfield (e, fld, _) -> ( + match f e with None -> None | Some s -> Some (s ^ "_" ^ Typ.Fieldname.to_string fld) ) + | Exp.Lindex (e, ind) -> ( + match f e with None -> None | Some s -> Some (s ^ "_" ^ Exp.to_string ind) ) + | Exp.Lvar _ + -> Some (Exp.to_string path) + | Exp.Const Const.Cstr s + -> Some ("_const_str_" ^ s) + | Exp.Const Const.Cclass c + -> Some ("_const_class_" ^ Ident.name_to_string c) + | Exp.Const _ + -> None + | _ + -> L.d_str "path_to_id undefined on " ; + Sil.d_exp path ; + L.d_ln () ; + assert false + (* None *) + in if !Config.footprint then Ident.create_fresh Ident.kfootprint - else match f path with - | None -> Ident.create_fresh Ident.kfootprint - | Some s -> Ident.create_path s + else + match f path with None -> Ident.create_fresh Ident.kfootprint | Some s -> Ident.create_path s (** Implication for the length of arrays *) let array_len_imply tenv calc_missing subs len1 len2 indices2 = - match len1, len2 with + match (len1, len2) with | _, Exp.Var _ | _, Exp.BinOp (Binop.PlusA, Exp.Var _, _) | _, Exp.BinOp (Binop.PlusA, _, Exp.Var _) - | Exp.BinOp (Binop.Mult, _, _), _ -> - (try exp_imply tenv calc_missing subs len1 len2 with - | IMPL_EXC (s, subs', x) -> - raise (IMPL_EXC ("array len:" ^ s, subs', x))) - | _ -> - ProverState.add_bounds_check (ProverState.BClen_imply (len1, len2, indices2)); + | Exp.BinOp (Binop.Mult, _, _), _ -> ( + try exp_imply tenv calc_missing subs len1 len2 + with IMPL_EXC (s, subs', x) -> raise (IMPL_EXC ("array len:" ^ s, subs', x)) ) + | _ + -> ProverState.add_bounds_check (ProverState.BClen_imply (len1, len2, indices2)) ; subs (** Extend [sub1] and [sub2] to witnesses that each instance of [se1[sub1]] is an instance of [se2[sub2]]. Raise IMPL_FALSE if not possible. *) -let rec sexp_imply tenv source calc_index_frame calc_missing subs se1 se2 typ2 : subst2 * (Sil.strexp option) * (Sil.strexp option) = +let rec sexp_imply tenv source calc_index_frame calc_missing subs se1 se2 typ2 + : subst2 * Sil.strexp option * Sil.strexp option = (* L.d_str "sexp_imply "; Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_str " : "; Typ.d_full typ2; L.d_ln(); *) - match se1, se2 with - | Sil.Eexp (e1, _), Sil.Eexp (e2, _) -> - (exp_imply tenv calc_missing subs e1 e2, None, None) - | Sil.Estruct (fsel1, inst1), Sil.Estruct (fsel2, _) -> - let subs', fld_frame, fld_missing = struct_imply tenv source calc_missing subs fsel1 fsel2 typ2 in - let fld_frame_opt = if fld_frame <> [] then Some (Sil.Estruct (fld_frame, inst1)) else None in - let fld_missing_opt = if fld_missing <> [] then Some (Sil.Estruct (fld_missing, inst1)) else None in - subs', fld_frame_opt, fld_missing_opt - | Sil.Estruct _, Sil.Eexp (e2, _) -> - begin - let e2' = Sil.exp_sub (`Exp (snd subs)) e2 in - match e2' with - | Exp.Var id2 when Ident.is_primed id2 -> - let id2' = Ident.create_fresh Ident.knormal in - let sub2' = extend_sub (snd subs) id2 (Exp.Var id2') in - (fst subs, sub2'), None, None - | _ -> - d_impl_err ("sexp_imply not implemented", subs, (EXC_FALSE_SEXPS (se1, se2))); - raise (Exceptions.Abduction_case_not_implemented __POS__) - end - | Sil.Earray (len1, esel1, inst1), Sil.Earray (len2, esel2, _) -> - let indices2 = List.map ~f:fst esel2 in + match (se1, se2) with + | Sil.Eexp (e1, _), Sil.Eexp (e2, _) + -> (exp_imply tenv calc_missing subs e1 e2, None, None) + | Sil.Estruct (fsel1, inst1), Sil.Estruct (fsel2, _) + -> let subs', fld_frame, fld_missing = + struct_imply tenv source calc_missing subs fsel1 fsel2 typ2 + in + let fld_frame_opt = + if fld_frame <> [] then Some (Sil.Estruct (fld_frame, inst1)) else None + in + let fld_missing_opt = + if fld_missing <> [] then Some (Sil.Estruct (fld_missing, inst1)) else None + in + (subs', fld_frame_opt, fld_missing_opt) + | Sil.Estruct _, Sil.Eexp (e2, _) + -> ( + let e2' = Sil.exp_sub (`Exp (snd subs)) e2 in + match e2' with + | Exp.Var id2 when Ident.is_primed id2 + -> let id2' = Ident.create_fresh Ident.knormal in + let sub2' = extend_sub (snd subs) id2 (Exp.Var id2') in + ((fst subs, sub2'), None, None) + | _ + -> d_impl_err ("sexp_imply not implemented", subs, EXC_FALSE_SEXPS (se1, se2)) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) ) + | Sil.Earray (len1, esel1, inst1), Sil.Earray (len2, esel2, _) + -> let indices2 = List.map ~f:fst esel2 in let subs' = array_len_imply tenv calc_missing subs len1 len2 indices2 in let subs'', index_frame, index_missing = - array_imply tenv source calc_index_frame calc_missing subs' esel1 esel2 typ2 in - let index_frame_opt = if index_frame <> [] - then Some (Sil.Earray (len1, index_frame, inst1)) - else None in + array_imply tenv source calc_index_frame calc_missing subs' esel1 esel2 typ2 + in + let index_frame_opt = + if index_frame <> [] then Some (Sil.Earray (len1, index_frame, inst1)) else None + in let index_missing_opt = - if index_missing <> [] && - (Config.allow_missing_index_in_proc_call || !Config.footprint) + if index_missing <> [] && (Config.allow_missing_index_in_proc_call || !Config.footprint) then Some (Sil.Earray (len1, index_missing, inst1)) - else None in - subs'', index_frame_opt, index_missing_opt - | Sil.Eexp (_, inst), Sil.Estruct (fsel, inst') -> - d_impl_err ("WARNING: function call with parameters of struct type, treating as unknown", subs, (EXC_FALSE_SEXPS (se1, se2))); + else None + in + (subs'', index_frame_opt, index_missing_opt) + | Sil.Eexp (_, inst), Sil.Estruct (fsel, inst') + -> d_impl_err + ( "WARNING: function call with parameters of struct type, treating as unknown" + , subs + , EXC_FALSE_SEXPS (se1, se2) ) ; let fsel' = let g (f, _) = (f, Sil.Eexp (Exp.Var (Ident.create_fresh Ident.knormal), inst)) in - List.map ~f:g fsel in - sexp_imply tenv source calc_index_frame calc_missing subs (Sil.Estruct (fsel', inst')) se2 typ2 - | Sil.Eexp _, Sil.Earray (len, _, inst) - | Sil.Estruct _, Sil.Earray (len, _, inst) -> - let se1' = Sil.Earray (len, [(Exp.zero, se1)], inst) in + List.map ~f:g fsel + in + sexp_imply tenv source calc_index_frame calc_missing subs (Sil.Estruct (fsel', inst')) se2 + typ2 + | Sil.Eexp _, Sil.Earray (len, _, inst) | Sil.Estruct _, Sil.Earray (len, _, inst) + -> let se1' = Sil.Earray (len, [(Exp.zero, se1)], inst) in sexp_imply tenv source calc_index_frame calc_missing subs se1' se2 typ2 - | Sil.Earray (len, _, _), Sil.Eexp (_, inst) -> - let se2' = Sil.Earray (len, [(Exp.zero, se2)], inst) in + | Sil.Earray (len, _, _), Sil.Eexp (_, inst) + -> let se2' = Sil.Earray (len, [(Exp.zero, se2)], inst) in let typ2' = Typ.mk (Tarray (typ2, None, None)) in (* In the sexp_imply, struct_imply, array_imply, and sexp_imply_nolhs functions, the typ2 argument is only used by eventually passing its value to Typ.Struct.fld, Exp.Lfield, Typ.Struct.fld, or Typ.array_elem. None of these are sensitive to the length field of Tarray, so forgetting the length of typ2' here is not a problem. Not one of those functions use typ.quals either *) - sexp_imply tenv source true calc_missing subs se1 se2' typ2' (* calculate index_frame because the rhs is a singleton array *) - | _ -> - d_impl_err ("sexp_imply not implemented", subs, (EXC_FALSE_SEXPS (se1, se2))); + sexp_imply tenv source true calc_missing subs se1 se2' typ2' + (* calculate index_frame because the rhs is a singleton array *) + | _ + -> d_impl_err ("sexp_imply not implemented", subs, EXC_FALSE_SEXPS (se1, se2)) ; raise (Exceptions.Abduction_case_not_implemented __POS__) -and struct_imply tenv source calc_missing subs fsel1 fsel2 typ2 : subst2 * ((Typ.Fieldname.t * Sil.strexp) list) * ((Typ.Fieldname.t * Sil.strexp) list) = +and struct_imply tenv source calc_missing subs fsel1 fsel2 typ2 + : subst2 * (Typ.Fieldname.t * Sil.strexp) list * (Typ.Fieldname.t * Sil.strexp) list = let lookup = Tenv.lookup tenv in - match fsel1, fsel2 with - | _, [] -> subs, fsel1, [] - | (f1, se1) :: fsel1', (f2, se2) :: fsel2' -> - begin - match Typ.Fieldname.compare f1 f2 with - | 0 -> - let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in - let subs', se_frame, se_missing = - sexp_imply tenv (Exp.Lfield (source, f2, typ2)) false calc_missing subs se1 se2 typ' in - let subs'', fld_frame, fld_missing = struct_imply tenv source calc_missing subs' fsel1' fsel2' typ2 in - let fld_frame' = match se_frame with - | None -> fld_frame - | Some se -> (f1, se):: fld_frame in - let fld_missing' = match se_missing with - | None -> fld_missing - | Some se -> (f1, se):: fld_missing in - subs'', fld_frame', fld_missing' - | n when n < 0 -> - let subs', fld_frame, fld_missing = struct_imply tenv source calc_missing subs fsel1' fsel2 typ2 in - subs', ((f1, se1) :: fld_frame), fld_missing - | _ -> - let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in - let subs' = - sexp_imply_nolhs tenv (Exp.Lfield (source, f2, typ2)) calc_missing subs se2 typ' in - let subs', fld_frame, fld_missing = struct_imply tenv source calc_missing subs' fsel1 fsel2' typ2 in - let fld_missing' = (f2, se2) :: fld_missing in - subs', fld_frame, fld_missing' - end - | [], (f2, se2) :: fsel2' -> - let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in - let subs' = sexp_imply_nolhs tenv (Exp.Lfield (source, f2, typ2)) calc_missing subs se2 typ' in - let subs'', fld_frame, fld_missing = struct_imply tenv source calc_missing subs' [] fsel2' typ2 in - subs'', fld_frame, (f2, se2):: fld_missing + match (fsel1, fsel2) with + | _, [] + -> (subs, fsel1, []) + | (f1, se1) :: fsel1', (f2, se2) :: fsel2' -> ( + match Typ.Fieldname.compare f1 f2 with + | 0 + -> let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in + let subs', se_frame, se_missing = + sexp_imply tenv (Exp.Lfield (source, f2, typ2)) false calc_missing subs se1 se2 typ' + in + let subs'', fld_frame, fld_missing = + struct_imply tenv source calc_missing subs' fsel1' fsel2' typ2 + in + let fld_frame' = + match se_frame with None -> fld_frame | Some se -> (f1, se) :: fld_frame + in + let fld_missing' = + match se_missing with None -> fld_missing | Some se -> (f1, se) :: fld_missing + in + (subs'', fld_frame', fld_missing') + | n when n < 0 + -> let subs', fld_frame, fld_missing = + struct_imply tenv source calc_missing subs fsel1' fsel2 typ2 + in + (subs', (f1, se1) :: fld_frame, fld_missing) + | _ + -> let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in + let subs' = + sexp_imply_nolhs tenv (Exp.Lfield (source, f2, typ2)) calc_missing subs se2 typ' + in + let subs', fld_frame, fld_missing = + struct_imply tenv source calc_missing subs' fsel1 fsel2' typ2 + in + let fld_missing' = (f2, se2) :: fld_missing in + (subs', fld_frame, fld_missing') ) + | [], (f2, se2) :: fsel2' + -> let typ' = Typ.Struct.fld_typ ~lookup ~default:(Typ.mk Tvoid) f2 typ2 in + let subs' = + sexp_imply_nolhs tenv (Exp.Lfield (source, f2, typ2)) calc_missing subs se2 typ' + in + let subs'', fld_frame, fld_missing = + struct_imply tenv source calc_missing subs' [] fsel2' typ2 + in + (subs'', fld_frame, (f2, se2) :: fld_missing) and array_imply tenv source calc_index_frame calc_missing subs esel1 esel2 typ2 - : subst2 * ((Exp.t * Sil.strexp) list) * ((Exp.t * Sil.strexp) list) - = + : subst2 * (Exp.t * Sil.strexp) list * (Exp.t * Sil.strexp) list = let typ_elem = Typ.array_elem (Some (Typ.mk Tvoid)) typ2 in - match esel1, esel2 with - | _,[] -> subs, esel1, [] - | (e1, se1) :: esel1', (e2, se2) :: esel2' -> - let e1n = Prop.exp_normalize_noabs tenv (`Exp (fst subs)) e1 in + match (esel1, esel2) with + | _, [] + -> (subs, esel1, []) + | (e1, se1) :: esel1', (e2, se2) :: esel2' + -> let e1n = Prop.exp_normalize_noabs tenv (`Exp (fst subs)) e1 in let e2n = Prop.exp_normalize_noabs tenv (`Exp (snd subs)) e2 in let n = Exp.compare e1n e2n in if n < 0 then array_imply tenv source calc_index_frame calc_missing subs esel1' esel2 typ2 - else if n > 0 then array_imply tenv source calc_index_frame calc_missing subs esel1 esel2' typ2 - else (* n=0 *) + else if n > 0 then + array_imply tenv source calc_index_frame calc_missing subs esel1 esel2' typ2 + else + (* n=0 *) let subs', _, _ = - sexp_imply tenv (Exp.Lindex (source, e1)) false calc_missing subs se1 se2 typ_elem in + sexp_imply tenv (Exp.Lindex (source, e1)) false calc_missing subs se1 se2 typ_elem + in array_imply tenv source calc_index_frame calc_missing subs' esel1' esel2' typ2 - | [], (e2, se2) :: esel2' -> - let subs' = sexp_imply_nolhs tenv (Exp.Lindex (source, e2)) calc_missing subs se2 typ_elem in - let subs'', index_frame, index_missing = array_imply tenv source calc_index_frame calc_missing subs' [] esel2' typ2 in + | [], (e2, se2) :: esel2' + -> let subs' = sexp_imply_nolhs tenv (Exp.Lindex (source, e2)) calc_missing subs se2 typ_elem in + let subs'', index_frame, index_missing = + array_imply tenv source calc_index_frame calc_missing subs' [] esel2' typ2 + in let index_missing' = (e2, se2) :: index_missing in - subs'', index_frame, index_missing' + (subs'', index_frame, index_missing') -and sexp_imply_nolhs tenv source calc_missing (subs : subst2) se2 typ2 = +and sexp_imply_nolhs tenv source calc_missing (subs: subst2) se2 typ2 = match se2 with - | Sil.Eexp (_e2, _) -> + | Sil.Eexp (_e2, _) + -> ( let e2 = Sil.exp_sub (`Exp (snd subs)) _e2 in - begin - match e2 with - | Exp.Var v2 when Ident.is_primed v2 -> - let v2' = path_to_id source in - (* L.d_str "called path_to_id on "; Sil.d_exp e2; *) - (* L.d_str " returns "; Sil.d_exp (Exp.Var v2'); L.d_ln (); *) - let sub2' = extend_sub (snd subs) v2 (Exp.Var v2') in - (fst subs, sub2') - | Exp.Var _ -> - if calc_missing then subs - else raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) - | Exp.Const _ when calc_missing -> - let id = path_to_id source in - ProverState.add_missing_pi (Sil.Aeq (Exp.Var id, _e2)); - subs - | _ -> - raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) - end - | Sil.Estruct (fsel2, _) -> - (fun (x, _, _) -> x) (struct_imply tenv source calc_missing subs [] fsel2 typ2) - | Sil.Earray (_, esel2, _) -> - (fun (x, _, _) -> x) (array_imply tenv source false calc_missing subs [] esel2 typ2) - -let rec exp_list_imply tenv calc_missing subs l1 l2 = match l1, l2 with - | [],[] -> subs - | e1:: l1, e2:: l2 -> - exp_list_imply tenv calc_missing (exp_imply tenv calc_missing subs e1 e2) l1 l2 - | _ -> assert false + match e2 with + | Exp.Var v2 when Ident.is_primed v2 + -> let v2' = path_to_id source in + (* L.d_str "called path_to_id on "; Sil.d_exp e2; *) + (* L.d_str " returns "; Sil.d_exp (Exp.Var v2'); L.d_ln (); *) + let sub2' = extend_sub (snd subs) v2 (Exp.Var v2') in + (fst subs, sub2') + | Exp.Var _ + -> if calc_missing then subs + else raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) + | Exp.Const _ when calc_missing + -> let id = path_to_id source in + ProverState.add_missing_pi (Sil.Aeq (Exp.Var id, _e2)) ; + subs + | _ + -> raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) ) + | Sil.Estruct (fsel2, _) + -> (fun (x, _, _) -> x) (struct_imply tenv source calc_missing subs [] fsel2 typ2) + | Sil.Earray (_, esel2, _) + -> (fun (x, _, _) -> x) (array_imply tenv source false calc_missing subs [] esel2 typ2) + +let rec exp_list_imply tenv calc_missing subs l1 l2 = + match (l1, l2) with + | [], [] + -> subs + | e1 :: l1, e2 :: l2 + -> exp_list_imply tenv calc_missing (exp_imply tenv calc_missing subs e1 e2) l1 l2 + | _ + -> assert false let filter_ne_lhs sub e0 = function - | Sil.Hpointsto (e, _, _) -> if Exp.equal e0 (Sil.exp_sub sub e) then Some () else None - | Sil.Hlseg (Sil.Lseg_NE, _, e, _, _) -> - if Exp.equal e0 (Sil.exp_sub sub e) then Some () else None - | Sil.Hdllseg (Sil.Lseg_NE, _, e, _, _, e', _) -> - if Exp.equal e0 (Sil.exp_sub sub e) || Exp.equal e0 (Sil.exp_sub sub e') - then Some () + | Sil.Hpointsto (e, _, _) + -> if Exp.equal e0 (Sil.exp_sub sub e) then Some () else None + | Sil.Hlseg (Sil.Lseg_NE, _, e, _, _) + -> if Exp.equal e0 (Sil.exp_sub sub e) then Some () else None + | Sil.Hdllseg (Sil.Lseg_NE, _, e, _, _, e', _) + -> if Exp.equal e0 (Sil.exp_sub sub e) || Exp.equal e0 (Sil.exp_sub sub e') then Some () else None - | _ -> None + | _ + -> None -let filter_hpred sub hpred2 hpred1 = match (Sil.hpred_sub (`Exp sub) hpred1), hpred2 with - | Sil.Hlseg(Sil.Lseg_NE, hpara1, e1, f1, el1), Sil.Hlseg(Sil.Lseg_PE, _, _, _, _) -> - if Sil.equal_hpred (Sil.Hlseg(Sil.Lseg_PE, hpara1, e1, f1, el1)) hpred2 then Some false else None - | Sil.Hlseg(Sil.Lseg_PE, hpara1, e1, f1, el1), Sil.Hlseg(Sil.Lseg_NE, _, _, _, _) -> - if Sil.equal_hpred (Sil.Hlseg(Sil.Lseg_NE, hpara1, e1, f1, el1)) hpred2 then Some true else None (* return missing disequality *) - | Sil.Hpointsto(e1, _, _), Sil.Hlseg(_, _, e2, _, _) -> - if Exp.equal e1 e2 then Some false else None - | hpred1, hpred2 -> if Sil.equal_hpred hpred1 hpred2 then Some false else None +let filter_hpred sub hpred2 hpred1 = + match (Sil.hpred_sub (`Exp sub) hpred1, hpred2) with + | Sil.Hlseg (Sil.Lseg_NE, hpara1, e1, f1, el1), Sil.Hlseg (Sil.Lseg_PE, _, _, _, _) + -> if Sil.equal_hpred (Sil.Hlseg (Sil.Lseg_PE, hpara1, e1, f1, el1)) hpred2 then Some false + else None + | Sil.Hlseg (Sil.Lseg_PE, hpara1, e1, f1, el1), Sil.Hlseg (Sil.Lseg_NE, _, _, _, _) + -> if Sil.equal_hpred (Sil.Hlseg (Sil.Lseg_NE, hpara1, e1, f1, el1)) hpred2 then Some true + else None + (* return missing disequality *) + | Sil.Hpointsto (e1, _, _), Sil.Hlseg (_, _, e2, _, _) + -> if Exp.equal e1 e2 then Some false else None + | hpred1, hpred2 + -> if Sil.equal_hpred hpred1 hpred2 then Some false else None let hpred_has_primed_lhs sub hpred = - let rec find_primed e = match e with - | Exp.Lfield (e, _, _) -> - find_primed e - | Exp.Lindex (e, _) -> - find_primed e - | Exp.BinOp (Binop.PlusPI, e1, _) -> - find_primed e1 - | _ -> - Sil.fav_exists (Sil.exp_fav e) Ident.is_primed in + let rec find_primed e = + match e with + | Exp.Lfield (e, _, _) + -> find_primed e + | Exp.Lindex (e, _) + -> find_primed e + | Exp.BinOp (Binop.PlusPI, e1, _) + -> find_primed e1 + | _ + -> Sil.fav_exists (Sil.exp_fav e) Ident.is_primed + in let exp_has_primed e = find_primed (Sil.exp_sub sub e) in match hpred with - | Sil.Hpointsto (e, _, _) -> - exp_has_primed e - | Sil.Hlseg (_, _, e, _, _) -> - exp_has_primed e - | Sil.Hdllseg (_, _, iF, _, _, iB, _) -> - exp_has_primed iF && exp_has_primed iB - -let move_primed_lhs_from_front subs sigma = match sigma with - | [] -> sigma - | hpred:: _ -> - if hpred_has_primed_lhs (`Exp (snd subs)) hpred then - let (sigma_primed, sigma_unprimed) = + | Sil.Hpointsto (e, _, _) + -> exp_has_primed e + | Sil.Hlseg (_, _, e, _, _) + -> exp_has_primed e + | Sil.Hdllseg (_, _, iF, _, _, iB, _) + -> exp_has_primed iF && exp_has_primed iB + +let move_primed_lhs_from_front subs sigma = + match sigma with + | [] + -> sigma + | hpred :: _ + -> if hpred_has_primed_lhs (`Exp (snd subs)) hpred then + let sigma_primed, sigma_unprimed = List.partition_tf ~f:(hpred_has_primed_lhs (`Exp (snd subs))) sigma - in match sigma_unprimed with - | [] -> raise (IMPL_EXC ("every hpred has primed lhs, cannot proceed", subs, (EXC_FALSE_SIGMA sigma))) - | _:: _ -> sigma_unprimed @ sigma_primed + in + match sigma_unprimed with + | [] + -> raise + (IMPL_EXC ("every hpred has primed lhs, cannot proceed", subs, EXC_FALSE_SIGMA sigma)) + | _ :: _ + -> sigma_unprimed @ sigma_primed else sigma (** [expand_hpred_pointer calc_index_frame hpred] expands [hpred] if it is a |-> whose lhs is a Lfield or Lindex or ptr+off. @@ -1522,150 +1689,180 @@ let move_primed_lhs_from_front subs sigma = match sigma with let expand_hpred_pointer = let count = ref 0 in fun tenv calc_index_frame hpred -> - let rec expand changed calc_index_frame hpred = match hpred with - | Sil.Hpointsto (Lfield (adr_base, fld, adr_typ), cnt, cnt_texp) -> - let cnt_texp' = + let rec expand changed calc_index_frame hpred = + match hpred with + | Sil.Hpointsto (Lfield (adr_base, fld, adr_typ), cnt, cnt_texp) + -> let cnt_texp' = match match adr_typ.desc with | Tstruct name -> ( - match Tenv.lookup tenv name with - | Some _ -> - (* type of struct at adr_base is known *) - Some (Exp.Sizeof {typ=adr_typ; nbytes=None; - dynamic_length=None; subtype=Subtype.exact}) - | None -> None - ) - | _ -> None + match Tenv.lookup tenv name with + | Some _ + -> (* type of struct at adr_base is known *) + Some + (Exp.Sizeof + {typ= adr_typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact}) + | None + -> None ) + | _ + -> None with - | Some se -> se + | Some se + -> se | None -> - match cnt_texp with - | Sizeof ({typ=cnt_typ} as sizeof_data) -> - (* type of struct at adr_base is unknown (typically Tvoid), but + match cnt_texp with + | Sizeof ({typ= cnt_typ} as sizeof_data) + -> (* type of struct at adr_base is unknown (typically Tvoid), but type of contents is known, so construct struct type for single fld:cnt_typ *) - let name = Typ.Name.C.from_string ("counterfeit" ^ string_of_int !count) in - incr count ; - let fields = [(fld, cnt_typ, Annot.Item.empty)] in - ignore (Tenv.mk_struct tenv ~fields name) ; - Exp.Sizeof {sizeof_data with typ=Typ.mk (Tstruct name)} - | _ -> - (* type of struct at adr_base and of contents are both unknown: give up *) - raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lfield") in - let hpred' = Sil.Hpointsto (adr_base, Estruct ([(fld, cnt)], Sil.inst_none), cnt_texp') in + let name = Typ.Name.C.from_string ("counterfeit" ^ string_of_int !count) in + incr count ; + let fields = [(fld, cnt_typ, Annot.Item.empty)] in + ignore (Tenv.mk_struct tenv ~fields name) ; + Exp.Sizeof {sizeof_data with typ= Typ.mk (Tstruct name)} + | _ + -> (* type of struct at adr_base and of contents are both unknown: give up *) + raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lfield") + in + let hpred' = + Sil.Hpointsto (adr_base, Estruct ([(fld, cnt)], Sil.inst_none), cnt_texp') + in expand true true hpred' - | Sil.Hpointsto (Exp.Lindex (e, ind), se, t) -> - let t' = match t with - | Exp.Sizeof ({typ=t_} as sizeof_data) -> - Exp.Sizeof {sizeof_data with typ=Typ.mk (Tarray (t_, None, None))} - | _ -> raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lindex") in - let len = match t' with - | Exp.Sizeof {dynamic_length=Some len} -> len - | _ -> Exp.get_undefined false in + | Sil.Hpointsto (Exp.Lindex (e, ind), se, t) + -> let t' = + match t with + | Exp.Sizeof ({typ= t_} as sizeof_data) + -> Exp.Sizeof {sizeof_data with typ= Typ.mk (Tarray (t_, None, None))} + | _ + -> raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lindex") + in + let len = + match t' with + | Exp.Sizeof {dynamic_length= Some len} + -> len + | _ + -> Exp.get_undefined false + in let hpred' = Sil.Hpointsto (e, Sil.Earray (len, [(ind, se)], Sil.inst_none), t') in expand true true hpred' - | Sil.Hpointsto (Exp.BinOp (Binop.PlusPI, e1, e2), Sil.Earray (len, esel, inst), t) -> - let shift_exp e = Exp.BinOp (Binop.PlusA, e, e2) in + | Sil.Hpointsto (Exp.BinOp (Binop.PlusPI, e1, e2), Sil.Earray (len, esel, inst), t) + -> let shift_exp e = Exp.BinOp (Binop.PlusA, e, e2) in let len' = shift_exp len in let esel' = List.map ~f:(fun (e, se) -> (shift_exp e, se)) esel in let hpred' = Sil.Hpointsto (e1, Sil.Earray (len', esel', inst), t) in expand true calc_index_frame hpred' - | _ -> changed, calc_index_frame, hpred in + | _ + -> (changed, calc_index_frame, hpred) + in expand false calc_index_frame hpred -module Subtyping_check = -struct - +module Subtyping_check = struct (** check that t1 and t2 are the same primitive type *) let check_subtype_basic_type t1 t2 = match t2.Typ.desc with - | Typ.Tint Typ.IInt | Typ.Tint Typ.IBool - | Typ.Tint Typ.IChar | Typ.Tfloat Typ.FDouble - | Typ.Tfloat Typ.FFloat | Typ.Tint Typ.ILong - | Typ.Tint Typ.IShort -> Typ.equal t1 t2 - | _ -> false + | Typ.Tint Typ.IInt + | Typ.Tint Typ.IBool + | Typ.Tint Typ.IChar + | Typ.Tfloat Typ.FDouble + | Typ.Tfloat Typ.FFloat + | Typ.Tint Typ.ILong + | Typ.Tint Typ.IShort + -> Typ.equal t1 t2 + | _ + -> false (** check if t1 is a subtype of t2, in Java *) let rec check_subtype_java tenv (t1: Typ.t) (t2: Typ.t) = - match t1.Typ.desc, t2.Typ.desc with - | Tstruct (JavaClass _ as cn1), Tstruct (JavaClass _ as cn2) -> - Subtype.is_known_subtype tenv cn1 cn2 - | Tarray (dom_type1, _, _), Tarray (dom_type2, _, _) -> - check_subtype_java tenv dom_type1 dom_type2 - | Tptr (dom_type1, _), Tptr (dom_type2, _) -> - check_subtype_java tenv dom_type1 dom_type2 - | Tarray _, Tstruct (JavaClass _ as cn2) -> - Typ.Name.equal cn2 Typ.Name.Java.java_io_serializable + match (t1.Typ.desc, t2.Typ.desc) with + | Tstruct (JavaClass _ as cn1), Tstruct (JavaClass _ as cn2) + -> Subtype.is_known_subtype tenv cn1 cn2 + | Tarray (dom_type1, _, _), Tarray (dom_type2, _, _) + -> check_subtype_java tenv dom_type1 dom_type2 + | Tptr (dom_type1, _), Tptr (dom_type2, _) + -> check_subtype_java tenv dom_type1 dom_type2 + | Tarray _, Tstruct (JavaClass _ as cn2) + -> Typ.Name.equal cn2 Typ.Name.Java.java_io_serializable || Typ.Name.equal cn2 Typ.Name.Java.java_lang_cloneable || Typ.Name.equal cn2 Typ.Name.Java.java_lang_object - | _ -> check_subtype_basic_type t1 t2 + | _ + -> check_subtype_basic_type t1 t2 (** check if t1 is a subtype of t2 *) let check_subtype tenv t1 t2 = - if is_java_class tenv t1 - then - check_subtype_java tenv t1 t2 + if is_java_class tenv t1 then check_subtype_java tenv t1 t2 else - match Typ.name t1, Typ.name t2 with - | Some cn1, Some cn2 -> Subtype.is_known_subtype tenv cn1 cn2 - | _ -> false + match (Typ.name t1, Typ.name t2) with + | Some cn1, Some cn2 + -> Subtype.is_known_subtype tenv cn1 cn2 + | _ + -> false let rec case_analysis_type tenv ((t1: Typ.t), st1) ((t2: Typ.t), st2) = - match t1.desc, t2.desc with - | Tstruct (JavaClass _ as cn1), Tstruct (JavaClass _ as cn2) -> - Subtype.case_analysis tenv (cn1, st1) (cn2, st2) + match (t1.desc, t2.desc) with + | Tstruct (JavaClass _ as cn1), Tstruct (JavaClass _ as cn2) + -> Subtype.case_analysis tenv (cn1, st1) (cn2, st2) | Tstruct (JavaClass _ as cn1), Tarray _ - when (Typ.Name.equal cn1 Typ.Name.Java.java_io_serializable - || Typ.Name.equal cn1 Typ.Name.Java.java_lang_cloneable - || Typ.Name.equal cn1 Typ.Name.Java.java_lang_object) && - st1 <> Subtype.exact -> - Some st1, None + when ( Typ.Name.equal cn1 Typ.Name.Java.java_io_serializable + || Typ.Name.equal cn1 Typ.Name.Java.java_lang_cloneable + || Typ.Name.equal cn1 Typ.Name.Java.java_lang_object ) + && st1 <> Subtype.exact + -> (Some st1, None) | Tstruct cn1, Tstruct cn2 - (* cn1 <: cn2 or cn2 <: cn1 is implied in Java when we get two types compared *) - (* that get through the type system, but not in C++ because of multiple inheritance, *) - (* and not in ObjC because of being weakly typed, *) - (* and the algorithm will only work correctly if this is the case *) - when Subtype.is_known_subtype tenv cn1 cn2 || Subtype.is_known_subtype tenv cn2 cn1 -> - Subtype.case_analysis tenv (cn1, st1) (cn2, st2) - | Tarray (dom_type1, _, _), Tarray (dom_type2, _, _) -> - case_analysis_type tenv (dom_type1, st1) (dom_type2, st2) - | Tptr (dom_type1, _), Tptr (dom_type2, _) -> - case_analysis_type tenv (dom_type1, st1) (dom_type2, st2) - | _ when check_subtype_basic_type t1 t2 -> - Some st1, None - | _ -> - (* The case analysis did not succeed *) - None, Some st1 + (* cn1 <: cn2 or cn2 <: cn1 is implied in Java when we get two types compared *) + (* that get through the type system, but not in C++ because of multiple inheritance, *) + (* and not in ObjC because of being weakly typed, *) + (* and the algorithm will only work correctly if this is the case *) + when Subtype.is_known_subtype tenv cn1 cn2 || Subtype.is_known_subtype tenv cn2 cn1 + -> Subtype.case_analysis tenv (cn1, st1) (cn2, st2) + | Tarray (dom_type1, _, _), Tarray (dom_type2, _, _) + -> case_analysis_type tenv (dom_type1, st1) (dom_type2, st2) + | Tptr (dom_type1, _), Tptr (dom_type2, _) + -> case_analysis_type tenv (dom_type1, st1) (dom_type2, st2) + | _ when check_subtype_basic_type t1 t2 + -> (Some st1, None) + | _ + -> (* The case analysis did not succeed *) + (None, Some st1) (** perform case analysis on [texp1 <: texp2], and return the updated types in the true and false case, if they are possible *) let subtype_case_analysis tenv texp1 texp2 = - match texp1, texp2 with - | Exp.Sizeof sizeof1, Exp.Sizeof sizeof2 -> - let pos_opt, neg_opt = case_analysis_type tenv - (sizeof1.typ, sizeof1.subtype) (sizeof2.typ, sizeof2.subtype) in - let pos_type_opt = match pos_opt with - | None -> None - | Some subtype -> - if check_subtype tenv sizeof1.typ sizeof2.typ then + match (texp1, texp2) with + | Exp.Sizeof sizeof1, Exp.Sizeof sizeof2 + -> let pos_opt, neg_opt = + case_analysis_type tenv (sizeof1.typ, sizeof1.subtype) (sizeof2.typ, sizeof2.subtype) + in + let pos_type_opt = + match pos_opt with + | None + -> None + | Some subtype + -> if check_subtype tenv sizeof1.typ sizeof2.typ then Some (Exp.Sizeof {sizeof1 with subtype}) - else - Some (Exp.Sizeof {sizeof2 with subtype}) in - let neg_type_opt = match neg_opt with - | None -> None - | Some subtype -> Some (Exp.Sizeof {sizeof1 with subtype}) in - pos_type_opt, neg_type_opt - | _ -> (* don't know, consider both possibilities *) - Some texp1, Some texp1 + else Some (Exp.Sizeof {sizeof2 with subtype}) + in + let neg_type_opt = + match neg_opt with + | None + -> None + | Some subtype + -> Some (Exp.Sizeof {sizeof1 with subtype}) + in + (pos_type_opt, neg_type_opt) + | _ + -> (* don't know, consider both possibilities *) + (Some texp1, Some texp1) end let cast_exception tenv texp1 texp2 e1 subs = - let _ = match texp1, texp2 with - | Exp.Sizeof {typ=t1}, Exp.Sizeof {typ=t2; subtype=st2} -> - if Config.developer_mode || - (Subtype.is_cast st2 && - not (Subtyping_check.check_subtype tenv t1 t2)) then - ProverState.checks := Class_cast_check (texp1, texp2, e1) :: !ProverState.checks - | _ -> () in + let _ = + match (texp1, texp2) with + | Exp.Sizeof {typ= t1}, Exp.Sizeof {typ= t2; subtype= st2} + -> if Config.developer_mode + || Subtype.is_cast st2 && not (Subtyping_check.check_subtype tenv t1 t2) + then ProverState.checks := Class_cast_check (texp1, texp2, e1) :: !ProverState.checks + | _ + -> () + in raise (IMPL_EXC ("class cast exception", subs, EXC_FALSE)) (** get all methods that override [supertype].[pname] in [tenv]. @@ -1675,434 +1872,539 @@ let get_overrides_of tenv supertype pname = let typ_has_method pname (typ: Typ.t) = match typ.desc with | Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { methods } -> - List.exists ~f:(fun m -> Typ.Procname.equal pname m) methods - | None -> - false - ) - | _ -> false in + match Tenv.lookup tenv name with + | Some {methods} + -> List.exists ~f:(fun m -> Typ.Procname.equal pname m) methods + | None + -> false ) + | _ + -> false + in let gather_overrides tname _ overrides_acc = - let typ = Typ.mk (Tstruct tname) in (* TODO shouldn't really create type here...*) + let typ = Typ.mk (Tstruct tname) in + (* TODO shouldn't really create type here...*) (* get all types in the type environment that are non-reflexive subtypes of [supertype] *) if not (Typ.equal typ supertype) && Subtyping_check.check_subtype tenv typ supertype then (* only select the ones that implement [pname] as overrides *) - let resolved_pname = - Typ.Procname.replace_class pname tname in + let resolved_pname = Typ.Procname.replace_class pname tname in if typ_has_method resolved_pname typ then (typ, resolved_pname) :: overrides_acc else overrides_acc - else overrides_acc in + else overrides_acc + in Tenv.fold gather_overrides tenv [] (** Check the equality of two types ignoring flags in the subtyping components *) -let texp_equal_modulo_subtype_flag texp1 texp2 = match texp1, texp2 with - | Exp.Sizeof {typ=t1; dynamic_length=len1; subtype=st1}, - Exp.Sizeof {typ=t2; dynamic_length=len2; subtype=st2} -> - [%compare.equal: Typ.t * Exp.t option] (t1, len1) (t2, len2) +let texp_equal_modulo_subtype_flag texp1 texp2 = + match (texp1, texp2) with + | ( Exp.Sizeof {typ= t1; dynamic_length= len1; subtype= st1} + , Exp.Sizeof {typ= t2; dynamic_length= len2; subtype= st2} ) + -> [%compare.equal : Typ.t * Exp.t option] (t1, len1) (t2, len2) && Subtype.equal_modulo_flag st1 st2 - | _ -> Exp.equal texp1 texp2 + | _ + -> Exp.equal texp1 texp2 (** check implication between type expressions *) let texp_imply tenv subs texp1 texp2 e1 calc_missing = (* check whether the types could be subject to dynamic cast: *) (* classes and arrays in Java, and just classes in C++ and ObjC *) let types_subject_to_dynamic_cast = - match texp1, texp2 with - | Exp.Sizeof {typ=typ1}, Exp.Sizeof {typ=typ2} -> ( - match typ1.desc, typ2.desc with - | (Tstruct _ | Tarray _), (Tstruct _ | Tarray _) -> - is_java_class tenv typ1 - || (Typ.is_cpp_class typ1 && Typ.is_cpp_class typ2) - || (Typ.is_objc_class typ1 && Typ.is_objc_class typ2) - | _ -> - false - ) - | _ -> false in + match (texp1, texp2) with + | Exp.Sizeof {typ= typ1}, Exp.Sizeof {typ= typ2} -> ( + match (typ1.desc, typ2.desc) with + | (Tstruct _ | Tarray _), (Tstruct _ | Tarray _) + -> is_java_class tenv typ1 || Typ.is_cpp_class typ1 && Typ.is_cpp_class typ2 + || Typ.is_objc_class typ1 && Typ.is_objc_class typ2 + | _ + -> false ) + | _ + -> false + in if types_subject_to_dynamic_cast then - begin - let pos_type_opt, neg_type_opt = Subtyping_check.subtype_case_analysis tenv texp1 texp2 in - let has_changed = match pos_type_opt with - | Some texp1' -> - not (texp_equal_modulo_subtype_flag texp1' texp1) - | None -> false in - if calc_missing then (* footprint *) - begin - match pos_type_opt with - | None -> cast_exception tenv texp1 texp2 e1 subs - | Some _ -> - if has_changed then None, pos_type_opt (* missing *) - else pos_type_opt, None (* frame *) - end - else (* re-execution *) - begin - match neg_type_opt with - | Some _ -> cast_exception tenv texp1 texp2 e1 subs - | None -> - if has_changed then cast_exception tenv texp1 texp2 e1 subs (* missing *) - else pos_type_opt, None (* frame *) - end - end - else - None, None + let pos_type_opt, neg_type_opt = Subtyping_check.subtype_case_analysis tenv texp1 texp2 in + let has_changed = + match pos_type_opt with + | Some texp1' + -> not (texp_equal_modulo_subtype_flag texp1' texp1) + | None + -> false + in + if calc_missing then + (* footprint *) + match pos_type_opt with + | None + -> cast_exception tenv texp1 texp2 e1 subs + | Some _ + -> if has_changed then (None, pos_type_opt) (* missing *) else (pos_type_opt, None) + (* frame *) + else + (* re-execution *) + match neg_type_opt with + | Some _ + -> cast_exception tenv texp1 texp2 e1 subs + | None + -> if has_changed then cast_exception tenv texp1 texp2 e1 subs (* missing *) + else (pos_type_opt, None) (* frame *) + else (None, None) (** pre-process implication between a non-array and an array: the non-array is turned into an array of length given by its type only active in type_size mode *) -let sexp_imply_preprocess se1 texp1 se2 = match se1, texp1, se2 with - | Sil.Eexp (_, inst), Exp.Sizeof _, Sil.Earray _ when Config.type_size -> - let se1' = Sil.Earray (texp1, [(Exp.zero, se1)], inst) in - L.d_strln_color Orange "sexp_imply_preprocess"; L.d_str " se1: "; Sil.d_sexp se1; L.d_ln (); L.d_str " se1': "; Sil.d_sexp se1'; L.d_ln (); +let sexp_imply_preprocess se1 texp1 se2 = + match (se1, texp1, se2) with + | Sil.Eexp (_, inst), Exp.Sizeof _, Sil.Earray _ when Config.type_size + -> let se1' = Sil.Earray (texp1, [(Exp.zero, se1)], inst) in + L.d_strln_color Orange "sexp_imply_preprocess" ; + L.d_str " se1: " ; + Sil.d_sexp se1 ; + L.d_ln () ; + L.d_str " se1': " ; + Sil.d_sexp se1' ; + L.d_ln () ; se1' - | _ -> se1 + | _ + -> se1 (** handle parameter subtype: when the type of a callee variable in the caller is a strict subtype of the one in the callee, add a type frame and type missing *) let handle_parameter_subtype tenv prop1 sigma2 subs (e1, se1, texp1) (se2, texp2) = - let is_callee = match e1 with - | Exp.Lvar pv -> Pvar.is_callee pv - | _ -> false in + let is_callee = match e1 with Exp.Lvar pv -> Pvar.is_callee pv | _ -> false in let is_allocated_lhs e = - let filter = function - | Sil.Hpointsto(e', _, _) -> Exp.equal e' e - | _ -> false in - List.exists ~f:filter prop1.Prop.sigma in + let filter = function Sil.Hpointsto (e', _, _) -> Exp.equal e' e | _ -> false in + List.exists ~f:filter prop1.Prop.sigma + in let type_rhs e = let sub_opt = ref None in let filter = function - | Sil.Hpointsto(e', _, Exp.Sizeof sizeof_data) when Exp.equal e' e -> - sub_opt := Some sizeof_data; + | Sil.Hpointsto (e', _, Exp.Sizeof sizeof_data) when Exp.equal e' e + -> sub_opt := Some sizeof_data ; true - | _ -> false in - if List.exists ~f:filter sigma2 then !sub_opt else None in - let add_subtype () = match texp1, texp2, se1, se2 with - | Exp.Sizeof {typ={desc=Tptr (t1, _)}; dynamic_length=None}, - Exp.Sizeof {typ={desc=Tptr (t2, _)}; dynamic_length=None}, - Sil.Eexp (e1', _), Sil.Eexp (e2', _) - when not (is_allocated_lhs e1') -> - begin - match type_rhs e2' with - | Some sizeof_data2 -> - if not (Typ.equal t1 t2) && Subtyping_check.check_subtype tenv t1 t2 - then begin - let pos_type_opt, _ = - Subtyping_check.subtype_case_analysis tenv - (Exp.Sizeof {typ=t1; nbytes=None; - dynamic_length=None; subtype=Subtype.subtypes}) - (Exp.Sizeof sizeof_data2) in - match pos_type_opt with - | Some t1_noptr -> - ProverState.add_frame_typ (e1', t1_noptr); - ProverState.add_missing_typ (e2', t1_noptr) - | None -> cast_exception tenv texp1 texp2 e1 subs - end - | None -> () - end - | _ -> () in + | _ + -> false + in + if List.exists ~f:filter sigma2 then !sub_opt else None + in + let add_subtype () = + match (texp1, texp2, se1, se2) with + | ( Exp.Sizeof {typ= {desc= Tptr (t1, _)}; dynamic_length= None} + , Exp.Sizeof {typ= {desc= Tptr (t2, _)}; dynamic_length= None} + , Sil.Eexp (e1', _) + , Sil.Eexp (e2', _) ) + when not (is_allocated_lhs e1') -> ( + match type_rhs e2' with + | Some sizeof_data2 + -> ( + if not (Typ.equal t1 t2) && Subtyping_check.check_subtype tenv t1 t2 then + let pos_type_opt, _ = + Subtyping_check.subtype_case_analysis tenv + (Exp.Sizeof {typ= t1; nbytes= None; dynamic_length= None; subtype= Subtype.subtypes}) + (Exp.Sizeof sizeof_data2) + in + match pos_type_opt with + | Some t1_noptr + -> ProverState.add_frame_typ (e1', t1_noptr) ; + ProverState.add_missing_typ (e2', t1_noptr) + | None + -> cast_exception tenv texp1 texp2 e1 subs ) + | None + -> () ) + | _ + -> () + in if is_callee && !Config.footprint then add_subtype () -let rec hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2 : subst2 * Prop.normal Prop.t = match hpred2 with - | Sil.Hpointsto (_e2, se2, texp2) -> +let rec hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2 + : subst2 * Prop.normal Prop.t = + match hpred2 with + | Sil.Hpointsto (_e2, se2, texp2) + -> ( let e2 = Sil.exp_sub (`Exp (snd subs)) _e2 in - let _ = match e2 with - | Exp.Lvar _ -> () - | Exp.Var v -> if Ident.is_primed v then - (d_impl_err ("rhs |-> not implemented", subs, (EXC_FALSE_HPRED hpred2)); - raise (Exceptions.Abduction_case_not_implemented __POS__)) - | _ -> () in - (match Prop.prop_iter_create prop1 with - | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) - | Some iter1 -> - (match Prop.prop_iter_find iter1 (filter_ne_lhs (`Exp (fst subs)) e2) with - | None -> raise (IMPL_EXC ("lhs does not have e|->", subs, (EXC_FALSE_HPRED hpred2))) - | Some iter1' -> - (match Prop.prop_iter_current tenv iter1' with - | Sil.Hpointsto (e1, se1, texp1), _ -> - (try - let typ2 = Exp.texp_to_typ (Some (Typ.mk Tvoid)) texp2 in - let typing_frame, typing_missing = texp_imply tenv subs texp1 texp2 e1 calc_missing in - let se1' = sexp_imply_preprocess se1 texp1 se2 in - let subs', fld_frame, fld_missing = sexp_imply tenv e1 calc_index_frame calc_missing subs se1' se2 typ2 in - if calc_missing then - begin - handle_parameter_subtype tenv prop1 sigma2 subs (e1, se1, texp1) (se2, texp2); - (match fld_missing with - | Some fld_missing -> - ProverState.add_missing_fld (Sil.Hpointsto(_e2, fld_missing, texp1)) - | None -> ()); - (match fld_frame with - | Some fld_frame -> - ProverState.add_frame_fld (Sil.Hpointsto(e1, fld_frame, texp1)) - | None -> ()); - (match typing_missing with - | Some t_missing -> - ProverState.add_missing_typ (_e2, t_missing) - | None -> ()); - (match typing_frame with - | Some t_frame -> - ProverState.add_frame_typ (e1, t_frame) - | None -> ()) - end; - let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' - in (subs', prop1') - with - | IMPL_EXC (s, _, _) when calc_missing -> - raise (MISSING_EXC s)) - | Sil.Hlseg (Sil.Lseg_NE, para1, e1, f1, elist1), _ -> (* Unroll lseg *) - let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_instantiate para1 e1 n' elist1 in - let hpred_list1 = para_inst1@[Prop.mk_lseg tenv Sil.Lseg_PE para1 n' f1 elist1] in - let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2) in - L.d_decrease_indent 1; - res - | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ - when Exp.equal (Sil.exp_sub (`Exp (fst subs)) iF1) e2 -> (* Unroll dllseg forward *) - let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_dll_instantiate para1 iF1 oB1 n' elist1 in - let hpred_list1 = para_inst1@[Prop.mk_dllseg tenv Sil.Lseg_PE para1 n' iF1 oF1 iB1 elist1] in - let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2) in - L.d_decrease_indent 1; - res - | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ - when Exp.equal (Sil.exp_sub (`Exp (fst subs)) iB1) e2 -> (* Unroll dllseg backward *) - let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_dll_instantiate para1 iB1 n' oF1 elist1 in - let hpred_list1 = para_inst1@[Prop.mk_dllseg tenv Sil.Lseg_PE para1 iF1 oB1 iB1 n' elist1] in - let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2) in - L.d_decrease_indent 1; - res - | _ -> assert false - ) - ) - ) - | Sil.Hlseg (k, para2, _e2, _f2, _elist2) -> (* for now ignore implications between PE and NE *) - let e2, f2 = Sil.exp_sub (`Exp (snd subs)) _e2, Sil.exp_sub (`Exp (snd subs)) _f2 in - let _ = match e2 with - | Exp.Lvar _ -> () - | Exp.Var v -> if Ident.is_primed v then - (d_impl_err ("rhs |-> not implemented", subs, (EXC_FALSE_HPRED hpred2)); - raise (Exceptions.Abduction_case_not_implemented __POS__)) - | _ -> () + let _ = + match e2 with + | Exp.Lvar _ + -> () + | Exp.Var v + -> if Ident.is_primed v then ( + d_impl_err ("rhs |-> not implemented", subs, EXC_FALSE_HPRED hpred2) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) ) + | _ + -> () + in + match Prop.prop_iter_create prop1 with + | None + -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + match Prop.prop_iter_find iter1 (filter_ne_lhs (`Exp (fst subs)) e2) with + | None + -> raise (IMPL_EXC ("lhs does not have e|->", subs, EXC_FALSE_HPRED hpred2)) + | Some iter1' -> + match Prop.prop_iter_current tenv iter1' with + | Sil.Hpointsto (e1, se1, texp1), _ -> ( + try + let typ2 = Exp.texp_to_typ (Some (Typ.mk Tvoid)) texp2 in + let typing_frame, typing_missing = + texp_imply tenv subs texp1 texp2 e1 calc_missing + in + let se1' = sexp_imply_preprocess se1 texp1 se2 in + let subs', fld_frame, fld_missing = + sexp_imply tenv e1 calc_index_frame calc_missing subs se1' se2 typ2 + in + if calc_missing then ( + handle_parameter_subtype tenv prop1 sigma2 subs (e1, se1, texp1) (se2, texp2) ; + ( match fld_missing with + | Some fld_missing + -> ProverState.add_missing_fld (Sil.Hpointsto (_e2, fld_missing, texp1)) + | None + -> () ) ; + ( match fld_frame with + | Some fld_frame + -> ProverState.add_frame_fld (Sil.Hpointsto (e1, fld_frame, texp1)) + | None + -> () ) ; + ( match typing_missing with + | Some t_missing + -> ProverState.add_missing_typ (_e2, t_missing) + | None + -> () ) ; + match typing_frame with + | Some t_frame + -> ProverState.add_frame_typ (e1, t_frame) + | None + -> () ) ; + let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' in + (subs', prop1') + with IMPL_EXC (s, _, _) when calc_missing -> raise (MISSING_EXC s) ) + | Sil.Hlseg (Sil.Lseg_NE, para1, e1, f1, elist1), _ + -> (* Unroll lseg *) + let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in + let _, para_inst1 = Sil.hpara_instantiate para1 e1 n' elist1 in + let hpred_list1 = para_inst1 @ [Prop.mk_lseg tenv Sil.Lseg_PE para1 n' f1 elist1] in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + hpred_imply tenv calc_index_frame calc_missing subs + (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2 ) + in + L.d_decrease_indent 1 ; res + | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ + when Exp.equal (Sil.exp_sub (`Exp (fst subs)) iF1) e2 + -> (* Unroll dllseg forward *) + let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in + let _, para_inst1 = Sil.hpara_dll_instantiate para1 iF1 oB1 n' elist1 in + let hpred_list1 = + para_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_PE para1 n' iF1 oF1 iB1 elist1] + in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + hpred_imply tenv calc_index_frame calc_missing subs + (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2 ) + in + L.d_decrease_indent 1 ; res + | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ + when Exp.equal (Sil.exp_sub (`Exp (fst subs)) iB1) e2 + -> (* Unroll dllseg backward *) + let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in + let _, para_inst1 = Sil.hpara_dll_instantiate para1 iB1 n' oF1 elist1 in + let hpred_list1 = + para_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_PE para1 iF1 oB1 iB1 n' elist1] + in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + hpred_imply tenv calc_index_frame calc_missing subs + (Prop.prop_iter_to_prop tenv iter1'') sigma2 hpred2 ) + in + L.d_decrease_indent 1 ; res + | _ + -> assert false ) + | Sil.Hlseg (k, para2, _e2, _f2, _elist2) + -> ( + (* for now ignore implications between PE and NE *) + let e2, f2 = (Sil.exp_sub (`Exp (snd subs)) _e2, Sil.exp_sub (`Exp (snd subs)) _f2) in + let _ = + match e2 with + | Exp.Lvar _ + -> () + | Exp.Var v + -> if Ident.is_primed v then ( + d_impl_err ("rhs |-> not implemented", subs, EXC_FALSE_HPRED hpred2) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) ) + | _ + -> () in if Exp.equal e2 f2 && Sil.equal_lseg_kind k Sil.Lseg_PE then (subs, prop1) else - (match Prop.prop_iter_create prop1 with - | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) - | Some iter1 -> - (match Prop.prop_iter_find iter1 (filter_hpred (fst subs) (Sil.hpred_sub (`Exp (snd subs)) hpred2)) with - | None -> - let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) _elist2 in - let _, para_inst2 = Sil.hpara_instantiate para2 e2 f2 elist2 in - L.d_increase_indent 1; + match Prop.prop_iter_create prop1 with + | None + -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + match + Prop.prop_iter_find iter1 + (filter_hpred (fst subs) (Sil.hpred_sub (`Exp (snd subs)) hpred2)) + with + | None + -> let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) _elist2 in + let _, para_inst2 = Sil.hpara_instantiate para2 e2 f2 elist2 in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + sigma_imply tenv calc_index_frame false subs prop1 para_inst2 ) + in + (* calc_missing is false as we're checking an instantiation of the original list *) + L.d_decrease_indent 1 ; res + | Some iter1' + -> let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) _elist2 in + (* force instantiation of existentials *) + let subs' = exp_list_imply tenv calc_missing subs (f2 :: elist2) (f2 :: elist2) in + let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' in + let hpred1 = + match Prop.prop_iter_current tenv iter1' + with hpred1, b -> + if b then ProverState.add_missing_pi (Sil.Aneq (_e2, _f2)) ; + (* for PE |- NE *) + hpred1 + in + match hpred1 with + | Sil.Hlseg _ + -> (subs', prop1') + | Sil.Hpointsto _ + -> (* unroll rhs list and try again *) + let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in + let _, para_inst2 = Sil.hpara_instantiate para2 _e2 n' elist2 in + let hpred_list2 = + para_inst2 @ [Prop.mk_lseg tenv Sil.Lseg_PE para2 n' _f2 _elist2] + in + L.d_increase_indent 1 ; let res = - decrease_indent_when_exception - (fun () -> sigma_imply tenv calc_index_frame false subs prop1 para_inst2) in - (* calc_missing is false as we're checking an instantiation of the original list *) - L.d_decrease_indent 1; - res - | Some iter1' -> - let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) _elist2 in - (* force instantiation of existentials *) - let subs' = exp_list_imply tenv calc_missing subs (f2:: elist2) (f2:: elist2) in - let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' in - let hpred1 = match Prop.prop_iter_current tenv iter1' with - | hpred1, b -> - if b then ProverState.add_missing_pi (Sil.Aneq(_e2, _f2)); (* for PE |- NE *) - hpred1 - in match hpred1 with - | Sil.Hlseg _ -> (subs', prop1') - | Sil.Hpointsto _ -> (* unroll rhs list and try again *) - let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst2) = Sil.hpara_instantiate para2 _e2 n' elist2 in - let hpred_list2 = para_inst2@[Prop.mk_lseg tenv Sil.Lseg_PE para2 n' _f2 _elist2] in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> - try sigma_imply tenv calc_index_frame calc_missing subs prop1 hpred_list2 - with exn when SymOp.exn_not_failure exn -> - begin - (L.d_strln_color Red) "backtracking lseg: trying rhs of length exactly 1"; - let (_, para_inst3) = Sil.hpara_instantiate para2 _e2 _f2 elist2 in - sigma_imply tenv calc_index_frame calc_missing subs prop1 para_inst3 - end) in - L.d_decrease_indent 1; - res - | Sil.Hdllseg _ -> assert false - ) - ) - | Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) -> - (d_impl_err ("rhs dllsegPE not implemented", subs, (EXC_FALSE_HPRED hpred2)); - raise (Exceptions.Abduction_case_not_implemented __POS__)) - | Sil.Hdllseg (_, para2, iF2, oB2, oF2, iB2, elist2) -> - (* for now ignore implications between PE and NE *) - let iF2, oF2 = Sil.exp_sub (`Exp (snd subs)) iF2, Sil.exp_sub (`Exp (snd subs)) oF2 in - let iB2, oB2 = Sil.exp_sub (`Exp (snd subs)) iB2, Sil.exp_sub (`Exp (snd subs)) oB2 in - let _ = match oF2 with - | Exp.Lvar _ -> () - | Exp.Var v -> if Ident.is_primed v then - (d_impl_err ("rhs dllseg not implemented", subs, (EXC_FALSE_HPRED hpred2)); - raise (Exceptions.Abduction_case_not_implemented __POS__)) - | _ -> () + decrease_indent_when_exception (fun () -> + try sigma_imply tenv calc_index_frame calc_missing subs prop1 hpred_list2 + with exn when SymOp.exn_not_failure exn -> + L.d_strln_color Red "backtracking lseg: trying rhs of length exactly 1" ; + let _, para_inst3 = Sil.hpara_instantiate para2 _e2 _f2 elist2 in + sigma_imply tenv calc_index_frame calc_missing subs prop1 para_inst3 ) + in + L.d_decrease_indent 1 ; res + | Sil.Hdllseg _ + -> assert false ) + | Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) + -> d_impl_err ("rhs dllsegPE not implemented", subs, EXC_FALSE_HPRED hpred2) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) + | Sil.Hdllseg (_, para2, iF2, oB2, oF2, iB2, elist2) + -> (* for now ignore implications between PE and NE *) + let iF2, oF2 = (Sil.exp_sub (`Exp (snd subs)) iF2, Sil.exp_sub (`Exp (snd subs)) oF2) in + let iB2, oB2 = (Sil.exp_sub (`Exp (snd subs)) iB2, Sil.exp_sub (`Exp (snd subs)) oB2) in + let _ = + match oF2 with + | Exp.Lvar _ + -> () + | Exp.Var v + -> if Ident.is_primed v then ( + d_impl_err ("rhs dllseg not implemented", subs, EXC_FALSE_HPRED hpred2) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) ) + | _ + -> () in - let _ = match oB2 with - | Exp.Lvar _ -> () - | Exp.Var v -> if Ident.is_primed v then - (d_impl_err ("rhs dllseg not implemented", subs, (EXC_FALSE_HPRED hpred2)); - raise (Exceptions.Abduction_case_not_implemented __POS__)) - | _ -> () + let _ = + match oB2 with + | Exp.Lvar _ + -> () + | Exp.Var v + -> if Ident.is_primed v then ( + d_impl_err ("rhs dllseg not implemented", subs, EXC_FALSE_HPRED hpred2) ; + raise (Exceptions.Abduction_case_not_implemented __POS__) ) + | _ + -> () in - (match Prop.prop_iter_create prop1 with - | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) - | Some iter1 -> - (match Prop.prop_iter_find iter1 (filter_hpred (fst subs) (Sil.hpred_sub (`Exp (snd subs)) hpred2)) with - | None -> - let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) elist2 in - let _, para_inst2 = - if Exp.equal iF2 iB2 then - Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 elist2 - else assert false (* Only base case of rhs list considered for now *) in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> sigma_imply tenv calc_index_frame false subs prop1 para_inst2) in - (* calc_missing is false as we're checking an instantiation of the original list *) - L.d_decrease_indent 1; - res - | Some iter1' -> (* Only consider implications between identical listsegs for now *) - let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) elist2 in - (* force instantiation of existentials *) - let subs' = - exp_list_imply tenv calc_missing subs - (iF2:: oB2:: oF2:: iB2:: elist2) (iF2:: oB2:: oF2:: iB2:: elist2) in - let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' - in (subs', prop1') - ) - ) + match Prop.prop_iter_create prop1 with + | None + -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + match + Prop.prop_iter_find iter1 + (filter_hpred (fst subs) (Sil.hpred_sub (`Exp (snd subs)) hpred2)) + with + | None + -> let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) elist2 in + let _, para_inst2 = + if Exp.equal iF2 iB2 then Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 elist2 + else assert false + (* Only base case of rhs list considered for now *) + in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + sigma_imply tenv calc_index_frame false subs prop1 para_inst2 ) + in + (* calc_missing is false as we're checking an instantiation of the original list *) + L.d_decrease_indent 1 ; res + | Some iter1' + -> (* Only consider implications between identical listsegs for now *) + let elist2 = List.map ~f:(fun e -> Sil.exp_sub (`Exp (snd subs)) e) elist2 in + (* force instantiation of existentials *) + let subs' = + exp_list_imply tenv calc_missing subs (iF2 :: oB2 :: oF2 :: iB2 :: elist2) + (iF2 :: oB2 :: oF2 :: iB2 :: elist2) + in + let prop1' = Prop.prop_iter_remove_curr_then_to_prop tenv iter1' in + (subs', prop1') (** Check that [sigma1] implies [sigma2] and return two substitution instantiations for the primed variables of [sigma1] and [sigma2] and a frame. Raise IMPL_FALSE if the implication cannot be proven. *) -and sigma_imply tenv calc_index_frame calc_missing subs prop1 sigma2 : (subst2 * Prop.normal Prop.t) = - let is_constant_string_class subs = function (* if the hpred represents a constant string, return the string *) - | Sil.Hpointsto (_e2, _, _) -> +and sigma_imply tenv calc_index_frame calc_missing subs prop1 sigma2 : subst2 * Prop.normal Prop.t = + let is_constant_string_class subs = function + (* if the hpred represents a constant string, return the string *) + | Sil.Hpointsto (_e2, _, _) + -> ( let e2 = Sil.exp_sub (`Exp (snd subs)) _e2 in - (match e2 with - | Exp.Const (Const.Cstr s) -> Some (s, true) - | Exp.Const (Const.Cclass c) -> Some (Ident.name_to_string c, false) - | _ -> None) - | _ -> None in - let mk_constant_string_hpred s = (* create an hpred from a constant string *) + match e2 with + | Exp.Const Const.Cstr s + -> Some (s, true) + | Exp.Const Const.Cclass c + -> Some (Ident.name_to_string c, false) + | _ + -> None ) + | _ + -> None + in + let mk_constant_string_hpred s = + (* create an hpred from a constant string *) let len = IntLit.of_int (1 + String.length s) in let root = Exp.Const (Const.Cstr s) in let sexp = let index = Exp.int (IntLit.of_int (String.length s)) in match !Config.curr_language with - | Config.Clang -> - Sil.Earray - (Exp.int len, [(index, Sil.Eexp (Exp.zero, Sil.inst_none))], Sil.inst_none) - | Config.Java -> - let mk_fld_sexp s = + | Config.Clang + -> Sil.Earray (Exp.int len, [(index, Sil.Eexp (Exp.zero, Sil.inst_none))], Sil.inst_none) + | Config.Java + -> let mk_fld_sexp s = let fld = Typ.Fieldname.Java.from_string s in let se = Sil.Eexp (Exp.Var (Ident.create_fresh Ident.kprimed), Sil.Inone) in - (fld, se) in - let fields = ["java.lang.String.count"; "java.lang.String.hash"; - "java.lang.String.offset"; "java.lang.String.value"] in - Sil.Estruct (List.map ~f:mk_fld_sexp fields, Sil.inst_none) in + (fld, se) + in + let fields = + [ "java.lang.String.count" + ; "java.lang.String.hash" + ; "java.lang.String.offset" + ; "java.lang.String.value" ] + in + Sil.Estruct (List.map ~f:mk_fld_sexp fields, Sil.inst_none) + in let const_string_texp = match !Config.curr_language with - | Config.Clang -> - Exp.Sizeof {typ=Typ.mk (Tarray (Typ.mk (Tint Typ.IChar), - Some len, Some (IntLit.of_int 1))); - nbytes=None; dynamic_length=None; subtype=Subtype.exact} - | Config.Java -> - let object_type = Typ.Name.Java.from_string "java.lang.String" in - Exp.Sizeof {typ=Typ.mk (Tstruct object_type); - nbytes=None; dynamic_length=None; subtype=Subtype.exact} in - Sil.Hpointsto (root, sexp, const_string_texp) in - let mk_constant_class_hpred s = (* creat an hpred from a constant class *) + | Config.Clang + -> Exp.Sizeof + { typ= Typ.mk (Tarray (Typ.mk (Tint Typ.IChar), Some len, Some (IntLit.of_int 1))) + ; nbytes= None + ; dynamic_length= None + ; subtype= Subtype.exact } + | Config.Java + -> let object_type = Typ.Name.Java.from_string "java.lang.String" in + Exp.Sizeof + { typ= Typ.mk (Tstruct object_type) + ; nbytes= None + ; dynamic_length= None + ; subtype= Subtype.exact } + in + Sil.Hpointsto (root, sexp, const_string_texp) + in + let mk_constant_class_hpred s = + (* creat an hpred from a constant class *) let root = Exp.Const (Const.Cclass (Ident.string_to_name s)) in - let sexp = (* TODO: add appropriate fields *) + let sexp = + (* TODO: add appropriate fields *) Sil.Estruct - ([(Typ.Fieldname.Java.from_string "java.lang.Class.name", - Sil.Eexp ((Exp.Const (Const.Cstr s), Sil.Inone)))], Sil.inst_none) in + ( [ ( Typ.Fieldname.Java.from_string "java.lang.Class.name" + , Sil.Eexp (Exp.Const (Const.Cstr s), Sil.Inone) ) ] + , Sil.inst_none ) + in let class_texp = let class_type = Typ.Name.Java.from_string "java.lang.Class" in - Exp.Sizeof {typ=Typ.mk (Tstruct class_type); - nbytes=None; dynamic_length=None; subtype=Subtype.exact} in - Sil.Hpointsto (root, sexp, class_texp) in + Exp.Sizeof + { typ= Typ.mk (Tstruct class_type) + ; nbytes= None + ; dynamic_length= None + ; subtype= Subtype.exact } + in + Sil.Hpointsto (root, sexp, class_texp) + in try - (match move_primed_lhs_from_front subs sigma2 with - | [] -> - L.d_strln "Final Implication"; - d_impl subs (prop1, Prop.prop_emp); - (subs, prop1) - | hpred2 :: sigma2' -> - L.d_strln "Current Implication"; - d_impl subs (prop1, Prop.normalize tenv (Prop.from_sigma (hpred2 :: sigma2'))); - L.d_ln (); - L.d_ln (); - let normal_case hpred2' = - let (subs', prop1') = - try - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2') in - L.d_decrease_indent 1; - res - with IMPL_EXC _ when calc_missing -> - begin - match is_constant_string_class subs hpred2' with - | Some (s, is_string) -> (* allocate constant string hpred1', do implication, then add hpred1' as missing *) - let hpred1' = if is_string then mk_constant_string_hpred s else mk_constant_class_hpred s in - let prop1' = - Prop.normalize tenv (Prop.set prop1 ~sigma:(hpred1' :: prop1.Prop.sigma)) in - let subs', frame_prop = hpred_imply tenv calc_index_frame calc_missing subs prop1' sigma2 hpred2' in - (* ProverState.add_missing_sigma [hpred1']; *) - subs', frame_prop - | None -> - let subs' = match hpred2' with - | Sil.Hpointsto (e2, se2, te2) -> - let typ2 = Exp.texp_to_typ (Some (Typ.mk Tvoid)) te2 in - sexp_imply_nolhs tenv e2 calc_missing subs se2 typ2 - | _ -> subs in - ProverState.add_missing_sigma [hpred2']; - subs', prop1 - end in - L.d_increase_indent 1; - let res = - decrease_indent_when_exception - (fun () -> sigma_imply tenv calc_index_frame calc_missing subs' prop1' sigma2') in - L.d_decrease_indent 1; - res in - (match hpred2 with - | Sil.Hpointsto(_e2, se2, t) -> - let changed, calc_index_frame', hpred2' = expand_hpred_pointer tenv calc_index_frame (Sil.Hpointsto (Prop.exp_normalize_noabs tenv (`Exp (snd subs)) _e2, se2, t)) in - if changed - then sigma_imply tenv calc_index_frame' calc_missing subs prop1 (hpred2' :: sigma2') (* calc_index_frame=true *) - else normal_case hpred2' - | _ -> normal_case hpred2) - ) + match move_primed_lhs_from_front subs sigma2 with + | [] + -> L.d_strln "Final Implication" ; + d_impl subs (prop1, Prop.prop_emp) ; + (subs, prop1) + | hpred2 :: sigma2' + -> L.d_strln "Current Implication" ; + d_impl subs (prop1, Prop.normalize tenv (Prop.from_sigma (hpred2 :: sigma2'))) ; + L.d_ln () ; + L.d_ln () ; + let normal_case hpred2' = + let subs', prop1' = + try + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2' ) + in + L.d_decrease_indent 1 ; res + with IMPL_EXC _ when calc_missing -> + match is_constant_string_class subs hpred2' with + | Some (s, is_string) + -> (* allocate constant string hpred1', do implication, then add hpred1' as missing *) + let hpred1' = + if is_string then mk_constant_string_hpred s else mk_constant_class_hpred s + in + let prop1' = + Prop.normalize tenv (Prop.set prop1 ~sigma:(hpred1' :: prop1.Prop.sigma)) + in + let subs', frame_prop = + hpred_imply tenv calc_index_frame calc_missing subs prop1' sigma2 hpred2' + in + (* ProverState.add_missing_sigma [hpred1']; *) + (subs', frame_prop) + | None + -> let subs' = + match hpred2' with + | Sil.Hpointsto (e2, se2, te2) + -> let typ2 = Exp.texp_to_typ (Some (Typ.mk Tvoid)) te2 in + sexp_imply_nolhs tenv e2 calc_missing subs se2 typ2 + | _ + -> subs + in + ProverState.add_missing_sigma [hpred2'] ; + (subs', prop1) + in + L.d_increase_indent 1 ; + let res = + decrease_indent_when_exception (fun () -> + sigma_imply tenv calc_index_frame calc_missing subs' prop1' sigma2' ) + in + L.d_decrease_indent 1 ; res + in + match hpred2 with + | Sil.Hpointsto (_e2, se2, t) + -> let changed, calc_index_frame', hpred2' = + expand_hpred_pointer tenv calc_index_frame + (Sil.Hpointsto (Prop.exp_normalize_noabs tenv (`Exp (snd subs)) _e2, se2, t)) + in + if changed then + sigma_imply tenv calc_index_frame' calc_missing subs prop1 (hpred2' :: sigma2') + (* calc_index_frame=true *) + else normal_case hpred2' + | _ + -> normal_case hpred2 with IMPL_EXC (s, _, _) when calc_missing -> - L.d_strln ("Adding rhs as missing: " ^ s); - ProverState.add_missing_sigma sigma2; - subs, prop1 + L.d_strln ("Adding rhs as missing: " ^ s) ; + ProverState.add_missing_sigma sigma2 ; + (subs, prop1) let prepare_prop_for_implication tenv (_, sub2) pi1 sigma1 = - let pi1' = (Prop.pi_sub (`Exp sub2) (ProverState.get_missing_pi ())) @ pi1 in - let sigma1' = (Prop.sigma_sub (`Exp sub2) (ProverState.get_missing_sigma ())) @ sigma1 in + let pi1' = Prop.pi_sub (`Exp sub2) (ProverState.get_missing_pi ()) @ pi1 in + let sigma1' = Prop.sigma_sub (`Exp sub2) (ProverState.get_missing_sigma ()) @ sigma1 in let ep = Prop.set Prop.prop_emp ~sub:sub2 ~sigma:sigma1' ~pi:pi1' in Prop.normalize tenv ep @@ -2110,12 +2412,14 @@ let imply_pi tenv calc_missing (sub1, sub2) prop pi2 = let do_atom a = let a' = Sil.atom_sub (`Exp sub2) a in try - if not (check_atom tenv prop a') - then raise (IMPL_EXC ("rhs atom missing in lhs", (sub1, sub2), (EXC_FALSE_ATOM a'))) - with - | IMPL_EXC _ when calc_missing -> - L.d_str "imply_pi: adding missing atom "; Sil.d_atom a; L.d_ln (); - ProverState.add_missing_pi a in + if not (check_atom tenv prop a') then + raise (IMPL_EXC ("rhs atom missing in lhs", (sub1, sub2), EXC_FALSE_ATOM a')) + with IMPL_EXC _ when calc_missing -> + L.d_str "imply_pi: adding missing atom " ; + Sil.d_atom a ; + L.d_ln () ; + ProverState.add_missing_pi a + in List.iter ~f:do_atom pi2 let imply_atom tenv calc_missing (sub1, sub2) prop a = @@ -2124,121 +2428,152 @@ let imply_atom tenv calc_missing (sub1, sub2) prop a = (** Check pure implications before looking at the spatial part. Add necessary instantiations for equalities and check that instantiations are possible for disequalities. *) -let rec pre_check_pure_implication tenv calc_missing (subs : subst2) pi1 pi2 = +let rec pre_check_pure_implication tenv calc_missing (subs: subst2) pi1 pi2 = match pi2 with - | [] -> subs - | (Sil.Aeq (e2_in, f2_in) as a) :: pi2' when not (Prop.atom_is_inequality a) -> - let e2, f2 = Sil.exp_sub (`Exp (snd subs)) e2_in, Sil.exp_sub (`Exp (snd subs)) f2_in in + | [] + -> subs + | (Sil.Aeq (e2_in, f2_in) as a) :: pi2' when not (Prop.atom_is_inequality a) + -> ( + let e2, f2 = (Sil.exp_sub (`Exp (snd subs)) e2_in, Sil.exp_sub (`Exp (snd subs)) f2_in) in if Exp.equal e2 f2 then pre_check_pure_implication tenv calc_missing subs pi1 pi2' else - (match e2, f2 with - | Exp.Var v2, f2 - when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) -> - (* The commented-out condition should always hold. *) - let sub2' = extend_sub (snd subs) v2 f2 in - pre_check_pure_implication tenv calc_missing (fst subs, sub2') pi1 pi2' - | e2, Exp.Var v2 - when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) -> - (* The commented-out condition should always hold. *) - let sub2' = extend_sub (snd subs) v2 e2 in - pre_check_pure_implication tenv calc_missing (fst subs, sub2') pi1 pi2' - | _ -> - let pi1' = Prop.pi_sub (`Exp (fst subs)) pi1 in - let prop_for_impl = prepare_prop_for_implication tenv subs pi1' [] in - imply_atom tenv calc_missing subs prop_for_impl (Sil.Aeq (e2_in, f2_in)); - pre_check_pure_implication tenv calc_missing subs pi1 pi2' - ) + match (e2, f2) with + | Exp.Var v2, f2 when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) + -> (* The commented-out condition should always hold. *) + let sub2' = extend_sub (snd subs) v2 f2 in + pre_check_pure_implication tenv calc_missing (fst subs, sub2') pi1 pi2' + | e2, Exp.Var v2 when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) + -> (* The commented-out condition should always hold. *) + let sub2' = extend_sub (snd subs) v2 e2 in + pre_check_pure_implication tenv calc_missing (fst subs, sub2') pi1 pi2' + | _ + -> let pi1' = Prop.pi_sub (`Exp (fst subs)) pi1 in + let prop_for_impl = prepare_prop_for_implication tenv subs pi1' [] in + imply_atom tenv calc_missing subs prop_for_impl (Sil.Aeq (e2_in, f2_in)) ; + pre_check_pure_implication tenv calc_missing subs pi1 pi2' ) | (Sil.Aneq (e, _) | Apred (_, e :: _) | Anpred (_, e :: _)) :: _ - when not calc_missing && (match e with Var v -> not (Ident.is_primed v) | _ -> true) -> - raise (IMPL_EXC ("ineq e2=f2 in rhs with e2 not primed var", - (Sil.exp_sub_empty, Sil.exp_sub_empty), EXC_FALSE)) - | (Sil.Aeq _ | Aneq _ | Apred _ | Anpred _) :: pi2' -> - pre_check_pure_implication tenv calc_missing subs pi1 pi2' + when not calc_missing && match e with Var v -> not (Ident.is_primed v) | _ -> true + -> raise + (IMPL_EXC + ( "ineq e2=f2 in rhs with e2 not primed var" + , (Sil.exp_sub_empty, Sil.exp_sub_empty) + , EXC_FALSE )) + | (Sil.Aeq _ | Aneq _ | Apred _ | Anpred _) :: pi2' + -> pre_check_pure_implication tenv calc_missing subs pi1 pi2' (** Perform the array bound checks delayed (to instantiate variables) by the prover. If there is a provable violation of the array bounds, set the prover status to Bounds_check and make the proof fail. *) let check_array_bounds tenv (sub1, sub2) prop = let check_failed atom = - ProverState.checks := Bounds_check :: !ProverState.checks; - L.d_str_color Red "bounds_check failed: provable atom: "; Sil.d_atom atom; L.d_ln(); - if (not Config.bound_error_allowed_in_procedure_call) then - raise (IMPL_EXC ("bounds check", (sub1, sub2), EXC_FALSE)) in + ProverState.checks := Bounds_check :: !ProverState.checks ; + L.d_str_color Red "bounds_check failed: provable atom: " ; + Sil.d_atom atom ; + L.d_ln () ; + if not Config.bound_error_allowed_in_procedure_call then + raise (IMPL_EXC ("bounds check", (sub1, sub2), EXC_FALSE)) + in let fail_if_le e' e'' = - let lt_ineq = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, e', e'')) in - if check_atom tenv prop lt_ineq then check_failed lt_ineq in + let lt_ineq = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, e', e'')) in + if check_atom tenv prop lt_ineq then check_failed lt_ineq + in let check_bound = function - | ProverState.BClen_imply (len1_, len2_, _indices2) -> - let len1 = Sil.exp_sub (`Exp sub1) len1_ in + | ProverState.BClen_imply (len1_, len2_, _indices2) + -> let len1 = Sil.exp_sub (`Exp sub1) len1_ in let len2 = Sil.exp_sub (`Exp sub2) len2_ in (* L.d_strln_color Orange "check_bound "; Sil.d_exp len1; L.d_str " "; Sil.d_exp len2; L.d_ln(); *) - let indices_to_check = match len2 with - | _ -> [Exp.BinOp(Binop.PlusA, len2, Exp.minus_one)] (* only check len *) in + let indices_to_check = + match len2 + with _ -> [Exp.BinOp (Binop.PlusA, len2, Exp.minus_one)] + (* only check len *) + in List.iter ~f:(fail_if_le len1) indices_to_check - | ProverState.BCfrom_pre _atom -> - let atom_neg = atom_negate tenv (Sil.atom_sub (`Exp sub2) _atom) in + | ProverState.BCfrom_pre _atom + -> let atom_neg = atom_negate tenv (Sil.atom_sub (`Exp sub2) _atom) in (* L.d_strln_color Orange "BCFrom_pre"; Sil.d_atom atom_neg; L.d_ln (); *) - if check_atom tenv prop atom_neg then check_failed atom_neg in + if check_atom tenv prop atom_neg then check_failed atom_neg + in List.iter ~f:check_bound (ProverState.get_bounds_checks ()) (** [check_implication_base] returns true if [prop1|-prop2], ignoring the footprint part of the props *) let check_implication_base pname tenv check_frame_empty calc_missing prop1 prop2 = try - ProverState.reset prop1 prop2; - let filter (id, e) = - Ident.is_normal id && Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in - let sub1_base = - Sil.sub_filter_pair ~f:filter prop1.Prop.sub in - let pi1, pi2 = Prop.get_pure prop1, Prop.get_pure prop2 in - let sigma1, sigma2 = prop1.Prop.sigma, prop2.Prop.sigma in + ProverState.reset prop1 prop2 ; + let filter (id, e) = Ident.is_normal id && Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + let sub1_base = Sil.sub_filter_pair ~f:filter prop1.Prop.sub in + let pi1, pi2 = (Prop.get_pure prop1, Prop.get_pure prop2) in + let sigma1, sigma2 = (prop1.Prop.sigma, prop2.Prop.sigma) in let subs = pre_check_pure_implication tenv calc_missing (prop1.Prop.sub, sub1_base) pi1 pi2 in - let pi2_bcheck, pi2_nobcheck = (* find bounds checks implicit in pi2 *) - List.partition_tf ~f:ProverState.atom_is_array_bounds_check pi2 in - List.iter ~f:(fun a -> ProverState.add_bounds_check (ProverState.BCfrom_pre a)) pi2_bcheck; - L.d_strln "pre_check_pure_implication"; - L.d_strln "pi1:"; - L.d_increase_indent 1; Prop.d_pi pi1; L.d_decrease_indent 1; L.d_ln (); - L.d_strln "pi2:"; - L.d_increase_indent 1; Prop.d_pi pi2; L.d_decrease_indent 1; L.d_ln (); - if pi2_bcheck <> [] - then (L.d_str "pi2 bounds checks: "; Prop.d_pi pi2_bcheck; L.d_ln ()); - L.d_strln "returns"; - L.d_strln "sub1: "; - L.d_increase_indent 1; Prop.d_sub (`Exp (fst subs)); L.d_decrease_indent 1; L.d_ln (); - L.d_strln "sub2: "; - L.d_increase_indent 1; Prop.d_sub (`Exp (snd subs)); L.d_decrease_indent 1; L.d_ln (); + let pi2_bcheck, pi2_nobcheck = + (* find bounds checks implicit in pi2 *) + List.partition_tf ~f:ProverState.atom_is_array_bounds_check pi2 + in + List.iter ~f:(fun a -> ProverState.add_bounds_check (ProverState.BCfrom_pre a)) pi2_bcheck ; + L.d_strln "pre_check_pure_implication" ; + L.d_strln "pi1:" ; + L.d_increase_indent 1 ; + Prop.d_pi pi1 ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_strln "pi2:" ; + L.d_increase_indent 1 ; + Prop.d_pi pi2 ; + L.d_decrease_indent 1 ; + L.d_ln () ; + if pi2_bcheck <> [] then ( L.d_str "pi2 bounds checks: " ; Prop.d_pi pi2_bcheck ; L.d_ln () ) ; + L.d_strln "returns" ; + L.d_strln "sub1: " ; + L.d_increase_indent 1 ; + Prop.d_sub (`Exp (fst subs)) ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_strln "sub2: " ; + L.d_increase_indent 1 ; + Prop.d_sub (`Exp (snd subs)) ; + L.d_decrease_indent 1 ; + L.d_ln () ; let (sub1, sub2), frame_prop = sigma_imply tenv false calc_missing subs prop1 sigma2 in let pi1' = Prop.pi_sub (`Exp sub1) pi1 in let sigma1' = Prop.sigma_sub (`Exp sub1) sigma1 in - L.d_ln (); + L.d_ln () ; let prop_for_impl = prepare_prop_for_implication tenv (sub1, sub2) pi1' sigma1' in (* only deal with pi2 without bound checks *) - imply_pi tenv calc_missing (sub1, sub2) prop_for_impl pi2_nobcheck; + imply_pi tenv calc_missing (sub1, sub2) prop_for_impl pi2_nobcheck ; (* handle implicit bound checks, plus those from array_len_imply *) - check_array_bounds tenv (sub1, sub2) prop_for_impl; - L.d_strln "Result of Abduction"; - L.d_increase_indent 1; d_impl (sub1, sub2) (prop1, prop2); L.d_decrease_indent 1; L.d_ln (); - L.d_strln"returning TRUE"; + check_array_bounds tenv (sub1, sub2) prop_for_impl ; + L.d_strln "Result of Abduction" ; + L.d_increase_indent 1 ; + d_impl (sub1, sub2) (prop1, prop2) ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_strln "returning TRUE" ; let frame = frame_prop.Prop.sigma in - if check_frame_empty && frame <> [] then raise (IMPL_EXC("frame not empty", subs, EXC_FALSE)); + if check_frame_empty && frame <> [] then raise (IMPL_EXC ("frame not empty", subs, EXC_FALSE)) ; Some ((sub1, sub2), frame) with - | IMPL_EXC (s, subs, body) -> - d_impl_err (s, subs, body); - None - | MISSING_EXC s -> - L.d_strln ("WARNING: footprint failed to find MISSING because: " ^ s); + | IMPL_EXC (s, subs, body) + -> d_impl_err (s, subs, body) ; None - | (Exceptions.Abduction_case_not_implemented _ as exn) -> - Reporting.log_error_deprecated pname exn; + | MISSING_EXC s + -> L.d_strln ("WARNING: footprint failed to find MISSING because: " ^ s) ; None + | Exceptions.Abduction_case_not_implemented _ as exn + -> Reporting.log_error_deprecated pname exn ; None type implication_result = | ImplOK of - (check list * Sil.exp_subst * Sil.exp_subst * Sil.hpred list * (Sil.atom list) * (Sil.hpred list) * - (Sil.hpred list) * (Sil.hpred list) * ((Exp.t * Exp.t) list) * ((Exp.t * Exp.t) list)) + ( check list + * Sil.exp_subst + * Sil.exp_subst + * Sil.hpred list + * Sil.atom list + * Sil.hpred list + * Sil.hpred list + * Sil.hpred list + * (Exp.t * Exp.t) list + * (Exp.t * Exp.t) list ) | ImplFail of check list (** [check_implication_for_footprint p1 p2] returns @@ -2249,9 +2584,20 @@ let check_implication_for_footprint pname tenv p1 (p2: Prop.exposed Prop.t) = let check_frame_empty = false in let calc_missing = true in match check_implication_base pname tenv check_frame_empty calc_missing p1 p2 with - | Some ((sub1, sub2), frame) -> - ImplOK (!ProverState.checks, sub1, sub2, frame, ProverState.get_missing_pi (), ProverState.get_missing_sigma (), ProverState.get_frame_fld (), ProverState.get_missing_fld (), ProverState.get_frame_typ (), ProverState.get_missing_typ ()) - | None -> ImplFail !ProverState.checks + | Some ((sub1, sub2), frame) + -> ImplOK + ( !ProverState.checks + , sub1 + , sub2 + , frame + , ProverState.get_missing_pi () + , ProverState.get_missing_sigma () + , ProverState.get_frame_fld () + , ProverState.get_missing_fld () + , ProverState.get_frame_typ () + , ProverState.get_missing_typ () ) + | None + -> ImplFail !ProverState.checks (** [check_implication p1 p2] returns true if [p1|-p2] *) let check_implication pname tenv p1 p2 = @@ -2259,25 +2605,35 @@ let check_implication pname tenv p1 p2 = let check_frame_empty = true in let calc_missing = false in match check_implication_base pname tenv check_frame_empty calc_missing p1 p2 with - | Some _ -> true - | None -> false in - check p1 p2 && - (if !Config.footprint then check (Prop.normalize tenv (Prop.extract_footprint p1)) (Prop.extract_footprint p2) else true) + | Some _ + -> true + | None + -> false + in + check p1 p2 + && + if !Config.footprint then + check (Prop.normalize tenv (Prop.extract_footprint p1)) (Prop.extract_footprint p2) + else true (** {2 Cover: miminum set of pi's whose disjunction is equivalent to true} *) (** check if the pi's in [cases] cover true *) let is_cover tenv cases = - let cnt = ref 0 in (* counter for timeout checks, as this function can take exponential time *) + let cnt = ref 0 in + (* counter for timeout checks, as this function can take exponential time *) let check () = - incr cnt; - if Int.equal (!cnt mod 100) 0 then SymOp.check_wallclock_alarm () in + incr cnt ; + if Int.equal (!cnt mod 100) 0 then SymOp.check_wallclock_alarm () + in let rec _is_cover acc_pi cases = - check (); + check () ; match cases with - | [] -> check_inconsistency_pi tenv acc_pi - | (pi, _):: cases' -> - List.for_all ~f:(fun a -> _is_cover ((atom_negate tenv a) :: acc_pi) cases') pi in + | [] + -> check_inconsistency_pi tenv acc_pi + | (pi, _) :: cases' + -> List.for_all ~f:(fun a -> _is_cover (atom_negate tenv a :: acc_pi) cases') pi + in _is_cover [] cases exception NO_COVER @@ -2285,22 +2641,26 @@ exception NO_COVER (** Find miminum set of pi's in [cases] whose disjunction covers true *) let find_minimum_pure_cover tenv cases = let cases = - let compare (pi1, _) (pi2, _) = Int.compare (List.length pi1) (List.length pi2) - in List.sort ~cmp:compare cases in - let rec grow seen todo = match todo with - | [] -> raise NO_COVER - | (pi, x):: todo' -> - if is_cover tenv ((pi, x):: seen) then (pi, x):: seen - else grow ((pi, x):: seen) todo' in - let rec _shrink seen todo = match todo with - | [] -> seen - | (pi, x):: todo' -> - if is_cover tenv (seen @ todo') then _shrink seen todo' - else _shrink ((pi, x):: seen) todo' in - let shrink cases = - if List.length cases > 2 then _shrink [] cases - else cases - in try Some (shrink (grow [] cases)) + let compare (pi1, _) (pi2, _) = Int.compare (List.length pi1) (List.length pi2) in + List.sort ~cmp:compare cases + in + let rec grow seen todo = + match todo with + | [] + -> raise NO_COVER + | (pi, x) :: todo' + -> if is_cover tenv ((pi, x) :: seen) then (pi, x) :: seen else grow ((pi, x) :: seen) todo' + in + let rec _shrink seen todo = + match todo with + | [] + -> seen + | (pi, x) :: todo' + -> if is_cover tenv (seen @ todo') then _shrink seen todo' + else _shrink ((pi, x) :: seen) todo' + in + let shrink cases = if List.length cases > 2 then _shrink [] cases else cases in + try Some (shrink (grow [] cases)) with NO_COVER -> None (* diff --git a/infer/src/backend/prover.mli b/infer/src/backend/prover.mli index a7011a9cd..2c4a8b4e5 100644 --- a/infer/src/backend/prover.mli +++ b/infer/src/backend/prover.mli @@ -14,102 +14,107 @@ open! IStd open Sil -(** Negate an atom *) val atom_negate : Tenv.t -> Sil.atom -> Sil.atom +(** Negate an atom *) (** {2 Ordinary Theorem Proving} *) -(** Check [ |- e=0]. Result [false] means "don't know". *) val check_zero : Tenv.t -> Exp.t -> bool +(** Check [ |- e=0]. Result [false] means "don't know". *) -(** Check [prop |- exp1=exp2]. Result [false] means "don't know". *) val check_equal : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> bool +(** Check [prop |- exp1=exp2]. Result [false] means "don't know". *) -(** Check whether [prop |- exp1!=exp2]. Result [false] means "don't know". *) val check_disequal : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> bool +(** Check whether [prop |- exp1!=exp2]. Result [false] means "don't know". *) val check_le : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> bool -(** Return true if the two types have sizes which can be compared *) val type_size_comparable : Typ.t -> Typ.t -> bool +(** Return true if the two types have sizes which can be compared *) -(** Check <= on the size of comparable types *) val check_type_size_leq : Typ.t -> Typ.t -> bool +(** Check <= on the size of comparable types *) -(** Check < on the size of comparable types *) val check_type_size_lt : Typ.t -> Typ.t -> bool +(** Check < on the size of comparable types *) -(** Check whether [prop |- a]. Result [false] means "don't know". *) val check_atom : Tenv.t -> Prop.normal Prop.t -> atom -> bool +(** Check whether [prop |- a]. Result [false] means "don't know". *) -(** Inconsistency checking ignoring footprint. *) val check_inconsistency_base : Tenv.t -> Prop.normal Prop.t -> bool +(** Inconsistency checking ignoring footprint. *) -(** Inconsistency checking. *) val check_inconsistency : Tenv.t -> Prop.normal Prop.t -> bool +(** Inconsistency checking. *) -(** Check whether [prop |- allocated(exp)]. *) val check_allocatedness : Tenv.t -> Prop.normal Prop.t -> Exp.t -> bool +(** Check whether [prop |- allocated(exp)]. *) +val is_root : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> offset list option (** [is_root prop base_exp exp] checks whether [base_exp = exp.offlist] for some list of offsets [offlist]. If so, it returns [Some(offlist)]. Otherwise, it returns [None]. Assumes that [base_exp] points to the beginning of a structure, not the middle. *) -val is_root : Tenv.t -> Prop.normal Prop.t -> Exp.t -> Exp.t -> offset list option +val expand_hpred_pointer : Tenv.t -> bool -> Sil.hpred -> bool * bool * Sil.hpred (** [expand_hpred_pointer calc_index_frame hpred] expands [hpred] if it is a |-> whose lhs is a Lfield or Lindex or ptr+off. Return [(changed, calc_index_frame', hpred')] where [changed] indicates whether the predicate has changed. *) -val expand_hpred_pointer : Tenv.t -> bool -> Sil.hpred -> bool * bool * Sil.hpred -(** Get upper and lower bounds of an expression, if any *) val get_bounds : Tenv.t -> Prop.normal Prop.t -> Exp.t -> IntLit.t option * IntLit.t option +(** Get upper and lower bounds of an expression, if any *) (** {2 Abduction prover} *) +val check_implication : + Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.exposed Prop.t -> bool (** [check_implication p1 p2] returns true if [p1|-p2] *) -val check_implication : Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.exposed Prop.t -> bool -type check = - | Bounds_check - | Class_cast_check of Exp.t * Exp.t * Exp.t +type check = Bounds_check | Class_cast_check of Exp.t * Exp.t * Exp.t val d_typings : (Exp.t * Exp.t) list -> unit type implication_result = | ImplOK of - (check list * Sil.exp_subst * Sil.exp_subst * Sil.hpred list * (Sil.atom list) * (Sil.hpred list) * - (Sil.hpred list) * (Sil.hpred list) * ((Exp.t * Exp.t) list) * ((Exp.t * Exp.t) list)) + ( check list + * Sil.exp_subst + * Sil.exp_subst + * Sil.hpred list + * Sil.atom list + * Sil.hpred list + * Sil.hpred list + * Sil.hpred list + * (Exp.t * Exp.t) list + * (Exp.t * Exp.t) list ) | ImplFail of check list +val check_implication_for_footprint : + Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.exposed Prop.t -> implication_result (** [check_implication_for_footprint p1 p2] returns [Some(sub, frame, missing)] if [sub(p1 * missing) |- sub(p2 * frame)] where [sub] is a substitution which instantiates the primed vars of [p1] and [p2], which are assumed to be disjoint. *) -val check_implication_for_footprint : - Typ.Procname.t -> Tenv.t -> Prop.normal Prop.t -> Prop.exposed Prop.t -> implication_result (** {2 Cover: miminum set of pi's whose disjunction is equivalent to true} *) +val find_minimum_pure_cover : + Tenv.t -> (Sil.atom list * 'a) list -> (Sil.atom list * 'a) list option (** Find miminum set of pi's in [cases] whose disjunction covers true *) -val find_minimum_pure_cover : Tenv.t -> (Sil.atom list * 'a) list -> (Sil.atom list * 'a) list option (** {2 Compute various lower or upper bounds} *) -(** Computer an upper bound of an expression *) val compute_upper_bound_of_exp : Tenv.t -> Prop.normal Prop.t -> Exp.t -> IntLit.t option +(** Computer an upper bound of an expression *) (** {2 Subtype checking} *) -module Subtyping_check : -sig - - (** check_subtype t1 t2 checks whether t1 is a subtype of t2, given the type environment tenv. *) +module Subtyping_check : sig val check_subtype : Tenv.t -> Typ.t -> Typ.t -> bool + (** check_subtype t1 t2 checks whether t1 is a subtype of t2, given the type environment tenv. *) + val subtype_case_analysis : Tenv.t -> Exp.t -> Exp.t -> Exp.t option * Exp.t option (** subtype_case_analysis tenv tecp1 texp2 performs case analysis on [texp1 <: texp2], and returns the updated types in the true and false case, if they are possible *) - val subtype_case_analysis : Tenv.t -> Exp.t -> Exp.t -> Exp.t option * Exp.t option - end val get_overrides_of : Tenv.t -> Typ.t -> Typ.Procname.t -> (Typ.t * Typ.Procname.t) list diff --git a/infer/src/backend/rearrange.ml b/infer/src/backend/rearrange.ml index ece74cea5..d820c9c89 100644 --- a/infer/src/backend/rearrange.ml +++ b/infer/src/backend/rearrange.ml @@ -19,14 +19,11 @@ let list_product l1 l2 = let l1' = List.rev l1 in let l2' = List.rev l2 in List.fold - ~f:(fun acc x -> List.fold ~f:(fun acc' y -> (x, y):: acc') ~init:acc l2') - ~init:[] - l1' + ~f:(fun acc x -> List.fold ~f:(fun acc' y -> (x, y) :: acc') ~init:acc l2') + ~init:[] l1' let rec list_rev_and_concat l1 l2 = - match l1 with - | [] -> l2 - | x1:: l1' -> list_rev_and_concat l1' (x1:: l2) + match l1 with [] -> l2 | x1 :: l1' -> list_rev_and_concat l1' (x1 :: l2) (** Check whether the index is out of bounds. If the length is - 1, no check is performed. @@ -34,153 +31,159 @@ let rec list_rev_and_concat l1 l2 = If the length is a constant and the index is not provably in bound, a warning is given. *) let check_bad_index tenv pname p len index loc = - let len_is_constant = match len with - | Exp.Const _ -> true - | _ -> false in + let len_is_constant = match len with Exp.Const _ -> true | _ -> false in let index_provably_out_of_bound () = let index_too_large = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, len, index)) in let index_negative = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, index, Exp.minus_one)) in - (Prover.check_atom tenv p index_too_large) || (Prover.check_atom tenv p index_negative) in + Prover.check_atom tenv p index_too_large || Prover.check_atom tenv p index_negative + in let index_provably_in_bound () = - let len_minus_one = Exp.BinOp(Binop.PlusA, len, Exp.minus_one) in - let index_not_too_large = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, index, len_minus_one)) in - let index_nonnegative = Prop.mk_inequality tenv (Exp.BinOp(Binop.Le, Exp.zero, index)) in - Prover.check_zero tenv index || (* index 0 always in bound, even when we know nothing about len *) - ((Prover.check_atom tenv p index_not_too_large) && (Prover.check_atom tenv p index_nonnegative)) in + let len_minus_one = Exp.BinOp (Binop.PlusA, len, Exp.minus_one) in + let index_not_too_large = + Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, index, len_minus_one)) + in + let index_nonnegative = Prop.mk_inequality tenv (Exp.BinOp (Binop.Le, Exp.zero, index)) in + Prover.check_zero tenv index + || (* index 0 always in bound, even when we know nothing about len *) + Prover.check_atom tenv p index_not_too_large && Prover.check_atom tenv p index_nonnegative + in let index_has_bounds () = - match Prover.get_bounds tenv p index with - | Some _, Some _ -> true - | _ -> false in - let get_const_opt = function - | Exp.Const (Const.Cint n) -> Some n - | _ -> None in + match Prover.get_bounds tenv p index with Some _, Some _ -> true | _ -> false + in + let get_const_opt = function Exp.Const Const.Cint n -> Some n | _ -> None in if not (index_provably_in_bound ()) then - begin - let len_const_opt = get_const_opt len in - let index_const_opt = get_const_opt index in - if index_provably_out_of_bound () then - let deref_str = Localise.deref_str_array_bound len_const_opt index_const_opt in - let exn = - Exceptions.Array_out_of_bounds_l1 - (Errdesc.explain_array_access tenv deref_str p loc, __POS__) in - Reporting.log_warning_deprecated pname exn - else if len_is_constant then - let deref_str = Localise.deref_str_array_bound len_const_opt index_const_opt in - let desc = Errdesc.explain_array_access tenv deref_str p loc in - let exn = if index_has_bounds () - then Exceptions.Array_out_of_bounds_l2 (desc, __POS__) - else Exceptions.Array_out_of_bounds_l3 (desc, __POS__) in - Reporting.log_warning_deprecated pname exn - end + let len_const_opt = get_const_opt len in + let index_const_opt = get_const_opt index in + if index_provably_out_of_bound () then + let deref_str = Localise.deref_str_array_bound len_const_opt index_const_opt in + let exn = + Exceptions.Array_out_of_bounds_l1 + (Errdesc.explain_array_access tenv deref_str p loc, __POS__) + in + Reporting.log_warning_deprecated pname exn + else if len_is_constant then + let deref_str = Localise.deref_str_array_bound len_const_opt index_const_opt in + let desc = Errdesc.explain_array_access tenv deref_str p loc in + let exn = + if index_has_bounds () then Exceptions.Array_out_of_bounds_l2 (desc, __POS__) + else Exceptions.Array_out_of_bounds_l3 (desc, __POS__) + in + Reporting.log_warning_deprecated pname exn (** Perform bounds checking *) let bounds_check tenv pname prop len e = - if Config.trace_rearrange then - begin - L.d_str "Bounds check index:"; Sil.d_exp e; - L.d_str " len: "; Sil.d_exp len; - L.d_ln() - end; + if Config.trace_rearrange then ( + L.d_str "Bounds check index:" ; Sil.d_exp e ; L.d_str " len: " ; Sil.d_exp len ; L.d_ln () ) ; check_bad_index tenv pname prop len e let rec create_struct_values pname tenv orig_prop footprint_part kind max_stamp (t: Typ.t) (off: Sil.offset list) inst : Sil.atom list * Sil.strexp * Typ.t = - if Config.trace_rearrange then - begin - L.d_increase_indent 1; - L.d_strln "entering create_struct_values"; - L.d_str "typ: "; Typ.d_full t; L.d_ln (); - L.d_str "off: "; Sil.d_offset_list off; L.d_ln (); L.d_ln () - end; + if Config.trace_rearrange then ( + L.d_increase_indent 1 ; + L.d_strln "entering create_struct_values" ; + L.d_str "typ: " ; + Typ.d_full t ; + L.d_ln () ; + L.d_str "off: " ; + Sil.d_offset_list off ; + L.d_ln () ; + L.d_ln () ) ; let new_id () = - incr max_stamp; - Ident.create kind !max_stamp in + incr max_stamp ; + Ident.create kind !max_stamp + in let res = let fail t off pos = - L.d_str "create_struct_values type:"; Typ.d_full t; - L.d_str " off: "; Sil.d_offset_list off; L.d_ln(); - raise (Exceptions.Bad_footprint pos) in - match t.desc, off with - | Tstruct _, [] -> - ([], Sil.Estruct ([], inst), t) + L.d_str "create_struct_values type:" ; + Typ.d_full t ; + L.d_str " off: " ; + Sil.d_offset_list off ; + L.d_ln () ; + raise (Exceptions.Bad_footprint pos) + in + match (t.desc, off) with + | Tstruct _, [] + -> ([], Sil.Estruct ([], inst), t) | Tstruct name, (Off_fld (f, _)) :: off' -> ( - match Tenv.lookup tenv name with - | Some ({ fields; statics; } as struct_typ) -> ( - match List.find - ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') - (fields @ statics) with - | Some (_, t', _) -> - let atoms', se', res_t' = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp t' off' inst in - let se = Sil.Estruct ([(f, se')], inst) in - let replace_typ_of_f (f', t', a') = - if Typ.Fieldname.equal f f' then (f, res_t', a') else (f', t', a') in - let fields' = - List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_typ_of_f fields) in - ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; - (atoms', se, t) - | None -> - fail t off __POS__ - ) - | None -> - fail t off __POS__ - ) - | Tstruct _, (Off_index e) :: off' -> - let atoms', se', res_t' = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp t off' inst in + match Tenv.lookup tenv name with + | Some ({fields; statics} as struct_typ) -> ( + match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') (fields @ statics) with + | Some (_, t', _) + -> let atoms', se', res_t' = + create_struct_values pname tenv orig_prop footprint_part kind max_stamp t' off' inst + in + let se = Sil.Estruct ([(f, se')], inst) in + let replace_typ_of_f (f', t', a') = + if Typ.Fieldname.equal f f' then (f, res_t', a') else (f', t', a') + in + let fields' = + List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_typ_of_f fields) + in + ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; + (atoms', se, t) + | None + -> fail t off __POS__ ) + | None + -> fail t off __POS__ ) + | Tstruct _, (Off_index e) :: off' + -> let atoms', se', res_t' = + create_struct_values pname tenv orig_prop footprint_part kind max_stamp t off' inst + in let e' = Sil.array_clean_new_index footprint_part e in let len = Exp.Var (new_id ()) in let se = Sil.Earray (len, [(e', se')], inst) in let res_t = Typ.mk (Tarray (res_t', None, None)) in (Sil.Aeq (e, e') :: atoms', se, res_t) - | Tarray (t', len_, stride_), off -> - let len = match len_ with - | None -> Exp.Var (new_id ()) - | Some len -> Exp.Const (Const.Cint len) in - (match off with - | [] -> - ([], Sil.Earray (len, [], inst), t) - | (Sil.Off_index e) :: off' -> - bounds_check tenv pname orig_prop len e (State.get_loc ()); - let atoms', se', res_t' = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp t' off' inst in - let e' = Sil.array_clean_new_index footprint_part e in - let se = Sil.Earray (len, [(e', se')], inst) in - let res_t = Typ.mk ~default:t (Tarray (res_t', len_, stride_)) in - (Sil.Aeq(e, e') :: atoms', se, res_t) - | (Sil.Off_fld _) :: _ -> - assert false - ) - | Tint _, [] | Tfloat _, [] | Tvoid, [] | Tfun _, [] | Tptr _, [] | TVar _, [] -> - let id = new_id () in + | Tarray (t', len_, stride_), off + -> ( + let len = + match len_ with None -> Exp.Var (new_id ()) | Some len -> Exp.Const (Const.Cint len) + in + match off with + | [] + -> ([], Sil.Earray (len, [], inst), t) + | (Sil.Off_index e) :: off' + -> bounds_check tenv pname orig_prop len e (State.get_loc ()) ; + let atoms', se', res_t' = + create_struct_values pname tenv orig_prop footprint_part kind max_stamp t' off' inst + in + let e' = Sil.array_clean_new_index footprint_part e in + let se = Sil.Earray (len, [(e', se')], inst) in + let res_t = Typ.mk ~default:t (Tarray (res_t', len_, stride_)) in + (Sil.Aeq (e, e') :: atoms', se, res_t) + | (Sil.Off_fld _) :: _ + -> assert false ) + | Tint _, [] | Tfloat _, [] | Tvoid, [] | Tfun _, [] | Tptr _, [] | TVar _, [] + -> let id = new_id () in ([], Sil.Eexp (Exp.Var id, inst), t) - | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), (Off_index e) :: off' -> - (* In this case, we lift t to the t array. *) - let t', mk_typ_f = match t.Typ.desc with - | Typ.Tptr(t', _) -> t', (function desc -> Typ.mk ~default:t desc) - | _ -> t, fun desc -> Typ.mk desc in + | (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _ | TVar _), (Off_index e) :: off' + -> (* In this case, we lift t to the t array. *) + let t', mk_typ_f = + match t.Typ.desc with + | Typ.Tptr (t', _) + -> ( + (t', function desc -> Typ.mk ~default:t desc) ) + | _ + -> (t, fun desc -> Typ.mk desc) + in let len = Exp.Var (new_id ()) in let atoms', se', res_t' = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp t' off' inst in + create_struct_values pname tenv orig_prop footprint_part kind max_stamp t' off' inst + in let e' = Sil.array_clean_new_index footprint_part e in let se = Sil.Earray (len, [(e', se')], inst) in let res_t = mk_typ_f (Tarray (res_t', None, None)) in - (Sil.Aeq(e, e') :: atoms', se, res_t) - | Tint _, _ | Tfloat _, _ | Tvoid, _ | Tfun _, _ | Tptr _, _ | TVar _, _ -> - fail t off __POS__ + (Sil.Aeq (e, e') :: atoms', se, res_t) + | Tint _, _ | Tfloat _, _ | Tvoid, _ | Tfun _, _ | Tptr _, _ | TVar _, _ + -> fail t off __POS__ in - if Config.trace_rearrange then - begin + ( if Config.trace_rearrange then let _, se, _ = res in - L.d_strln "exiting create_struct_values, returning"; - Sil.d_sexp se; - L.d_decrease_indent 1; - L.d_ln (); L.d_ln () - end; + L.d_strln "exiting create_struct_values, returning" ; + Sil.d_sexp se ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_ln () ) ; res (** Extend the strexp by populating the path indicated by [off]. @@ -188,233 +191,250 @@ let rec create_struct_values pname tenv orig_prop footprint_part kind max_stamp for array accesses. This does not catch the array - bounds errors. If we want to implement the checks for array bounds errors, we need to change this function. *) -let rec _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp - se (typ: Typ.t) (off : Sil.offset list) inst = +let rec _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se (typ: Typ.t) + (off: Sil.offset list) inst = let new_id () = - incr max_stamp; - Ident.create kind !max_stamp in - match off, se, typ.desc with - | [], Sil.Eexp _, _ - | [], Sil.Estruct _, _ -> - [([], se, typ)] - | [], Sil.Earray _, _ -> - let off_new = Sil.Off_index (Exp.zero):: off in - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst - | (Off_fld _) :: _, Sil.Earray _, Tarray _ -> - let off_new = Sil.Off_index (Exp.zero):: off in - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst + incr max_stamp ; + Ident.create kind !max_stamp + in + match (off, se, typ.desc) with + | [], Sil.Eexp _, _ | [], Sil.Estruct _, _ + -> [([], se, typ)] + | [], Sil.Earray _, _ + -> let off_new = Sil.Off_index Exp.zero :: off in + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst + | (Off_fld _) :: _, Sil.Earray _, Tarray _ + -> let off_new = Sil.Off_index Exp.zero :: off in + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst | (Off_fld (f, _)) :: off', Sil.Estruct (fsel, inst'), Tstruct name -> ( - match Tenv.lookup tenv name with - | Some ({ fields; statics; } as struct_typ) -> ( - match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') (fields @ statics) with - | Some (_, typ', _) -> ( - match List.find ~f:(fun (f', _) -> Typ.Fieldname.equal f f') fsel with - | Some (_, se') -> - let atoms_se_typ_list' = - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se' typ' off' inst in - let replace acc (res_atoms', res_se', res_typ') = - let replace_fse ((f1, _) as ft1) = - if Typ.Fieldname.equal f1 f then (f1, res_se') else ft1 in - let res_fsel' = - List.sort - ~cmp:[%compare: Typ.Fieldname.t * Sil.strexp] - (List.map ~f:replace_fse fsel) in - let replace_fta ((f1, _, a1) as fta1) = - if Typ.Fieldname.equal f f1 then (f1, res_typ', a1) else fta1 in - let fields' = - List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_fta fields) in - ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; - (res_atoms', Sil.Estruct (res_fsel', inst'), typ) :: acc in - List.fold ~f:replace ~init:[] atoms_se_typ_list' - | None -> - let atoms', se', res_typ' = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp typ' off' inst in - let res_fsel' = - List.sort ~cmp:[%compare: Typ.Fieldname.t * Sil.strexp] ((f, se'):: fsel) in - let replace_fta (f', t', a') = - if Typ.Fieldname.equal f' f then (f, res_typ', a') else (f', t', a') in - let fields' = - List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_fta fields) in - ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; - [(atoms', Sil.Estruct (res_fsel', inst'), typ)] - ) - | None -> - raise (Exceptions.Missing_fld (f, __POS__)) - ) - | None -> - raise (Exceptions.Missing_fld (f, __POS__)) - ) - | (Off_fld _) :: _, _, _ -> - raise (Exceptions.Bad_footprint __POS__) - + match Tenv.lookup tenv name with + | Some ({fields; statics} as struct_typ) -> ( + match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') (fields @ statics) with + | Some (_, typ', _) -> ( + match List.find ~f:(fun (f', _) -> Typ.Fieldname.equal f f') fsel with + | Some (_, se') + -> let atoms_se_typ_list' = + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se' typ' + off' inst + in + let replace acc (res_atoms', res_se', res_typ') = + let replace_fse (f1, _ as ft1) = + if Typ.Fieldname.equal f1 f then (f1, res_se') else ft1 + in + let res_fsel' = + List.sort + ~cmp:[%compare : Typ.Fieldname.t * Sil.strexp] + (List.map ~f:replace_fse fsel) + in + let replace_fta (f1, _, a1 as fta1) = + if Typ.Fieldname.equal f f1 then (f1, res_typ', a1) else fta1 + in + let fields' = + List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_fta fields) + in + ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; + (res_atoms', Sil.Estruct (res_fsel', inst'), typ) :: acc + in + List.fold ~f:replace ~init:[] atoms_se_typ_list' + | None + -> let atoms', se', res_typ' = + create_struct_values pname tenv orig_prop footprint_part kind max_stamp typ' off' + inst + in + let res_fsel' = + List.sort ~cmp:[%compare : Typ.Fieldname.t * Sil.strexp] ((f, se') :: fsel) + in + let replace_fta (f', t', a') = + if Typ.Fieldname.equal f' f then (f, res_typ', a') else (f', t', a') + in + let fields' = + List.sort ~cmp:Typ.Struct.compare_field (List.map ~f:replace_fta fields) + in + ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; + [(atoms', Sil.Estruct (res_fsel', inst'), typ)] ) + | None + -> raise (Exceptions.Missing_fld (f, __POS__)) ) + | None + -> raise (Exceptions.Missing_fld (f, __POS__)) ) + | (Off_fld _) :: _, _, _ + -> raise (Exceptions.Bad_footprint __POS__) | (Off_index _) :: _, Sil.Eexp _, (Tint _ | Tfloat _ | Tvoid | Tfun _ | Tptr _) - | (Off_index _) :: _, Sil.Estruct _, Tstruct _ -> - (* L.d_strln_color Orange "turn into an array"; *) - let len = match se with - | Sil.Eexp (_, Sil.Ialloc) -> Exp.one (* if allocated explicitly, we know len is 1 *) - | _ -> - if Config.type_size then Exp.one (* Exp.Sizeof (typ, Subtype.exact) *) - else Exp.Var (new_id ()) in + | (Off_index _) :: _, Sil.Estruct _, Tstruct _ + -> (* L.d_strln_color Orange "turn into an array"; *) + let len = + match se with + | Sil.Eexp (_, Sil.Ialloc) + -> Exp.one (* if allocated explicitly, we know len is 1 *) + | _ + -> if Config.type_size then Exp.one (* Exp.Sizeof (typ, Subtype.exact) *) + else Exp.Var (new_id ()) + in let se_new = Sil.Earray (len, [(Exp.zero, se)], inst) in let typ_new = Typ.mk (Tarray (typ, None, None)) in - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se_new typ_new off inst - | (Off_index e) :: off', - Sil.Earray (len, esel, inst_arr), - Tarray (typ', len_for_typ', stride) -> ( - bounds_check tenv pname orig_prop len e (State.get_loc ()); + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se_new typ_new off + inst + | (Off_index e) :: off', Sil.Earray (len, esel, inst_arr), Tarray (typ', len_for_typ', stride) + -> ( + bounds_check tenv pname orig_prop len e (State.get_loc ()) ; match List.find ~f:(fun (e', _) -> Exp.equal e e') esel with - | Some (_, se') -> - let atoms_se_typ_list' = - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se' typ' off' inst in + | Some (_, se') + -> let atoms_se_typ_list' = + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se' typ' off' + inst + in let replace acc (res_atoms', res_se', res_typ') = let replace_ise ise = if Exp.equal e (fst ise) then (e, res_se') else ise in let res_esel' = List.map ~f:replace_ise esel in - if (Typ.equal res_typ' typ') || Int.equal (List.length res_esel') 1 then + if Typ.equal res_typ' typ' || Int.equal (List.length res_esel') 1 then ( res_atoms' , Sil.Earray (len, res_esel', inst_arr) - , Typ.mk ~default:typ (Tarray (res_typ', len_for_typ', stride)) ) - :: acc - else - raise (Exceptions.Bad_footprint __POS__) in + , Typ.mk ~default:typ (Tarray (res_typ', len_for_typ', stride)) ) :: acc + else raise (Exceptions.Bad_footprint __POS__) + in List.fold ~f:replace ~init:[] atoms_se_typ_list' - | None -> - array_case_analysis_index pname tenv orig_prop - footprint_part kind max_stamp - len esel - len_for_typ' typ' typ - e off' inst_arr inst - ) - | _, _, _ -> - raise (Exceptions.Bad_footprint __POS__) + | None + -> array_case_analysis_index pname tenv orig_prop footprint_part kind max_stamp len esel + len_for_typ' typ' typ e off' inst_arr inst ) + | _, _, _ + -> raise (Exceptions.Bad_footprint __POS__) -and array_case_analysis_index pname tenv orig_prop - footprint_part kind max_stamp - array_len array_cont - typ_array_len typ_cont typ_array - index off inst_arr inst - = +and array_case_analysis_index pname tenv orig_prop footprint_part kind max_stamp array_len + array_cont typ_array_len typ_cont typ_array index off inst_arr inst = let check_sound t' = - if not (Typ.equal typ_cont t' || List.is_empty array_cont) - then raise (Exceptions.Bad_footprint __POS__) in + if not (Typ.equal typ_cont t' || List.is_empty array_cont) then + raise (Exceptions.Bad_footprint __POS__) + in let index_in_array = - List.exists ~f:(fun (i, _) -> Prover.check_equal tenv Prop.prop_emp index i) array_cont in + List.exists ~f:(fun (i, _) -> Prover.check_equal tenv Prop.prop_emp index i) array_cont + in let array_is_full = match array_len with - | Exp.Const (Const.Cint n') -> IntLit.geq (IntLit.of_int (List.length array_cont)) n' - | _ -> false in - + | Exp.Const Const.Cint n' + -> IntLit.geq (IntLit.of_int (List.length array_cont)) n' + | _ + -> false + in if index_in_array then let array_default = Sil.Earray (array_len, array_cont, inst_arr) in let typ_default = Typ.mk ~default:typ_array (Tarray (typ_cont, typ_array_len, None)) in [([], array_default, typ_default)] - else if !Config.footprint then begin + else if !Config.footprint then let atoms, elem_se, elem_typ = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst in - check_sound elem_typ; - let cont_new = List.sort ~cmp:[%compare: Exp.t * Sil.strexp] ((index, elem_se):: array_cont) in + create_struct_values pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst + in + check_sound elem_typ ; + let cont_new = + List.sort ~cmp:[%compare : Exp.t * Sil.strexp] ((index, elem_se) :: array_cont) + in let array_new = Sil.Earray (array_len, cont_new, inst_arr) in let typ_new = Typ.mk ~default:typ_array (Tarray (elem_typ, typ_array_len, None)) in [(atoms, array_new, typ_new)] - end - else begin + else let res_new = if array_is_full then [] - else begin + else let atoms, elem_se, elem_typ = - create_struct_values - pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst in - check_sound elem_typ; + create_struct_values pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst + in + check_sound elem_typ ; let cont_new = - List.sort ~cmp:[%compare: Exp.t * Sil.strexp] ((index, elem_se):: array_cont) in + List.sort ~cmp:[%compare : Exp.t * Sil.strexp] ((index, elem_se) :: array_cont) + in let array_new = Sil.Earray (array_len, cont_new, inst_arr) in let typ_new = Typ.mk ~default:typ_array (Tarray (elem_typ, typ_array_len, None)) in [(atoms, array_new, typ_new)] - end in + in let rec handle_case acc isel_seen_rev = function - | [] -> List.concat (List.rev (res_new:: acc)) - | (i, se) as ise :: isel_unseen -> - let atoms_se_typ_list = - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se typ_cont off inst in + | [] + -> List.concat (List.rev (res_new :: acc)) + | (i, se as ise) :: isel_unseen + -> let atoms_se_typ_list = + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se typ_cont + off inst + in let atoms_se_typ_list' = List.fold ~f:(fun acc' (atoms', se', typ') -> - check_sound typ'; - let atoms_new = Sil.Aeq (index, i) :: atoms' in - let isel_new = list_rev_and_concat isel_seen_rev ((i, se'):: isel_unseen) in - let array_new = Sil.Earray (array_len, isel_new, inst_arr) in - let typ_new = Typ.mk ~default:typ_array (Tarray (typ', typ_array_len, None)) in - (atoms_new, array_new, typ_new):: acc') - ~init:[] - atoms_se_typ_list in + check_sound typ' ; + let atoms_new = Sil.Aeq (index, i) :: atoms' in + let isel_new = list_rev_and_concat isel_seen_rev ((i, se') :: isel_unseen) in + let array_new = Sil.Earray (array_len, isel_new, inst_arr) in + let typ_new = Typ.mk ~default:typ_array (Tarray (typ', typ_array_len, None)) in + (atoms_new, array_new, typ_new) :: acc') + ~init:[] atoms_se_typ_list + in let acc_new = atoms_se_typ_list' :: acc in let isel_seen_rev_new = ise :: isel_seen_rev in - handle_case acc_new isel_seen_rev_new isel_unseen in + handle_case acc_new isel_seen_rev_new isel_unseen + in handle_case [] [] array_cont - end let exp_has_only_footprint_ids e = let fav = Sil.exp_fav e in - Sil.fav_filter_ident fav (fun id -> not (Ident.is_footprint id)); + Sil.fav_filter_ident fav (fun id -> not (Ident.is_footprint id)) ; Sil.fav_is_empty fav let laundry_offset_for_footprint max_stamp offs_in = let rec laundry offs_seen eqs offs = match offs with - | [] -> - (List.rev offs_seen, List.rev eqs) - | (Sil.Off_fld _ as off):: offs' -> - let offs_seen' = off:: offs_seen in + | [] + -> (List.rev offs_seen, List.rev eqs) + | (Sil.Off_fld _ as off) :: offs' + -> let offs_seen' = off :: offs_seen in laundry offs_seen' eqs offs' - | (Sil.Off_index(idx) as off):: offs' -> - if exp_has_only_footprint_ids idx then - let offs_seen' = off:: offs_seen in + | (Sil.Off_index idx as off) :: offs' + -> if exp_has_only_footprint_ids idx then + let offs_seen' = off :: offs_seen in laundry offs_seen' eqs offs' else let () = incr max_stamp in let fid_new = Ident.create Ident.kfootprint !max_stamp in let exp_new = Exp.Var fid_new in let off_new = Sil.Off_index exp_new in - let offs_seen' = off_new:: offs_seen in - let eqs' = (fid_new, idx):: eqs in - laundry offs_seen' eqs' offs' in + let offs_seen' = off_new :: offs_seen in + let eqs' = (fid_new, idx) :: eqs in + laundry offs_seen' eqs' offs' + in laundry [] [] offs_in -let strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp - se te (off : Sil.offset list) inst = +let strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se te + (off: Sil.offset list) inst = let typ = Exp.texp_to_typ None te in let off', laundry_atoms = let off', eqs = laundry_offset_for_footprint max_stamp off in (* do laundry_offset whether footprint_part is true or not, so max_stamp is modified anyway *) - if footprint_part then - off', List.map ~f:(fun (id, e) -> Prop.mk_eq tenv (Exp.Var id) e) eqs - else off, [] in - if Config.trace_rearrange then - (L.d_str "entering strexp_extend_values se: "; Sil.d_sexp se; L.d_str " typ: "; - Typ.d_full typ; L.d_str " off': "; Sil.d_offset_list off'; - L.d_strln (if footprint_part then " FP" else " RE")); + if footprint_part then (off', List.map ~f:(fun (id, e) -> Prop.mk_eq tenv (Exp.Var id) e) eqs) + else (off, []) + in + if Config.trace_rearrange then ( + L.d_str "entering strexp_extend_values se: " ; + Sil.d_sexp se ; + L.d_str " typ: " ; + Typ.d_full typ ; + L.d_str " off': " ; + Sil.d_offset_list off' ; + L.d_strln (if footprint_part then " FP" else " RE") ) ; let atoms_se_typ_list = - _strexp_extend_values - pname tenv orig_prop footprint_part kind max_stamp se typ off' inst in + _strexp_extend_values pname tenv orig_prop footprint_part kind max_stamp se typ off' inst + in let atoms_se_typ_list_filtered = - let check_neg_atom atom = Prover.check_atom tenv Prop.prop_emp (Prover.atom_negate tenv atom) in + let check_neg_atom atom = + Prover.check_atom tenv Prop.prop_emp (Prover.atom_negate tenv atom) + in let check_not_inconsistent (atoms, _, _) = not (List.exists ~f:check_neg_atom atoms) in - List.filter ~f:check_not_inconsistent atoms_se_typ_list in - if Config.trace_rearrange then L.d_strln "exiting strexp_extend_values"; - let sizeof_data = match te with - | Exp.Sizeof sizeof_data -> sizeof_data - | _ -> {Exp.typ=Typ.mk Typ.Tvoid; nbytes=None; dynamic_length=None; subtype=Subtype.exact} in - List.map ~f:(fun (atoms', se', typ') -> - (laundry_atoms @ atoms', se', Exp.Sizeof { sizeof_data with typ=typ'})) + List.filter ~f:check_not_inconsistent atoms_se_typ_list + in + if Config.trace_rearrange then L.d_strln "exiting strexp_extend_values" ; + let sizeof_data = + match te with + | Exp.Sizeof sizeof_data + -> sizeof_data + | _ + -> {Exp.typ= Typ.mk Typ.Tvoid; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in + List.map + ~f:(fun (atoms', se', typ') -> + (laundry_atoms @ atoms', se', Exp.Sizeof {sizeof_data with typ= typ'})) atoms_se_typ_list_filtered let collect_root_offset exp = @@ -423,46 +443,51 @@ let collect_root_offset exp = (root, offsets) (** Exp.Construct a points-to predicate for an expression, to add to a footprint. *) -let mk_ptsto_exp_footprint - pname tenv orig_prop (lexp, typ) max_stamp inst : Sil.hpred * Sil.hpred * Sil.atom list = +let mk_ptsto_exp_footprint pname tenv orig_prop (lexp, typ) max_stamp inst + : Sil.hpred * Sil.hpred * Sil.atom list = let root, off = collect_root_offset lexp in - if not (exp_has_only_footprint_ids root) - then begin - (* in angelic mode, purposely ignore dangling pointer warnings during the footprint phase -- we + if not (exp_has_only_footprint_ids root) then + if (* in angelic mode, purposely ignore dangling pointer warnings during the footprint phase -- we * will fix them during the re - execution phase *) - if not (Config.angelic_execution && !Config.footprint) then - begin - L.internal_error "!!!! Footprint Error, Bad Root : %a !!!! @\n" Exp.pp lexp; - let deref_str = Localise.deref_str_dangling None in - let err_desc = - Errdesc.explain_dereference tenv deref_str orig_prop (State.get_loc ()) in - raise - (Exceptions.Dangling_pointer_dereference - (None, err_desc, __POS__)) - end - end; + not (Config.angelic_execution && !Config.footprint) + then ( + L.internal_error "!!!! Footprint Error, Bad Root : %a !!!! @\n" Exp.pp lexp ; + let deref_str = Localise.deref_str_dangling None in + let err_desc = Errdesc.explain_dereference tenv deref_str orig_prop (State.get_loc ()) in + raise (Exceptions.Dangling_pointer_dereference (None, err_desc, __POS__)) ) ; let off_foot, eqs = laundry_offset_for_footprint max_stamp off in - let subtype = match !Config.curr_language with - | Config.Clang -> Subtype.exact - | Config.Java -> Subtype.subtypes in - let create_ptsto footprint_part off0 = match root, off0, typ.Typ.desc with - | Exp.Lvar pvar, [], Typ.Tfun _ -> - let fun_name = Typ.Procname.from_string_c_fun (Mangled.to_string (Pvar.get_name pvar)) in + let subtype = + match !Config.curr_language with + | Config.Clang + -> Subtype.exact + | Config.Java + -> Subtype.subtypes + in + let create_ptsto footprint_part off0 = + match (root, off0, typ.Typ.desc) with + | Exp.Lvar pvar, [], Typ.Tfun _ + -> let fun_name = Typ.Procname.from_string_c_fun (Mangled.to_string (Pvar.get_name pvar)) in let fun_exp = Exp.Const (Const.Cfun fun_name) in - ([], Prop.mk_ptsto tenv root (Sil.Eexp (fun_exp, inst)) - (Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype})) - | _, [], Typ.Tfun _ -> - let atoms, se, typ = - create_struct_values - pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ off0 inst in - (atoms, Prop.mk_ptsto tenv root se - (Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype})) - | _ -> - let atoms, se, typ = - create_struct_values - pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ off0 inst in - (atoms, Prop.mk_ptsto tenv root se - (Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype})) in + ( [] + , Prop.mk_ptsto tenv root (Sil.Eexp (fun_exp, inst)) + (Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype}) ) + | _, [], Typ.Tfun _ + -> let atoms, se, typ = + create_struct_values pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ + off0 inst + in + ( atoms + , Prop.mk_ptsto tenv root se + (Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype}) ) + | _ + -> let atoms, se, typ = + create_struct_values pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ + off0 inst + in + ( atoms + , Prop.mk_ptsto tenv root se + (Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype}) ) + in let atoms, ptsto_foot = create_ptsto true off_foot in let sub = Sil.subst_of_list eqs in let ptsto = Sil.hpred_sub sub ptsto_foot in @@ -473,27 +498,35 @@ let mk_ptsto_exp_footprint If it exists, return None. Otherwise, return [Some fld] with [fld] the missing field. *) let prop_iter_check_fields_ptsto_shallow tenv iter lexp = let offset = Sil.exp_get_offsets lexp in - let (_, se, _) = + let _, se, _ = match Prop.prop_iter_current tenv iter with - | Sil.Hpointsto (e, se, t), _ -> (e, se, t) - | _ -> assert false in + | Sil.Hpointsto (e, se, t), _ + -> (e, se, t) + | _ + -> assert false + in let rec check_offset se = function - | [] -> None - | (Sil.Off_fld (fld, _)):: off' -> - (match se with - | Sil.Estruct (fsel, _) -> - (match List.find ~f:(fun (fld', _) -> Typ.Fieldname.equal fld fld') fsel with - | Some (_, se') -> - check_offset se' off' - | None -> Some fld) - | _ -> Some fld) - | (Sil.Off_index _):: _ -> None in + | [] + -> None + | (Sil.Off_fld (fld, _)) :: off' -> ( + match se with + | Sil.Estruct (fsel, _) -> ( + match List.find ~f:(fun (fld', _) -> Typ.Fieldname.equal fld fld') fsel with + | Some (_, se') + -> check_offset se' off' + | None + -> Some fld ) + | _ + -> Some fld ) + | (Sil.Off_index _) :: _ + -> None + in check_offset se offset let fav_max_stamp fav = let max_stamp = ref 0 in let f id = max_stamp := max !max_stamp (Ident.get_stamp id) in - List.iter ~f (Sil.fav_to_list fav); + List.iter ~f (Sil.fav_to_list fav) ; max_stamp (** [prop_iter_extend_ptsto iter lexp] extends the current psto @@ -501,108 +534,143 @@ let fav_max_stamp fav = [lexp] -- field splitting model. It also materializes all indices accessed in lexp. *) let prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst = - if Config.trace_rearrange then - (L.d_str "entering prop_iter_extend_ptsto lexp: "; Sil.d_exp lexp; L.d_ln ()); + if Config.trace_rearrange then ( + L.d_str "entering prop_iter_extend_ptsto lexp: " ; Sil.d_exp lexp ; L.d_ln () ) ; let offset = Sil.exp_get_offsets lexp in let max_stamp = fav_max_stamp (Prop.prop_iter_fav iter) in let max_stamp_val = !max_stamp in let extend_footprint_pred = function - | Sil.Hpointsto(e, se, te) -> - let atoms_se_te_list = - strexp_extend_values - pname tenv orig_prop true Ident.kfootprint (ref max_stamp_val) se te offset inst in + | Sil.Hpointsto (e, se, te) + -> let atoms_se_te_list = + strexp_extend_values pname tenv orig_prop true Ident.kfootprint (ref max_stamp_val) se te + offset inst + in List.map ~f:(fun (atoms', se', te') -> (atoms', Sil.Hpointsto (e, se', te'))) atoms_se_te_list - | Sil.Hlseg (k, hpara, e1, e2, el) -> - begin - match hpara.Sil.body with - | Sil.Hpointsto(e', se', te'):: body_rest -> - let atoms_se_te_list = - strexp_extend_values - pname tenv orig_prop true Ident.kfootprint - (ref max_stamp_val) se' te' offset inst in - let atoms_body_list = - List.map - ~f:(fun (atoms0, se0, te0) -> (atoms0, Sil.Hpointsto(e', se0, te0):: body_rest)) - atoms_se_te_list in - let atoms_hpara_list = - List.map - ~f:(fun (atoms, body') -> (atoms, { hpara with Sil.body = body'})) - atoms_body_list in - List.map - ~f:(fun (atoms, hpara') -> (atoms, Sil.Hlseg(k, hpara', e1, e2, el))) - atoms_hpara_list - | _ -> assert false - end - | _ -> assert false in + | Sil.Hlseg (k, hpara, e1, e2, el) -> ( + match hpara.Sil.body with + | (Sil.Hpointsto (e', se', te')) :: body_rest + -> let atoms_se_te_list = + strexp_extend_values pname tenv orig_prop true Ident.kfootprint (ref max_stamp_val) se' + te' offset inst + in + let atoms_body_list = + List.map + ~f:(fun (atoms0, se0, te0) -> (atoms0, Sil.Hpointsto (e', se0, te0) :: body_rest)) + atoms_se_te_list + in + let atoms_hpara_list = + List.map + ~f:(fun (atoms, body') -> (atoms, {hpara with Sil.body= body'})) + atoms_body_list + in + List.map + ~f:(fun (atoms, hpara') -> (atoms, Sil.Hlseg (k, hpara', e1, e2, el))) + atoms_hpara_list + | _ + -> assert false ) + | _ + -> assert false + in let atoms_se_te_to_iter e (atoms, se, te) = let iter' = List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter atoms in - Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se, te)) in + Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se, te)) + in let do_extend e se te = - if Config.trace_rearrange then begin - L.d_strln "entering do_extend"; - L.d_str "e: "; Sil.d_exp e; L.d_str " se : "; Sil.d_sexp se; L.d_str " te: "; Sil.d_texp_full te; - L.d_ln (); L.d_ln () - end; - let extend_kind = match e with (* Determine whether to extend the footprint part or just the normal part *) - | Exp.Var id when not (Ident.is_footprint id) -> Ident.kprimed - | Exp.Lvar pvar when Pvar.is_local pvar -> Ident.kprimed - | _ -> Ident.kfootprint in + if Config.trace_rearrange then ( + L.d_strln "entering do_extend" ; + L.d_str "e: " ; + Sil.d_exp e ; + L.d_str " se : " ; + Sil.d_sexp se ; + L.d_str " te: " ; + Sil.d_texp_full te ; + L.d_ln () ; + L.d_ln () ) ; + let extend_kind = + match e with + (* Determine whether to extend the footprint part or just the normal part *) + | Exp.Var id + when not (Ident.is_footprint id) + -> Ident.kprimed + | Exp.Lvar pvar when Pvar.is_local pvar + -> Ident.kprimed + | _ + -> Ident.kfootprint + in let iter_list = let atoms_se_te_list = - strexp_extend_values - pname tenv orig_prop false extend_kind max_stamp se te offset inst in - List.map ~f:(atoms_se_te_to_iter e) atoms_se_te_list in + strexp_extend_values pname tenv orig_prop false extend_kind max_stamp se te offset inst + in + List.map ~f:(atoms_se_te_to_iter e) atoms_se_te_list + in let res_iter_list = - if Ident.equal_kind extend_kind Ident.kprimed - then iter_list (* normal part already extended: nothing to do *) - else (* extend footprint part *) + if Ident.equal_kind extend_kind Ident.kprimed then iter_list + (* normal part already extended: nothing to do *) + else + (* extend footprint part *) let atoms_fp_sigma_list = let footprint_sigma = Prop.prop_iter_get_footprint_sigma iter in let sigma_pto, sigma_rest = - List.partition_tf ~f:(function - | Sil.Hpointsto(e', _, _) -> Exp.equal e e' - | Sil.Hlseg (_, _, e1, _, _) -> Exp.equal e e1 - | Sil.Hdllseg (_, _, e_iF, _, _, e_iB, _) -> - Exp.equal e e_iF || Exp.equal e e_iB - ) footprint_sigma in + List.partition_tf + ~f:(function + | Sil.Hpointsto (e', _, _) + -> Exp.equal e e' + | Sil.Hlseg (_, _, e1, _, _) + -> Exp.equal e e1 + | Sil.Hdllseg (_, _, e_iF, _, _, e_iB, _) + -> Exp.equal e e_iF || Exp.equal e e_iB) + footprint_sigma + in let atoms_sigma_list = match sigma_pto with - | [hpred] -> - let atoms_hpred_list = extend_footprint_pred hpred in + | [hpred] + -> let atoms_hpred_list = extend_footprint_pred hpred in List.map ~f:(fun (atoms, hpred') -> (atoms, hpred' :: sigma_rest)) atoms_hpred_list - | _ -> - L.d_warning "Cannot extend "; Sil.d_exp lexp; L.d_strln " in"; Prop.d_prop (Prop.prop_iter_to_prop tenv iter); L.d_ln(); - [([], footprint_sigma)] in + | _ + -> L.d_warning "Cannot extend " ; + Sil.d_exp lexp ; + L.d_strln " in" ; + Prop.d_prop (Prop.prop_iter_to_prop tenv iter) ; + L.d_ln () ; + [([], footprint_sigma)] + in List.map ~f:(fun (atoms, sigma') -> (atoms, List.stable_sort ~cmp:Sil.compare_hpred sigma')) - atoms_sigma_list in - let iter_atoms_fp_sigma_list = - list_product iter_list atoms_fp_sigma_list in - List.map ~f:(fun (iter, (atoms, fp_sigma)) -> + atoms_sigma_list + in + let iter_atoms_fp_sigma_list = list_product iter_list atoms_fp_sigma_list in + List.map + ~f:(fun (iter, (atoms, fp_sigma)) -> let iter' = - List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter atoms in - Prop.prop_iter_replace_footprint_sigma iter' fp_sigma - ) iter_atoms_fp_sigma_list in - let res_prop_list = - List.map ~f:(Prop.prop_iter_to_prop tenv) res_iter_list in - begin - L.d_str "in prop_iter_extend_ptsto lexp: "; Sil.d_exp lexp; L.d_ln (); - L.d_strln "prop before:"; - let prop_before = Prop.prop_iter_to_prop tenv iter in - Prop.d_prop prop_before; L.d_ln (); - L.d_ln (); L.d_ln (); - L.d_strln "prop list after:"; - Propgraph.d_proplist prop_before res_prop_list; L.d_ln (); - L.d_ln (); L.d_ln (); - res_iter_list - end in - begin - match Prop.prop_iter_current tenv iter with - | Sil.Hpointsto (e, se, te), _ -> do_extend e se te - | _ -> assert false - end + List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter atoms + in + Prop.prop_iter_replace_footprint_sigma iter' fp_sigma) + iter_atoms_fp_sigma_list + in + let res_prop_list = List.map ~f:(Prop.prop_iter_to_prop tenv) res_iter_list in + L.d_str "in prop_iter_extend_ptsto lexp: " ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_strln "prop before:" ; + let prop_before = Prop.prop_iter_to_prop tenv iter in + Prop.d_prop prop_before ; + L.d_ln () ; + L.d_ln () ; + L.d_ln () ; + L.d_strln "prop list after:" ; + Propgraph.d_proplist prop_before res_prop_list ; + L.d_ln () ; + L.d_ln () ; + L.d_ln () ; + res_iter_list + in + match Prop.prop_iter_current tenv iter with + | Sil.Hpointsto (e, se, te), _ + -> do_extend e se te + | _ + -> assert false (** Add a pointsto for [root(lexp): typ] to the sigma and footprint of a prop, if it's compatible with the allowed footprint @@ -612,25 +680,28 @@ let prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst = let prop_iter_add_hpred_footprint_to_prop pname tenv prop (lexp, typ) inst = let max_stamp = fav_max_stamp (Prop.prop_footprint_fav prop) in let ptsto, ptsto_foot, atoms = - mk_ptsto_exp_footprint pname tenv prop (lexp, typ) max_stamp inst in - L.d_strln "++++ Adding footprint frame"; - Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto); - L.d_ln (); L.d_ln (); + mk_ptsto_exp_footprint pname tenv prop (lexp, typ) max_stamp inst + in + L.d_strln "++++ Adding footprint frame" ; + Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto) ; + L.d_ln () ; + L.d_ln () ; let eprop = Prop.expose prop in let sigma_fp = ptsto_foot :: eprop.Prop.sigma_fp in let nsigma_fp = Prop.sigma_normalize_prop tenv Prop.prop_emp sigma_fp in let prop' = Prop.normalize tenv (Prop.set eprop ~sigma_fp:nsigma_fp) in let prop_new = - List.fold ~f:(Prop.prop_atom_and tenv ~footprint:!Config.footprint) ~init:prop' atoms in - let iter = match (Prop.prop_iter_create prop_new) with - | None -> + List.fold ~f:(Prop.prop_atom_and tenv ~footprint:!Config.footprint) ~init:prop' atoms + in + let iter = + match Prop.prop_iter_create prop_new with + | None + -> ( let prop_new' = Prop.normalize tenv (Prop.prop_hpred_star prop_new ptsto) in - begin - match (Prop.prop_iter_create prop_new') with - | None -> assert false - | Some iter -> iter - end - | Some iter -> Prop.prop_iter_prev_then_insert iter ptsto in + match Prop.prop_iter_create prop_new' with None -> assert false | Some iter -> iter ) + | Some iter + -> Prop.prop_iter_prev_then_insert iter ptsto + in let offsets_default = Sil.exp_get_offsets lexp in Prop.prop_iter_set_state iter offsets_default @@ -641,212 +712,218 @@ let add_guarded_by_constraints tenv prop lexp pdesc = let pname = Procdesc.get_proc_name pdesc in let excluded_guardedby_string str = (* nothing with a space in it can be a valid Java expression, shouldn't warn *) - let is_invalid_exp_str str = - String.contains str ' ' in + let is_invalid_exp_str str = String.contains str ' ' in (* don't warn on @GuardedBy("ui_thread") in any form *) let is_ui_thread str = let lowercase_str = String.lowercase str in - String.equal lowercase_str "ui_thread" || - String.equal lowercase_str "ui-thread" || - String.equal lowercase_str "uithread" in - is_invalid_exp_str str || is_ui_thread str in - let guarded_by_str_is_this guarded_by_str = - String.is_suffix ~suffix:"this" guarded_by_str in + String.equal lowercase_str "ui_thread" || String.equal lowercase_str "ui-thread" + || String.equal lowercase_str "uithread" + in + is_invalid_exp_str str || is_ui_thread str + in + let guarded_by_str_is_this guarded_by_str = String.is_suffix ~suffix:"this" guarded_by_str in let guarded_by_str_is_class guarded_by_str class_str = let dollar_normalize s = String.map s ~f:(function '$' -> '.' | c -> c) in - String.is_suffix ~suffix:(dollar_normalize guarded_by_str) (dollar_normalize (class_str ^ ".class")) in + String.is_suffix ~suffix:(dollar_normalize guarded_by_str) + (dollar_normalize (class_str ^ ".class")) + in let guarded_by_str_is_current_class guarded_by_str = function - | Typ.Procname.Java java_pname -> - (* programmers write @GuardedBy("MyClass.class") when the field is guarded by the class *) + | Typ.Procname.Java java_pname + -> (* programmers write @GuardedBy("MyClass.class") when the field is guarded by the class *) guarded_by_str_is_class guarded_by_str (Typ.Procname.java_get_class_name java_pname) - | _ -> false in - + | _ + -> false + in let guarded_by_str_is_class_this class_name guarded_by_str = - let fully_qualified_this = - Printf.sprintf "%s.this" class_name in + let fully_qualified_this = Printf.sprintf "%s.this" class_name in String.is_suffix ~suffix:guarded_by_str fully_qualified_this in - (* return true if [guarded_by_str] is a suffix of ".this" *) let guarded_by_str_is_super_class_this guarded_by_str pname = match pname with - | Typ.Procname.Java java_pname -> - let current_class_type_name = (Typ.Procname.java_get_class_type_name java_pname) in + | Typ.Procname.Java java_pname + -> let current_class_type_name = Typ.Procname.java_get_class_type_name java_pname in let comparison class_type_name _ = - guarded_by_str_is_class_this (Typ.Name.to_string class_type_name) guarded_by_str in + guarded_by_str_is_class_this (Typ.Name.to_string class_type_name) guarded_by_str + in PatternMatch.supertype_exists tenv comparison current_class_type_name - | _ -> false in - - + | _ + -> false + in (* return true if [guarded_by_str] is as suffix of ".this" *) let guarded_by_str_is_current_class_this guarded_by_str = function - | Typ.Procname.Java java_pname -> - guarded_by_str_is_class_this (Typ.Procname.java_get_class_name java_pname) guarded_by_str - | _ -> false in - + | Typ.Procname.Java java_pname + -> guarded_by_str_is_class_this (Typ.Procname.java_get_class_name java_pname) guarded_by_str + | _ + -> false + in let extract_guarded_by_str item_annot = let annot_extract_guarded_by_str ((annot: Annot.t), _) = - if Annotations.annot_ends_with annot Annotations.guarded_by - then + if Annotations.annot_ends_with annot Annotations.guarded_by then match annot.parameters with - | [guarded_by_str] when not (excluded_guardedby_string guarded_by_str) -> - Some guarded_by_str - | _ -> - None - else - None in - List.find_map ~f:annot_extract_guarded_by_str item_annot in + | [guarded_by_str] when not (excluded_guardedby_string guarded_by_str) + -> Some guarded_by_str + | _ + -> None + else None + in + List.find_map ~f:annot_extract_guarded_by_str item_annot + in let extract_suppress_warnings_str item_annot = let annot_suppress_warnings_str ((annot: Annot.t), _) = - if Annotations.annot_ends_with annot Annotations.suppress_lint - then - match annot.parameters with - | [suppr_str] -> - Some suppr_str - | _ -> - None - else - None in - List.find_map ~f:annot_suppress_warnings_str item_annot in + if Annotations.annot_ends_with annot Annotations.suppress_lint then + match annot.parameters with [suppr_str] -> Some suppr_str | _ -> None + else None + in + List.find_map ~f:annot_suppress_warnings_str item_annot + in (* if [fld] is annotated with @GuardedBy("mLock"), return mLock *) let get_guarded_by_fld_str fld typ = match Typ.Struct.get_field_type_and_annotation ~lookup fld typ with - | Some (_, item_annot) -> - begin - match extract_guarded_by_str item_annot with - | Some "this" -> - (* expand "this" into .this *) - Some (Printf.sprintf "%s.this" (Typ.Fieldname.java_get_class fld)) - | guarded_by_str_opt -> - guarded_by_str_opt - end - | _ -> None in + | Some (_, item_annot) -> ( + match extract_guarded_by_str item_annot with + | Some "this" + -> (* expand "this" into .this *) + Some (Printf.sprintf "%s.this" (Typ.Fieldname.java_get_class fld)) + | guarded_by_str_opt + -> guarded_by_str_opt ) + | _ + -> None + in (* find A.guarded_by_fld_str |-> B and return Some B, or None if there is no such hpred *) let find_guarded_by_exp guarded_by_str0 sigma = - let is_guarded_by_fld guarded_by_str fld _ = (* this comparison needs to be somewhat fuzzy, since programmers are free to write @GuardedBy("mLock"), @GuardedBy("MyClass.mLock"), or use other conventions *) - String.equal (Typ.Fieldname.to_flat_string fld) guarded_by_str || - String.equal (Typ.Fieldname.to_string fld) guarded_by_str in - + String.equal (Typ.Fieldname.to_flat_string fld) guarded_by_str + || String.equal (Typ.Fieldname.to_string fld) guarded_by_str + in let get_fld_strexp_and_typ typ f flds = let match_one (fld, strexp) = match Typ.Struct.get_field_type_and_annotation ~lookup fld typ with - | Some (fld_typ, _) when f fld fld_typ -> Some (strexp, fld_typ) - | _ -> None in - List.find_map ~f:match_one flds in - + | Some (fld_typ, _) when f fld fld_typ + -> Some (strexp, fld_typ) + | _ + -> None + in + List.find_map ~f:match_one flds + in (* sometimes, programmers will write @GuardedBy("T.f") with the meaning "guarded by the field f of the object of type T in the current state." note that this is ambiguous when there are multiple objects of type T, but let's try to respect the intention *) let match_on_field_type typ flds = match String.rsplit2 guarded_by_str0 ~on:'.' with - | Some (class_part, field_part) -> - let typ_matches_guarded_by _ {Typ.desc} = match desc with - | Typ.Tptr (ptr_typ, _) -> - String.is_suffix ~suffix:class_part (Typ.to_string ptr_typ); - | _ -> - false in - begin - match get_fld_strexp_and_typ typ typ_matches_guarded_by flds with - | Some (Sil.Eexp (matching_exp, _), _) -> - List.find_map - ~f:(function - | Sil.Hpointsto (lhs_exp, Estruct (matching_flds, _), - Sizeof {typ=fld_typ}) - when Exp.equal lhs_exp matching_exp -> - get_fld_strexp_and_typ - fld_typ (is_guarded_by_fld field_part) matching_flds - | _ -> - None) - sigma - | _ -> - None - end - | _ -> - None in - - List.find_map ~f:(fun hpred -> (match hpred with - | Sil.Hpointsto ((Const (Cclass clazz) as lhs_exp), _, - Exp.Sizeof {typ}) - | Sil.Hpointsto (_, Sil.Eexp (Const (Cclass clazz) as lhs_exp, _), - Exp.Sizeof {typ}) - when guarded_by_str_is_class guarded_by_str0 (Ident.name_to_string clazz) -> - Some (Sil.Eexp (lhs_exp, Sil.inst_none), typ) - | Sil.Hpointsto (_, Estruct (flds, _), Exp.Sizeof {typ}) -> - begin - (* first, try to find a field that exactly matches the guarded-by string *) - match get_fld_strexp_and_typ typ (is_guarded_by_fld guarded_by_str0) flds with - | None when guarded_by_str_is_this guarded_by_str0 -> - (* if the guarded-by string is "OuterClass.this", look for "this$n" for some n. + | Some (class_part, field_part) + -> ( + let typ_matches_guarded_by _ {Typ.desc} = + match desc with + | Typ.Tptr (ptr_typ, _) + -> String.is_suffix ~suffix:class_part (Typ.to_string ptr_typ) + | _ + -> false + in + match get_fld_strexp_and_typ typ typ_matches_guarded_by flds with + | Some (Sil.Eexp (matching_exp, _), _) + -> List.find_map + ~f:(function + | Sil.Hpointsto (lhs_exp, Estruct (matching_flds, _), Sizeof {typ= fld_typ}) + when Exp.equal lhs_exp matching_exp + -> get_fld_strexp_and_typ fld_typ (is_guarded_by_fld field_part) matching_flds + | _ + -> None) + sigma + | _ + -> None ) + | _ + -> None + in + List.find_map + ~f:(fun hpred -> + match[@warning "-57"] (* FIXME: silenced warning may be legit *) hpred with + | Sil.Hpointsto ((Const Cclass clazz as lhs_exp), _, Exp.Sizeof {typ}) + | Sil.Hpointsto (_, Sil.Eexp ((Const Cclass clazz as lhs_exp), _), Exp.Sizeof {typ}) + when guarded_by_str_is_class guarded_by_str0 (Ident.name_to_string clazz) + -> Some (Sil.Eexp (lhs_exp, Sil.inst_none), typ) + | Sil.Hpointsto (_, Estruct (flds, _), Exp.Sizeof {typ}) -> ( + match + (* first, try to find a field that exactly matches the guarded-by string *) + get_fld_strexp_and_typ typ (is_guarded_by_fld guarded_by_str0) flds + with + | None when guarded_by_str_is_this guarded_by_str0 + -> (* if the guarded-by string is "OuterClass.this", look for "this$n" for some n. note that this is a bit sketchy when there are mutliple this$n's, but there's nothing we can do to disambiguate them. *) - get_fld_strexp_and_typ - typ - (fun f _ -> Typ.Fieldname.java_is_outer_instance f) - flds - | None -> - (* can't find an exact match. try a different convention. *) - match_on_field_type typ flds - | Some _ as res_opt -> - res_opt - end + get_fld_strexp_and_typ typ (fun f _ -> Typ.Fieldname.java_is_outer_instance f) flds + | None + -> (* can't find an exact match. try a different convention. *) + match_on_field_type typ flds + | Some _ as res_opt + -> res_opt ) | Sil.Hpointsto (Lvar pvar, rhs_exp, Exp.Sizeof {typ}) - when (guarded_by_str_is_current_class_this guarded_by_str0 pname || - guarded_by_str_is_super_class_this guarded_by_str0 pname - ) && Pvar.is_this pvar -> - Some (rhs_exp, typ) - | _ -> - None) [@warning "-57"] (* FIXME: silenced warning may be legit *)) - sigma in + when ( guarded_by_str_is_current_class_this guarded_by_str0 pname + || guarded_by_str_is_super_class_this guarded_by_str0 pname ) + && Pvar.is_this pvar + -> Some (rhs_exp, typ) + | _ + -> None) + sigma + in (* warn if the access to [lexp] is not protected by the [guarded_by_fld_str] lock *) let enforce_guarded_access_ accessed_fld guarded_by_str prop = (* return true if [pdesc] has an annotation that matches [guarded_by_str] *) let proc_has_matching_annot pdesc guarded_by_str = match extract_guarded_by_str (Annotations.pdesc_get_return_annot pdesc) with - | Some proc_guarded_by_str -> - (* the lock is not held, but the procedure is annotated with @GuardedBy *) + | Some proc_guarded_by_str + -> (* the lock is not held, but the procedure is annotated with @GuardedBy *) String.equal proc_guarded_by_str guarded_by_str - | None -> false in + | None + -> false + in let is_synchronized_on_class guarded_by_str = - guarded_by_str_is_current_class guarded_by_str pname && - Procdesc.is_java_synchronized pdesc && Typ.Procname.java_is_static pname in + guarded_by_str_is_current_class guarded_by_str pname && Procdesc.is_java_synchronized pdesc + && Typ.Procname.java_is_static pname + in let warn accessed_fld guarded_by_str = let loc = State.get_loc () in - let err_desc = - Localise.desc_unsafe_guarded_by_access accessed_fld guarded_by_str loc in + let err_desc = Localise.desc_unsafe_guarded_by_access accessed_fld guarded_by_str loc in let exn = Exceptions.Unsafe_guarded_by_access (err_desc, __POS__) in - Reporting.log_error_deprecated pname exn in + Reporting.log_error_deprecated pname exn + in let rec is_read_write_lock typ = - let str_is_read_write_lock str = String.is_suffix ~suffix:"ReadWriteUpdateLock" str || - String.is_suffix ~suffix:"ReadWriteLock" str in + let str_is_read_write_lock str = + String.is_suffix ~suffix:"ReadWriteUpdateLock" str + || String.is_suffix ~suffix:"ReadWriteLock" str + in match typ.Typ.desc with - | Typ.Tstruct name -> str_is_read_write_lock (Typ.Name.name name) - | Typ.Tptr (typ, _) -> is_read_write_lock typ - | _ -> false in + | Typ.Tstruct name + -> str_is_read_write_lock (Typ.Name.name name) + | Typ.Tptr (typ, _) + -> is_read_write_lock typ + | _ + -> false + in let has_lock guarded_by_exp = - ((guarded_by_str_is_current_class_this guarded_by_str pname || - guarded_by_str_is_super_class_this guarded_by_str pname ) - && - Procdesc.is_java_synchronized pdesc) - || - (guarded_by_str_is_current_class guarded_by_str pname && - Procdesc.is_java_synchronized pdesc && Typ.Procname.java_is_static pname) || - (* or the prop says we already have the lock *) - List.exists - ~f:(function - | Sil.Apred (Alocked, _) -> true - | _ -> false) - (Attribute.get_for_exp tenv prop guarded_by_exp) in + ( guarded_by_str_is_current_class_this guarded_by_str pname + || guarded_by_str_is_super_class_this guarded_by_str pname ) + && Procdesc.is_java_synchronized pdesc + || guarded_by_str_is_current_class guarded_by_str pname + && Procdesc.is_java_synchronized pdesc && Typ.Procname.java_is_static pname + || (* or the prop says we already have the lock *) + List.exists + ~f:(function Sil.Apred (Alocked, _) -> true | _ -> false) + (Attribute.get_for_exp tenv prop guarded_by_exp) + in let guardedby_is_self_referential = - String.equal "itself" (String.lowercase guarded_by_str) || - String.is_suffix ~suffix:guarded_by_str (Typ.Fieldname.to_string accessed_fld) in + String.equal "itself" (String.lowercase guarded_by_str) + || String.is_suffix ~suffix:guarded_by_str (Typ.Fieldname.to_string accessed_fld) + in let proc_has_suppress_guarded_by_annot pdesc = match extract_suppress_warnings_str (Annotations.pdesc_get_return_annot pdesc) with - | Some suppression_str-> - String.equal suppression_str "InvalidAccessToGuardedField" - | None -> false in + | Some suppression_str + -> String.equal suppression_str "InvalidAccessToGuardedField" + | None + -> false + in let should_warn pdesc = (* adding this check implements "by reference" semantics for guarded-by rather than "by value" semantics. if this access is through a local L or field V.f @@ -855,51 +932,49 @@ let add_guarded_by_constraints tenv prop lexp pdesc = let is_accessible_through_local_ref exp = List.exists ~f:(function - | Sil.Hpointsto (Lvar _, Eexp (rhs_exp, _), _) -> - Exp.equal exp rhs_exp - | Sil.Hpointsto (_, Estruct (flds, _), _) -> - List.exists - ~f:(fun (fld, strexp) -> match strexp with - | Sil.Eexp (rhs_exp, _) -> - Exp.equal exp rhs_exp && not (Typ.Fieldname.equal fld accessed_fld) - | _ -> - false) + | Sil.Hpointsto (Lvar _, Eexp (rhs_exp, _), _) + -> Exp.equal exp rhs_exp + | Sil.Hpointsto (_, Estruct (flds, _), _) + -> List.exists + ~f:(fun (fld, strexp) -> + match strexp with + | Sil.Eexp (rhs_exp, _) + -> Exp.equal exp rhs_exp && not (Typ.Fieldname.equal fld accessed_fld) + | _ + -> false) flds - | _ -> false) - prop.Prop.sigma in - Procdesc.get_access pdesc <> PredSymb.Private && - not (Annotations.pdesc_return_annot_ends_with pdesc Annotations.visibleForTesting) && - not (Typ.Procname.java_is_access_method (Procdesc.get_proc_name pdesc)) && - not (is_accessible_through_local_ref lexp) && - not guardedby_is_self_referential && - not (proc_has_suppress_guarded_by_annot pdesc) + | _ + -> false) + prop.Prop.sigma + in + Procdesc.get_access pdesc <> PredSymb.Private + && not (Annotations.pdesc_return_annot_ends_with pdesc Annotations.visibleForTesting) + && not (Typ.Procname.java_is_access_method (Procdesc.get_proc_name pdesc)) + && not (is_accessible_through_local_ref lexp) && not guardedby_is_self_referential + && not (proc_has_suppress_guarded_by_annot pdesc) in match find_guarded_by_exp guarded_by_str prop.Prop.sigma with - | Some (Sil.Eexp (guarded_by_exp, _), typ) -> - if is_read_write_lock typ - then + | Some (Sil.Eexp (guarded_by_exp, _), typ) + -> if is_read_write_lock typ then (* TODO: model/understand read-write locks rather than ignoring them *) prop - else if has_lock guarded_by_exp - then + else if has_lock guarded_by_exp then (* we have the lock; no need to add a proof obligation *) (* TODO: materialize [fld], but don't add [fld] to the footprint. *) prop - else - (* we don't know if we have the lock or not. *) - if should_warn pdesc - then - begin - (* non-private method; can't ensure that the lock is held. warn. *) - warn accessed_fld guarded_by_str; - prop - end + else if (* we don't know if we have the lock or not. *) + should_warn pdesc then ( + (* non-private method; can't ensure that the lock is held. warn. *) + warn accessed_fld guarded_by_str ; + prop ) else (* private method. add locked proof obligation to [pdesc] *) Attribute.add tenv ~footprint:true prop Alocked [guarded_by_exp] - | _ -> - if not (proc_has_matching_annot pdesc guarded_by_str - || is_synchronized_on_class guarded_by_str) && should_warn pdesc + | _ + -> if not + ( proc_has_matching_annot pdesc guarded_by_str + || is_synchronized_on_class guarded_by_str ) + && should_warn pdesc then (* can't find the object the annotation refers to, and procedure is not annotated. warn *) warn accessed_fld guarded_by_str @@ -907,26 +982,35 @@ let add_guarded_by_constraints tenv prop lexp pdesc = (* procedure has same GuardedBy annotation as the field. we would like to add a proof obligation, but we can't (because we can't find an expression corresponding to the lock in the current prop). so just be silent. *) - (); - prop in + () ; + prop + in let enforce_guarded_access fld typ prop = match get_guarded_by_fld_str fld typ with - | Some guarded_by_fld_str -> enforce_guarded_access_ fld guarded_by_fld_str prop - | None -> prop in - let check_fld_locks typ prop_acc (fld, strexp) = match strexp with - | Sil.Eexp (exp, _) when Exp.equal exp lexp -> enforce_guarded_access fld typ prop_acc - | _ -> prop_acc in + | Some guarded_by_fld_str + -> enforce_guarded_access_ fld guarded_by_fld_str prop + | None + -> prop + in + let check_fld_locks typ prop_acc (fld, strexp) = + match strexp with + | Sil.Eexp (exp, _) when Exp.equal exp lexp + -> enforce_guarded_access fld typ prop_acc + | _ + -> prop_acc + in let hpred_check_flds prop_acc = function - | Sil.Hpointsto (_, Estruct (flds, _), Sizeof {typ}) -> - List.fold ~f:(check_fld_locks typ) ~init:prop_acc flds - | _ -> - prop_acc in + | Sil.Hpointsto (_, Estruct (flds, _), Sizeof {typ}) + -> List.fold ~f:(check_fld_locks typ) ~init:prop_acc flds + | _ + -> prop_acc + in match lexp with - | Exp.Lfield (_, fld, typ) -> - (* check for direct access to field annotated with @GuardedBy *) + | Exp.Lfield (_, fld, typ) + -> (* check for direct access to field annotated with @GuardedBy *) enforce_guarded_access fld typ prop - | _ -> - (* check for access via alias *) + | _ + -> (* check for access via alias *) List.fold ~f:hpred_check_flds ~init:prop prop.Prop.sigma (** Add a pointsto for [root(lexp): typ] to the iterator and to the @@ -935,20 +1019,26 @@ let add_guarded_by_constraints tenv prop lexp pdesc = current hpred of the iterator. typ is the type of the root of lexp. *) let prop_iter_add_hpred_footprint pname tenv orig_prop iter (lexp, typ) inst = if Config.trace_rearrange then ( - L.d_strln "entering prop_iter_add_hpred_footprint"; - L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); - L.d_str "typ:"; Typ.d_full typ; L.d_ln (); - ); + L.d_strln "entering prop_iter_add_hpred_footprint" ; + L.d_str "lexp: " ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_str "typ:" ; + Typ.d_full typ ; + L.d_ln () ) ; let max_stamp = fav_max_stamp (Prop.prop_iter_footprint_fav iter) in let ptsto, ptsto_foot, atoms = - mk_ptsto_exp_footprint pname tenv orig_prop (lexp, typ) max_stamp inst in - L.d_strln "++++ Adding footprint frame"; - Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto); - L.d_ln (); L.d_ln (); - let sigma_fp = ptsto_foot :: (Prop.prop_iter_get_footprint_sigma iter) in + mk_ptsto_exp_footprint pname tenv orig_prop (lexp, typ) max_stamp inst + in + L.d_strln "++++ Adding footprint frame" ; + Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto) ; + L.d_ln () ; + L.d_ln () ; + let sigma_fp = ptsto_foot :: Prop.prop_iter_get_footprint_sigma iter in let iter_foot = Prop.prop_iter_prev_then_insert iter ptsto in let iter_foot_atoms = - List.fold ~f:(Prop.prop_iter_add_atom (!Config.footprint)) ~init:iter_foot atoms in + List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter_foot atoms + in let iter' = Prop.prop_iter_replace_footprint_sigma iter_foot_atoms sigma_fp in let offsets_default = Sil.exp_get_offsets lexp in Prop.prop_iter_set_state iter' offsets_default @@ -956,225 +1046,268 @@ let prop_iter_add_hpred_footprint pname tenv orig_prop iter (lexp, typ) inst = exception ARRAY_ACCESS let rearrange_arith tenv lexp prop = - if Config.trace_rearrange then begin - L.d_strln "entering rearrange_arith"; - L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); - L.d_str "prop: "; L.d_ln (); Prop.d_prop prop; L.d_ln (); L.d_ln () - end; - if (Config.array_level >= 2) then raise ARRAY_ACCESS + if Config.trace_rearrange then ( + L.d_strln "entering rearrange_arith" ; + L.d_str "lexp: " ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_str "prop: " ; + L.d_ln () ; + Prop.d_prop prop ; + L.d_ln () ; + L.d_ln () ) ; + if Config.array_level >= 2 then raise ARRAY_ACCESS else let root = Exp.root_of_lexp lexp in - if Prover.check_allocatedness tenv prop root then - raise ARRAY_ACCESS - else - raise (Exceptions.Symexec_memory_error __POS__) + if Prover.check_allocatedness tenv prop root then raise ARRAY_ACCESS + else raise (Exceptions.Symexec_memory_error __POS__) let pp_rearrangement_error message prop lexp = - L.d_strln (".... Rearrangement Error .... " ^ message); - L.d_str "Exp:"; Sil.d_exp lexp; L.d_ln (); - L.d_str "Prop:"; L.d_ln (); Prop.d_prop prop; L.d_ln (); L.d_ln () + L.d_strln (".... Rearrangement Error .... " ^ message) ; + L.d_str "Exp:" ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_str "Prop:" ; + L.d_ln () ; + Prop.d_prop prop ; + L.d_ln () ; + L.d_ln () (** do re-arrangment for an iter whose current element is a pointsto *) let iter_rearrange_ptsto pname tenv orig_prop iter lexp inst = - if Config.trace_rearrange then begin - L.d_increase_indent 1; - L.d_strln "entering iter_rearrange_ptsto"; - L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); - L.d_strln "prop:"; Prop.d_prop orig_prop; L.d_ln (); - L.d_strln "iter:"; Prop.d_prop (Prop.prop_iter_to_prop tenv iter); - L.d_ln (); L.d_ln () - end; + if Config.trace_rearrange then ( + L.d_increase_indent 1 ; + L.d_strln "entering iter_rearrange_ptsto" ; + L.d_str "lexp: " ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_strln "prop:" ; + Prop.d_prop orig_prop ; + L.d_ln () ; + L.d_strln "iter:" ; + Prop.d_prop (Prop.prop_iter_to_prop tenv iter) ; + L.d_ln () ; + L.d_ln () ) ; let check_field_splitting () = match prop_iter_check_fields_ptsto_shallow tenv iter lexp with - | None -> () - | Some fld -> - begin - pp_rearrangement_error "field splitting check failed" orig_prop lexp; - raise (Exceptions.Missing_fld (fld, __POS__)) - end in + | None + -> () + | Some fld + -> pp_rearrangement_error "field splitting check failed" orig_prop lexp ; + raise (Exceptions.Missing_fld (fld, __POS__)) + in let res = - if !Config.footprint - then - prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst - else - begin - check_field_splitting (); - match Prop.prop_iter_current tenv iter with - | Sil.Hpointsto (e, se, te), offset -> - let max_stamp = fav_max_stamp (Prop.prop_iter_fav iter) in - let atoms_se_te_list = - strexp_extend_values - pname tenv orig_prop false Ident.kprimed max_stamp se te offset inst in - let handle_case (atoms', se', te') = - let iter' = - List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter atoms' in - Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se', te')) in - let filter it = - let p = Prop.prop_iter_to_prop tenv it in - not (Prover.check_inconsistency tenv p) in - List.filter ~f:filter (List.map ~f:handle_case atoms_se_te_list) - | _ -> [iter] - end in - begin - if Config.trace_rearrange then begin - L.d_strln "exiting iter_rearrange_ptsto, returning results"; - Prop.d_proplist_with_typ (List.map ~f:(Prop.prop_iter_to_prop tenv) res); - L.d_decrease_indent 1; - L.d_ln (); L.d_ln () - end; - res - end + if !Config.footprint then prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst + else ( + check_field_splitting () ; + match Prop.prop_iter_current tenv iter with + | Sil.Hpointsto (e, se, te), offset + -> let max_stamp = fav_max_stamp (Prop.prop_iter_fav iter) in + let atoms_se_te_list = + strexp_extend_values pname tenv orig_prop false Ident.kprimed max_stamp se te offset + inst + in + let handle_case (atoms', se', te') = + let iter' = + List.fold ~f:(Prop.prop_iter_add_atom !Config.footprint) ~init:iter atoms' + in + Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se', te')) + in + let filter it = + let p = Prop.prop_iter_to_prop tenv it in + not (Prover.check_inconsistency tenv p) + in + List.filter ~f:filter (List.map ~f:handle_case atoms_se_te_list) + | _ + -> [iter] ) + in + if Config.trace_rearrange then ( + L.d_strln "exiting iter_rearrange_ptsto, returning results" ; + Prop.d_proplist_with_typ (List.map ~f:(Prop.prop_iter_to_prop tenv) res) ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_ln () ) ; + res (** do re-arrangment for an iter whose current element is a nonempty listseg *) let iter_rearrange_ne_lseg tenv recurse_on_iters iter para e1 e2 elist = if Config.nelseg then let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in - let hpred_list1 = para_inst1@[Prop.mk_lseg tenv Sil.Lseg_NE para n' e2 elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_inst1 = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1 @ [Prop.mk_lseg tenv Sil.Lseg_NE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_base_case = - let (_, para_inst) = Sil.hpara_instantiate para e1 e2 elist in - Prop.prop_iter_update_current_by_list iter para_inst in + let _, para_inst = Sil.hpara_instantiate para e1 e2 elist in + Prop.prop_iter_update_current_by_list iter para_inst + in recurse_on_iters [iter_inductive_case; iter_base_case] else let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in - let hpred_list1 = para_inst1@[Prop.mk_lseg tenv Sil.Lseg_PE para n' e2 elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_inst1 = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1 @ [Prop.mk_lseg tenv Sil.Lseg_PE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in recurse_on_iters [iter_inductive_case] (** do re-arrangment for an iter whose current element is a nonempty dllseg to be unrolled from lhs *) let iter_rearrange_ne_dllseg_first tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist = let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in - let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg tenv Sil.Lseg_NE para_dll n' e1 e3 e4 elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_dll_inst1 = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in + let hpred_list1 = + para_dll_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_NE para_dll n' e1 e3 e4 elist] + in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_base_case = - let (_, para_dll_inst) = Sil.hpara_dll_instantiate para_dll e1 e2 e3 elist in + let _, para_dll_inst = Sil.hpara_dll_instantiate para_dll e1 e2 e3 elist in let iter' = Prop.prop_iter_update_current_by_list iter para_dll_inst in let prop' = Prop.prop_iter_to_prop tenv iter' in - let prop'' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e1 e4 prop' in - match (Prop.prop_iter_create prop'') with - | None -> assert false - | Some iter' -> iter' in + let prop'' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e1 e4 prop' in + match Prop.prop_iter_create prop'' with None -> assert false | Some iter' -> iter' + in recurse_on_iters [iter_inductive_case; iter_base_case] (** do re-arrangment for an iter whose current element is a nonempty dllseg to be unrolled from rhs *) let iter_rearrange_ne_dllseg_last tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist = let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in - let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg tenv Sil.Lseg_NE para_dll e1 e2 e4 n' elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_dll_inst1 = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in + let hpred_list1 = + para_dll_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_NE para_dll e1 e2 e4 n' elist] + in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_base_case = - let (_, para_dll_inst) = Sil.hpara_dll_instantiate para_dll e4 e2 e3 elist in + let _, para_dll_inst = Sil.hpara_dll_instantiate para_dll e4 e2 e3 elist in let iter' = Prop.prop_iter_update_current_by_list iter para_dll_inst in let prop' = Prop.prop_iter_to_prop tenv iter' in - let prop'' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e1 e4 prop' in - match (Prop.prop_iter_create prop'') with - | None -> assert false - | Some iter' -> iter' in + let prop'' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e1 e4 prop' in + match Prop.prop_iter_create prop'' with None -> assert false | Some iter' -> iter' + in recurse_on_iters [iter_inductive_case; iter_base_case] (** do re-arrangment for an iter whose current element is a possibly empty listseg *) let iter_rearrange_pe_lseg tenv recurse_on_iters default_case_iter iter para e1 e2 elist = let iter_nonemp_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in - let hpred_list1 = para_inst1@[Prop.mk_lseg tenv Sil.Lseg_PE para n' e2 elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_inst1 = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1 @ [Prop.mk_lseg tenv Sil.Lseg_PE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_subcases = let removed_prop = Prop.prop_iter_remove_curr_then_to_prop tenv iter in - let prop' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e1 e2 removed_prop in - match (Prop.prop_iter_create prop') with - | None -> - let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + let prop' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e1 e2 removed_prop in + match Prop.prop_iter_create prop' with + | None + -> let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in [Prop.prop_iter_set_state iter' ()] - | Some iter' -> [iter_nonemp_case; iter'] in + | Some iter' + -> [iter_nonemp_case; iter'] + in recurse_on_iters iter_subcases (** do re-arrangment for an iter whose current element is a possibly empty dllseg to be unrolled from lhs *) -let iter_rearrange_pe_dllseg_first tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist = +let iter_rearrange_pe_dllseg_first tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 + e4 elist = let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in - let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg tenv Sil.Lseg_PE para_dll n' e1 e3 e4 elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_dll_inst1 = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in + let hpred_list1 = + para_dll_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_PE para_dll n' e1 e3 e4 elist] + in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_subcases = let removed_prop = Prop.prop_iter_remove_curr_then_to_prop tenv iter in - let prop' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e1 e3 removed_prop in - let prop'' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e2 e4 prop' in - match (Prop.prop_iter_create prop'') with - | None -> - let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + let prop' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e1 e3 removed_prop in + let prop'' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e2 e4 prop' in + match Prop.prop_iter_create prop'' with + | None + -> let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in [Prop.prop_iter_set_state iter' ()] - | Some iter' -> [iter_inductive_case; iter'] in + | Some iter' + -> [iter_inductive_case; iter'] + in recurse_on_iters iter_subcases (** do re-arrangment for an iter whose current element is a possibly empty dllseg to be unrolled from rhs *) -let iter_rearrange_pe_dllseg_last tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist = +let iter_rearrange_pe_dllseg_last tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 + elist = let iter_inductive_case = let n' = Exp.Var (Ident.create_fresh Ident.kprimed) in - let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in - let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg tenv Sil.Lseg_PE para_dll e1 e2 e4 n' elist] in - Prop.prop_iter_update_current_by_list iter hpred_list1 in + let _, para_dll_inst1 = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in + let hpred_list1 = + para_dll_inst1 @ [Prop.mk_dllseg tenv Sil.Lseg_PE para_dll e1 e2 e4 n' elist] + in + Prop.prop_iter_update_current_by_list iter hpred_list1 + in let iter_subcases = let removed_prop = Prop.prop_iter_remove_curr_then_to_prop tenv iter in - let prop' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e1 e3 removed_prop in - let prop'' = Prop.conjoin_eq tenv ~footprint: (!Config.footprint) e2 e4 prop' in - match (Prop.prop_iter_create prop'') with - | None -> - let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + let prop' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e1 e3 removed_prop in + let prop'' = Prop.conjoin_eq tenv ~footprint:!Config.footprint e2 e4 prop' in + match Prop.prop_iter_create prop'' with + | None + -> let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in [Prop.prop_iter_set_state iter' ()] - | Some iter' -> [iter_inductive_case; iter'] in + | Some iter' + -> [iter_inductive_case; iter'] + in recurse_on_iters iter_subcases (** find the type at the offset from the given type expression, if any *) let type_at_offset tenv texp off = let rec strip_offset (off: Sil.offset list) (typ: Typ.t) = - match off, typ.desc with - | [], _ -> Some typ + match (off, typ.desc) with + | [], _ + -> Some typ | (Off_fld (f, _)) :: off', Tstruct name -> ( - match Tenv.lookup tenv name with - | Some { fields } -> ( - match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') fields with - | Some (_, typ', _) -> strip_offset off' typ' - | None -> None - ) - | None -> - None - ) - | (Off_index _) :: off', Tarray (typ', _, _) -> - strip_offset off' typ' - | _ -> None in - match texp with - | Exp.Sizeof {typ} -> strip_offset off typ - | _ -> None + match Tenv.lookup tenv name with + | Some {fields} -> ( + match List.find ~f:(fun (f', _, _) -> Typ.Fieldname.equal f f') fields with + | Some (_, typ', _) + -> strip_offset off' typ' + | None + -> None ) + | None + -> None ) + | (Off_index _) :: off', Tarray (typ', _, _) + -> strip_offset off' typ' + | _ + -> None + in + match texp with Exp.Sizeof {typ} -> strip_offset off typ | _ -> None (** Check that the size of a type coming from an instruction does not exceed the size of the type from the pointsto predicate For example, that a pointer to int is not used to assign to a char *) let check_type_size tenv pname prop texp off typ_from_instr = - L.d_strln_color Orange "check_type_size"; - L.d_str "off: "; Sil.d_offset_list off; L.d_ln (); - L.d_str "typ_from_instr: "; Typ.d_full typ_from_instr; L.d_ln (); + L.d_strln_color Orange "check_type_size" ; + L.d_str "off: " ; + Sil.d_offset_list off ; + L.d_ln () ; + L.d_str "typ_from_instr: " ; + Typ.d_full typ_from_instr ; + L.d_ln () ; match type_at_offset tenv texp off with - | Some typ_of_object -> - L.d_str "typ_o: "; Typ.d_full typ_of_object; L.d_ln (); - if Prover.type_size_comparable typ_from_instr typ_of_object && - not (Prover.check_type_size_leq typ_from_instr typ_of_object) - then begin + | Some typ_of_object + -> L.d_str "typ_o: " ; + Typ.d_full typ_of_object ; + L.d_ln () ; + if Prover.type_size_comparable typ_from_instr typ_of_object + && not (Prover.check_type_size_leq typ_from_instr typ_of_object) + then let deref_str = Localise.deref_str_pointer_size_mismatch typ_from_instr typ_of_object in let loc = State.get_loc () in let exn = - Exceptions.Pointer_size_mismatch ( - Errdesc.explain_dereference tenv deref_str prop loc, __POS__) in + Exceptions.Pointer_size_mismatch + (Errdesc.explain_dereference tenv deref_str prop loc, __POS__) + in Reporting.log_warning_deprecated pname exn - end - | None -> - L.d_str "texp: "; Sil.d_texp_full texp; L.d_ln () + | None + -> L.d_str "texp: " ; Sil.d_texp_full texp ; L.d_ln () (** Exposes lexp |->- from iter. In case that it is not possible to * expose lexp |->-, this function prints an error message and @@ -1185,224 +1318,261 @@ let check_type_size tenv pname prop texp off typ_from_instr = * only after unrolling some predicates in prop. This function ensures * that the theorem prover cannot prove the inconsistency of any of the * new iters in the result. *) -let rec iter_rearrange - pname tenv lexp typ_from_instr prop iter - inst: (Sil.offset list) Prop.prop_iter list = +let rec iter_rearrange pname tenv lexp typ_from_instr prop iter inst + : Sil.offset list Prop.prop_iter list = let rec root_typ_of_offsets = function - | Sil.Off_fld (f, fld_typ) :: _ -> ( - match fld_typ.desc with - | Tstruct _ -> - (* access through field: get the struct type from the field *) - if Config.trace_rearrange then begin - L.d_increase_indent 1; - L.d_str "iter_rearrange: root of lexp accesses field "; L.d_strln (Typ.Fieldname.to_string f); - L.d_str " struct type from field: "; Typ.d_full fld_typ; L.d_ln(); - L.d_decrease_indent 1; - L.d_ln(); - end; - fld_typ - | _ -> - typ_from_instr - ) - | Sil.Off_index _ :: off -> - Typ.mk (Tarray (root_typ_of_offsets off, None, None)) - | _ -> - typ_from_instr in + | (Sil.Off_fld (f, fld_typ)) :: _ -> ( + match fld_typ.desc with + | Tstruct _ + -> (* access through field: get the struct type from the field *) + if Config.trace_rearrange then ( + L.d_increase_indent 1 ; + L.d_str "iter_rearrange: root of lexp accesses field " ; + L.d_strln (Typ.Fieldname.to_string f) ; + L.d_str " struct type from field: " ; + Typ.d_full fld_typ ; + L.d_ln () ; + L.d_decrease_indent 1 ; + L.d_ln () ) ; + fld_typ + | _ + -> typ_from_instr ) + | (Sil.Off_index _) :: off + -> Typ.mk (Tarray (root_typ_of_offsets off, None, None)) + | _ + -> typ_from_instr + in let typ = root_typ_of_offsets (Sil.exp_get_offsets lexp) in - if Config.trace_rearrange then begin - L.d_increase_indent 1; - L.d_strln "entering iter_rearrange"; - L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); - L.d_str "typ: "; Typ.d_full typ; L.d_ln (); - L.d_str "type from instruction: "; Typ.d_full typ_from_instr; L.d_ln(); - L.d_strln "prop:"; Prop.d_prop prop; L.d_ln (); - L.d_strln "iter:"; Prop.d_prop (Prop.prop_iter_to_prop tenv iter); - L.d_ln (); L.d_ln () - end; + if Config.trace_rearrange then ( + L.d_increase_indent 1 ; + L.d_strln "entering iter_rearrange" ; + L.d_str "lexp: " ; + Sil.d_exp lexp ; + L.d_ln () ; + L.d_str "typ: " ; + Typ.d_full typ ; + L.d_ln () ; + L.d_str "type from instruction: " ; + Typ.d_full typ_from_instr ; + L.d_ln () ; + L.d_strln "prop:" ; + Prop.d_prop prop ; + L.d_ln () ; + L.d_strln "iter:" ; + Prop.d_prop (Prop.prop_iter_to_prop tenv iter) ; + L.d_ln () ; + L.d_ln () ) ; let default_case_iter (iter': unit Prop.prop_iter) = - if Config.trace_rearrange then L.d_strln "entering default_case_iter"; - if !Config.footprint then - prop_iter_add_hpred_footprint pname tenv prop iter' (lexp, typ) inst - else - if (Config.array_level >= 1 && not !Config.footprint && Exp.pointer_arith lexp) - then rearrange_arith tenv lexp prop - else begin - pp_rearrangement_error "cannot find predicate with root" prop lexp; - if not !Config.footprint then Printer.force_delayed_prints (); - raise (Exceptions.Symexec_memory_error __POS__) - end in + if Config.trace_rearrange then L.d_strln "entering default_case_iter" ; + if !Config.footprint then prop_iter_add_hpred_footprint pname tenv prop iter' (lexp, typ) inst + else if Config.array_level >= 1 && not !Config.footprint && Exp.pointer_arith lexp then + rearrange_arith tenv lexp prop + else ( + pp_rearrangement_error "cannot find predicate with root" prop lexp ; + if not !Config.footprint then Printer.force_delayed_prints () ; + raise (Exceptions.Symexec_memory_error __POS__) ) + in let recurse_on_iters iters = let f_one_iter iter' = let prop' = Prop.prop_iter_to_prop tenv iter' in - if Prover.check_inconsistency tenv prop' then - [] + if Prover.check_inconsistency tenv prop' then [] else - iter_rearrange pname tenv (Prop.lexp_normalize_prop tenv prop' lexp) typ prop' iter' inst in + iter_rearrange pname tenv (Prop.lexp_normalize_prop tenv prop' lexp) typ prop' iter' inst + in let rec f_many_iters iters_lst = function - | [] -> List.concat (List.rev iters_lst) - | iter':: iters' -> - let iters_res' = f_one_iter iter' in - f_many_iters (iters_res':: iters_lst) iters' in - f_many_iters [] iters in + | [] + -> List.concat (List.rev iters_lst) + | iter' :: iters' + -> let iters_res' = f_one_iter iter' in + f_many_iters (iters_res' :: iters_lst) iters' + in + f_many_iters [] iters + in let filter = function - | Sil.Hpointsto (base, _, _) | Sil.Hlseg (_, _, base, _, _) -> - Prover.is_root tenv prop base lexp - | Sil.Hdllseg (_, _, first, _, _, last, _) -> - let result_first = Prover.is_root tenv prop first lexp in + | Sil.Hpointsto (base, _, _) | Sil.Hlseg (_, _, base, _, _) + -> Prover.is_root tenv prop base lexp + | Sil.Hdllseg (_, _, first, _, _, last, _) + -> let result_first = Prover.is_root tenv prop first lexp in match result_first with - | None -> Prover.is_root tenv prop last lexp - | Some _ -> result_first in + | None + -> Prover.is_root tenv prop last lexp + | Some _ + -> result_first + in let res = match Prop.prop_iter_find iter filter with - | None -> - [default_case_iter iter] + | None + -> [default_case_iter iter] | Some iter -> - match Prop.prop_iter_current tenv iter with - | (Sil.Hpointsto (_, _, texp), off) -> - if Config.type_size then check_type_size tenv pname prop texp off typ_from_instr; - iter_rearrange_ptsto pname tenv prop iter lexp inst - | (Sil.Hlseg (Sil.Lseg_NE, para, e1, e2, elist), _) -> - iter_rearrange_ne_lseg tenv recurse_on_iters iter para e1 e2 elist - | (Sil.Hlseg (Sil.Lseg_PE, para, e1, e2, elist), _) -> - iter_rearrange_pe_lseg tenv recurse_on_iters default_case_iter iter para e1 e2 elist - | (Sil.Hdllseg (Sil.Lseg_NE, para_dll, e1, e2, e3, e4, elist), _) -> - begin - match Prover.is_root tenv prop e1 lexp, Prover.is_root tenv prop e4 lexp with - | None, None -> assert false - | Some _, _ -> iter_rearrange_ne_dllseg_first tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist - | _, Some _ -> iter_rearrange_ne_dllseg_last tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist - end - | (Sil.Hdllseg (Sil.Lseg_PE, para_dll, e1, e2, e3, e4, elist), _) -> - begin - match Prover.is_root tenv prop e1 lexp, Prover.is_root tenv prop e4 lexp with - | None, None -> assert false - | Some _, _ -> iter_rearrange_pe_dllseg_first tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist - | _, Some _ -> iter_rearrange_pe_dllseg_last tenv recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist - end in - if Config.trace_rearrange then begin - L.d_strln "exiting iter_rearrange, returning results"; - Prop.d_proplist_with_typ (List.map ~f:(Prop.prop_iter_to_prop tenv) res); - L.d_decrease_indent 1; - L.d_ln (); L.d_ln () - end; + match Prop.prop_iter_current tenv iter with + | Sil.Hpointsto (_, _, texp), off + -> if Config.type_size then check_type_size tenv pname prop texp off typ_from_instr ; + iter_rearrange_ptsto pname tenv prop iter lexp inst + | Sil.Hlseg (Sil.Lseg_NE, para, e1, e2, elist), _ + -> iter_rearrange_ne_lseg tenv recurse_on_iters iter para e1 e2 elist + | Sil.Hlseg (Sil.Lseg_PE, para, e1, e2, elist), _ + -> iter_rearrange_pe_lseg tenv recurse_on_iters default_case_iter iter para e1 e2 elist + | Sil.Hdllseg (Sil.Lseg_NE, para_dll, e1, e2, e3, e4, elist), _ -> ( + match (Prover.is_root tenv prop e1 lexp, Prover.is_root tenv prop e4 lexp) with + | None, None + -> assert false + | Some _, _ + -> iter_rearrange_ne_dllseg_first tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist + | _, Some _ + -> iter_rearrange_ne_dllseg_last tenv recurse_on_iters iter para_dll e1 e2 e3 e4 elist ) + | Sil.Hdllseg (Sil.Lseg_PE, para_dll, e1, e2, e3, e4, elist), _ -> + match (Prover.is_root tenv prop e1 lexp, Prover.is_root tenv prop e4 lexp) with + | None, None + -> assert false + | Some _, _ + -> iter_rearrange_pe_dllseg_first tenv recurse_on_iters default_case_iter iter para_dll e1 + e2 e3 e4 elist + | _, Some _ + -> iter_rearrange_pe_dllseg_last tenv recurse_on_iters default_case_iter iter para_dll e1 + e2 e3 e4 elist + in + if Config.trace_rearrange then ( + L.d_strln "exiting iter_rearrange, returning results" ; + Prop.d_proplist_with_typ (List.map ~f:(Prop.prop_iter_to_prop tenv) res) ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_ln () ) ; res let is_weak_captured_var pdesc var_name = let pname = Procdesc.get_proc_name pdesc in match pname with - | Block _ -> - let is_weak_captured (var, typ) = + | Block _ + -> let is_weak_captured (var, typ) = match typ.Typ.desc with - | Typ.Tptr (_, Pk_objc_weak) -> - String.equal var_name (Mangled.to_string var) - | _ -> false in + | Typ.Tptr (_, Pk_objc_weak) + -> String.equal var_name (Mangled.to_string var) + | _ + -> false + in List.exists ~f:is_weak_captured (Procdesc.get_captured pdesc) - | _ -> false + | _ + -> false -let var_has_annotation ?(check_weak_captured_var=false) pdesc is_annotation pvar = +let var_has_annotation ?(check_weak_captured_var= false) pdesc is_annotation pvar = let is_weak_captured_var = is_weak_captured_var pdesc (Pvar.to_string pvar) in let ann_sig = Models.get_modelled_annotated_signature (Specs.pdesc_resolve_attributes pdesc) in - AnnotatedSignature.param_has_annot is_annotation pvar ann_sig || - (check_weak_captured_var && is_weak_captured_var) + AnnotatedSignature.param_has_annot is_annotation pvar ann_sig + || check_weak_captured_var && is_weak_captured_var let attr_has_annot is_annotation tenv prop exp = let attr_has_annot = function | Sil.Apred ((Aretval (pname, ret_attr) | Aundef (pname, ret_attr, _, _)), _) - when is_annotation ret_attr -> - Some (Typ.Procname.to_string pname) - | _ -> None in + when is_annotation ret_attr + -> Some (Typ.Procname.to_string pname) + | _ + -> None + in try List.find_map ~f:attr_has_annot (Attribute.get_for_exp tenv prop exp) with Not_found -> None -let is_strexp_pt_fld_with_annot tenv obj_str is_annotation typ deref_exp (fld, strexp) = +let is_strexp_pt_fld_with_annot tenv obj_str is_annotation typ deref_exp (fld, strexp) = let lookup = Tenv.lookup tenv in let fld_has_annot fld = match Typ.Struct.get_field_type_and_annotation ~lookup fld typ with - | Some (_, annot) -> is_annotation annot - | _ -> false in + | Some (_, annot) + -> is_annotation annot + | _ + -> false + in match strexp with - | Sil.Eexp (Exp.Var _ as exp, _) when Exp.equal exp deref_exp -> - let has_annot = fld_has_annot fld in - if has_annot then - obj_str := Some (Typ.Fieldname.to_simplified_string fld); + | Sil.Eexp ((Exp.Var _ as exp), _) when Exp.equal exp deref_exp + -> let has_annot = fld_has_annot fld in + if has_annot then obj_str := Some (Typ.Fieldname.to_simplified_string fld) ; has_annot - | _ -> true + | _ + -> true (* This returns true if the exp is pointed to only by fields or parameters with a given annotation. In that case it also returns a string representation of the annotation recipient. *) -let is_only_pt_by_fld_or_param_with_annot - ?(check_weak_captured_var=false) pdesc tenv prop deref_exp is_annotation = +let is_only_pt_by_fld_or_param_with_annot ?(check_weak_captured_var= false) pdesc tenv prop + deref_exp is_annotation = let obj_str = ref None in let is_pt_by_fld_or_param_with_annot hpred = match hpred with - | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp (Exp.Var _ as exp, _), _) - when Exp.equal exp deref_exp -> - let var_has_annotation = - var_has_annotation ~check_weak_captured_var pdesc is_annotation pvar in - if var_has_annotation then obj_str := Some (Pvar.to_string pvar); + | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp ((Exp.Var _ as exp), _), _) + when Exp.equal exp deref_exp + -> let var_has_annotation = + var_has_annotation ~check_weak_captured_var pdesc is_annotation pvar + in + if var_has_annotation then obj_str := Some (Pvar.to_string pvar) ; let procname_str_opt = attr_has_annot is_annotation tenv prop exp in - if Option.is_some procname_str_opt then obj_str := procname_str_opt; + if Option.is_some procname_str_opt then obj_str := procname_str_opt ; (* it's ok for a local with no annotation to point to deref_exp *) var_has_annotation || Option.is_some procname_str_opt || Pvar.is_local pvar - | Sil.Hpointsto (_, Sil.Estruct (flds, _), Exp.Sizeof {typ}) -> - List.for_all ~f:(is_strexp_pt_fld_with_annot tenv obj_str is_annotation typ deref_exp) flds - | _ -> true in - if List.for_all ~f:is_pt_by_fld_or_param_with_annot prop.Prop.sigma && !obj_str <> None - then !obj_str + | Sil.Hpointsto (_, Sil.Estruct (flds, _), Exp.Sizeof {typ}) + -> List.for_all ~f:(is_strexp_pt_fld_with_annot tenv obj_str is_annotation typ deref_exp) flds + | _ + -> true + in + if List.for_all ~f:is_pt_by_fld_or_param_with_annot prop.Prop.sigma && !obj_str <> None then + !obj_str else None let is_only_pt_by_fld_or_param_nullable pdesc tenv prop deref_exp = - is_only_pt_by_fld_or_param_with_annot ~check_weak_captured_var:true - pdesc tenv prop deref_exp Annotations.ia_is_nullable + is_only_pt_by_fld_or_param_with_annot ~check_weak_captured_var:true pdesc tenv prop deref_exp + Annotations.ia_is_nullable let is_only_pt_by_fld_or_param_nonnull pdesc tenv prop deref_exp = Option.is_some (is_only_pt_by_fld_or_param_with_annot pdesc tenv prop deref_exp Annotations.ia_is_nonnull) (** Check for dereference errors: dereferencing 0, a freed value, or an undefined value *) -let check_dereference_error tenv pdesc (prop : Prop.normal Prop.t) lexp loc = +let check_dereference_error tenv pdesc (prop: Prop.normal Prop.t) lexp loc = let root = Exp.root_of_lexp lexp in - let nullable_var_opt = - is_only_pt_by_fld_or_param_nullable pdesc tenv prop root in + let nullable_var_opt = is_only_pt_by_fld_or_param_nullable pdesc tenv prop root in let is_deref_of_nullable = - let is_definitely_non_null exp prop = - Prover.check_disequal tenv prop exp Exp.zero in - Config.report_nullable_inconsistency - && Option.is_some nullable_var_opt - && not (is_definitely_non_null root prop) in - let relevant_attributes_getters = [ - Attribute.get_resource tenv; - Attribute.get_undef tenv; - ] in + let is_definitely_non_null exp prop = Prover.check_disequal tenv prop exp Exp.zero in + Config.report_nullable_inconsistency && Option.is_some nullable_var_opt + && not (is_definitely_non_null root prop) + in + let relevant_attributes_getters = [Attribute.get_resource tenv; Attribute.get_undef tenv] in let get_relevant_attributes exp = let rec fold_getters = function - | [] -> None - | getter:: tl -> match getter prop exp with - | Some _ as some_attr -> some_attr - | None -> fold_getters tl in - fold_getters relevant_attributes_getters in - let attribute_opt = match get_relevant_attributes root with - | Some att -> Some att - | None -> (* try to remove an offset if any, and find the attribute there *) - let root_no_offset = match root with - | Exp.BinOp((Binop.PlusPI | Binop.PlusA | Binop.MinusPI | Binop.MinusA), base, _) -> base - | _ -> root in - get_relevant_attributes root_no_offset in - if Prover.check_zero tenv (Exp.root_of_lexp root) || is_deref_of_nullable then - begin + | [] + -> None + | getter :: tl -> + match getter prop exp with Some _ as some_attr -> some_attr | None -> fold_getters tl + in + fold_getters relevant_attributes_getters + in + let attribute_opt = + match get_relevant_attributes root with + | Some att + -> Some att + | None + -> (* try to remove an offset if any, and find the attribute there *) + let root_no_offset = + match root with + | Exp.BinOp ((Binop.PlusPI | Binop.PlusA | Binop.MinusPI | Binop.MinusA), base, _) + -> base + | _ + -> root + in + get_relevant_attributes root_no_offset + in + ( if Prover.check_zero tenv (Exp.root_of_lexp root) || is_deref_of_nullable then let deref_str = if is_deref_of_nullable then match nullable_var_opt with - | Some str -> - if is_weak_captured_var pdesc str then + | Some str + -> if is_weak_captured_var pdesc str then Localise.deref_str_weak_variable_in_block None str else Localise.deref_str_nullable None str - | None -> Localise.deref_str_nullable None "" - else Localise.deref_str_null None in + | None + -> Localise.deref_str_nullable None "" + else Localise.deref_str_null None + in let err_desc = - Errdesc.explain_dereference tenv ~use_buckets: true ~is_nullable: is_deref_of_nullable - deref_str prop loc in + Errdesc.explain_dereference tenv ~use_buckets:true ~is_nullable:is_deref_of_nullable + deref_str prop loc + in if Localise.is_parameter_not_null_checked_desc err_desc then raise (Exceptions.Parameter_not_null_checked (err_desc, __POS__)) else if Localise.is_field_not_null_checked_desc err_desc then @@ -1411,25 +1581,24 @@ let check_dereference_error tenv pdesc (prop : Prop.normal Prop.t) lexp loc = raise (Exceptions.Double_lock (err_desc, __POS__)) else if Localise.is_empty_vector_access_desc err_desc then raise (Exceptions.Empty_vector_access (err_desc, __POS__)) - else raise (Exceptions.Null_dereference (err_desc, __POS__)) - end; + else raise (Exceptions.Null_dereference (err_desc, __POS__)) ) ; match attribute_opt with - | Some (Apred (Adangling dk, _)) -> - let deref_str = Localise.deref_str_dangling (Some dk) in + | Some Apred (Adangling dk, _) + -> let deref_str = Localise.deref_str_dangling (Some dk) in let err_desc = Errdesc.explain_dereference tenv deref_str prop (State.get_loc ()) in raise (Exceptions.Dangling_pointer_dereference (Some dk, err_desc, __POS__)) - | Some (Apred (Aundef (s, _, undef_loc, _), _)) -> - if Config.angelic_execution then () + | Some Apred (Aundef (s, _, undef_loc, _), _) + -> if Config.angelic_execution then () else let deref_str = Localise.deref_str_undef (s, undef_loc) in let err_desc = Errdesc.explain_dereference tenv deref_str prop loc in raise (Exceptions.Skip_pointer_dereference (err_desc, __POS__)) - | Some (Apred (Aresource ({ ra_kind = Rrelease } as ra), _)) -> - let deref_str = Localise.deref_str_freed ra in - let err_desc = Errdesc.explain_dereference tenv ~use_buckets: true deref_str prop loc in + | Some Apred (Aresource ({ra_kind= Rrelease} as ra), _) + -> let deref_str = Localise.deref_str_freed ra in + let err_desc = Errdesc.explain_dereference tenv ~use_buckets:true deref_str prop loc in raise (Exceptions.Use_after_free (err_desc, __POS__)) - | _ -> - if Prover.check_equal tenv Prop.prop_emp (Exp.root_of_lexp root) Exp.minus_one then + | _ + -> if Prover.check_equal tenv Prop.prop_emp (Exp.root_of_lexp root) Exp.minus_one then let deref_str = Localise.deref_str_dangling None in let err_desc = Errdesc.explain_dereference tenv deref_str prop loc in raise (Exceptions.Dangling_pointer_dereference (None, err_desc, __POS__)) @@ -1437,96 +1606,122 @@ let check_dereference_error tenv pdesc (prop : Prop.normal Prop.t) lexp loc = (* Check that an expression representin an objc block can be null and raise a [B1] null exception.*) (* It's used to check that we don't call possibly null blocks *) let check_call_to_objc_block_error tenv pdesc prop fun_exp loc = - let fun_exp_may_be_null () = (* may be null if we don't know if it is definitely not null *) - not (Prover.check_disequal tenv prop (Exp.root_of_lexp fun_exp) Exp.zero) in - let try_explaining_exp e = (* when e is a temp var, try to find the pvar defining e*) + let fun_exp_may_be_null () = + (* may be null if we don't know if it is definitely not null *) + not (Prover.check_disequal tenv prop (Exp.root_of_lexp fun_exp) Exp.zero) + in + let try_explaining_exp e = + (* when e is a temp var, try to find the pvar defining e*) match e with - | Exp.Var id -> - (match (Errdesc.find_ident_assignment (State.get_node ()) id) with - | Some (_, e') -> e' - | None -> e) - | _ -> e in - let get_exp_called () = (* Exp called in the block's function call*) + | Exp.Var id -> ( + match Errdesc.find_ident_assignment (State.get_node ()) id with + | Some (_, e') + -> e' + | None + -> e ) + | _ + -> e + in + let get_exp_called () = + (* Exp called in the block's function call*) match State.get_instr () with - | Some Sil.Call(_, Exp.Var id, _, _, _) -> - Errdesc.find_ident_assignment (State.get_node ()) id - | _ -> None in - let is_fun_exp_captured_var () = (* Called expression is a captured variable of the block *) + | Some Sil.Call (_, Exp.Var id, _, _, _) + -> Errdesc.find_ident_assignment (State.get_node ()) id + | _ + -> None + in + let is_fun_exp_captured_var () = + (* Called expression is a captured variable of the block *) match get_exp_called () with - | Some (_, Exp.Lvar pvar) -> (* pvar is the block *) + | Some (_, Exp.Lvar pvar) + -> (* pvar is the block *) let name = Pvar.get_name pvar in - List.exists ~f:(fun (cn, _) -> (Mangled.equal name cn)) (Procdesc.get_captured pdesc) - | _ -> false in - let is_field_deref () = (*Called expression is a field *) + List.exists ~f:(fun (cn, _) -> Mangled.equal name cn) (Procdesc.get_captured pdesc) + | _ + -> false + in + let is_field_deref () = + (*Called expression is a field *) match get_exp_called () with - | Some (_, (Exp.Lfield(e', fn, t))) -> - let e'' = try_explaining_exp e' in - Some (Exp.Lfield(e'', fn, t)), true (* the block dereferences is a field of an object*) - | Some (_, e) -> Some e, false - | _ -> None, false in - if Config.curr_language_is Config.Clang && - fun_exp_may_be_null () && - not (is_fun_exp_captured_var ()) then - begin - let deref_str = Localise.deref_str_null None in - let err_desc_nobuckets = Errdesc.explain_dereference tenv ~is_nullable: true deref_str prop loc in - match fun_exp with - | Exp.Var id when Ident.is_footprint id -> - let e_opt, is_field_deref = is_field_deref () in - let err_desc_nobuckets' = (match e_opt with - | Some e -> Localise.parameter_field_not_null_checked_desc err_desc_nobuckets e - | _ -> err_desc_nobuckets) in - let err_desc = - Localise.error_desc_set_bucket - err_desc_nobuckets' Localise.BucketLevel.b1 Config.show_buckets in - if is_field_deref then - raise - (Exceptions.Field_not_null_checked - (err_desc, __POS__)) - else - raise - (Exceptions.Parameter_not_null_checked - (err_desc, __POS__)) - | _ -> - (* HP: fun_exp is not a footprint therefore, + | Some (_, Exp.Lfield (e', fn, t)) + -> let e'' = try_explaining_exp e' in + (Some (Exp.Lfield (e'', fn, t)), true) + (* the block dereferences is a field of an object*) + | Some (_, e) + -> (Some e, false) + | _ + -> (None, false) + in + if Config.curr_language_is Config.Clang && fun_exp_may_be_null () + && not (is_fun_exp_captured_var ()) + then + let deref_str = Localise.deref_str_null None in + let err_desc_nobuckets = + Errdesc.explain_dereference tenv ~is_nullable:true deref_str prop loc + in + match fun_exp with + | Exp.Var id when Ident.is_footprint id + -> let e_opt, is_field_deref = is_field_deref () in + let err_desc_nobuckets' = + match e_opt with + | Some e + -> Localise.parameter_field_not_null_checked_desc err_desc_nobuckets e + | _ + -> err_desc_nobuckets + in + let err_desc = + Localise.error_desc_set_bucket err_desc_nobuckets' Localise.BucketLevel.b1 + Config.show_buckets + in + if is_field_deref then raise (Exceptions.Field_not_null_checked (err_desc, __POS__)) + else raise (Exceptions.Parameter_not_null_checked (err_desc, __POS__)) + | _ + -> (* HP: fun_exp is not a footprint therefore, either is a local or it's a modified param *) - let err_desc = - Localise.error_desc_set_bucket - err_desc_nobuckets Localise.BucketLevel.b1 Config.show_buckets in - raise (Exceptions.Null_dereference - (err_desc, __POS__)) - end + let err_desc = + Localise.error_desc_set_bucket err_desc_nobuckets Localise.BucketLevel.b1 + Config.show_buckets + in + raise (Exceptions.Null_dereference (err_desc, __POS__)) (** [rearrange lexp prop] rearranges [prop] into the form [prop' * lexp|->strexp:typ]. It returns an iterator with [lexp |-> strexp: typ] as current predicate and the path (an [offsetlist]) which leads to [lexp] as the iterator state. *) -let rearrange ?(report_deref_errors=true) pdesc tenv lexp typ prop loc - : (Sil.offset list) Prop.prop_iter list = - - let nlexp = match Prop.exp_normalize_prop tenv prop lexp with - | Exp.BinOp(Binop.PlusPI, ep, e) -> (* array access with pointer arithmetic *) - Exp.Lindex(ep, e) - | e -> e in - let ptr_tested_for_zero = - Prover.check_disequal tenv prop (Exp.root_of_lexp nlexp) Exp.zero in +let rearrange ?(report_deref_errors= true) pdesc tenv lexp typ prop loc + : Sil.offset list Prop.prop_iter list = + let nlexp = + match Prop.exp_normalize_prop tenv prop lexp with + | Exp.BinOp (Binop.PlusPI, ep, e) + -> (* array access with pointer arithmetic *) + Exp.Lindex (ep, e) + | e + -> e + in + let ptr_tested_for_zero = Prover.check_disequal tenv prop (Exp.root_of_lexp nlexp) Exp.zero in let inst = Sil.inst_rearrange (not ptr_tested_for_zero) loc (State.get_path_pos ()) in - L.d_strln ".... Rearrangement Start ...."; - L.d_str "Exp: "; Sil.d_exp nlexp; L.d_ln (); - L.d_str "Prop: "; L.d_ln(); Prop.d_prop prop; L.d_ln (); L.d_ln (); - if report_deref_errors then check_dereference_error tenv pdesc prop nlexp (State.get_loc ()); + L.d_strln ".... Rearrangement Start ...." ; + L.d_str "Exp: " ; + Sil.d_exp nlexp ; + L.d_ln () ; + L.d_str "Prop: " ; + L.d_ln () ; + Prop.d_prop prop ; + L.d_ln () ; + L.d_ln () ; + if report_deref_errors then check_dereference_error tenv pdesc prop nlexp (State.get_loc ()) ; let pname = Procdesc.get_proc_name pdesc in let prop' = - if Config.csl_analysis && !Config.footprint && Typ.Procname.is_java pname && - not (Typ.Procname.is_constructor pname || Typ.Procname.is_class_initializer pname) + if Config.csl_analysis && !Config.footprint && Typ.Procname.is_java pname + && not (Typ.Procname.is_constructor pname || Typ.Procname.is_class_initializer pname) then add_guarded_by_constraints tenv prop lexp pdesc - else prop in + else prop + in match Prop.prop_iter_create prop' with - | None -> - if !Config.footprint then + | None + -> if !Config.footprint then [prop_iter_add_hpred_footprint_to_prop pname tenv prop' (nlexp, typ) inst] - else - begin - pp_rearrangement_error "sigma is empty" prop nlexp; - raise (Exceptions.Symexec_memory_error __POS__) - end - | Some iter -> iter_rearrange pname tenv nlexp typ prop' iter inst + else ( + pp_rearrangement_error "sigma is empty" prop nlexp ; + raise (Exceptions.Symexec_memory_error __POS__) ) + | Some iter + -> iter_rearrange pname tenv nlexp typ prop' iter inst diff --git a/infer/src/backend/rearrange.mli b/infer/src/backend/rearrange.mli index 1e4c3fbad..cf6e9c4e1 100644 --- a/infer/src/backend/rearrange.mli +++ b/infer/src/backend/rearrange.mli @@ -10,30 +10,31 @@ open! IStd -(** Re-arrangement and extension of structures with fresh variables *) (* TODO: this description is not clear *) +(** Re-arrangement and extension of structures with fresh variables *) + +(* TODO: this description is not clear *) exception ARRAY_ACCESS val is_only_pt_by_fld_or_param_with_annot : - ?check_weak_captured_var:bool -> Procdesc.t -> Tenv.t -> Prop.normal Prop.t -> - Exp.t -> (Annot.Item.t -> bool) -> string option + ?check_weak_captured_var:bool -> Procdesc.t -> Tenv.t -> Prop.normal Prop.t -> Exp.t + -> (Annot.Item.t -> bool) -> string option -val is_only_pt_by_fld_or_param_nonnull : Procdesc.t -> Tenv.t -> Prop.normal Prop.t -> - Exp.t -> bool +val is_only_pt_by_fld_or_param_nonnull : + Procdesc.t -> Tenv.t -> Prop.normal Prop.t -> Exp.t -> bool -(** Check for dereference errors: dereferencing 0, a freed value, or an undefined value *) val check_dereference_error : Tenv.t -> Procdesc.t -> Prop.normal Prop.t -> Exp.t -> Location.t -> unit +(** Check for dereference errors: dereferencing 0, a freed value, or an undefined value *) -(** Check that an expression representing an objc block can be null and raise a [B1] null exception. - It's used to check that we don't call possibly null blocks *) val check_call_to_objc_block_error : Tenv.t -> Procdesc.t -> Prop.normal Prop.t -> Exp.t -> Location.t -> unit +(** Check that an expression representing an objc block can be null and raise a [B1] null exception. + It's used to check that we don't call possibly null blocks *) +val rearrange : + ?report_deref_errors:bool -> Procdesc.t -> Tenv.t -> Exp.t -> Typ.t -> Prop.normal Prop.t + -> Location.t -> Sil.offset list Prop.prop_iter list (** [rearrange lexp prop] rearranges [prop] into the form [prop' * lexp|->strexp:typ]. It returns an iterator with [lexp |-> strexp: typ] as current predicate and the path (an [offsetlist]) which leads to [lexp] as the iterator state. *) -val rearrange : - ?report_deref_errors:bool -> Procdesc.t -> Tenv.t -> Exp.t -> - Typ.t -> Prop.normal Prop.t -> - Location.t -> (Sil.offset list) Prop.prop_iter list diff --git a/infer/src/backend/reporting.ml b/infer/src/backend/reporting.ml index a5cda59c9..76d20f7ca 100644 --- a/infer/src/backend/reporting.ml +++ b/infer/src/backend/reporting.ml @@ -8,97 +8,80 @@ *) open! IStd - module L = Logging type log_t = - ?loc: Location.t -> - ?node_id: (int * int) -> - ?session: int -> - ?ltr: Errlog.loc_trace -> - ?linters_def_file:string -> - ?doc_url:string -> - exn -> - unit + ?loc:Location.t -> ?node_id:int * int -> ?session:int -> ?ltr:Errlog.loc_trace + -> ?linters_def_file:string -> ?doc_url:string -> exn -> unit type log_issue_from_errlog = Errlog.t -> log_t -let log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file - ?doc_url exn = - let loc = match loc with - | None -> State.get_loc () - | Some loc -> loc in - let node_id = match node_id with - | None -> (State.get_node_id_key () :> int * int) - | Some node_id -> node_id in - let session = match session with - | None -> (State.get_session () :> int) - | Some session -> session in - let ltr = match ltr with - | None -> State.get_loc_trace () - | Some ltr -> ltr in - let err_name = match exn with - | Exceptions.Frontend_warning ((err_name, _), _, _) -> err_name - | _ -> let err_name, _, _, _, _, _, _ = Exceptions.recognize_exception exn in - (Localise.to_issue_id err_name) in - if (Inferconfig.is_checker_enabled err_name) then +let log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file ?doc_url + exn = + let loc = match loc with None -> State.get_loc () | Some loc -> loc in + let node_id = + match node_id with None -> (State.get_node_id_key () :> int * int) | Some node_id -> node_id + in + let session = + match session with None -> (State.get_session () :> int) | Some session -> session + in + let ltr = match ltr with None -> State.get_loc_trace () | Some ltr -> ltr in + let err_name = + match exn with + | Exceptions.Frontend_warning ((err_name, _), _, _) + -> err_name + | _ + -> let err_name, _, _, _, _, _, _ = Exceptions.recognize_exception exn in + Localise.to_issue_id err_name + in + if Inferconfig.is_checker_enabled err_name then Errlog.log_issue err_kind err_log loc node_id session ltr ?linters_def_file ?doc_url exn - -let log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file - ?doc_url exn = - let is_generated_method = - Typ.Procname.java_is_generated (Specs.get_proc_name summary) in +let log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file ?doc_url + exn = + let is_generated_method = Typ.Procname.java_is_generated (Specs.get_proc_name summary) in let should_suppress_lint = - Config.curr_language_is Config.Java && - Annotations.ia_is_suppress_lint - (fst summary.Specs.attributes.ProcAttributes.method_annotation) in - if should_suppress_lint || is_generated_method - then - () (* Skip the reporting *) + Config.curr_language_is Config.Java + && Annotations.ia_is_suppress_lint + (fst summary.Specs.attributes.ProcAttributes.method_annotation) + in + if should_suppress_lint || is_generated_method then () (* Skip the reporting *) else let err_log = summary.Specs.attributes.ProcAttributes.err_log in - log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file - ?doc_url exn + log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file ?doc_url + exn -let log_issue_deprecated - ?(store_summary=false) - err_kind - proc_name - ?loc - ?node_id - ?session - ?ltr - ?linters_def_file - ?doc_url - exn = +let log_issue_deprecated ?(store_summary= false) err_kind proc_name ?loc ?node_id ?session ?ltr + ?linters_def_file ?doc_url exn = match Specs.get_summary proc_name with - | Some summary -> - log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file - ?doc_url exn; + | Some summary + -> log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file + ?doc_url exn ; if store_summary then (* TODO (#16348004): This is currently needed as ThreadSafety works as a cluster checker *) Specs.store_summary summary - | None -> - failwithf - "Trying to report error on procedure %a, but cannot because no summary exists for this \ - procedure. Did you mean to log the error on the caller of %a instead?" - Typ.Procname.pp proc_name - Typ.Procname.pp proc_name + | None + -> failwithf + "Trying to report error on procedure %a, but cannot because no summary exists for this procedure. Did you mean to log the error on the caller of %a instead?" + Typ.Procname.pp proc_name Typ.Procname.pp proc_name let log_error_from_errlog = log_issue_from_errlog Exceptions.Kerror + let log_warning_from_errlog = log_issue_from_errlog Exceptions.Kwarning + let log_info_from_errlog = log_issue_from_errlog Exceptions.Kinfo let log_error = log_issue_from_summary Exceptions.Kerror + let log_warning = log_issue_from_summary Exceptions.Kwarning + let log_info = log_issue_from_summary Exceptions.Kwarning -let log_error_deprecated ?(store_summary=false) = +let log_error_deprecated ?(store_summary= false) = log_issue_deprecated ~store_summary Exceptions.Kerror -let log_warning_deprecated ?(store_summary=false) = +let log_warning_deprecated ?(store_summary= false) = log_issue_deprecated ~store_summary Exceptions.Kwarning -let log_info_deprecated ?(store_summary=false) = +let log_info_deprecated ?(store_summary= false) = log_issue_deprecated ~store_summary Exceptions.Kinfo diff --git a/infer/src/backend/reporting.mli b/infer/src/backend/reporting.mli index f4dcd21d5..cdec3de5e 100644 --- a/infer/src/backend/reporting.mli +++ b/infer/src/backend/reporting.mli @@ -12,49 +12,43 @@ open! IStd (** Type of functions to report issues to the error_log in a spec. *) type log_t = - ?loc: Location.t -> - ?node_id: (int * int) -> - ?session: int -> - ?ltr: Errlog.loc_trace -> - ?linters_def_file:string -> - ?doc_url:string -> - exn -> - unit + ?loc:Location.t -> ?node_id:int * int -> ?session:int -> ?ltr:Errlog.loc_trace + -> ?linters_def_file:string -> ?doc_url:string -> exn -> unit type log_issue_from_errlog = Errlog.t -> log_t +val log_error_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t (** Report an error in the given procedure. DEPRECATED as it can create race conditions between checkers. Use log_error instead *) -val log_error_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t +val log_warning_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t (** Report a warning in the given procedure. DEPRECATED as it can create race conditions between checkers. Use log_warning instead *) -val log_warning_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t +val log_info_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t (** Report an info in the given procedure. DEPRECATED as it can create race conditions between checkers. Use log_info instead *) -val log_info_deprecated : ?store_summary:bool -> Typ.Procname.t -> log_t -(** Report an issue of a given kind in the given error log. *) val log_issue_from_errlog : Exceptions.err_kind -> log_issue_from_errlog +(** Report an issue of a given kind in the given error log. *) -(** Report an error in the given error log. *) val log_error_from_errlog : log_issue_from_errlog +(** Report an error in the given error log. *) -(** Report a warning in the given error log. *) val log_warning_from_errlog : log_issue_from_errlog +(** Report a warning in the given error log. *) -(** Report an info in the given error log. *) val log_info_from_errlog : log_issue_from_errlog +(** Report an info in the given error log. *) -(** Add an error to the given summary. *) val log_error : Specs.summary -> log_t +(** Add an error to the given summary. *) -(** Add an warning to the given summary. *) val log_warning : Specs.summary -> log_t +(** Add an warning to the given summary. *) -(** Add an info to the given summary. *) val log_info : Specs.summary -> log_t +(** Add an info to the given summary. *) diff --git a/infer/src/backend/specs.ml b/infer/src/backend/specs.ml index 57361aa1e..d648d84f5 100644 --- a/infer/src/backend/specs.ml +++ b/infer/src/backend/specs.ml @@ -21,128 +21,127 @@ module F = Format (** Module for joined props *) module Jprop = struct - (* type aliases for component of t values that compare should ignore *) type _id = int + let compare__id _ _ = 0 (** Remember when a prop is obtained as the join of two other props; the first parameter is an id *) type 'a t = | Prop of _id * 'a Prop.t | Joined of _id * 'a Prop.t * 'a t * 'a t - [@@deriving compare] + [@@deriving compare] (** Comparison for joined_prop *) let compare jp1 jp2 = compare (fun _ _ -> 0) jp1 jp2 (** Return true if the two join_prop's are equal *) - let equal jp1 jp2 = - Int.equal (compare jp1 jp2) 0 + let equal jp1 jp2 = Int.equal (compare jp1 jp2) 0 - let to_prop = function - | Prop (_, p) -> p - | Joined (_, p, _, _) -> p + let to_prop = function Prop (_, p) -> p | Joined (_, p, _, _) -> p - let to_number = function - | Prop (n, _) -> n - | Joined (n, _, _, _) -> n + let to_number = function Prop (n, _) -> n | Joined (n, _, _, _) -> n let rec fav_add_dfs tenv fav = function - | Prop (_, p) -> Prop.prop_fav_add_dfs tenv fav p - | Joined (_, p, jp1, jp2) -> - Prop.prop_fav_add_dfs tenv fav p; - fav_add_dfs tenv fav jp1; - fav_add_dfs tenv fav jp2 + | Prop (_, p) + -> Prop.prop_fav_add_dfs tenv fav p + | Joined (_, p, jp1, jp2) + -> Prop.prop_fav_add_dfs tenv fav p ; fav_add_dfs tenv fav jp1 ; fav_add_dfs tenv fav jp2 let rec normalize tenv = function - | Prop (n, p) -> Prop (n, Prop.normalize tenv p) - | Joined (n, p, jp1, jp2) -> Joined (n, Prop.normalize tenv p, normalize tenv jp1, normalize tenv jp2) + | Prop (n, p) + -> Prop (n, Prop.normalize tenv p) + | Joined (n, p, jp1, jp2) + -> Joined (n, Prop.normalize tenv p, normalize tenv jp1, normalize tenv jp2) (** Return a compact representation of the jprop *) let rec compact sh = function - | Prop (n, p) -> - Prop (n, Prop.prop_compact sh p) - | Joined(n, p, jp1, jp2) -> - Joined(n, Prop.prop_compact sh p, compact sh jp1, compact sh jp2) + | Prop (n, p) + -> Prop (n, Prop.prop_compact sh p) + | Joined (n, p, jp1, jp2) + -> Joined (n, Prop.prop_compact sh p, compact sh jp1, compact sh jp2) (** Print the toplevel prop *) - let pp_short pe f jp = - Prop.pp_prop pe f (to_prop jp) + let pp_short pe f jp = Prop.pp_prop pe f (to_prop jp) (** Dump the toplevel prop *) let d_shallow (jp: Prop.normal t) = L.add_print_action (L.PTjprop_short, Obj.repr jp) (** Get identifies of the jprop *) - let get_id = function - | Prop (n, _) -> n - | Joined (n, _, _, _) -> n + let get_id = function Prop (n, _) -> n | Joined (n, _, _, _) -> n (** Print a list of joined props, the boolean indicates whether to print subcomponents of joined props *) let pp_list pe shallow f jplist = let rec pp_seq_newline f = function - | [] -> () - | [Prop (n, p)] -> F.fprintf f "PROP %d:@\n%a" n (Prop.pp_prop pe) p - | [Joined (n, p, p1, p2)] -> - if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p1]; - if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p2]; + | [] + -> () + | [(Prop (n, p))] + -> F.fprintf f "PROP %d:@\n%a" n (Prop.pp_prop pe) p + | [(Joined (n, p, p1, p2))] + -> if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p1] ; + if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p2] ; F.fprintf f "PROP %d (join of %d,%d):@\n%a" n (get_id p1) (get_id p2) (Prop.pp_prop pe) p - | jp:: l -> - F.fprintf f "%a@\n" pp_seq_newline [jp]; - pp_seq_newline f l in + | jp :: l + -> F.fprintf f "%a@\n" pp_seq_newline [jp] ; + pp_seq_newline f l + in pp_seq_newline f jplist (** dump a joined prop list, the boolean indicates whether to print toplevel props only *) - let d_list (shallow: bool) (jplist: Prop.normal t list) = L.add_print_action (L.PTjprop_list, Obj.repr (shallow, jplist)) + let d_list (shallow: bool) (jplist: Prop.normal t list) = + L.add_print_action (L.PTjprop_list, Obj.repr (shallow, jplist)) let rec fav_add fav = function - | Prop (_, p) -> Prop.prop_fav_add fav p - | Joined (_, p, jp1, jp2) -> - Prop.prop_fav_add fav p; - fav_add fav jp1; - fav_add fav jp2 + | Prop (_, p) + -> Prop.prop_fav_add fav p + | Joined (_, p, jp1, jp2) + -> Prop.prop_fav_add fav p ; fav_add fav jp1 ; fav_add fav jp2 let rec jprop_sub sub = function - | Prop (n, p) -> Prop (n, Prop.prop_sub sub p) - | Joined (n, p, jp1, jp2) -> - let p' = Prop.prop_sub sub p in + | Prop (n, p) + -> Prop (n, Prop.prop_sub sub p) + | Joined (n, p, jp1, jp2) + -> let p' = Prop.prop_sub sub p in let jp1' = jprop_sub sub jp1 in let jp2' = jprop_sub sub jp2 in Joined (n, p', jp1', jp2') let filter (f: 'a t -> 'b option) jpl = let rec do_filter acc = function - | [] -> acc - | (Prop _ as jp) :: jpl -> - (match f jp with - | Some x -> - do_filter (x:: acc) jpl - | None -> do_filter acc jpl) + | [] + -> acc + | (Prop _ as jp) :: jpl -> ( + match f jp with Some x -> do_filter (x :: acc) jpl | None -> do_filter acc jpl ) | (Joined (_, _, jp1, jp2) as jp) :: jpl -> - (match f jp with - | Some x -> - do_filter (x:: acc) jpl - | None -> - do_filter acc (jpl @ [jp1; jp2])) in + match f jp with + | Some x + -> do_filter (x :: acc) jpl + | None + -> do_filter acc (jpl @ [jp1; jp2]) + in do_filter [] jpl - let rec map (f : 'a Prop.t -> 'b Prop.t) = function - | Prop (n, p) -> Prop (n, f p) - | Joined (n, p, jp1, jp2) -> Joined (n, f p, map f jp1, map f jp2) + let rec map (f: 'a Prop.t -> 'b Prop.t) = function + | Prop (n, p) + -> Prop (n, f p) + | Joined (n, p, jp1, jp2) + -> Joined (n, f p, map f jp1, map f jp2) -(* + (* let rec jprop_sub sub = function | Prop (n, p) -> Prop (n, Prop.prop_sub sub p) | Joined (n, p, jp1, jp2) -> Joined (n, Prop.prop_sub sub p, jprop_sub sub jp1, jprop_sub sub jp2) *) end + (***** End of module Jprop *****) -module Visitedset = - Caml.Set.Make (struct - type t = Procdesc.Node.id * int list - let compare (node_id1, _) (node_id2, _) = Procdesc.Node.compare_id node_id1 node_id2 - end) +module Visitedset = Caml.Set.Make (struct + type t = Procdesc.Node.id * int list + + let compare (node_id1, _) (node_id2, _) = Procdesc.Node.compare_id node_id1 node_id2 +end) let visited_str vis = let s = ref "" in @@ -154,16 +153,17 @@ let visited_str vis = List.iter ~f:(fun n -> ss := !ss ^ " " ^ string_of_int n) ns; L.out "Node %d has lines %s@." node !ss end; *) - List.iter ~f:(fun n -> lines := Int.Set.add !lines n) ns in - Visitedset.iter do_one vis; - Int.Set.iter ~f:(fun n -> s := !s ^ " " ^ string_of_int n) !lines; + List.iter ~f:(fun n -> lines := Int.Set.add !lines n) ns + in + Visitedset.iter do_one vis ; + Int.Set.iter ~f:(fun n -> s := !s ^ " " ^ string_of_int n) !lines ; !s (** A spec consists of: pre: a joined prop post: a list of props with path visited: a list of pairs (node_id, line) for the visited nodes *) -type 'a spec = { pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited : Visitedset.t } +type 'a spec = {pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited: Visitedset.t} (** encapsulate type for normalized specs *) module NormSpec : sig @@ -173,9 +173,11 @@ module NormSpec : sig val tospecs : t list -> Prop.normal spec list - val compact : Sil.sharing_env -> t -> t (** Return a compact representation of the spec *) + val compact : Sil.sharing_env -> t -> t + (** Return a compact representation of the spec *) - val erase_join_info_pre : Tenv.t -> t -> t (** Erase join info from pre of spec *) + val erase_join_info_pre : Tenv.t -> t -> t + (** Erase join info from pre of spec *) end = struct type t = Prop.normal spec @@ -183,15 +185,15 @@ end = struct let spec_fav tenv (spec: Prop.normal spec) : Sil.fav = let fav = Sil.fav_new () in - Jprop.fav_add_dfs tenv fav spec.pre; - List.iter ~f:(fun (p, _) -> Prop.prop_fav_add_dfs tenv fav p) spec.posts; + Jprop.fav_add_dfs tenv fav spec.pre ; + List.iter ~f:(fun (p, _) -> Prop.prop_fav_add_dfs tenv fav p) spec.posts ; fav let spec_sub tenv sub spec = - { pre = Jprop.normalize tenv (Jprop.jprop_sub sub spec.pre); - posts = - List.map ~f:(fun (p, path) -> (Prop.normalize tenv (Prop.prop_sub sub p), path)) spec.posts; - visited = spec.visited } + { pre= Jprop.normalize tenv (Jprop.jprop_sub sub spec.pre) + ; posts= + List.map ~f:(fun (p, path) -> (Prop.normalize tenv (Prop.prop_sub sub p), path)) spec.posts + ; visited= spec.visited } (** Convert spec into normal form w.r.t. variable renaming *) let normalize tenv (spec: Prop.normal spec) : Prop.normal spec = @@ -199,90 +201,100 @@ end = struct let idlist = Sil.fav_to_list fav in let count = ref 0 in let sub = - Sil.subst_of_list (List.map ~f:(fun id -> - incr count; (id, Exp.Var (Ident.create_normal Ident.name_spec !count))) idlist) in + Sil.subst_of_list + (List.map + ~f:(fun id -> incr count ; (id, Exp.Var (Ident.create_normal Ident.name_spec !count))) + idlist) + in spec_sub tenv sub spec (** Return a compact representation of the spec *) let compact sh spec = let pre = Jprop.compact sh spec.pre in let posts = List.map ~f:(fun (p, path) -> (Prop.prop_compact sh p, path)) spec.posts in - { pre = pre; posts = posts; visited = spec.visited } + {pre; posts; visited= spec.visited} (** Erase join info from pre of spec *) let erase_join_info_pre tenv spec = - let spec' = { spec with pre = Jprop.Prop (1, Jprop.to_prop spec.pre) } in + let spec' = {spec with pre= Jprop.Prop (1, Jprop.to_prop spec.pre)} in normalize tenv spec' end (** Convert spec into normal form w.r.t. variable renaming *) -let spec_normalize = - NormSpec.normalize +let spec_normalize = NormSpec.normalize (** Cast a list of normalized specs to a list of specs *) -let normalized_specs_to_specs = - NormSpec.tospecs +let normalized_specs_to_specs = NormSpec.tospecs -module CallStats = struct (** module for tracing stats of function calls *) +module CallStats = struct + (** module for tracing stats of function calls *) module PnameLocHash = Hashtbl.Make (struct - type t = Typ.Procname.t * Location.t - let hash (pname, loc) = Hashtbl.hash (Typ.Procname.hash_pname pname, loc.Location.line) - let equal = [%compare.equal: Typ.Procname.t * Location.t] - end) + type t = Typ.Procname.t * Location.t + + let hash (pname, loc) = Hashtbl.hash (Typ.Procname.hash_pname pname, loc.Location.line) + + let equal = [%compare.equal : Typ.Procname.t * Location.t] + end) (** kind of result of a procedure call *) type call_result = - | CR_success (** successful call *) - | CR_not_met (** precondition not met *) - | CR_not_found (** the callee has no specs *) - | CR_skip (** the callee was skipped *) + | CR_success (** successful call *) + | CR_not_met (** precondition not met *) + | CR_not_found (** the callee has no specs *) + | CR_skip (** the callee was skipped *) type trace = (call_result * bool) list type t = trace PnameLocHash.t - let trace_add tr (res : call_result) in_footprint = (res, in_footprint) :: tr + let trace_add tr (res: call_result) in_footprint = (res, in_footprint) :: tr let empty_trace : trace = [] let init calls = let hash = PnameLocHash.create 1 in let do_call pn_loc = PnameLocHash.add hash pn_loc empty_trace in - List.iter ~f:do_call calls; - hash + List.iter ~f:do_call calls ; hash let trace t proc_name loc res in_footprint = - let tr_old = try PnameLocHash.find t (proc_name, loc) with - | Not_found -> - PnameLocHash.add t (proc_name, loc) empty_trace; - empty_trace in + let tr_old = + try PnameLocHash.find t (proc_name, loc) + with Not_found -> + PnameLocHash.add t (proc_name, loc) empty_trace ; + empty_trace + in let tr_new = trace_add tr_old res in_footprint in PnameLocHash.replace t (proc_name, loc) tr_new let tr_elem_str (cr, in_footprint) = - let s1 = match cr with - | CR_success -> "OK" - | CR_not_met -> "NotMet" - | CR_not_found -> "NotFound" - | CR_skip -> "Skip" in + let s1 = + match cr with + | CR_success + -> "OK" + | CR_not_met + -> "NotMet" + | CR_not_found + -> "NotFound" + | CR_skip + -> "Skip" + in let s2 = if in_footprint then "FP" else "RE" in s1 ^ ":" ^ s2 - let pp_trace fmt tr = - Pp.seq - (fun fmt x -> F.fprintf fmt "%s" (tr_elem_str x)) - fmt (List.rev tr) + let pp_trace fmt tr = Pp.seq (fun fmt x -> F.fprintf fmt "%s" (tr_elem_str x)) fmt (List.rev tr) let iter f t = let elems = ref [] in - PnameLocHash.iter (fun x tr -> elems := (x, tr) :: !elems) t; + PnameLocHash.iter (fun x tr -> elems := (x, tr) :: !elems) t ; let sorted_elems = let compare (pname_loc1, _) (pname_loc2, _) = - [%compare: Typ.Procname.t * Location.t] pname_loc1 pname_loc2 in - List.sort ~cmp:compare !elems in + [%compare : Typ.Procname.t * Location.t] pname_loc1 pname_loc2 + in + List.sort ~cmp:compare !elems + in List.iter ~f:(fun (x, tr) -> f x tr) sorted_elems -(* + (* let pp fmt t = let do_call (pname, loc) tr = F.fprintf fmt "%a %a: %a@\n" Typ.Procname.pp pname Location.pp loc pp_trace tr in @@ -295,22 +307,18 @@ type call_stats = CallStats.t (** Execution statistics *) type stats = - { stats_failure: - SymOp.failure_kind option; (** what type of failure stopped the analysis (if any) *) - symops: int; (** Number of SymOp's throughout the whole analysis of the function *) - mutable nodes_visited_fp : IntSet.t; (** Nodes visited during the footprint phase *) - mutable nodes_visited_re : IntSet.t; (** Nodes visited during the re-execution phase *) - call_stats : call_stats; - } + { stats_failure: SymOp.failure_kind option + (** what type of failure stopped the analysis (if any) *) + ; symops: int (** Number of SymOp's throughout the whole analysis of the function *) + ; mutable nodes_visited_fp: IntSet.t (** Nodes visited during the footprint phase *) + ; mutable nodes_visited_re: IntSet.t (** Nodes visited during the re-execution phase *) + ; call_stats: call_stats } type status = Pending | Analyzed [@@deriving compare] -let string_of_status = function - | Pending -> "Pending" - | Analyzed -> "Analyzed" +let string_of_status = function Pending -> "Pending" | Analyzed -> "Analyzed" -let pp_status fmt status = - F.fprintf fmt "%s" (string_of_status status) +let pp_status fmt status = F.fprintf fmt "%s" (string_of_status status) let equal_status = [%compare.equal : status] @@ -320,70 +328,79 @@ let equal_phase = [%compare.equal : phase] (** Payload: results of some analysis *) type payload = - { - preposts : NormSpec.t list option; (** list of specs *) - typestate : unit TypeState.t option; (** final typestate *) - annot_map : AnnotReachabilityDomain.astate option; - crashcontext_frame: Stacktree_t.stacktree option; - (** Proc location and blame_range info for crashcontext analysis *) - quandary : QuandarySummary.t option; - resources : ResourceLeakDomain.summary option; - siof : SiofDomain.astate option; - threadsafety : ThreadSafetyDomain.summary option; - buffer_overrun : BufferOverrunDomain.Summary.t option; - } - -type summary = { - nodes: Procdesc.Node.id list; (** ids of cfg nodes of the procedure *) - phase: phase; (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) - payload: payload; (** payload containing the result of some analysis *) - sessions: int ref; (** Session number: how many nodes went trough symbolic execution *) - stats: stats; (** statistics: execution time and list of errors *) - status: status; (** Analysis status of the procedure *) - attributes : ProcAttributes.t; (** Attributes of the procedure *) - proc_desc_option : Procdesc.t option; -} + { preposts: NormSpec.t list option (** list of specs *) + ; typestate: unit TypeState.t option (** final typestate *) + ; annot_map: AnnotReachabilityDomain.astate option + ; crashcontext_frame: Stacktree_t.stacktree option + (** Proc location and blame_range info for crashcontext analysis *) + ; quandary: QuandarySummary.t option + ; resources: ResourceLeakDomain.summary option + ; siof: SiofDomain.astate option + ; threadsafety: ThreadSafetyDomain.summary option + ; buffer_overrun: BufferOverrunDomain.Summary.t option } + +type summary = + { nodes: Procdesc.Node.id list (** ids of cfg nodes of the procedure *) + ; phase: phase (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) + ; payload: payload (** payload containing the result of some analysis *) + ; sessions: int ref (** Session number: how many nodes went trough symbolic execution *) + ; stats: stats (** statistics: execution time and list of errors *) + ; status: status (** Analysis status of the procedure *) + ; attributes: ProcAttributes.t (** Attributes of the procedure *) + ; proc_desc_option: Procdesc.t option } type spec_tbl = summary Typ.Procname.Hash.t -let spec_tbl: spec_tbl = Typ.Procname.Hash.create 128 +let spec_tbl : spec_tbl = Typ.Procname.Hash.create 128 let clear_spec_tbl () = Typ.Procname.Hash.clear spec_tbl -let pp_failure_kind_opt fmt failure_kind_opt = match failure_kind_opt with - | Some failure_kind -> SymOp.pp_failure_kind fmt failure_kind - | None -> F.fprintf fmt "NONE" +let pp_failure_kind_opt fmt failure_kind_opt = + match failure_kind_opt with + | Some failure_kind + -> SymOp.pp_failure_kind fmt failure_kind + | None + -> F.fprintf fmt "NONE" let pp_errlog fmt err_log = - F.fprintf fmt "ERRORS: @[%a@]@\n%!" Errlog.pp_errors err_log; + F.fprintf fmt "ERRORS: @[%a@]@\n%!" Errlog.pp_errors err_log ; F.fprintf fmt "WARNINGS: @[%a@]" Errlog.pp_warnings err_log let pp_stats fmt stats = F.fprintf fmt "FAILURE:%a SYMOPS:%d@\n" pp_failure_kind_opt stats.stats_failure stats.symops - (** Print the spec *) let pp_spec pe num_opt fmt spec = - let num_str = match num_opt with - | None -> "----------" - | Some (n, tot) -> Format.sprintf "%d of %d [nvisited:%s]" n tot (visited_str spec.visited) in + let num_str = + match num_opt with + | None + -> "----------" + | Some (n, tot) + -> Format.sprintf "%d of %d [nvisited:%s]" n tot (visited_str spec.visited) + in let pre = Jprop.to_prop spec.pre in let pe_post = Prop.prop_update_obj_sub pe pre in let post_list = List.map ~f:fst spec.posts in match pe.Pp.kind with - | TEXT -> - F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str; - F.fprintf fmt "PRE:@\n%a@\n" (Prop.pp_prop Pp.text) pre; - F.fprintf fmt "%a@\n" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list; + | TEXT + -> F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str ; + F.fprintf fmt "PRE:@\n%a@\n" (Prop.pp_prop Pp.text) pre ; + F.fprintf fmt "%a@\n" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list ; F.fprintf fmt "----------------------------------------------------------------" - | HTML -> - F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str; - F.fprintf fmt "PRE:@\n%a%a%a@\n" Io_infer.Html.pp_start_color Pp.Blue (Prop.pp_prop (Pp.html Blue)) pre Io_infer.Html.pp_end_color (); - F.fprintf fmt "%a" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list; + | HTML + -> F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str ; + F.fprintf fmt "PRE:@\n%a%a%a@\n" Io_infer.Html.pp_start_color Pp.Blue + (Prop.pp_prop (Pp.html Blue)) + pre Io_infer.Html.pp_end_color () ; + F.fprintf fmt "%a" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list ; F.fprintf fmt "----------------------------------------------------------------" - | LATEX -> - F.fprintf fmt "\\textbf{\\large Requires}\\\\@\n@[%a%a%a@]\\\\@\n" Latex.pp_color Pp.Blue (Prop.pp_prop (Pp.latex Blue)) pre Latex.pp_color pe.Pp.color; - F.fprintf fmt "\\textbf{\\large Ensures}\\\\@\n@[%a@]" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list + | LATEX + -> F.fprintf fmt "\\textbf{\\large Requires}\\\\@\n@[%a%a%a@]\\\\@\n" Latex.pp_color Pp.Blue + (Prop.pp_prop (Pp.latex Blue)) + pre Latex.pp_color pe.Pp.color ; + F.fprintf fmt "\\textbf{\\large Ensures}\\\\@\n@[%a@]" + (Propgraph.pp_proplist pe_post "POST" (pre, true)) + post_list (** Dump a spec *) let d_spec (spec: 'a spec) = L.add_print_action (L.PTspec, Obj.repr spec) @@ -392,16 +409,25 @@ let pp_specs pe fmt specs = let total = List.length specs in let cnt = ref 0 in match pe.Pp.kind with - | TEXT -> - List.iter ~f:(fun spec -> incr cnt; - F.fprintf fmt "%a" (pp_spec pe (Some (!cnt, total))) spec) specs - | HTML -> - List.iter ~f:(fun spec -> incr cnt; - F.fprintf fmt "%a
@\n" (pp_spec pe (Some (!cnt, total))) spec) specs - | LATEX -> - List.iter ~f:(fun spec -> incr cnt; - F.fprintf fmt "\\subsection*{Spec %d of %d}@\n\\(%a\\)@\n" - !cnt total (pp_spec pe None) spec) specs + | TEXT + -> List.iter + ~f:(fun spec -> + incr cnt ; + F.fprintf fmt "%a" (pp_spec pe (Some (!cnt, total))) spec) + specs + | HTML + -> List.iter + ~f:(fun spec -> + incr cnt ; + F.fprintf fmt "%a
@\n" (pp_spec pe (Some (!cnt, total))) spec) + specs + | LATEX + -> List.iter + ~f:(fun spec -> + incr cnt ; + F.fprintf fmt "\\subsection*{Spec %d of %d}@\n\\(%a\\)@\n" !cnt total (pp_spec pe None) + spec) + specs let describe_phase summary = ("Phase", if equal_phase summary.phase FOOTPRINT then "FOOTPRINT" else "RE_EXECUTION") @@ -411,107 +437,103 @@ let get_signature summary = let s = ref "" in List.iter ~f:(fun (p, typ) -> - let pp f = F.fprintf f "%a %a" (Typ.pp_full Pp.text) typ Mangled.pp p in - let decl = F.asprintf "%t" pp in - s := if String.equal !s "" then decl else !s ^ ", " ^ decl) - summary.attributes.ProcAttributes.formals; + let pp f = F.fprintf f "%a %a" (Typ.pp_full Pp.text) typ Mangled.pp p in + let decl = F.asprintf "%t" pp in + s := if String.equal !s "" then decl else !s ^ ", " ^ decl) + summary.attributes.ProcAttributes.formals ; let pp f = - F.fprintf - f - "%a %a" - (Typ.pp_full Pp.text) - summary.attributes.ProcAttributes.ret_type - Typ.Procname.pp summary.attributes.ProcAttributes.proc_name in + F.fprintf f "%a %a" (Typ.pp_full Pp.text) summary.attributes.ProcAttributes.ret_type + Typ.Procname.pp summary.attributes.ProcAttributes.proc_name + in let decl = F.asprintf "%t" pp in decl ^ "(" ^ !s ^ ")" -let get_specs_from_preposts preposts = - Option.value_map ~f:NormSpec.tospecs ~default:[] preposts +let get_specs_from_preposts preposts = Option.value_map ~f:NormSpec.tospecs ~default:[] preposts -let get_specs_from_payload summary = - get_specs_from_preposts summary.payload.preposts +let get_specs_from_payload summary = get_specs_from_preposts summary.payload.preposts let pp_summary_no_stats_specs fmt summary = let pp_pair fmt (x, y) = F.fprintf fmt "%s: %s" x y in - F.fprintf fmt "%s@\n" (get_signature summary); - F.fprintf fmt "%a@\n" pp_status summary.status; + F.fprintf fmt "%s@\n" (get_signature summary) ; + F.fprintf fmt "%a@\n" pp_status summary.status ; F.fprintf fmt "%a@\n" pp_pair (describe_phase summary) let pp_payload pe fmt - { preposts; typestate; crashcontext_frame; - quandary; siof; threadsafety; buffer_overrun; annot_map } = + { preposts + ; typestate + ; crashcontext_frame + ; quandary + ; siof + ; threadsafety + ; buffer_overrun + ; annot_map } = let pp_opt prefix pp fmt = function - | Some x -> F.fprintf fmt "%s: %a@\n" prefix pp x - | None -> () in + | Some x + -> F.fprintf fmt "%s: %a@\n" prefix pp x + | None + -> () + in F.fprintf fmt "%a%a%a%a%a%a%a%a@\n" - (pp_opt "PrePosts" (pp_specs pe)) (Option.map ~f:NormSpec.tospecs preposts) - (pp_opt "TypeState" (TypeState.pp TypeState.unit_ext)) typestate - (pp_opt "CrashContext" Crashcontext.pp_stacktree) crashcontext_frame - (pp_opt "Quandary" QuandarySummary.pp) quandary - (pp_opt "Siof" SiofDomain.pp) siof + (pp_opt "PrePosts" (pp_specs pe)) + (Option.map ~f:NormSpec.tospecs preposts) + (pp_opt "TypeState" (TypeState.pp TypeState.unit_ext)) + typestate (pp_opt "CrashContext" Crashcontext.pp_stacktree) crashcontext_frame + (pp_opt "Quandary" QuandarySummary.pp) quandary (pp_opt "Siof" SiofDomain.pp) siof (pp_opt "ThreadSafety" ThreadSafetyDomain.pp_summary) threadsafety (pp_opt "BufferOverrun" BufferOverrunDomain.Summary.pp) buffer_overrun (pp_opt "AnnotationReachability" AnnotReachabilityDomain.pp) annot_map - let pp_summary_text fmt summary = let err_log = summary.attributes.ProcAttributes.err_log in let pe = Pp.text in - pp_summary_no_stats_specs fmt summary; - F.fprintf fmt "%a@\n%a%a" - pp_errlog err_log - pp_stats summary.stats - (pp_payload pe) summary.payload + pp_summary_no_stats_specs fmt summary ; + F.fprintf fmt "%a@\n%a%a" pp_errlog err_log pp_stats summary.stats (pp_payload pe) + summary.payload let pp_summary_latex color fmt summary = let err_log = summary.attributes.ProcAttributes.err_log in let pe = Pp.latex color in - F.fprintf fmt "\\begin{verbatim}@\n"; - pp_summary_no_stats_specs fmt summary; - F.fprintf fmt "%a@\n" pp_errlog err_log; - F.fprintf fmt "%a@\n" pp_stats summary.stats; - F.fprintf fmt "\\end{verbatim}@\n"; + F.fprintf fmt "\\begin{verbatim}@\n" ; + pp_summary_no_stats_specs fmt summary ; + F.fprintf fmt "%a@\n" pp_errlog err_log ; + F.fprintf fmt "%a@\n" pp_stats summary.stats ; + F.fprintf fmt "\\end{verbatim}@\n" ; F.fprintf fmt "%a@\n" (pp_specs pe) (get_specs_from_payload summary) let pp_summary_html source color fmt summary = let err_log = summary.attributes.ProcAttributes.err_log in let pe = Pp.html color in - Io_infer.Html.pp_start_color fmt Black; - F.fprintf fmt "@\n%a" pp_summary_no_stats_specs summary; - Io_infer.Html.pp_end_color fmt (); - F.fprintf fmt "
%a
@\n" pp_stats summary.stats; - Errlog.pp_html source [] fmt err_log; - Io_infer.Html.pp_hline fmt (); - F.fprintf fmt "@\n"; - pp_payload pe fmt summary.payload; + Io_infer.Html.pp_start_color fmt Black ; + F.fprintf fmt "@\n%a" pp_summary_no_stats_specs summary ; + Io_infer.Html.pp_end_color fmt () ; + F.fprintf fmt "
%a
@\n" pp_stats summary.stats ; + Errlog.pp_html source [] fmt err_log ; + Io_infer.Html.pp_hline fmt () ; + F.fprintf fmt "@\n" ; + pp_payload pe fmt summary.payload ; F.fprintf fmt "@\n" let empty_stats calls = - { stats_failure = None; - symops = 0; - nodes_visited_fp = IntSet.empty; - nodes_visited_re = IntSet.empty; - call_stats = CallStats.init calls; - } + { stats_failure= None + ; symops= 0 + ; nodes_visited_fp= IntSet.empty + ; nodes_visited_re= IntSet.empty + ; call_stats= CallStats.init calls } let payload_compact sh payload = match payload.preposts with - | Some specs -> - { payload with - preposts = Some (List.map ~f:(NormSpec.compact sh) specs); - } - | None -> - payload + | Some specs + -> {payload with preposts= Some (List.map ~f:(NormSpec.compact sh) specs)} + | None + -> payload (** Return a compact representation of the summary *) -let summary_compact sh summary = - { summary with payload = payload_compact sh summary.payload } +let summary_compact sh summary = {summary with payload= payload_compact sh summary.payload} (** Add the summary to the table for the given function *) -let add_summary (proc_name : Typ.Procname.t) (summary: summary) : unit = - L.(debug Analysis Medium) "Adding summary for %a@\n@[ %a@]@." - Typ.Procname.pp proc_name - pp_summary_text summary; +let add_summary (proc_name: Typ.Procname.t) (summary: summary) : unit = + L.(debug Analysis Medium) + "Adding summary for %a@\n@[ %a@]@." Typ.Procname.pp proc_name pp_summary_text summary ; Typ.Procname.Hash.replace spec_tbl proc_name summary let specs_filename pname = @@ -520,12 +542,14 @@ let specs_filename pname = (** path to the .specs file for the given procedure in the current results directory *) let res_dir_specs_filename pname = - DB.Results_dir.path_to_filename DB.Results_dir.Abs_root [Config.specs_dir_name; specs_filename pname] + DB.Results_dir.path_to_filename DB.Results_dir.Abs_root + [Config.specs_dir_name; specs_filename pname] (** paths to the .specs file for the given procedure in the current spec libraries *) let specs_library_filenames pname = List.map - ~f:(fun specs_dir -> DB.filename_from_string (Filename.concat specs_dir (specs_filename pname))) + ~f:(fun specs_dir -> + DB.filename_from_string (Filename.concat specs_dir (specs_filename pname))) Config.specs_library (** paths to the .specs file for the given procedure in the models folder *) @@ -539,61 +563,63 @@ let summary_serializer : summary Serialization.serializer = Serialization.create_serializer Serialization.Key.summary (** Load procedure summary from the given file *) -let load_summary specs_file = - Serialization.read_from_file summary_serializer specs_file +let load_summary specs_file = Serialization.read_from_file summary_serializer specs_file (** Load procedure summary for the given procedure name and update spec table *) let load_summary_to_spec_table proc_name = - let add summ = - add_summary proc_name summ; - true in + let add summ = add_summary proc_name summ ; true in let load_summary_models models_dir = - match load_summary models_dir with - | None -> false - | Some summ -> add summ in - let rec load_summary_libs = function (* try to load the summary from a list of libs *) - | [] -> false + match load_summary models_dir with None -> false | Some summ -> add summ + in + let rec load_summary_libs = function + | (* try to load the summary from a list of libs *) + [] + -> false | spec_path :: spec_paths -> - (match load_summary spec_path with - | None -> load_summary_libs spec_paths - | Some summ -> - add summ) in + match load_summary spec_path with + | None + -> load_summary_libs spec_paths + | Some summ + -> add summ + in let load_summary_ziplibs zip_specs_filename = let zip_specs_path = Filename.concat Config.specs_dir_name zip_specs_filename in match ZipLib.load summary_serializer zip_specs_path with - | None -> false - | Some summary -> add summary in + | None + -> false + | Some summary + -> add summary + in let default_spec_dir = res_dir_specs_filename proc_name in match load_summary default_spec_dir with - | None -> - (* search on models, libzips, and libs *) - load_summary_models (specs_models_filename proc_name) || - load_summary_ziplibs (specs_filename proc_name) || - load_summary_libs (specs_library_filenames proc_name) - | Some summ -> - add summ + | None + -> (* search on models, libzips, and libs *) + load_summary_models (specs_models_filename proc_name) + || load_summary_ziplibs (specs_filename proc_name) + || load_summary_libs (specs_library_filenames proc_name) + | Some summ + -> add summ let rec get_summary proc_name = - try - Some (Typ.Procname.Hash.find spec_tbl proc_name) - with Not_found -> - if load_summary_to_spec_table proc_name then - get_summary proc_name - else None + try Some (Typ.Procname.Hash.find spec_tbl proc_name) + with Not_found -> if load_summary_to_spec_table proc_name then get_summary proc_name else None let get_summary_unsafe s proc_name = match get_summary proc_name with - | None -> - failwithf "[%s] Specs.get_summary_unsafe: %a Not found" s Typ.Procname.pp proc_name - | Some summary -> summary + | None + -> failwithf "[%s] Specs.get_summary_unsafe: %a Not found" s Typ.Procname.pp proc_name + | Some summary + -> summary (** Check if the procedure is from a library: It's not defined, and there is no spec file for it. *) let proc_is_library proc_attributes = if not proc_attributes.ProcAttributes.is_defined then match get_summary proc_attributes.ProcAttributes.proc_name with - | None -> true - | Some _ -> false + | None + -> true + | Some _ + -> false else false (** Try to find the attributes for a defined proc. @@ -602,143 +628,113 @@ let proc_is_library proc_attributes = If no attributes can be found, return None. *) let proc_resolve_attributes proc_name = - let from_attributes_table () = - AttributesTable.load_attributes ~cache:true proc_name in - let from_specs () = match get_summary proc_name with - | Some summary -> - Some summary.attributes - | None -> None in + let from_attributes_table () = AttributesTable.load_attributes ~cache:true proc_name in + let from_specs () = + match get_summary proc_name with Some summary -> Some summary.attributes | None -> None + in match from_specs () with - | Some attributes -> - if attributes.ProcAttributes.is_defined - then Some attributes - else begin + | Some attributes + -> ( + if attributes.ProcAttributes.is_defined then Some attributes + else match from_attributes_table () with - | Some attributes' -> - Some attributes' - | None -> - Some attributes - end - | None -> - from_attributes_table () + | Some attributes' + -> Some attributes' + | None + -> Some attributes ) + | None + -> from_attributes_table () (** Like proc_resolve_attributes but start from a proc_desc. *) let pdesc_resolve_attributes proc_desc = let proc_name = Procdesc.get_proc_name proc_desc in match proc_resolve_attributes proc_name with - | Some proc_attributes -> - proc_attributes - | None -> - (* this should not happen *) + | Some proc_attributes + -> proc_attributes + | None + -> (* this should not happen *) assert false -let summary_exists proc_name = - match get_summary proc_name with - | Some _ -> true - | None -> false +let summary_exists proc_name = match get_summary proc_name with Some _ -> true | None -> false -let get_status summary = - summary.status +let get_status summary = summary.status -let get_proc_name summary = - summary.attributes.ProcAttributes.proc_name +let get_proc_name summary = summary.attributes.ProcAttributes.proc_name -let get_ret_type summary = - summary.attributes.ProcAttributes.ret_type +let get_ret_type summary = summary.attributes.ProcAttributes.ret_type -let get_formals summary = - summary.attributes.ProcAttributes.formals +let get_formals summary = summary.attributes.ProcAttributes.formals -let get_attributes summary = - summary.attributes +let get_attributes summary = summary.attributes (** Get the flag with the given key for the procedure, if any *) let get_flag summary key = let proc_flags = summary.attributes.ProcAttributes.proc_flags in - try - Some (Hashtbl.find proc_flags key) + try Some (Hashtbl.find proc_flags key) with Not_found -> None (** Return the current phase for the proc *) -let get_phase summary = - summary.phase +let get_phase summary = summary.phase (** Save summary for the procedure into the spec database *) let store_summary (summ1: summary) = - let summ2 = if Config.save_compact_summaries - then summary_compact (Sil.create_sharing_env ()) summ1 - else summ1 in - let final_summary = { summ2 with status = Analyzed } in + let summ2 = + if Config.save_compact_summaries then summary_compact (Sil.create_sharing_env ()) summ1 + else summ1 + in + let final_summary = {summ2 with status= Analyzed} in let proc_name = get_proc_name final_summary in (* Make sure the summary in memory is identical to the saved one *) - add_summary proc_name final_summary; - Serialization.write_to_file - summary_serializer - (res_dir_specs_filename proc_name) + add_summary proc_name final_summary ; + Serialization.write_to_file summary_serializer (res_dir_specs_filename proc_name) ~data:final_summary let empty_payload = - { - preposts = None; - typestate = None; - annot_map = None; - crashcontext_frame = None; - quandary = None; - resources = None; - siof = None; - threadsafety = None; - buffer_overrun = None; - } + { preposts= None + ; typestate= None + ; annot_map= None + ; crashcontext_frame= None + ; quandary= None + ; resources= None + ; siof= None + ; threadsafety= None + ; buffer_overrun= None } (** [init_summary (depend_list, nodes, proc_flags, calls, in_out_calls_opt, proc_attributes)] initializes the summary for [proc_name] given dependent procs in list [depend_list]. *) -let init_summary - (nodes, - proc_flags, calls, - proc_attributes, - proc_desc_option) = +let init_summary (nodes, proc_flags, calls, proc_attributes, proc_desc_option) = let summary = - { - nodes = nodes; - phase = FOOTPRINT; - sessions = ref 0; - payload = empty_payload; - stats = empty_stats calls; - status = Pending; - attributes = - { proc_attributes with - ProcAttributes.proc_flags = proc_flags; }; - proc_desc_option; - } in - Typ.Procname.Hash.replace spec_tbl proc_attributes.ProcAttributes.proc_name summary; - summary + { nodes + ; phase= FOOTPRINT + ; sessions= ref 0 + ; payload= empty_payload + ; stats= empty_stats calls + ; status= Pending + ; attributes= {proc_attributes with ProcAttributes.proc_flags= proc_flags} + ; proc_desc_option } + in + Typ.Procname.Hash.replace spec_tbl proc_attributes.ProcAttributes.proc_name summary ; summary let dummy = - init_summary ( - [], - ProcAttributes.proc_flags_empty (), - [], - ProcAttributes.default Typ.Procname.empty_block Config.Java, - None - ) + init_summary + ( [] + , ProcAttributes.proc_flags_empty () + , [] + , ProcAttributes.default Typ.Procname.empty_block Config.Java + , None ) (** Reset a summary rebuilding the dependents and preserving the proc attributes if present. *) let reset_summary proc_desc = - let proc_desc_option = - if Config.dynamic_dispatch = `Lazy - then Some proc_desc - else None in - init_summary ( - [], - ProcAttributes.proc_flags_empty (), - [], - Procdesc.get_attributes proc_desc, - proc_desc_option - ) + let proc_desc_option = if Config.dynamic_dispatch = `Lazy then Some proc_desc else None in + init_summary + ( [] + , ProcAttributes.proc_flags_empty () + , [] + , Procdesc.get_attributes proc_desc + , proc_desc_option ) (* =============== END of support for spec tables =============== *) - (* let rec post_equal pl1 pl2 = match pl1, pl2 with | [],[] -> true diff --git a/infer/src/backend/specs.mli b/infer/src/backend/specs.mli index 1746b1241..2e522943e 100644 --- a/infer/src/backend/specs.mli +++ b/infer/src/backend/specs.mli @@ -17,66 +17,65 @@ open! IStd (** Module for joined props: the result of joining together propositions repeatedly *) module Jprop : sig (** Remember when a prop is obtained as the join of two other props; the first parameter is an id *) - type 'a t = - | Prop of int * 'a Prop.t - | Joined of int * 'a Prop.t * 'a t * 'a t + type 'a t = Prop of int * 'a Prop.t | Joined of int * 'a Prop.t * 'a t * 'a t - (** Comparison for joined_prop *) val compare : 'a t -> 'a t -> int + (** Comparison for joined_prop *) - (** Return true if the two join_prop's are equal *) val equal : 'a t -> 'a t -> bool + (** Return true if the two join_prop's are equal *) - (** Dump the toplevel prop *) val d_shallow : Prop.normal t -> unit + (** Dump the toplevel prop *) - (** dump a joined prop list, the boolean indicates whether to print toplevel props only *) val d_list : bool -> Prop.normal t list -> unit + (** dump a joined prop list, the boolean indicates whether to print toplevel props only *) - (** Add fav to a jprop *) val fav_add : Sil.fav -> 'a t -> unit + (** Add fav to a jprop *) + val filter : ('a t -> 'b option) -> 'a t list -> 'b list (** [jprop_filter filter joinedprops] applies [filter] to the elements of [joindeprops] and applies it to the subparts if the result is [None]. Returns the most absract results which pass [filter]. *) - val filter : ('a t -> 'b option) -> 'a t list -> 'b list - (** apply a substitution to a jprop *) val jprop_sub : Sil.subst -> Prop.normal t -> Prop.exposed t + (** apply a substitution to a jprop *) - (** map the function to each prop in the jprop, pointwise *) val map : ('a Prop.t -> 'b Prop.t) -> 'a t -> 'b t + (** map the function to each prop in the jprop, pointwise *) - (** Print a list of joined props, the boolean indicates whether to print subcomponents of joined props *) val pp_list : Pp.env -> bool -> Format.formatter -> Prop.normal t list -> unit + (** Print a list of joined props, the boolean indicates whether to print subcomponents of joined props *) - (** Print the toplevel prop *) val pp_short : Pp.env -> Format.formatter -> Prop.normal t -> unit + (** Print the toplevel prop *) - (** Extract the number associated to the toplevel jprop of a prop *) val to_number : 'a t -> int + (** Extract the number associated to the toplevel jprop of a prop *) - (** Extract the toplevel jprop of a prop *) val to_prop : 'a t -> 'a Prop.t + (** Extract the toplevel jprop of a prop *) end (** set of visited nodes: node id and list of lines of all the instructions *) module Visitedset : Caml.Set.S with type elt = Procdesc.Node.id * int list -(** convert a Visitedset to a string *) val visited_str : Visitedset.t -> string +(** convert a Visitedset to a string *) (** A spec consists of: pre: a joined prop posts: a list of props with path visited: a list of pairs (node_id, line) for the visited nodes *) -type 'a spec = { pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited : Visitedset.t } +type 'a spec = {pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited: Visitedset.t} (** encapsulate type for normalized specs *) module NormSpec : sig type t - val erase_join_info_pre : Tenv.t -> t -> t (** Erase join info from pre of spec *) + val erase_join_info_pre : Tenv.t -> t -> t + (** Erase join info from pre of spec *) end (** module for tracing stats of function calls *) @@ -85,33 +84,32 @@ module CallStats : sig (** kind of result of a procedure call *) type call_result = - | CR_success (** successful call *) - | CR_not_met (** precondition not met *) - | CR_not_found (** the callee has no specs *) - | CR_skip (** the callee was skipped *) + | CR_success (** successful call *) + | CR_not_met (** precondition not met *) + | CR_not_found (** the callee has no specs *) + | CR_skip (** the callee was skipped *) (** trace of an occurrence of function call *) type trace = (call_result * bool) list - (** iterate over results of procedure calls *) val iter : (Typ.Procname.t * Location.t -> trace -> unit) -> t -> unit + (** iterate over results of procedure calls *) - (** trace a procedure call *) val trace : t -> Typ.Procname.t -> Location.t -> call_result -> bool -> unit + (** trace a procedure call *) - (** pretty print a call trace *) val pp_trace : Format.formatter -> trace -> unit + (** pretty print a call trace *) end (** Execution statistics *) type stats = - { stats_failure: - SymOp.failure_kind option; (** what type of failure stopped the analysis (if any) *) - symops: int; (** Number of SymOp's throughout the whole analysis of the function *) - mutable nodes_visited_fp : IntSet.t; (** Nodes visited during the footprint phase *) - mutable nodes_visited_re : IntSet.t; (** Nodes visited during the re-execution phase *) - call_stats : CallStats.t; - } + { stats_failure: SymOp.failure_kind option + (** what type of failure stopped the analysis (if any) *) + ; symops: int (** Number of SymOp's throughout the whole analysis of the function *) + ; mutable nodes_visited_fp: IntSet.t (** Nodes visited during the footprint phase *) + ; mutable nodes_visited_re: IntSet.t (** Nodes visited during the re-execution phase *) + ; call_stats: CallStats.t } (** Analysis status of the procedure: - Pending means that the summary has been created by the procedure has not been analyzed yet @@ -130,138 +128,139 @@ val equal_phase : phase -> phase -> bool (** Payload: results of some analysis *) type payload = - { - preposts : NormSpec.t list option; (** list of specs *) - typestate : unit TypeState.t option; (** final typestate *) - annot_map: AnnotReachabilityDomain.astate option; (** list of calls of the form (call, loc) *) - crashcontext_frame: Stacktree_j.stacktree option; - (** Procedure location and blame_range info for crashcontext analysis *) - quandary : QuandarySummary.t option; - resources : ResourceLeakDomain.summary option; - siof : SiofDomain.astate option; - threadsafety : ThreadSafetyDomain.summary option; - buffer_overrun : BufferOverrunDomain.Summary.t option; - } + { preposts: NormSpec.t list option (** list of specs *) + ; typestate: unit TypeState.t option (** final typestate *) + ; annot_map: AnnotReachabilityDomain.astate option (** list of calls of the form (call, loc) *) + ; crashcontext_frame: Stacktree_j.stacktree option + (** Procedure location and blame_range info for crashcontext analysis *) + ; quandary: QuandarySummary.t option + ; resources: ResourceLeakDomain.summary option + ; siof: SiofDomain.astate option + ; threadsafety: ThreadSafetyDomain.summary option + ; buffer_overrun: BufferOverrunDomain.Summary.t option } (** Procedure summary *) -type summary = { - nodes: Procdesc.Node.id list; (** ids of cfg nodes of the procedure *) - phase: phase; (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) - payload: payload; (** payload containing the result of some analysis *) - sessions: int ref; (** Session number: how many nodes went trough symbolic execution *) - stats: stats; (** statistics: execution time and list of errors *) - status: status; (** Analysis status of the procedure *) - attributes : ProcAttributes.t; (** Attributes of the procedure *) - proc_desc_option : Procdesc.t option; -} +type summary = + { nodes: Procdesc.Node.id list (** ids of cfg nodes of the procedure *) + ; phase: phase (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) + ; payload: payload (** payload containing the result of some analysis *) + ; sessions: int ref (** Session number: how many nodes went trough symbolic execution *) + ; stats: stats (** statistics: execution time and list of errors *) + ; status: status (** Analysis status of the procedure *) + ; attributes: ProcAttributes.t (** Attributes of the procedure *) + ; proc_desc_option: Procdesc.t option } -(** dummy summary for testing *) val dummy : summary +(** dummy summary for testing *) -(** Add the summary to the table for the given function *) val add_summary : Typ.Procname.t -> summary -> unit +(** Add the summary to the table for the given function *) -(** Check if a summary for a given procedure exists in the models directory *) val summary_exists_in_models : Typ.Procname.t -> bool +(** Check if a summary for a given procedure exists in the models directory *) -(** remove all the elements from the spec table *) val clear_spec_tbl : unit -> unit +(** remove all the elements from the spec table *) -(** Dump a spec *) val d_spec : 'a spec -> unit +(** Dump a spec *) -(** Return the summary option for the procedure name *) val get_summary : Typ.Procname.t -> summary option +(** Return the summary option for the procedure name *) -(** Get the procedure name *) val get_proc_name : summary -> Typ.Procname.t +(** Get the procedure name *) -(** Get the attributes of the procedure. *) val get_attributes : summary -> ProcAttributes.t +(** Get the attributes of the procedure. *) -(** Get the return type of the procedure *) val get_ret_type : summary -> Typ.t +(** Get the return type of the procedure *) -(** Get the formal paramters of the procedure *) val get_formals : summary -> (Mangled.t * Typ.t) list +(** Get the formal paramters of the procedure *) -(** Get the flag with the given key for the procedure, if any *) val get_flag : summary -> string -> string option +(** Get the flag with the given key for the procedure, if any *) -(** Return the current phase for the proc *) val get_phase : summary -> phase +(** Return the current phase for the proc *) -(** Return the signature of a procedure declaration as a string *) val get_signature : summary -> string +(** Return the signature of a procedure declaration as a string *) -(** Get the specs from the payload of the summary. *) val get_specs_from_payload : summary -> Prop.normal spec list +(** Get the specs from the payload of the summary. *) -(** @deprecated Return the summary for the procedure name. Raises an exception when not found. *) val get_summary_unsafe : string -> Typ.Procname.t -> summary +(** @deprecated Return the summary for the procedure name. Raises an exception when not found. *) -(** Return the status (active v.s. inactive) of a procedure summary *) val get_status : summary -> status +(** Return the status (active v.s. inactive) of a procedure summary *) +val init_summary : + Procdesc.Node.id list + * (* nodes *) + ProcAttributes.proc_flags + * (* procedure flags *) + (Typ.Procname.t * Location.t) list + * (* calls *) + ProcAttributes.t + * (* attributes of the procedure *) + Procdesc.t option + (* procdesc option *) -> summary (** Initialize the summary for [proc_name] given dependent procs in list [depend_list]. This also stores the new summary in the spec table. *) -val init_summary : - ( Procdesc.Node.id list * (* nodes *) - ProcAttributes.proc_flags * (* procedure flags *) - (Typ.Procname.t * Location.t) list * (* calls *) - ProcAttributes.t * (* attributes of the procedure *) - Procdesc.t option) (* procdesc option *) - -> summary -(** Reset a summary rebuilding the dependents and preserving the proc attributes if present. *) val reset_summary : Procdesc.t -> summary +(** Reset a summary rebuilding the dependents and preserving the proc attributes if present. *) -(** Load procedure summary from the given file *) val load_summary : DB.filename -> summary option +(** Load procedure summary from the given file *) -(** Check if a procedure summary exists for the given procedure name *) val summary_exists : Typ.Procname.t -> bool +(** Check if a procedure summary exists for the given procedure name *) -(** Cast a list of normalized specs to a list of specs *) val normalized_specs_to_specs : NormSpec.t list -> Prop.normal spec list +(** Cast a list of normalized specs to a list of specs *) -(** Print the spec *) val pp_spec : Pp.env -> (int * int) option -> Format.formatter -> Prop.normal spec -> unit +(** Print the spec *) -(** Print the specs *) val pp_specs : Pp.env -> Format.formatter -> Prop.normal spec list -> unit +(** Print the specs *) -(** Print the summary in html format *) val pp_summary_html : SourceFile.t -> Pp.color -> Format.formatter -> summary -> unit +(** Print the summary in html format *) -(** Print the summary in latext format *) val pp_summary_latex : Pp.color -> Format.formatter -> summary -> unit +(** Print the summary in latext format *) -(** Print the summary in text format *) val pp_summary_text : Format.formatter -> summary -> unit +(** Print the summary in text format *) -(** Like proc_resolve_attributes but start from a proc_desc. *) val pdesc_resolve_attributes : Procdesc.t -> ProcAttributes.t +(** Like proc_resolve_attributes but start from a proc_desc. *) +val proc_resolve_attributes : Typ.Procname.t -> ProcAttributes.t option (** Try to find the attributes for a defined proc. First look at specs (to get attributes computed by analysis) then look at the attributes table. If no attributes can be found, return None. *) -val proc_resolve_attributes : Typ.Procname.t -> ProcAttributes.t option +val proc_is_library : ProcAttributes.t -> bool (** Check if the procedure is from a library: It's not defined, and there is no spec file for it. *) -val proc_is_library : ProcAttributes.t -> bool -(** Convert spec into normal form w.r.t. variable renaming *) val spec_normalize : Tenv.t -> Prop.normal spec -> NormSpec.t +(** Convert spec into normal form w.r.t. variable renaming *) -(** path to the .specs file for the given procedure in the current results dir *) val res_dir_specs_filename : Typ.Procname.t -> DB.filename +(** path to the .specs file for the given procedure in the current results dir *) -(** Save summary for the procedure into the spec database *) val store_summary : summary -> unit +(** Save summary for the procedure into the spec database *) -(** Return a compact representation of the summary *) val summary_compact : Sil.sharing_env -> summary -> summary +(** Return a compact representation of the summary *) diff --git a/infer/src/backend/state.ml b/infer/src/backend/state.ml index 0b08aaf02..22a8fbf04 100644 --- a/infer/src/backend/state.ml +++ b/infer/src/backend/state.ml @@ -18,58 +18,44 @@ module F = Format type const_map = Procdesc.Node.t -> Exp.t -> Const.t option (** failure statistics for symbolic execution on a given node *) -type failure_stats = { - mutable instr_fail: int; (* number of instruction failures since the current node started *) - mutable instr_ok: int; (* number of instruction successes since the current node started *) - mutable node_fail: int; (* number of node failures (i.e. at least one instruction failure) *) - mutable node_ok: int; (* number of node successes (i.e. no instruction failures) *) - mutable first_failure : - (Location.t * (int * int) * int * Errlog.loc_trace * exn) option - (* exception at the first failure *) -} +type failure_stats = + { mutable instr_fail: int + ; (* number of instruction failures since the current node started *) + mutable instr_ok: int + ; (* number of instruction successes since the current node started *) + mutable node_fail: int + ; (* number of node failures (i.e. at least one instruction failure) *) + mutable node_ok: int + ; (* number of node successes (i.e. no instruction failures) *) + mutable first_failure: (Location.t * (int * int) * int * Errlog.loc_trace * exn) option + (* exception at the first failure *) } module NodeHash = Procdesc.NodeHash -type t = { - mutable const_map : const_map; - (** Constant map for the procedure *) - - mutable diverging_states_node : Paths.PathSet.t; - (** Diverging states since the last reset for the node *) - - mutable diverging_states_proc : Paths.PathSet.t; - (** Diverging states since the last reset for the procedure *) - - mutable last_instr : Sil.instr option; - (** Last instruction seen *) - - mutable last_node : Procdesc.Node.t; - (** Last node seen *) - - mutable last_path : (Paths.Path.t * (PredSymb.path_pos option)) option; - (** Last path seen *) - - mutable last_prop_tenv_pdesc : (Prop.normal Prop.t * Tenv.t * Procdesc.t) option; - (** Last prop,tenv,pdesc seen *) - - mutable last_session : int; - (** Last session seen *) - - failure_map : failure_stats NodeHash.t; - (** Map visited nodes to failure statistics *) -} - -let initial () = { - const_map = (fun _ _ -> None); - diverging_states_node = Paths.PathSet.empty; - diverging_states_proc = Paths.PathSet.empty; - last_instr = None; - last_node = Procdesc.Node.dummy None; - last_path = None; - last_prop_tenv_pdesc = None; - last_session = 0; - failure_map = NodeHash.create 1; -} +type t = + { mutable const_map: const_map (** Constant map for the procedure *) + ; mutable diverging_states_node: Paths.PathSet.t + (** Diverging states since the last reset for the node *) + ; mutable diverging_states_proc: Paths.PathSet.t + (** Diverging states since the last reset for the procedure *) + ; mutable last_instr: Sil.instr option (** Last instruction seen *) + ; mutable last_node: Procdesc.Node.t (** Last node seen *) + ; mutable last_path: (Paths.Path.t * PredSymb.path_pos option) option (** Last path seen *) + ; mutable last_prop_tenv_pdesc: (Prop.normal Prop.t * Tenv.t * Procdesc.t) option + (** Last prop,tenv,pdesc seen *) + ; mutable last_session: int (** Last session seen *) + ; failure_map: failure_stats NodeHash.t (** Map visited nodes to failure statistics *) } + +let initial () = + { const_map= (fun _ _ -> None) + ; diverging_states_node= Paths.PathSet.empty + ; diverging_states_proc= Paths.PathSet.empty + ; last_instr= None + ; last_node= Procdesc.Node.dummy None + ; last_path= None + ; last_prop_tenv_pdesc= None + ; last_session= 0 + ; failure_map= NodeHash.create 1 } (** Global state *) let gs = ref (initial ()) @@ -77,45 +63,40 @@ let gs = ref (initial ()) (** Return the old state, and revert the current state to the initial one. *) let save_state () = let old = !gs in - gs := initial (); + gs := initial () ; old (** Restore the old state. *) -let restore_state st = - gs := st +let restore_state st = gs := st -let reset_diverging_states_node () = - !gs.diverging_states_node <- Paths.PathSet.empty +let reset_diverging_states_node () = !gs.diverging_states_node <- Paths.PathSet.empty -let reset () = - gs := initial () +let reset () = gs := initial () let get_failure_stats node = try NodeHash.find !gs.failure_map node with Not_found -> - let fs = { instr_fail = 0; instr_ok = 0; node_fail = 0; node_ok = 0; first_failure = None } in - NodeHash.add !gs.failure_map node fs; - fs + let fs = {instr_fail= 0; instr_ok= 0; node_fail= 0; node_ok= 0; first_failure= None} in + NodeHash.add !gs.failure_map node fs ; fs let add_diverging_states pset = - !gs.diverging_states_proc <- Paths.PathSet.union pset !gs.diverging_states_proc; + !gs.diverging_states_proc <- Paths.PathSet.union pset !gs.diverging_states_proc ; !gs.diverging_states_node <- Paths.PathSet.union pset !gs.diverging_states_node -let get_diverging_states_node () = - !gs.diverging_states_node +let get_diverging_states_node () = !gs.diverging_states_node -let get_diverging_states_proc () = - !gs.diverging_states_proc +let get_diverging_states_proc () = !gs.diverging_states_proc -let get_instr () = - !gs.last_instr +let get_instr () = !gs.last_instr -let get_loc () = match !gs.last_instr with - | Some instr -> Sil.instr_get_loc instr - | None -> Procdesc.Node.get_loc !gs.last_node +let get_loc () = + match !gs.last_instr with + | Some instr + -> Sil.instr_get_loc instr + | None + -> Procdesc.Node.get_loc !gs.last_node -let get_node () = - !gs.last_node +let get_node () = !gs.last_node (** simple key for a node: just look at the instructions *) let node_simple_key node = @@ -125,86 +106,95 @@ let node_simple_key node = if Sil.instr_is_auxiliary instr then () else match instr with - | Sil.Load _ -> add_key 1 - | Sil.Store _ -> add_key 2 - | Sil.Prune _ -> add_key 3 - | Sil.Call _ -> add_key 4 - | Sil.Nullify _ -> add_key 5 - | Sil.Abstract _ -> add_key 6 - | Sil.Remove_temps _ -> add_key 7 - | Sil.Declare_locals _ -> add_key 8 in - List.iter ~f:do_instr (Procdesc.Node.get_instrs node); + | Sil.Load _ + -> add_key 1 + | Sil.Store _ + -> add_key 2 + | Sil.Prune _ + -> add_key 3 + | Sil.Call _ + -> add_key 4 + | Sil.Nullify _ + -> add_key 5 + | Sil.Abstract _ + -> add_key 6 + | Sil.Remove_temps _ + -> add_key 7 + | Sil.Declare_locals _ + -> add_key 8 + in + List.iter ~f:do_instr (Procdesc.Node.get_instrs node) ; Hashtbl.hash !key (** key for a node: look at the current node, successors and predecessors *) let node_key node = let succs = Procdesc.Node.get_succs node in let preds = Procdesc.Node.get_preds node in - let v = (node_simple_key node, - List.map ~f:node_simple_key succs, - List.map ~f:node_simple_key preds) in + let v = + (node_simple_key node, List.map ~f:node_simple_key succs, List.map ~f:node_simple_key preds) + in Hashtbl.hash v (** normalize the list of instructions by renaming let-bound ids *) let instrs_normalize instrs = let bound_ids = - let do_instr ids = function - | Sil.Load (id, _, _, _) -> id :: ids - | _ -> ids in - List.fold ~f:do_instr ~init:[] instrs in + let do_instr ids = function Sil.Load (id, _, _, _) -> id :: ids | _ -> ids in + List.fold ~f:do_instr ~init:[] instrs + in let subst = let count = ref Int.min_value in let gensym id = - incr count; - Ident.set_stamp id !count in - Sil.subst_of_list (List.map ~f:(fun id -> (id, Exp.Var (gensym id))) bound_ids) in + incr count ; + Ident.set_stamp id !count + in + Sil.subst_of_list (List.map ~f:(fun id -> (id, Exp.Var (gensym id))) bound_ids) + in List.map ~f:(Sil.instr_sub subst) instrs (** Create a function to find duplicate nodes. A node is a duplicate of another one if they have the same kind and location and normalized (w.r.t. renaming of let - bound ids) list of instructions. *) -let mk_find_duplicate_nodes proc_desc : (Procdesc.Node.t -> Procdesc.NodeSet.t) = +let mk_find_duplicate_nodes proc_desc : Procdesc.Node.t -> Procdesc.NodeSet.t = let module M = (* map from (loc,kind) *) - Caml.Map.Make(struct - type t = Location.t * Procdesc.Node.nodekind [@@deriving compare] - end) in - + Caml.Map.Make (struct + type t = Location.t * Procdesc.Node.nodekind [@@deriving compare] + end) in let module S = (* set of nodes with normalized insructions *) - Caml.Set.Make(struct - type t = Procdesc.Node.t * Sil.instr list - let compare (n1, _) (n2, _) = - Procdesc.Node.compare n1 n2 - end) in + Caml.Set.Make (struct + type t = Procdesc.Node.t * Sil.instr list - let get_key node = (* map key *) + let compare (n1, _) (n2, _) = Procdesc.Node.compare n1 n2 + end) in + let get_key node = + (* map key *) let loc = Procdesc.Node.get_loc node in let kind = Procdesc.Node.get_kind node in - (loc, kind) in - + (loc, kind) + in let map = - let m = ref M.empty in (* map from (loc, kind) to (instructions, node) set *) - + let m = ref M.empty in + (* map from (loc, kind) to (instructions, node) set *) let module E = struct (** Threshold: do not build the map if too many nodes are duplicates. *) let threshold = 100 + exception Threshold end in - let do_node node = let normalized_instrs = instrs_normalize (Procdesc.Node.get_instrs node) in let key = get_key node in - let s = try M.find key !m with Not_found -> S.empty in - if S.cardinal s > E.threshold then raise E.Threshold; + let s = + try M.find key !m + with Not_found -> S.empty + in + if S.cardinal s > E.threshold then raise E.Threshold ; let s' = S.add (node, normalized_instrs) s in - m := M.add key s' !m in - + m := M.add key s' !m + in let nodes = Procdesc.get_nodes proc_desc in - try - List.iter ~f:do_node nodes; - !m - with E.Threshold -> - M.empty in - + try List.iter ~f:do_node nodes ; !m + with E.Threshold -> M.empty + in let find_duplicate_nodes node = try let s = M.find (get_key node) map in @@ -212,40 +202,44 @@ let mk_find_duplicate_nodes proc_desc : (Procdesc.Node.t -> Procdesc.NodeSet.t) let (_, node_normalized_instrs), _ = let filter (node', _) = Procdesc.Node.equal node node' in match List.partition_tf ~f:filter elements with - | [this], others -> this, others - | _ -> raise Not_found in + | [this], others + -> (this, others) + | _ + -> raise Not_found + in let duplicates = let equal_normalized_instrs (_, normalized_instrs') = - List.equal ~equal:Sil.equal_instr node_normalized_instrs normalized_instrs' in - List.filter ~f:equal_normalized_instrs elements in + List.equal ~equal:Sil.equal_instr node_normalized_instrs normalized_instrs' + in + List.filter ~f:equal_normalized_instrs elements + in List.fold ~f:(fun nset (node', _) -> Procdesc.NodeSet.add node' nset) - ~init:Procdesc.NodeSet.empty - duplicates - with Not_found -> Procdesc.NodeSet.singleton node in - + ~init:Procdesc.NodeSet.empty duplicates + with Not_found -> Procdesc.NodeSet.singleton node + in find_duplicate_nodes -let get_node_id () = - Procdesc.Node.get_id !gs.last_node +let get_node_id () = Procdesc.Node.get_id !gs.last_node -let get_node_id_key () = - (Procdesc.Node.get_id !gs.last_node, node_key !gs.last_node) +let get_node_id_key () = (Procdesc.Node.get_id !gs.last_node, node_key !gs.last_node) let get_inst_update pos = let loc = get_loc () in Sil.inst_update loc pos -let get_path () = match !gs.last_path with - | None -> Paths.Path.start !gs.last_node, None - | Some (path, pos_opt) -> path, pos_opt +let get_path () = + match !gs.last_path with + | None + -> (Paths.Path.start !gs.last_node, None) + | Some (path, pos_opt) + -> (path, pos_opt) let get_loc_trace () : Errlog.loc_trace = let path, pos_opt = get_path () in Paths.Path.create_loc_trace path pos_opt -let get_prop_tenv_pdesc () = - !gs.last_prop_tenv_pdesc +let get_prop_tenv_pdesc () = !gs.last_prop_tenv_pdesc (** extract the footprint of the prop, and turn it into a normalized precondition using spec variables *) let extract_pre p tenv pdesc abstract_fun = @@ -253,44 +247,53 @@ let extract_pre p tenv pdesc abstract_fun = let fav = Prop.prop_fav p in let idlist = Sil.fav_to_list fav in let count = ref 0 in - Sil.subst_of_list (List.map ~f:(fun id -> - incr count; (id, Exp.Var (Ident.create_normal Ident.name_spec !count))) idlist) in + Sil.subst_of_list + (List.map + ~f:(fun id -> incr count ; (id, Exp.Var (Ident.create_normal Ident.name_spec !count))) + idlist) + in let _, p' = PropUtil.remove_locals_formals tenv pdesc p in let pre, _ = Prop.extract_spec p' in - let pre' = try abstract_fun tenv pre with exn when SymOp.exn_not_failure exn -> pre in + let pre' = + try abstract_fun tenv pre + with exn when SymOp.exn_not_failure exn -> pre + in Prop.normalize tenv (Prop.prop_sub sub pre') (** return the normalized precondition extracted form the last prop seen, if any the abstraction function is a parameter to get around module dependencies *) -let get_normalized_pre (abstract_fun : Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t) - : Prop.normal Prop.t option = +let get_normalized_pre (abstract_fun: Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t) + : Prop.normal Prop.t option = match get_prop_tenv_pdesc () with - | None -> None - | Some (prop, tenv, pdesc) -> - Some (extract_pre prop tenv pdesc abstract_fun) + | None + -> None + | Some (prop, tenv, pdesc) + -> Some (extract_pre prop tenv pdesc abstract_fun) -let get_session () = - !gs.last_session +let get_session () = !gs.last_session let get_path_pos () = - let pname = match get_prop_tenv_pdesc () with - | Some (_, _, pdesc) -> Procdesc.get_proc_name pdesc - | None -> Typ.Procname.from_string_c_fun "unknown_procedure" in + let pname = + match get_prop_tenv_pdesc () with + | Some (_, _, pdesc) + -> Procdesc.get_proc_name pdesc + | None + -> Typ.Procname.from_string_c_fun "unknown_procedure" + in let nid = get_node_id () in (pname, (nid :> int)) let mark_execution_start node = let fs = get_failure_stats node in - fs.instr_ok <- 0; + fs.instr_ok <- 0 ; fs.instr_fail <- 0 let mark_execution_end node = let fs = get_failure_stats node in let success = Int.equal fs.instr_fail 0 in - fs.instr_ok <- 0; - fs.instr_fail <- 0; - if success then fs.node_ok <- fs.node_ok + 1 - else fs.node_fail <- fs.node_fail + 1 + fs.instr_ok <- 0 ; + fs.instr_fail <- 0 ; + if success then fs.node_ok <- fs.node_ok + 1 else fs.node_fail <- fs.node_fail + 1 let mark_instr_ok () = let fs = get_failure_stats (get_node ()) in @@ -303,51 +306,39 @@ let mark_instr_fail exn = let loc_trace = get_loc_trace () in let fs = get_failure_stats (get_node ()) in if is_none fs.first_failure then - fs.first_failure <- Some (loc, key, (session :> int), loc_trace, exn); + fs.first_failure <- Some (loc, key, (session :> int), loc_trace, exn) ; fs.instr_fail <- fs.instr_fail + 1 type log_issue = - ?store_summary: bool -> - Typ.Procname.t -> - ?loc: Location.t -> - ?node_id: (int * int) -> - ?session: int -> - ?ltr: Errlog.loc_trace -> - ?linters_def_file:string -> - ?doc_url:string -> - exn -> - unit - -let process_execution_failures (log_issue : log_issue) pname = + ?store_summary:bool -> Typ.Procname.t -> ?loc:Location.t -> ?node_id:int * int -> ?session:int + -> ?ltr:Errlog.loc_trace -> ?linters_def_file:string -> ?doc_url:string -> exn -> unit + +let process_execution_failures (log_issue: log_issue) pname = let do_failure _ fs = (* L.out "Node:%a node_ok:%d node_fail:%d@." Procdesc.Node.pp node fs.node_ok fs.node_fail; *) - match fs.node_ok, fs.first_failure with - | 0, Some (loc, key, _, loc_trace, exn) when not Config.debug_exceptions -> - let ex_name, _, ml_loc_opt, _, _, _, _ = Exceptions.recognize_exception exn in + match (fs.node_ok, fs.first_failure) with + | 0, Some (loc, key, _, loc_trace, exn) when not Config.debug_exceptions + -> let ex_name, _, ml_loc_opt, _, _, _, _ = Exceptions.recognize_exception exn in let desc' = Localise.verbatim_desc ("exception: " ^ Localise.to_issue_id ex_name) in let exn' = Exceptions.Analysis_stops (desc', ml_loc_opt) in log_issue pname ~loc ~node_id:key ~ltr:loc_trace exn' - | _ -> () in + | _ + -> () + in NodeHash.iter do_failure !gs.failure_map -let set_instr (instr: Sil.instr) = - !gs.last_instr <- Some instr +let set_instr (instr: Sil.instr) = !gs.last_instr <- Some instr -let set_path path pos_opt = - !gs.last_path <- Some (path, pos_opt) +let set_path path pos_opt = !gs.last_path <- Some (path, pos_opt) -let set_prop_tenv_pdesc prop tenv pdesc = - !gs.last_prop_tenv_pdesc <- Some (prop, tenv, pdesc) +let set_prop_tenv_pdesc prop tenv pdesc = !gs.last_prop_tenv_pdesc <- Some (prop, tenv, pdesc) let set_node (node: Procdesc.Node.t) = - !gs.last_instr <- None; + !gs.last_instr <- None ; !gs.last_node <- node -let set_session (session: int) = - !gs.last_session <- session +let set_session (session: int) = !gs.last_session <- session -let get_const_map () = - !gs.const_map +let get_const_map () = !gs.const_map -let set_const_map const_map' = - !gs.const_map <- const_map' +let set_const_map const_map' = !gs.const_map <- const_map' diff --git a/infer/src/backend/state.mli b/infer/src/backend/state.mli index dcfcbd8ee..b107c375e 100644 --- a/infer/src/backend/state.mli +++ b/infer/src/backend/state.mli @@ -15,116 +15,108 @@ open! IStd (** Internal state *) type t -(** Add diverging states *) val add_diverging_states : Paths.PathSet.t -> unit +(** Add diverging states *) type const_map = Procdesc.Node.t -> Exp.t -> Const.t option -(** Get the constant map for the current procedure. *) val get_const_map : unit -> const_map +(** Get the constant map for the current procedure. *) -(** Get the diverging states for the node *) val get_diverging_states_node : unit -> Paths.PathSet.t +(** Get the diverging states for the node *) -(** Get the diverging states for the procedure *) val get_diverging_states_proc : unit -> Paths.PathSet.t +(** Get the diverging states for the procedure *) -(** Get update instrumentation for the current loc *) val get_inst_update : PredSymb.path_pos -> Sil.inst +(** Get update instrumentation for the current loc *) -(** Get last instruction seen in symbolic execution *) val get_instr : unit -> Sil.instr option +(** Get last instruction seen in symbolic execution *) -(** Get last location seen in symbolic execution *) val get_loc : unit -> Location.t +(** Get last location seen in symbolic execution *) -(** Get the location trace of the last path seen in symbolic execution *) val get_loc_trace : unit -> Errlog.loc_trace +(** Get the location trace of the last path seen in symbolic execution *) -(** Get last node seen in symbolic execution *) val get_node : unit -> Procdesc.Node.t +(** Get last node seen in symbolic execution *) -(** Get id of last node seen in symbolic execution *) val get_node_id : unit -> Procdesc.Node.id +(** Get id of last node seen in symbolic execution *) -(** Get id and key of last node seen in symbolic execution *) val get_node_id_key : unit -> Procdesc.Node.id * int +(** Get id and key of last node seen in symbolic execution *) -(** return the normalized precondition extracted form the last prop seen, if any - the abstraction function is a parameter to get around module dependencies *) val get_normalized_pre : (Tenv.t -> Prop.normal Prop.t -> Prop.normal Prop.t) -> Prop.normal Prop.t option +(** return the normalized precondition extracted form the last prop seen, if any + the abstraction function is a parameter to get around module dependencies *) +val get_path : unit -> Paths.Path.t * PredSymb.path_pos option (** Get last path seen in symbolic execution *) -val get_path : unit -> Paths.Path.t * (PredSymb.path_pos option) -(** Get the last path position seen in symbolic execution *) val get_path_pos : unit -> PredSymb.path_pos +(** Get the last path position seen in symbolic execution *) -(** Get last last prop,tenv,pdesc seen in symbolic execution *) val get_prop_tenv_pdesc : unit -> (Prop.normal Prop.t * Tenv.t * Procdesc.t) option +(** Get last last prop,tenv,pdesc seen in symbolic execution *) -(** Get last session seen in symbolic execution *) val get_session : unit -> int +(** Get last session seen in symbolic execution *) -(** Mark the end of symbolic execution of a node *) val mark_execution_end : Procdesc.Node.t -> unit +(** Mark the end of symbolic execution of a node *) -(** Mark the start of symbolic execution of a node *) val mark_execution_start : Procdesc.Node.t -> unit +(** Mark the start of symbolic execution of a node *) -(** Mark that the execution of the current instruction failed *) val mark_instr_fail : exn -> unit +(** Mark that the execution of the current instruction failed *) -(** Mark that the execution of the current instruction was OK *) val mark_instr_ok : unit -> unit +(** Mark that the execution of the current instruction was OK *) +val mk_find_duplicate_nodes : Procdesc.t -> Procdesc.Node.t -> Procdesc.NodeSet.t (** Create a function to find duplicate nodes. A node is a duplicate of another one if they have the same kind and location and normalized (w.r.t. renaming of let - bound ids) list of instructions. *) -val mk_find_duplicate_nodes: Procdesc.t -> (Procdesc.Node.t -> Procdesc.NodeSet.t) type log_issue = - ?store_summary: bool -> - Typ.Procname.t -> - ?loc: Location.t -> - ?node_id: (int * int) -> - ?session: int -> - ?ltr: Errlog.loc_trace -> - ?linters_def_file:string -> - ?doc_url:string -> - exn -> - unit + ?store_summary:bool -> Typ.Procname.t -> ?loc:Location.t -> ?node_id:int * int -> ?session:int + -> ?ltr:Errlog.loc_trace -> ?linters_def_file:string -> ?doc_url:string -> exn -> unit -(** Process the failures during symbolic execution of a procedure *) val process_execution_failures : log_issue -> Typ.Procname.t -> unit +(** Process the failures during symbolic execution of a procedure *) -(** Reset all the global data. *) val reset : unit -> unit +(** Reset all the global data. *) -(** Reset the diverging states information for the node *) val reset_diverging_states_node : unit -> unit +(** Reset the diverging states information for the node *) -(** Restore the old state. *) val restore_state : t -> unit +(** Restore the old state. *) -(** Return the old state, and revert the current state to the initial one. *) val save_state : unit -> t +(** Return the old state, and revert the current state to the initial one. *) -(** Set the constant map for the current procedure. *) val set_const_map : const_map -> unit +(** Set the constant map for the current procedure. *) -(** Set last instruction seen in symbolic execution *) val set_instr : Sil.instr -> unit +(** Set last instruction seen in symbolic execution *) -(** Set last node seen in symbolic execution *) val set_node : Procdesc.Node.t -> unit +(** Set last node seen in symbolic execution *) -(** Get last path seen in symbolic execution *) val set_path : Paths.Path.t -> PredSymb.path_pos option -> unit +(** Get last path seen in symbolic execution *) -(** Set last prop,tenv,pdesc seen in symbolic execution *) val set_prop_tenv_pdesc : Prop.normal Prop.t -> Tenv.t -> Procdesc.t -> unit +(** Set last prop,tenv,pdesc seen in symbolic execution *) -(** Set last session seen in symbolic execution *) val set_session : int -> unit +(** Set last session seen in symbolic execution *) diff --git a/infer/src/backend/symExec.ml b/infer/src/backend/symExec.ml index ca65f27e3..49b2f010d 100644 --- a/infer/src/backend/symExec.ml +++ b/infer/src/backend/symExec.ml @@ -17,38 +17,44 @@ module L = Logging module F = Format let rec fldlist_assoc fld = function - | [] -> raise Not_found - | (fld', x, _):: l -> if Typ.Fieldname.equal fld fld' then x else fldlist_assoc fld l + | [] + -> raise Not_found + | (fld', x, _) :: l + -> if Typ.Fieldname.equal fld fld' then x else fldlist_assoc fld l let unroll_type tenv (typ: Typ.t) (off: Sil.offset) = let fail fld_to_string fld = - L.d_strln ".... Invalid Field Access ...."; - L.d_str ("Fld : " ^ fld_to_string fld); L.d_ln (); - L.d_str "Type : "; Typ.d_full typ; L.d_ln (); + L.d_strln ".... Invalid Field Access ...." ; + L.d_str ("Fld : " ^ fld_to_string fld) ; + L.d_ln () ; + L.d_str "Type : " ; + Typ.d_full typ ; + L.d_ln () ; raise (Exceptions.Bad_footprint __POS__) in match (typ.desc, off) with | Tstruct name, Off_fld (fld, _) -> ( - match Tenv.lookup tenv name with - | Some { fields; statics } -> ( - try fldlist_assoc fld (fields @ statics) - with Not_found -> fail Typ.Fieldname.to_string fld - ) - | None -> - fail Typ.Fieldname.to_string fld - ) - | Tarray (typ', _, _), Off_index _ -> - typ' - | _, Off_index (Const (Cint i)) when IntLit.iszero i -> - typ - | _ -> - fail Sil.offset_to_string off + match Tenv.lookup tenv name with + | Some {fields; statics} -> ( + try fldlist_assoc fld (fields @ statics) + with Not_found -> fail Typ.Fieldname.to_string fld ) + | None + -> fail Typ.Fieldname.to_string fld ) + | Tarray (typ', _, _), Off_index _ + -> typ' + | _, Off_index Const Cint i when IntLit.iszero i + -> typ + | _ + -> fail Sil.offset_to_string off (** Given a node, returns a list of pvar of blocks that have been nullified in the block. *) let get_blocks_nullified node = - let null_blocks = List.concat_map ~f:(fun i -> match i with - | Sil.Nullify(pvar, _) when Sil.is_block_pvar pvar -> [pvar] - | _ -> []) (Procdesc.Node.get_instrs node) in + let null_blocks = + List.concat_map + ~f:(fun i -> + match i with Sil.Nullify (pvar, _) when Sil.is_block_pvar pvar -> [pvar] | _ -> []) + (Procdesc.Node.get_instrs node) + in null_blocks (** Given a proposition and an objc block checks whether by existentially quantifying @@ -58,13 +64,14 @@ let check_block_retain_cycle tenv caller_pname prop block_nullified = let block_pname = Typ.Procname.mangled_objc_block (Mangled.to_string mblock) in let block_captured = match AttributesTable.load_attributes ~cache:true block_pname with - | Some attributes -> - fst (List.unzip attributes.ProcAttributes.captured) - | None -> - [] in + | Some attributes + -> fst (List.unzip attributes.ProcAttributes.captured) + | None + -> [] + in let prop' = Prop.remove_seed_captured_vars_block tenv block_captured prop in let prop'' = Prop.prop_rename_fav_with_existentials tenv prop' in - let _ : Prop.normal Prop.t = Abs.abstract_junk ~original_prop: prop caller_pname tenv prop'' in + let _ : Prop.normal Prop.t = Abs.abstract_junk ~original_prop:prop caller_pname tenv prop'' in () (** Apply function [f] to the expression at position [offlist] in [strexp]. @@ -81,122 +88,139 @@ let check_block_retain_cycle tenv caller_pname prop block_nullified = and offlist, so that all the necessary extensions of strexp are done before this function. If the tool follows this protocol, it will never hit the assert false cases for field and array accesses. *) -let rec apply_offlist - pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist +let rec apply_offlist pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist (f: Exp.t option -> Exp.t) inst lookup_inst = let pname = Procdesc.get_proc_name pdesc in let pp_error () = - L.d_strln ".... Invalid Field ...."; - L.d_str "strexp : "; Sil.d_sexp strexp; L.d_ln (); - L.d_str "offlist : "; Sil.d_offset_list offlist; L.d_ln (); - L.d_str "type : "; Typ.d_full typ; L.d_ln (); - L.d_str "prop : "; Prop.d_prop p; L.d_ln (); L.d_ln () in - match offlist, strexp, typ.Typ.desc with - | [], Sil.Eexp (e, inst_curr), _ -> - let inst_is_uninitialized = function - | Sil.Ialloc -> - (* java allocation initializes with default values *) + L.d_strln ".... Invalid Field ...." ; + L.d_str "strexp : " ; + Sil.d_sexp strexp ; + L.d_ln () ; + L.d_str "offlist : " ; + Sil.d_offset_list offlist ; + L.d_ln () ; + L.d_str "type : " ; + Typ.d_full typ ; + L.d_ln () ; + L.d_str "prop : " ; + Prop.d_prop p ; + L.d_ln () ; + L.d_ln () + in + match (offlist, strexp, typ.Typ.desc) with + | [], Sil.Eexp (e, inst_curr), _ + -> let inst_is_uninitialized = function + | Sil.Ialloc + -> (* java allocation initializes with default values *) !Config.curr_language <> Config.Java - | Sil.Iinitial -> true - | _ -> false in + | Sil.Iinitial + -> true + | _ + -> false + in let is_hidden_field () = match State.get_instr () with - | Some (Sil.Load (_, Exp.Lfield (_, fieldname, _), _, _)) -> - Typ.Fieldname.is_hidden fieldname - | _ -> false in - let inst_new = match inst with - | Sil.Ilookup when inst_is_uninitialized inst_curr && not (is_hidden_field()) -> - (* we are in a lookup of an uninitialized value *) - lookup_inst := Some inst_curr; + | Some Sil.Load (_, Exp.Lfield (_, fieldname, _), _, _) + -> Typ.Fieldname.is_hidden fieldname + | _ + -> false + in + let inst_new = + match inst with + | Sil.Ilookup when inst_is_uninitialized inst_curr && not (is_hidden_field ()) + -> (* we are in a lookup of an uninitialized value *) + lookup_inst := Some inst_curr ; let alloc_attribute_opt = if Sil.equal_inst inst_curr Sil.Iinitial then None - else Attribute.get_undef tenv p root_lexp in + else Attribute.get_undef tenv p root_lexp + in let deref_str = Localise.deref_str_uninitialized alloc_attribute_opt in let err_desc = Errdesc.explain_memory_access tenv deref_str p (State.get_loc ()) in - let exn = (Exceptions.Uninitialized_value (err_desc, __POS__)) in - Reporting.log_warning_deprecated pname exn; - Sil.update_inst inst_curr inst - | Sil.Ilookup -> (* a lookup does not change an inst unless it is inst_initial *) - lookup_inst := Some inst_curr; + let exn = Exceptions.Uninitialized_value (err_desc, __POS__) in + Reporting.log_warning_deprecated pname exn ; Sil.update_inst inst_curr inst + | Sil.Ilookup + -> (* a lookup does not change an inst unless it is inst_initial *) + lookup_inst := Some inst_curr ; inst_curr - | _ -> Sil.update_inst inst_curr inst in + | _ + -> Sil.update_inst inst_curr inst + in let e' = f (Some e) in (e', Sil.Eexp (e', inst_new), typ, None) - | [], Sil.Estruct (fesl, inst'), _ -> - if not nullify_struct then (f None, Sil.Estruct (fesl, inst'), typ, None) - else if fp_root then (pp_error(); assert false) - else - begin - L.d_strln "WARNING: struct assignment treated as nondeterministic assignment"; - (f None, Prop.create_strexp_of_type tenv Prop.Fld_init typ None inst, typ, None) - end - | [], Sil.Earray _, _ -> - let offlist' = (Sil.Off_index Exp.zero):: offlist in - apply_offlist - pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist' f inst lookup_inst - | (Sil.Off_fld _) :: _, Sil.Earray _, _ -> - let offlist_new = Sil.Off_index(Exp.zero) :: offlist in - apply_offlist - pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist_new f inst lookup_inst + | [], Sil.Estruct (fesl, inst'), _ + -> if not nullify_struct then (f None, Sil.Estruct (fesl, inst'), typ, None) + else if fp_root then ( + pp_error () ; + assert false ) + else ( + L.d_strln "WARNING: struct assignment treated as nondeterministic assignment" ; + (f None, Prop.create_strexp_of_type tenv Prop.Fld_init typ None inst, typ, None) ) + | [], Sil.Earray _, _ + -> let offlist' = Sil.Off_index Exp.zero :: offlist in + apply_offlist pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist' f inst + lookup_inst + | (Sil.Off_fld _) :: _, Sil.Earray _, _ + -> let offlist_new = Sil.Off_index Exp.zero :: offlist in + apply_offlist pdesc tenv p fp_root nullify_struct (root_lexp, strexp, typ) offlist_new f inst + lookup_inst | (Sil.Off_fld (fld, fld_typ)) :: offlist', Sil.Estruct (fsel, inst'), Typ.Tstruct name -> ( - match Tenv.lookup tenv name with - | Some ({fields} as struct_typ) -> ( - let t' = unroll_type tenv typ (Sil.Off_fld (fld, fld_typ)) in - match List.find ~f:(fun fse -> Typ.Fieldname.equal fld (fst fse)) fsel with - | Some (_, se') -> - let res_e', res_se', res_t', res_pred_insts_op' = - apply_offlist - pdesc tenv p fp_root nullify_struct - (root_lexp, se', t') offlist' f inst lookup_inst in - let replace_fse fse = - if Typ.Fieldname.equal fld (fst fse) then (fld, res_se') else fse in - let res_se = Sil.Estruct (List.map ~f:replace_fse fsel, inst') in - let replace_fta (f, t, a) = - if Typ.Fieldname.equal fld f then (fld, res_t', a) else (f, t, a) in - let fields' = List.map ~f:replace_fta fields in - ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; - (res_e', res_se, typ, res_pred_insts_op') - | None -> - (* This case should not happen. The rearrangement should + match Tenv.lookup tenv name with + | Some ({fields} as struct_typ) + -> ( + let t' = unroll_type tenv typ (Sil.Off_fld (fld, fld_typ)) in + match List.find ~f:(fun fse -> Typ.Fieldname.equal fld (fst fse)) fsel with + | Some (_, se') + -> let res_e', res_se', res_t', res_pred_insts_op' = + apply_offlist pdesc tenv p fp_root nullify_struct (root_lexp, se', t') offlist' f + inst lookup_inst + in + let replace_fse fse = + if Typ.Fieldname.equal fld (fst fse) then (fld, res_se') else fse + in + let res_se = Sil.Estruct (List.map ~f:replace_fse fsel, inst') in + let replace_fta (f, t, a) = + if Typ.Fieldname.equal fld f then (fld, res_t', a) else (f, t, a) + in + let fields' = List.map ~f:replace_fta fields in + ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:fields' name) ; + (res_e', res_se, typ, res_pred_insts_op') + | None + -> (* This case should not happen. The rearrangement should have materialized all the accessed cells. *) - pp_error(); - assert false - ) - | None -> - pp_error(); - assert false - ) - | (Sil.Off_fld _) :: _, _, _ -> - pp_error(); + pp_error () ; + assert false ) + | None + -> pp_error () ; + assert false ) + | (Sil.Off_fld _) :: _, _, _ + -> pp_error () ; assert false - - | (Sil.Off_index idx) :: offlist', - Sil.Earray (len, esel, inst1), - Typ.Tarray (t', len', stride') -> ( + | (Sil.Off_index idx) :: offlist', Sil.Earray (len, esel, inst1), Typ.Tarray (t', len', stride') + -> ( let nidx = Prop.exp_normalize_prop tenv p idx in match List.find ~f:(fun ese -> Prover.check_equal tenv p nidx (fst ese)) esel with - | Some (idx_ese', se') -> - let res_e', res_se', res_t', res_pred_insts_op' = - apply_offlist - pdesc tenv p fp_root nullify_struct - (root_lexp, se', t') offlist' f inst lookup_inst in + | Some (idx_ese', se') + -> let res_e', res_se', res_t', res_pred_insts_op' = + apply_offlist pdesc tenv p fp_root nullify_struct (root_lexp, se', t') offlist' f inst + lookup_inst + in let replace_ese ese = - if Exp.equal idx_ese' (fst ese) - then (idx_ese', res_se') - else ese in + if Exp.equal idx_ese' (fst ese) then (idx_ese', res_se') else ese + in let res_se = Sil.Earray (len, List.map ~f:replace_ese esel, inst1) in let res_t = Typ.mk ~default:typ (Tarray (res_t', len', stride')) in (res_e', res_se, res_t, res_pred_insts_op') - | None -> - (* return a nondeterministic value if the index is not found after rearrangement *) - L.d_str "apply_offlist: index "; Sil.d_exp idx; - L.d_strln " not materialized -- returning nondeterministic value"; + | None + -> (* return a nondeterministic value if the index is not found after rearrangement *) + L.d_str "apply_offlist: index " ; + Sil.d_exp idx ; + L.d_strln " not materialized -- returning nondeterministic value" ; let res_e' = Exp.Var (Ident.create_fresh Ident.kprimed) in - (res_e', strexp, typ, None)) - | (Sil.Off_index _) :: _, _, _ -> - (* This case should not happen. The rearrangement should + (res_e', strexp, typ, None) ) + | (Sil.Off_index _) :: _, _, _ + -> (* This case should not happen. The rearrangement should have materialized all the accessed cells. *) - pp_error(); + pp_error () ; raise (Exceptions.Internal_error (Localise.verbatim_desc "Array out of bounds in Symexec")) (** Given [lexp |-> se: typ], if the location [offlist] exists in [se], @@ -212,19 +236,18 @@ let rec apply_offlist in rearrange.ml for the same se and offlist, so that all the necessary extensions of se are done before this function. *) let ptsto_lookup pdesc tenv p (lexp, se, sizeof) offlist id = - let f = - function Some exp -> exp | None -> Exp.Var id in - let fp_root = - match lexp with Exp.Var id -> Ident.is_footprint id | _ -> false in + let f = function Some exp -> exp | None -> Exp.Var id in + let fp_root = match lexp with Exp.Var id -> Ident.is_footprint id | _ -> false in let lookup_inst = ref None in let e', se', typ', pred_insts_op' = - apply_offlist - pdesc tenv p fp_root false (lexp, se, sizeof.Exp.typ) offlist f Sil.inst_lookup lookup_inst in - let lookup_uninitialized = (* true if we have looked up an uninitialized value *) - match !lookup_inst with - | Some (Sil.Iinitial | Sil.Ialloc | Sil.Ilookup) -> true - | _ -> false in - let ptsto' = Prop.mk_ptsto tenv lexp se' (Exp.Sizeof {sizeof with typ=typ'}) in + apply_offlist pdesc tenv p fp_root false (lexp, se, sizeof.Exp.typ) offlist f Sil.inst_lookup + lookup_inst + in + let lookup_uninitialized = + (* true if we have looked up an uninitialized value *) + match !lookup_inst with Some (Sil.Iinitial | Sil.Ialloc | Sil.Ilookup) -> true | _ -> false + in + let ptsto' = Prop.mk_ptsto tenv lexp se' (Exp.Sizeof {sizeof with typ= typ'}) in (e', ptsto', pred_insts_op', lookup_uninitialized) (** [ptsto_update p (lexp,se,typ) offlist exp] takes @@ -240,15 +263,14 @@ let ptsto_lookup pdesc tenv p (lexp, se, sizeof) offlist id = before this function. *) let ptsto_update pdesc tenv p (lexp, se, sizeof) offlist exp = let f _ = exp in - let fp_root = - match lexp with Exp.Var id -> Ident.is_footprint id | _ -> false in + let fp_root = match lexp with Exp.Var id -> Ident.is_footprint id | _ -> false in let lookup_inst = ref None in let _, se', typ', pred_insts_op' = let pos = State.get_path_pos () in - apply_offlist - pdesc tenv p fp_root true (lexp, se, sizeof.Exp.typ) offlist f - (State.get_inst_update pos) lookup_inst in - let ptsto' = Prop.mk_ptsto tenv lexp se' (Exp.Sizeof {sizeof with typ=typ'}) in + apply_offlist pdesc tenv p fp_root true (lexp, se, sizeof.Exp.typ) offlist f + (State.get_inst_update pos) lookup_inst + in + let ptsto' = Prop.mk_ptsto tenv lexp se' (Exp.Sizeof {sizeof with typ= typ'}) in (ptsto', pred_insts_op') let update_iter iter pi sigma = @@ -258,25 +280,25 @@ let update_iter iter pi sigma = (** Precondition: se should not include hpara_psto that could mean nonempty heaps. *) let rec execute_nullify_se = function - | Sil.Eexp _ -> - Sil.Eexp (Exp.zero, Sil.inst_nullify) - | Sil.Estruct (fsel, _) -> - let fsel' = List.map ~f:(fun (fld, se) -> (fld, execute_nullify_se se)) fsel in + | Sil.Eexp _ + -> Sil.Eexp (Exp.zero, Sil.inst_nullify) + | Sil.Estruct (fsel, _) + -> let fsel' = List.map ~f:(fun (fld, se) -> (fld, execute_nullify_se se)) fsel in Sil.Estruct (fsel', Sil.inst_nullify) - | Sil.Earray (len, esel, _) -> - let esel' = List.map ~f:(fun (idx, se) -> (idx, execute_nullify_se se)) esel in + | Sil.Earray (len, esel, _) + -> let esel' = List.map ~f:(fun (idx, se) -> (idx, execute_nullify_se se)) esel in Sil.Earray (len, esel', Sil.inst_nullify) (** Do pruning for conditional [if (e1 != e2) ] if [positive] is true and [(if (e1 == e2)] if [positive] is false *) let prune_ne tenv ~positive e1 e2 prop = let is_inconsistent = - if positive then Prover.check_equal tenv prop e1 e2 - else Prover.check_disequal tenv prop e1 e2 in + if positive then Prover.check_equal tenv prop e1 e2 else Prover.check_disequal tenv prop e1 e2 + in if is_inconsistent then Propset.empty else let conjoin = if positive then Prop.conjoin_neq else Prop.conjoin_eq in - let new_prop = conjoin tenv ~footprint: (!Config.footprint) e1 e2 prop in + let new_prop = conjoin tenv ~footprint:!Config.footprint e1 e2 prop in if Prover.check_inconsistency tenv new_prop then Propset.empty else Propset.singleton tenv new_prop @@ -286,8 +308,7 @@ let prune_ne tenv ~positive e1 e2 prop = *) let prune_ineq tenv ~is_strict ~positive prop e1 e2 = if Exp.equal e1 e2 then - if (positive && not is_strict) || (not positive && is_strict) then - Propset.singleton tenv prop + if positive && not is_strict || not positive && is_strict then Propset.singleton tenv prop else Propset.empty else (* build the pruning condition and its negation, as explained in @@ -299,9 +320,9 @@ let prune_ineq tenv ~is_strict ~positive prop e1 e2 = let dual_cmp = if is_strict then Binop.Le else Binop.Lt in let not_e1_cmp_e2 = Exp.BinOp (dual_cmp, e2, e1) in (* take polarity into account *) - let (prune_cond, not_prune_cond) = - if positive then (e1_cmp_e2, not_e1_cmp_e2) - else (not_e1_cmp_e2, e1_cmp_e2) in + let prune_cond, not_prune_cond = + if positive then (e1_cmp_e2, not_e1_cmp_e2) else (not_e1_cmp_e2, e1_cmp_e2) + in let is_inconsistent = Prover.check_atom tenv prop (Prop.mk_inequality tenv not_prune_cond) in if is_inconsistent then Propset.empty else @@ -311,60 +332,54 @@ let prune_ineq tenv ~is_strict ~positive prop e1 e2 = let rec prune tenv ~positive condition prop = match condition with - | Exp.Var _ | Exp.Lvar _ -> - prune_ne tenv ~positive condition Exp.zero prop - | Exp.Const (Const.Cint i) when IntLit.iszero i -> - if positive then Propset.empty else Propset.singleton tenv prop - | Exp.Const (Const.Cint _ | Const.Cstr _ | Const.Cclass _) | Exp.Sizeof _ -> - if positive then Propset.singleton tenv prop else Propset.empty - | Exp.Const _ -> - assert false - | Exp.Cast (_, condition') -> - prune tenv ~positive condition' prop - | Exp.UnOp (Unop.LNot, condition', _) -> - prune tenv ~positive:(not positive) condition' prop - | Exp.UnOp _ -> - assert false - | Exp.BinOp (Binop.Eq, e, Exp.Const (Const.Cint i)) - when IntLit.iszero i && not (IntLit.isnull i) -> - prune tenv ~positive:(not positive) e prop - | Exp.BinOp (Binop.Eq, Exp.Const (Const.Cint i), e) - when IntLit.iszero i && not (IntLit.isnull i) -> - prune tenv ~positive:(not positive) e prop - | Exp.BinOp (Binop.Eq, e1, e2) -> - prune_ne tenv ~positive:(not positive) e1 e2 prop - | Exp.BinOp (Binop.Ne, e, Exp.Const (Const.Cint i)) - when IntLit.iszero i && not (IntLit.isnull i) -> - prune tenv ~positive e prop - | Exp.BinOp (Binop.Ne, Exp.Const (Const.Cint i), e) - when IntLit.iszero i && not (IntLit.isnull i) -> - prune tenv ~positive e prop - | Exp.BinOp (Binop.Ne, e1, e2) -> - prune_ne tenv ~positive e1 e2 prop - | Exp.BinOp (Binop.Ge, e2, e1) | Exp.BinOp (Binop.Le, e1, e2) -> - prune_ineq tenv ~is_strict:false ~positive prop e1 e2 - | Exp.BinOp (Binop.Gt, e2, e1) | Exp.BinOp (Binop.Lt, e1, e2) -> - prune_ineq tenv ~is_strict:true ~positive prop e1 e2 - | Exp.BinOp (Binop.LAnd, condition1, condition2) -> - let pruner = if positive then prune_inter tenv else prune_union tenv in + | Exp.Var _ | Exp.Lvar _ + -> prune_ne tenv ~positive condition Exp.zero prop + | Exp.Const Const.Cint i when IntLit.iszero i + -> if positive then Propset.empty else Propset.singleton tenv prop + | Exp.Const (Const.Cint _ | Const.Cstr _ | Const.Cclass _) | Exp.Sizeof _ + -> if positive then Propset.singleton tenv prop else Propset.empty + | Exp.Const _ + -> assert false + | Exp.Cast (_, condition') + -> prune tenv ~positive condition' prop + | Exp.UnOp (Unop.LNot, condition', _) + -> prune tenv ~positive:(not positive) condition' prop + | Exp.UnOp _ + -> assert false + | Exp.BinOp (Binop.Eq, e, Exp.Const Const.Cint i) when IntLit.iszero i && not (IntLit.isnull i) + -> prune tenv ~positive:(not positive) e prop + | Exp.BinOp (Binop.Eq, Exp.Const Const.Cint i, e) when IntLit.iszero i && not (IntLit.isnull i) + -> prune tenv ~positive:(not positive) e prop + | Exp.BinOp (Binop.Eq, e1, e2) + -> prune_ne tenv ~positive:(not positive) e1 e2 prop + | Exp.BinOp (Binop.Ne, e, Exp.Const Const.Cint i) when IntLit.iszero i && not (IntLit.isnull i) + -> prune tenv ~positive e prop + | Exp.BinOp (Binop.Ne, Exp.Const Const.Cint i, e) when IntLit.iszero i && not (IntLit.isnull i) + -> prune tenv ~positive e prop + | Exp.BinOp (Binop.Ne, e1, e2) + -> prune_ne tenv ~positive e1 e2 prop + | Exp.BinOp (Binop.Ge, e2, e1) | Exp.BinOp (Binop.Le, e1, e2) + -> prune_ineq tenv ~is_strict:false ~positive prop e1 e2 + | Exp.BinOp (Binop.Gt, e2, e1) | Exp.BinOp (Binop.Lt, e1, e2) + -> prune_ineq tenv ~is_strict:true ~positive prop e1 e2 + | Exp.BinOp (Binop.LAnd, condition1, condition2) + -> let pruner = if positive then prune_inter tenv else prune_union tenv in pruner ~positive condition1 condition2 prop - | Exp.BinOp (Binop.LOr, condition1, condition2) -> - let pruner = if positive then prune_union tenv else prune_inter tenv in + | Exp.BinOp (Binop.LOr, condition1, condition2) + -> let pruner = if positive then prune_union tenv else prune_inter tenv in pruner ~positive condition1 condition2 prop - | Exp.BinOp _ | Exp.Lfield _ | Exp.Lindex _ -> - prune_ne tenv ~positive condition Exp.zero prop - | Exp.Exn _ -> - assert false - | Exp.Closure _ -> - assert false + | Exp.BinOp _ | Exp.Lfield _ | Exp.Lindex _ + -> prune_ne tenv ~positive condition Exp.zero prop + | Exp.Exn _ + -> assert false + | Exp.Closure _ + -> assert false and prune_inter tenv ~positive condition1 condition2 prop = let res = ref Propset.empty in let pset1 = prune tenv ~positive condition1 prop in - let do_p p = - res := Propset.union (prune tenv ~positive condition2 p) !res in - Propset.iter do_p pset1; - !res + let do_p p = res := Propset.union (prune tenv ~positive condition2 p) !res in + Propset.iter do_p pset1 ; !res and prune_union tenv ~positive condition1 condition2 prop = let pset1 = prune tenv ~positive condition1 prop in @@ -373,13 +388,14 @@ and prune_union tenv ~positive condition1 condition2 prop = let dangerous_functions = let dangerous_list = ["gets"] in - ref ((List.map ~f:Typ.Procname.from_string_c_fun) dangerous_list) + ref (List.map ~f:Typ.Procname.from_string_c_fun dangerous_list) let check_inherently_dangerous_function caller_pname callee_pname = if List.exists ~f:(Typ.Procname.equal callee_pname) !dangerous_functions then let exn = Exceptions.Inherently_dangerous_function - (Localise.desc_inherently_dangerous_function callee_pname) in + (Localise.desc_inherently_dangerous_function callee_pname) + in Reporting.log_warning_deprecated caller_pname exn let call_should_be_skipped callee_summary = @@ -388,261 +404,294 @@ let call_should_be_skipped callee_summary = (* skip abstract methods *) || callee_summary.Specs.attributes.ProcAttributes.is_abstract (* treat calls with no specs as skip functions in angelic mode *) - || (Config.angelic_execution && List.is_empty (Specs.get_specs_from_payload callee_summary)) + || Config.angelic_execution && List.is_empty (Specs.get_specs_from_payload callee_summary) (** In case of constant string dereference, return the result immediately *) let check_constant_string_dereference lexp = let string_lookup s n = - let c = try Char.to_int (String.get s (IntLit.to_int n)) with Invalid_argument _ -> 0 in - Exp.int (IntLit.of_int c) in + let c = + try Char.to_int s.[IntLit.to_int n] + with Invalid_argument _ -> 0 + in + Exp.int (IntLit.of_int c) + in match lexp with - | Exp.BinOp(Binop.PlusPI, Exp.Const (Const.Cstr s), e) - | Exp.Lindex (Exp.Const (Const.Cstr s), e) -> - let value = match e with - | Exp.Const (Const.Cint n) - when IntLit.geq n IntLit.zero && - IntLit.leq n (IntLit.of_int (String.length s)) -> - string_lookup s n - | _ -> Exp.get_undefined false in + | Exp.BinOp (Binop.PlusPI, Exp.Const Const.Cstr s, e) | Exp.Lindex (Exp.Const Const.Cstr s, e) + -> let value = + match e with + | Exp.Const Const.Cint n + when IntLit.geq n IntLit.zero && IntLit.leq n (IntLit.of_int (String.length s)) + -> string_lookup s n + | _ + -> Exp.get_undefined false + in Some value - | Exp.Const (Const.Cstr s) -> - Some (string_lookup s IntLit.zero) - | _ -> None + | Exp.Const Const.Cstr s + -> Some (string_lookup s IntLit.zero) + | _ + -> None (** Normalize an expression and check for arithmetic problems *) let check_arith_norm_exp tenv pname exp prop = match Attribute.find_arithmetic_problem tenv (State.get_path_pos ()) prop exp with - | Some (Attribute.Div0 div), prop' -> - let desc = Errdesc.explain_divide_by_zero tenv div (State.get_node ()) (State.get_loc ()) in + | Some Attribute.Div0 div, prop' + -> let desc = Errdesc.explain_divide_by_zero tenv div (State.get_node ()) (State.get_loc ()) in let exn = Exceptions.Divide_by_zero (desc, __POS__) in - Reporting.log_warning_deprecated pname exn; - Prop.exp_normalize_prop tenv prop exp, prop' - | Some (Attribute.UminusUnsigned (e, typ)), prop' -> - let desc = - Errdesc.explain_unary_minus_applied_to_unsigned_expression tenv - e typ (State.get_node ()) (State.get_loc ()) in + Reporting.log_warning_deprecated pname exn ; (Prop.exp_normalize_prop tenv prop exp, prop') + | Some Attribute.UminusUnsigned (e, typ), prop' + -> let desc = + Errdesc.explain_unary_minus_applied_to_unsigned_expression tenv e typ (State.get_node ()) + (State.get_loc ()) + in let exn = Exceptions.Unary_minus_applied_to_unsigned_expression (desc, __POS__) in - Reporting.log_warning_deprecated pname exn; - Prop.exp_normalize_prop tenv prop exp, prop' - | None, prop' -> Prop.exp_normalize_prop tenv prop exp, prop' + Reporting.log_warning_deprecated pname exn ; (Prop.exp_normalize_prop tenv prop exp, prop') + | None, prop' + -> (Prop.exp_normalize_prop tenv prop exp, prop') (** Check if [cond] is testing for NULL a pointer already dereferenced *) let check_already_dereferenced tenv pname cond prop = let find_hpred lhs = - List.find ~f:(function - | Sil.Hpointsto (e, _, _) -> Exp.equal e lhs - | _ -> false) prop.Prop.sigma in + List.find + ~f:(function Sil.Hpointsto (e, _, _) -> Exp.equal e lhs | _ -> false) + prop.Prop.sigma + in let rec is_check_zero = function - | Exp.Var id -> - Some id - | Exp.UnOp(Unop.LNot, e, _) -> - is_check_zero e + | Exp.Var id + -> Some id + | Exp.UnOp (Unop.LNot, e, _) + -> is_check_zero e | Exp.BinOp ((Binop.Eq | Binop.Ne), Exp.Const Const.Cint i, Exp.Var id) - | Exp.BinOp ((Binop.Eq | Binop.Ne), Exp.Var id, Exp.Const Const.Cint i) when IntLit.iszero i -> - Some id - | _ -> None in - let dereferenced_line = match is_check_zero cond with - | Some id -> - (match find_hpred (Prop.exp_normalize_prop tenv prop (Exp.Var id)) with - | Some (Sil.Hpointsto (_, se, _)) -> - (match Tabulation.find_dereference_without_null_check_in_sexp se with - | Some n -> Some (id, n) - | None -> None) - | _ -> None) - | None -> - None in + | Exp.BinOp ((Binop.Eq | Binop.Ne), Exp.Var id, Exp.Const Const.Cint i) + when IntLit.iszero i + -> Some id + | _ + -> None + in + let dereferenced_line = + match is_check_zero cond with + | Some id -> ( + match find_hpred (Prop.exp_normalize_prop tenv prop (Exp.Var id)) with + | Some Sil.Hpointsto (_, se, _) -> ( + match Tabulation.find_dereference_without_null_check_in_sexp se with + | Some n + -> Some (id, n) + | None + -> None ) + | _ + -> None ) + | None + -> None + in match dereferenced_line with - | Some (id, (n, _)) -> - let desc = - Errdesc.explain_null_test_after_dereference tenv - (Exp.Var id) (State.get_node ()) n (State.get_loc ()) in - let exn = - (Exceptions.Null_test_after_dereference (desc, __POS__)) in + | Some (id, (n, _)) + -> let desc = + Errdesc.explain_null_test_after_dereference tenv (Exp.Var id) (State.get_node ()) n + (State.get_loc ()) + in + let exn = Exceptions.Null_test_after_dereference (desc, __POS__) in Reporting.log_warning_deprecated pname exn - | None -> () + | None + -> () (** Check whether symbolic execution de-allocated a stack variable or a constant string, raising an exception in that case *) let check_deallocate_static_memory prop_after = let check_deallocated_attribute = function - | Sil.Apred (Aresource ({ ra_kind = Rrelease } as ra), [Lvar pv]) - when Pvar.is_local pv || Pvar.is_global pv -> - let freed_desc = Errdesc.explain_deallocate_stack_var pv ra in + | Sil.Apred (Aresource ({ra_kind= Rrelease} as ra), [(Lvar pv)]) + when Pvar.is_local pv || Pvar.is_global pv + -> let freed_desc = Errdesc.explain_deallocate_stack_var pv ra in raise (Exceptions.Deallocate_stack_variable freed_desc) - | Sil.Apred (Aresource ({ ra_kind = Rrelease } as ra), [Const (Cstr s)]) -> - let freed_desc = Errdesc.explain_deallocate_constant_string s ra in + | Sil.Apred (Aresource ({ra_kind= Rrelease} as ra), [(Const Cstr s)]) + -> let freed_desc = Errdesc.explain_deallocate_constant_string s ra in raise (Exceptions.Deallocate_static_memory freed_desc) - | _ -> () in + | _ + -> () + in let exp_att_list = Attribute.get_all prop_after in - List.iter ~f:check_deallocated_attribute exp_att_list; - prop_after + List.iter ~f:check_deallocated_attribute exp_att_list ; prop_after let method_exists right_proc_name methods = if Config.curr_language_is Config.Java then List.exists ~f:(fun meth_name -> Typ.Procname.equal right_proc_name meth_name) methods - else (* ObjC/C++ case : The attribute map will only exist when we have code for the method or + else + (* ObjC/C++ case : The attribute map will only exist when we have code for the method or the method has been called directly somewhere. It can still be that this is not the case but we have a model for the method. *) match AttributesTable.load_attributes ~cache:true right_proc_name with - | Some attrs -> attrs.ProcAttributes.is_defined - | None -> Specs.summary_exists_in_models right_proc_name + | Some attrs + -> attrs.ProcAttributes.is_defined + | None + -> Specs.summary_exists_in_models right_proc_name let resolve_method tenv class_name proc_name = let found_class = let visited = ref Typ.Name.Set.empty in let rec resolve (class_name: Typ.Name.t) = - visited := Typ.Name.Set.add class_name !visited; - let right_proc_name = - Typ.Procname.replace_class proc_name class_name in + visited := Typ.Name.Set.add class_name !visited ; + let right_proc_name = Typ.Procname.replace_class proc_name class_name in match Tenv.lookup tenv class_name with - | Some { methods; supers } when Typ.Name.is_class class_name -> - if method_exists right_proc_name methods then - Some right_proc_name + | Some {methods; supers} when Typ.Name.is_class class_name + -> ( + if method_exists right_proc_name methods then Some right_proc_name else - (match supers with - | super_classname:: _ -> - if not (Typ.Name.Set.mem super_classname !visited) - then resolve super_classname - else None - | _ -> None) - | _ -> None in - resolve class_name in + match supers with + | super_classname :: _ + -> if not (Typ.Name.Set.mem super_classname !visited) then resolve super_classname + else None + | _ + -> None ) + | _ + -> None + in + resolve class_name + in match found_class with - | None -> - Logging.d_strln - ("Couldn't find method in the hierarchy of type "^(Typ.Name.name class_name)); - proc_name - | Some proc_name -> + | None + -> Logging.d_strln ("Couldn't find method in the hierarchy of type " ^ Typ.Name.name class_name) ; proc_name + | Some proc_name + -> proc_name let resolve_typename prop receiver_exp = let typexp_opt = let rec loop = function - | [] -> None - | Sil.Hpointsto(e, _, typexp) :: _ when Exp.equal e receiver_exp -> Some typexp - | _ :: hpreds -> loop hpreds in - loop prop.Prop.sigma in - match typexp_opt with - | Some (Exp.Sizeof {typ={desc=Tstruct name}}) -> Some name - | _ -> None + | [] + -> None + | (Sil.Hpointsto (e, _, typexp)) :: _ when Exp.equal e receiver_exp + -> Some typexp + | _ :: hpreds + -> loop hpreds + in + loop prop.Prop.sigma + in + match typexp_opt with Some Exp.Sizeof {typ= {desc= Tstruct name}} -> Some name | _ -> None (** If the dynamic type of the receiver actual T_actual is a subtype of the reciever type T_formal in the signature of [pname], resolve [pname] to T_actual.[pname]. *) let resolve_virtual_pname tenv prop actuals callee_pname call_flags : Typ.Procname.t list = - let resolve receiver_exp pname prop = match resolve_typename prop receiver_exp with - | Some class_name -> resolve_method tenv class_name pname - | None -> pname in + let resolve receiver_exp pname prop = + match resolve_typename prop receiver_exp with + | Some class_name + -> resolve_method tenv class_name pname + | None + -> pname + in let get_receiver_typ pname fallback_typ = match pname with - | Typ.Procname.Java pname_java -> - begin - let name = Typ.Procname.java_get_class_type_name pname_java in - match Tenv.lookup tenv name with - | Some _ -> Typ.mk (Typ.Tptr (Typ.mk (Tstruct name), Pk_pointer)) - | None -> fallback_typ - end - | _ -> - fallback_typ in + | Typ.Procname.Java pname_java + -> ( + let name = Typ.Procname.java_get_class_type_name pname_java in + match Tenv.lookup tenv name with + | Some _ + -> Typ.mk (Typ.Tptr (Typ.mk (Tstruct name), Pk_pointer)) + | None + -> fallback_typ ) + | _ + -> fallback_typ + in let receiver_types_equal pname actual_receiver_typ = (* the type of the receiver according to the function signature *) let formal_receiver_typ = get_receiver_typ pname actual_receiver_typ in - Typ.equal formal_receiver_typ actual_receiver_typ in + Typ.equal formal_receiver_typ actual_receiver_typ + in let do_resolve called_pname receiver_exp actual_receiver_typ = - if receiver_types_equal called_pname actual_receiver_typ - then resolve receiver_exp called_pname prop - else called_pname in + if receiver_types_equal called_pname actual_receiver_typ then + resolve receiver_exp called_pname prop + else called_pname + in match actuals with - | _ when not (call_flags.CallFlags.cf_virtual || call_flags.CallFlags.cf_interface) -> - (* if this is not a virtual or interface call, there's no need for resolution *) + | _ when not (call_flags.CallFlags.cf_virtual || call_flags.CallFlags.cf_interface) + -> (* if this is not a virtual or interface call, there's no need for resolution *) [callee_pname] - | (receiver_exp, actual_receiver_typ) :: _ -> + | (receiver_exp, actual_receiver_typ) :: _ + -> ( if !Config.curr_language <> Config.Java then (* default mode for Obj-C/C++/Java virtual calls: resolution only *) [do_resolve callee_pname receiver_exp actual_receiver_typ] else if Config.dynamic_dispatch = `Sound then let targets = - if call_flags.CallFlags.cf_virtual - then + if call_flags.CallFlags.cf_virtual then (* virtual call--either [called_pname] or an override in some subtype may be called *) callee_pname :: call_flags.CallFlags.cf_targets else (* interface call--[called_pname] has no implementation), we don't want to consider *) - call_flags.CallFlags.cf_targets (* interface call, don't want to consider *) in + call_flags.CallFlags.cf_targets + (* interface call, don't want to consider *) + in (* return true if (receiver typ of [target_pname]) <: [actual_receiver_typ] *) let may_dispatch_to target_pname = let target_receiver_typ = get_receiver_typ target_pname actual_receiver_typ in - Prover.Subtyping_check.check_subtype tenv target_receiver_typ actual_receiver_typ in + Prover.Subtyping_check.check_subtype tenv target_receiver_typ actual_receiver_typ + in let resolved_pname = do_resolve callee_pname receiver_exp actual_receiver_typ in let feasible_targets = List.filter ~f:may_dispatch_to targets in (* make sure [resolved_pname] is not a duplicate *) - if List.mem ~equal:Typ.Procname.equal feasible_targets resolved_pname - then feasible_targets + if List.mem ~equal:Typ.Procname.equal feasible_targets resolved_pname then feasible_targets else resolved_pname :: feasible_targets else - begin - let resolved_target = do_resolve callee_pname receiver_exp actual_receiver_typ in - match call_flags.CallFlags.cf_targets with - | target :: _ when call_flags.CallFlags.cf_interface && - receiver_types_equal callee_pname actual_receiver_typ && - Typ.Procname.equal resolved_target callee_pname -> - (* "production mode" of dynamic dispatch for Java: unsound, but faster. the handling + let resolved_target = do_resolve callee_pname receiver_exp actual_receiver_typ in + match call_flags.CallFlags.cf_targets with + | target :: _ + when call_flags.CallFlags.cf_interface + && receiver_types_equal callee_pname actual_receiver_typ + && Typ.Procname.equal resolved_target callee_pname + -> (* "production mode" of dynamic dispatch for Java: unsound, but faster. the handling is restricted to interfaces: if we can't resolve an interface call, we pick the first implementation of the interface and call it *) - [target] - | _ -> - (* default mode for Java virtual calls: resolution only *) - [resolved_target] - end - | _ -> failwith "A virtual call must have a receiver" - + [target] + | _ + -> (* default mode for Java virtual calls: resolution only *) + [resolved_target] ) + | _ + -> failwith "A virtual call must have a receiver" (** Resolve the name of the procedure to call based on the type of the arguments *) let resolve_java_pname tenv prop args pname_java call_flags : Typ.Procname.java = let resolve_from_args resolved_pname_java args = let parameters = Typ.Procname.java_get_parameters resolved_pname_java in - if List.length args <> List.length parameters then - resolved_pname_java + if List.length args <> List.length parameters then resolved_pname_java else let resolved_params = List.fold2_exn ~f:(fun accu (arg_exp, _) name -> - match resolve_typename prop arg_exp with - | Some class_name -> - (Typ.Procname.split_classname (Typ.Name.name class_name)) :: accu - | None -> name :: accu) - ~init:[] args (Typ.Procname.java_get_parameters resolved_pname_java) |> List.rev in - Typ.Procname.java_replace_parameters resolved_pname_java resolved_params in + match resolve_typename prop arg_exp with + | Some class_name + -> Typ.Procname.split_classname (Typ.Name.name class_name) :: accu + | None + -> name :: accu) + ~init:[] args (Typ.Procname.java_get_parameters resolved_pname_java) + |> List.rev + in + Typ.Procname.java_replace_parameters resolved_pname_java resolved_params + in let resolved_pname_java, other_args = match args with - | [] -> - pname_java, [] - | (first_arg, _) :: other_args when call_flags.CallFlags.cf_virtual -> - let resolved = - begin - match resolve_typename prop first_arg with - | Some class_name -> - begin - match resolve_method tenv class_name (Typ.Procname.Java pname_java) with - | Typ.Procname.Java resolved_pname_java -> - resolved_pname_java - | _ -> - pname_java - end - | None -> - pname_java - end in - resolved, other_args - | _ :: other_args when Typ.Procname.is_constructor (Typ.Procname.Java pname_java) -> - pname_java, other_args - | args -> - pname_java, args in + | [] + -> (pname_java, []) + | (first_arg, _) :: other_args when call_flags.CallFlags.cf_virtual + -> let resolved = + match resolve_typename prop first_arg with + | Some class_name -> ( + match resolve_method tenv class_name (Typ.Procname.Java pname_java) with + | Typ.Procname.Java resolved_pname_java + -> resolved_pname_java + | _ + -> pname_java ) + | None + -> pname_java + in + (resolved, other_args) + | _ :: other_args when Typ.Procname.is_constructor (Typ.Procname.Java pname_java) + -> (pname_java, other_args) + | args + -> (pname_java, args) + in resolve_from_args resolved_pname_java other_args - (** Resolve the procedure name and run the analysis of the resolved procedure if not already analyzed *) -let resolve_and_analyze - tenv caller_pdesc prop args callee_proc_name call_flags : Typ.Procname.t * Specs.summary option = +let resolve_and_analyze tenv caller_pdesc prop args callee_proc_name call_flags + : Typ.Procname.t * Specs.summary option = (* TODO (#15748878): Fix conflict with method overloading by encoding in the procedure name whether the method is defined or generated by the specialization *) let analyze_ondemand resolved_pname : Specs.summary option = @@ -651,60 +700,66 @@ let resolve_and_analyze else (* Create the type sprecialized procedure description and analyze it directly *) let analyze specialized_pdesc = - Ondemand.analyze_proc_desc ~propagate_exceptions:true caller_pdesc specialized_pdesc in + Ondemand.analyze_proc_desc ~propagate_exceptions:true caller_pdesc specialized_pdesc + in let resolved_proc_desc_option = match Ondemand.get_proc_desc resolved_pname with - | Some resolved_proc_desc -> - Some resolved_proc_desc - | None -> - (Option.map - ~f:(fun callee_proc_desc -> - Cfg.specialize_types callee_proc_desc resolved_pname args) - (Ondemand.get_proc_desc callee_proc_name)) in - Option.bind resolved_proc_desc_option ~f:analyze in - let resolved_pname = match callee_proc_name with - | Typ.Procname.Java callee_proc_name_java -> - Typ.Procname.Java - (resolve_java_pname tenv prop args callee_proc_name_java call_flags) - | _ -> - callee_proc_name in - resolved_pname, analyze_ondemand resolved_pname - + | Some resolved_proc_desc + -> Some resolved_proc_desc + | None + -> Option.map + ~f:(fun callee_proc_desc -> + Cfg.specialize_types callee_proc_desc resolved_pname args) + (Ondemand.get_proc_desc callee_proc_name) + in + Option.bind resolved_proc_desc_option ~f:analyze + in + let resolved_pname = + match callee_proc_name with + | Typ.Procname.Java callee_proc_name_java + -> Typ.Procname.Java (resolve_java_pname tenv prop args callee_proc_name_java call_flags) + | _ + -> callee_proc_name + in + (resolved_pname, analyze_ondemand resolved_pname) (** recognize calls to the constructor java.net.URL and splits the argument string to be only the protocol. *) let call_constructor_url_update_args pname actual_params = let url_pname = Typ.Procname.Java - (Typ.Procname.java - (Typ.Name.Java.from_string "java.net.URL") None "" - [(Some "java.lang"), "String"] Typ.Procname.Non_Static) in - if (Typ.Procname.equal url_pname pname) then - (match actual_params with - | [this; (Exp.Const (Const.Cstr s), atype)] -> - let parts = Str.split (Str.regexp_string "://") s in - (match parts with - | frst:: _ -> - if String.equal frst "http" || - String.equal frst "ftp" || - String.equal frst "https" || - String.equal frst "mailto" || - String.equal frst "jar" - then - [this; (Exp.Const (Const.Cstr frst), atype)] - else actual_params - | _ -> actual_params) - | [this; _, atype] -> [this; (Exp.Const (Const.Cstr "file"), atype)] - | _ -> actual_params) + (Typ.Procname.java (Typ.Name.Java.from_string "java.net.URL") None "" + [(Some "java.lang", "String")] Typ.Procname.Non_Static) + in + if Typ.Procname.equal url_pname pname then + match actual_params with + | [this; (Exp.Const Const.Cstr s, atype)] + -> ( + let parts = Str.split (Str.regexp_string "://") s in + match parts with + | frst :: _ + -> if String.equal frst "http" || String.equal frst "ftp" || String.equal frst "https" + || String.equal frst "mailto" || String.equal frst "jar" + then [this; (Exp.Const (Const.Cstr frst), atype)] + else actual_params + | _ + -> actual_params ) + | [this; (_, atype)] + -> [this; (Exp.Const (Const.Cstr "file"), atype)] + | _ + -> actual_params else actual_params let receiver_self receiver prop = - List.exists ~f:(fun hpred -> + List.exists + ~f:(fun hpred -> match hpred with - | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (e, _), _) -> - Exp.equal e receiver && Pvar.is_seed pv && - Mangled.equal (Pvar.get_name pv) (Mangled.from_string "self") - | _ -> false) (prop.Prop.sigma) + | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (e, _), _) + -> Exp.equal e receiver && Pvar.is_seed pv + && Mangled.equal (Pvar.get_name pv) (Mangled.from_string "self") + | _ + -> false) + prop.Prop.sigma (* When current ObjC method is an initializer and the method call is also an initializer, and the receiver is self, i.e. the call is [super init], then we want to assume that it @@ -712,17 +767,18 @@ let receiver_self receiver prop = a check for null, which is considered good practice. *) let force_objc_init_return_nil pdesc callee_pname tenv ret_id pre path receiver = let current_pname = Procdesc.get_proc_name pdesc in - if Typ.Procname.is_constructor callee_pname && - receiver_self receiver pre && - !Config.footprint && Typ.Procname.is_constructor current_pname then + if Typ.Procname.is_constructor callee_pname && receiver_self receiver pre && !Config.footprint + && Typ.Procname.is_constructor current_pname + then match ret_id with - | Some (ret_id, _) -> - let propset = prune_ne tenv ~positive:false (Exp.Var ret_id) Exp.zero pre in + | Some (ret_id, _) + -> let propset = prune_ne tenv ~positive:false (Exp.Var ret_id) Exp.zero pre in if Propset.is_empty propset then [] else let prop = List.hd_exn (Propset.to_proplist propset) in [(prop, path)] - | _ -> [] + | _ + -> [] else [] (* This method is used to handle the special semantics of ObjC instance method calls. *) @@ -731,40 +787,45 @@ let force_objc_init_return_nil pdesc callee_pname tenv ret_id pre path receiver (* 2. We don't know, but obj could be null, we return both options, *) (* (obj = null, res = null), (obj != null, res = [obj foo]) *) (* We want the same behavior even when we are going to skip the function. *) -let handle_objc_instance_method_call_or_skip pdesc tenv actual_pars path callee_pname pre - ret_id res = +let handle_objc_instance_method_call_or_skip pdesc tenv actual_pars path callee_pname pre ret_id + res = let path_description = - "Message " ^ - (Typ.Procname.to_simplified_string callee_pname) ^ - " with receiver nil returns nil." in - let receiver = (match actual_pars with - | (e, _):: _ -> e - | _ -> raise - (Exceptions.Internal_error - (Localise.verbatim_desc - "In Objective-C instance method call there should be a receiver."))) in - let is_receiver_null = + "Message " ^ Typ.Procname.to_simplified_string callee_pname ^ " with receiver nil returns nil." + in + let receiver = match actual_pars with | (e, _) :: _ - when Exp.equal e Exp.zero || - Option.is_some (Attribute.get_objc_null tenv pre e) -> true - | _ -> false in + -> e + | _ + -> raise + (Exceptions.Internal_error + (Localise.verbatim_desc + "In Objective-C instance method call there should be a receiver.")) + in + let is_receiver_null = + match actual_pars with + | (e, _) :: _ when Exp.equal e Exp.zero || Option.is_some (Attribute.get_objc_null tenv pre e) + -> true + | _ + -> false + in let add_objc_null_attribute_or_nullify_result prop = match ret_id with | Some (ret_id, _) -> ( - match Attribute.find_equal_formal_path tenv receiver prop with - | Some vfs -> - Attribute.add_or_replace tenv prop (Apred (Aobjc_null, [Exp.Var ret_id; vfs])) - | None -> Prop.conjoin_eq tenv (Exp.Var ret_id) Exp.zero prop - ) - | _ -> prop in + match Attribute.find_equal_formal_path tenv receiver prop with + | Some vfs + -> Attribute.add_or_replace tenv prop (Apred (Aobjc_null, [Exp.Var ret_id; vfs])) + | None + -> Prop.conjoin_eq tenv (Exp.Var ret_id) Exp.zero prop ) + | _ + -> prop + in if is_receiver_null then (* objective-c instance method with a null receiver just return objc_null(res). *) let path = Paths.Path.add_description path path_description in L.d_strln - ("Object-C method " ^ - Typ.Procname.to_string callee_pname ^ - " called with nil receiver. Returning 0/nil"); + ( "Object-C method " ^ Typ.Procname.to_string callee_pname + ^ " called with nil receiver. Returning 0/nil" ) ; (* We wish to nullify the result. However, in some cases, we want to add the attribute OBJC_NULL to it so that we *) (* can keep track of how this object became null, @@ -772,20 +833,25 @@ let handle_objc_instance_method_call_or_skip pdesc tenv actual_pars path callee_ [(add_objc_null_attribute_or_nullify_result pre, path)] else match force_objc_init_return_nil pdesc callee_pname tenv ret_id pre path receiver with - | [] -> - if !Config.footprint && Option.is_none (Attribute.get_undef tenv pre receiver) && - not (Rearrange.is_only_pt_by_fld_or_param_nonnull pdesc tenv pre receiver) then - let res_null = (* returns: (objc_null(res) /\ receiver=0) or an empty list of results *) + | [] + -> if !Config.footprint && Option.is_none (Attribute.get_undef tenv pre receiver) + && not (Rearrange.is_only_pt_by_fld_or_param_nonnull pdesc tenv pre receiver) + then + let res_null = + (* returns: (objc_null(res) /\ receiver=0) or an empty list of results *) let pre_with_attr_or_null = add_objc_null_attribute_or_nullify_result pre in let propset = prune_ne tenv ~positive:false receiver Exp.zero pre_with_attr_or_null in if Propset.is_empty propset then [] else let prop = List.hd_exn (Propset.to_proplist propset) in let path = Paths.Path.add_description path path_description in - [(prop, path)] in + [(prop, path)] + in List.append res_null (res ()) - else res () (* Not known if receiver = 0 and not footprint. Standard tabulation *) - | res_null -> List.append res_null (res ()) + else res () + (* Not known if receiver = 0 and not footprint. Standard tabulation *) + | res_null + -> List.append res_null (res ()) (* This method handles ObjC instance method calls, in particular the fact that calling a method *) (* with nil returns nil. The exec_call function is either standard call execution or execution *) @@ -798,65 +864,76 @@ let handle_objc_instance_method_call actual_pars actual_params pre tenv ret_id p let normalize_params tenv pdesc prop actual_params = let norm_arg (p, args) (e, t) = let e', p' = check_arith_norm_exp tenv pdesc e p in - (p', (e', t) :: args) in + (p', (e', t) :: args) + in let prop, args = List.fold ~f:norm_arg ~init:(prop, []) actual_params in (prop, List.rev args) let add_strexp_to_footprint tenv strexp abduced_pv typ prop = let abduced_lvar = Exp.Lvar abduced_pv in let lvar_pt_fpvar = - let sizeof_exp = Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.subtypes} in - Prop.mk_ptsto tenv abduced_lvar strexp sizeof_exp in + let sizeof_exp = + Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.subtypes} + in + Prop.mk_ptsto tenv abduced_lvar strexp sizeof_exp + in let sigma_fp = prop.Prop.sigma_fp in Prop.normalize tenv (Prop.set prop ~sigma_fp:(lvar_pt_fpvar :: sigma_fp)) let add_to_footprint tenv abduced_pv typ prop = let fresh_fp_var = Exp.Var (Ident.create_fresh Ident.kfootprint) in let prop' = - add_strexp_to_footprint tenv (Sil.Eexp (fresh_fp_var, Sil.Inone)) abduced_pv typ prop in - prop', fresh_fp_var + add_strexp_to_footprint tenv (Sil.Eexp (fresh_fp_var, Sil.Inone)) abduced_pv typ prop + in + (prop', fresh_fp_var) (* the current abduction mechanism treats struct values differently than all other types. abduction on struct values adds a a struct whose fields are initialized to fresh footprint vars to the footprint. regular abduction just adds a fresh footprint value of the correct type to the footprint. we can get rid of this special case if we fix the abduction on struct values *) let add_struct_value_to_footprint tenv abduced_pv typ prop = - let struct_strexp = - Prop.create_strexp_of_type tenv Prop.Fld_init typ None Sil.inst_none in + let struct_strexp = Prop.create_strexp_of_type tenv Prop.Fld_init typ None Sil.inst_none in let prop' = add_strexp_to_footprint tenv struct_strexp abduced_pv typ prop in - prop', struct_strexp + (prop', struct_strexp) -let add_constraints_on_retval tenv pdesc prop ret_exp ~has_nullable_annot typ callee_pname callee_loc= +let add_constraints_on_retval tenv pdesc prop ret_exp ~has_nullable_annot typ callee_pname + callee_loc = if Typ.Procname.is_infer_undefined callee_pname then prop else - let is_rec_call pname = (* TODO: (t7147096) extend this to detect mutual recursion *) - Typ.Procname.equal pname (Procdesc.get_proc_name pdesc) in + let is_rec_call pname = + (* TODO: (t7147096) extend this to detect mutual recursion *) + Typ.Procname.equal pname (Procdesc.get_proc_name pdesc) + in let already_has_abduced_retval p abduced_ret_pv = List.exists - ~f:(fun hpred -> match hpred with - | Sil.Hpointsto (Exp.Lvar pv, _, _) -> Pvar.equal pv abduced_ret_pv - | _ -> false) - p.Prop.sigma_fp in + ~f:(fun hpred -> + match hpred with + | Sil.Hpointsto (Exp.Lvar pv, _, _) + -> Pvar.equal pv abduced_ret_pv + | _ + -> false) + p.Prop.sigma_fp + in (* find an hpred [abduced] |-> A in [prop] and add [exp] = A to prop *) let bind_exp_to_abduced_val exp_to_bind abduced prop = let bind_exp prop = function - | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (rhs, _), _) - when Pvar.equal pv abduced -> - Prop.conjoin_eq tenv exp_to_bind rhs prop - | _ -> prop in - List.fold ~f:bind_exp ~init:prop prop.Prop.sigma in + | Sil.Hpointsto (Exp.Lvar pv, Sil.Eexp (rhs, _), _) when Pvar.equal pv abduced + -> Prop.conjoin_eq tenv exp_to_bind rhs prop + | _ + -> prop + in + List.fold ~f:bind_exp ~init:prop prop.Prop.sigma + in (* To avoid obvious false positives, assume skip functions do not return null pointers *) let add_ret_non_null exp typ prop = - if has_nullable_annot - then - prop (* don't assume nonnull if the procedure is annotated with @Nullable *) + if has_nullable_annot then prop + (* don't assume nonnull if the procedure is annotated with @Nullable *) else - match typ.Typ.desc with - | Typ.Tptr _ -> Prop.conjoin_neq tenv exp Exp.zero prop - | _ -> prop in + match typ.Typ.desc with Typ.Tptr _ -> Prop.conjoin_neq tenv exp Exp.zero prop | _ -> prop + in let add_tainted_post ret_exp callee_pname prop = - Attribute.add_or_replace tenv prop (Apred (Ataint callee_pname, [ret_exp])) in - + Attribute.add_or_replace tenv prop (Apred (Ataint callee_pname, [ret_exp])) + in if Config.angelic_execution && not (is_rec_call callee_pname) then (* introduce a fresh program variable to allow abduction on the return value *) let abduced_ret_pv = Pvar.mk_abduced_ret callee_pname callee_loc in @@ -865,98 +942,112 @@ let add_constraints_on_retval tenv pdesc prop ret_exp ~has_nullable_annot typ ca else let prop' = if !Config.footprint then - let (prop', fresh_fp_var) = add_to_footprint tenv abduced_ret_pv typ prop in - Prop.conjoin_eq tenv ~footprint: true ret_exp fresh_fp_var prop' + let prop', fresh_fp_var = add_to_footprint tenv abduced_ret_pv typ prop in + Prop.conjoin_eq tenv ~footprint:true ret_exp fresh_fp_var prop' else (* bind return id to the abduced value pointed to by the pvar we introduced *) - bind_exp_to_abduced_val ret_exp abduced_ret_pv prop in + bind_exp_to_abduced_val ret_exp abduced_ret_pv prop + in let prop'' = add_ret_non_null ret_exp typ prop' in if Config.taint_analysis then match Taint.returns_tainted callee_pname None with - | Some taint_kind -> - add_tainted_post ret_exp { taint_source = callee_pname; taint_kind; } prop'' - | None -> prop'' + | Some taint_kind + -> add_tainted_post ret_exp {taint_source= callee_pname; taint_kind} prop'' + | None + -> prop'' else prop'' else add_ret_non_null ret_exp typ prop -let add_taint prop lhs_id rhs_exp pname tenv = +let add_taint prop lhs_id rhs_exp pname tenv = let add_attribute_if_field_tainted prop fieldname struct_typ = - if Taint.has_taint_annotation fieldname struct_typ - then - let taint_info = { PredSymb.taint_source = pname; taint_kind = Tk_unknown; } in + if Taint.has_taint_annotation fieldname struct_typ then + let taint_info = {PredSymb.taint_source= pname; taint_kind= Tk_unknown} in Attribute.add_or_replace tenv prop (Apred (Ataint taint_info, [Exp.Var lhs_id])) - else - prop in + else prop + in match rhs_exp with - | Exp.Lfield (_, fieldname, ({desc=Tstruct typname} | {desc=Tptr ({desc=Tstruct typname}, _)})) -> - begin - match Tenv.lookup tenv typname with - | Some struct_typ -> add_attribute_if_field_tainted prop fieldname struct_typ - | None -> prop - end - | _ -> prop + | Exp.Lfield (_, fieldname, ({desc= Tstruct typname} | {desc= Tptr ({desc= Tstruct typname}, _)})) + -> ( + match Tenv.lookup tenv typname with + | Some struct_typ + -> add_attribute_if_field_tainted prop fieldname struct_typ + | None + -> prop ) + | _ + -> prop -let execute_load ?(report_deref_errors=true) pname pdesc tenv id rhs_exp typ loc prop_ = +let execute_load ?(report_deref_errors= true) pname pdesc tenv id rhs_exp typ loc prop_ = let execute_load_ pdesc tenv id loc acc_in iter = let iter_ren = Prop.prop_iter_make_id_primed tenv id iter in let prop_ren = Prop.prop_iter_to_prop tenv iter_ren in match Prop.prop_iter_current tenv iter_ren with - | (Sil.Hpointsto(lexp, strexp, Exp.Sizeof sizeof_data), offlist) -> + | Sil.Hpointsto (lexp, strexp, Exp.Sizeof sizeof_data), offlist + -> ( let contents, new_ptsto, pred_insts_op, lookup_uninitialized = - ptsto_lookup pdesc tenv prop_ren (lexp, strexp, sizeof_data) offlist id in + ptsto_lookup pdesc tenv prop_ren (lexp, strexp, sizeof_data) offlist id + in let update acc (pi, sigma) = - let pi' = Sil.Aeq (Exp.Var(id), contents):: pi in - let sigma' = new_ptsto:: sigma in + let pi' = Sil.Aeq (Exp.Var id, contents) :: pi in + let sigma' = new_ptsto :: sigma in let iter' = update_iter iter_ren pi' sigma' in let prop' = Prop.prop_iter_to_prop tenv iter' in let prop'' = if lookup_uninitialized then Attribute.add_or_replace tenv prop' (Apred (Adangling DAuninit, [Exp.Var id])) - else prop' in + else prop' + in let prop''' = - if Config.taint_analysis - then add_taint prop'' id rhs_exp pname tenv - else prop'' in - prop''' :: acc in - begin - match pred_insts_op with - | None -> update acc_in ([],[]) - | Some pred_insts -> List.rev (List.fold ~f:update ~init:acc_in pred_insts) - end - | (Sil.Hpointsto _, _) -> - Errdesc.warning_err loc "no offset access in execute_load -- treating as skip@."; - (Prop.prop_iter_to_prop tenv iter_ren) :: acc_in - | _ -> - (* The implementation of this case means that we + if Config.taint_analysis then add_taint prop'' id rhs_exp pname tenv else prop'' + in + prop''' :: acc + in + match pred_insts_op with + | None + -> update acc_in ([], []) + | Some pred_insts + -> List.rev (List.fold ~f:update ~init:acc_in pred_insts) ) + | Sil.Hpointsto _, _ + -> Errdesc.warning_err loc "no offset access in execute_load -- treating as skip@." ; + Prop.prop_iter_to_prop tenv iter_ren :: acc_in + | _ + -> (* The implementation of this case means that we ignore this dereferencing operator. When the analyzer treats numerical information and arrays more precisely later, we should change the implementation here. *) - assert false in + assert false + in try let n_rhs_exp, prop = check_arith_norm_exp tenv pname rhs_exp prop_ in let n_rhs_exp' = Prop.exp_collapse_consecutive_indices_prop typ n_rhs_exp in match check_constant_string_dereference n_rhs_exp' with - | Some value -> - [Prop.conjoin_eq tenv (Exp.Var id) value prop] - | None -> - let exp_get_undef_attr exp = + | Some value + -> [Prop.conjoin_eq tenv (Exp.Var id) value prop] + | None + -> let exp_get_undef_attr exp = let fold_undef_pname callee_opt atom = - match callee_opt, atom with - | None, Sil.Apred (Aundef _, _) -> Some atom - | _ -> callee_opt in - List.fold ~f:fold_undef_pname ~init:None (Attribute.get_for_exp tenv prop exp) in + match (callee_opt, atom) with + | None, Sil.Apred (Aundef _, _) + -> Some atom + | _ + -> callee_opt + in + List.fold ~f:fold_undef_pname ~init:None (Attribute.get_for_exp tenv prop exp) + in let prop' = if Config.angelic_execution && not (Exp.is_stack_addr n_rhs_exp') then (* when we try to deref an undefined value, add it to the footprint *) match exp_get_undef_attr n_rhs_exp' with - | Some (Apred (Aundef (callee_pname, ret_annots, callee_loc, _), _)) -> - let has_nullable_annot = Annotations.ia_is_nullable ret_annots in - add_constraints_on_retval tenv - pdesc prop n_rhs_exp' ~has_nullable_annot typ callee_pname callee_loc - | _ -> prop - else prop in + | Some Apred (Aundef (callee_pname, ret_annots, callee_loc, _), _) + -> let has_nullable_annot = Annotations.ia_is_nullable ret_annots in + add_constraints_on_retval tenv pdesc prop n_rhs_exp' ~has_nullable_annot typ + callee_pname callee_loc + | _ + -> prop + else prop + in let iter_list = - Rearrange.rearrange ~report_deref_errors pdesc tenv n_rhs_exp' typ prop' loc in + Rearrange.rearrange ~report_deref_errors pdesc tenv n_rhs_exp' typ prop' loc + in List.rev (List.fold ~f:(execute_load_ pdesc tenv id loc) ~init:[] iter_list) with Rearrange.ARRAY_ACCESS -> if Int.equal Config.array_level 0 then assert false @@ -966,30 +1057,37 @@ let execute_load ?(report_deref_errors=true) pname pdesc tenv id rhs_exp typ loc let load_ret_annots pname = match AttributesTable.load_attributes ~cache:true pname with - | Some attrs -> - let ret_annots, _ = attrs.ProcAttributes.method_annotation in + | Some attrs + -> let ret_annots, _ = attrs.ProcAttributes.method_annotation in ret_annots - | None -> - Annot.Item.empty + | None + -> Annot.Item.empty -let execute_store ?(report_deref_errors=true) pname pdesc tenv lhs_exp typ rhs_exp loc prop_ = +let execute_store ?(report_deref_errors= true) pname pdesc tenv lhs_exp typ rhs_exp loc prop_ = let execute_store_ pdesc tenv rhs_exp acc_in iter = - let (lexp, strexp, sizeof, offlist) = + let lexp, strexp, sizeof, offlist = match Prop.prop_iter_current tenv iter with - | (Sil.Hpointsto(lexp, strexp, Exp.Sizeof sizeof), offlist) -> - (lexp, strexp, sizeof, offlist) - | _ -> assert false in + | Sil.Hpointsto (lexp, strexp, Exp.Sizeof sizeof), offlist + -> (lexp, strexp, sizeof, offlist) + | _ + -> assert false + in let p = Prop.prop_iter_to_prop tenv iter in let new_ptsto, pred_insts_op = - ptsto_update pdesc tenv p (lexp, strexp, sizeof) offlist rhs_exp in + ptsto_update pdesc tenv p (lexp, strexp, sizeof) offlist rhs_exp + in let update acc (pi, sigma) = - let sigma' = new_ptsto:: sigma in + let sigma' = new_ptsto :: sigma in let iter' = update_iter iter pi sigma' in let prop' = Prop.prop_iter_to_prop tenv iter' in - prop' :: acc in + prop' :: acc + in match pred_insts_op with - | None -> update acc_in ([],[]) - | Some pred_insts -> List.fold ~f:update ~init:acc_in pred_insts in + | None + -> update acc_in ([], []) + | Some pred_insts + -> List.fold ~f:update ~init:acc_in pred_insts + in try let n_lhs_exp, prop_' = check_arith_norm_exp tenv pname lhs_exp prop_ in let n_rhs_exp, prop = check_arith_norm_exp tenv pname rhs_exp prop_' in @@ -997,285 +1095,338 @@ let execute_store ?(report_deref_errors=true) pname pdesc tenv lhs_exp typ rhs_e let n_lhs_exp' = Prop.exp_collapse_consecutive_indices_prop typ n_lhs_exp in let iter_list = Rearrange.rearrange ~report_deref_errors pdesc tenv n_lhs_exp' typ prop loc in List.rev (List.fold ~f:(execute_store_ pdesc tenv n_rhs_exp) ~init:[] iter_list) - with Rearrange.ARRAY_ACCESS -> - if Int.equal Config.array_level 0 then assert false - else [prop_] + with Rearrange.ARRAY_ACCESS -> if Int.equal Config.array_level 0 then assert false else [prop_] (** Execute [instr] with a symbolic heap [prop].*) let rec sym_exec tenv current_pdesc _instr (prop_: Prop.normal Prop.t) path - : (Prop.normal Prop.t * Paths.Path.t) list = + : (Prop.normal Prop.t * Paths.Path.t) list = let current_pname = Procdesc.get_proc_name current_pdesc in - State.set_instr _instr; (* mark instruction last seen *) - State.set_prop_tenv_pdesc prop_ tenv current_pdesc; (* mark prop,tenv,pdesc last seen *) - SymOp.pay(); (* pay one symop *) - let ret_old_path pl = (* return the old path unchanged *) - List.map ~f:(fun p -> (p, path)) pl in - let instr = match _instr with - | Sil.Call (ret, exp, par, loc, call_flags) -> - let exp' = Prop.exp_normalize_prop tenv prop_ exp in - let instr' = match exp' with - | Exp.Closure c -> - let proc_exp = Exp.Const (Const.Cfun c.name) in + State.set_instr _instr ; + (* mark instruction last seen *) + State.set_prop_tenv_pdesc prop_ tenv current_pdesc ; + (* mark prop,tenv,pdesc last seen *) + SymOp.pay () ; + (* pay one symop *) + let ret_old_path pl = + (* return the old path unchanged *) + List.map ~f:(fun p -> (p, path)) pl + in + let instr = + match _instr with + | Sil.Call (ret, exp, par, loc, call_flags) + -> let exp' = Prop.exp_normalize_prop tenv prop_ exp in + let instr' = + match exp' with + | Exp.Closure c + -> let proc_exp = Exp.Const (Const.Cfun c.name) in let proc_exp' = Prop.exp_normalize_prop tenv prop_ proc_exp in let par' = List.map ~f:(fun (id_exp, _, typ) -> (id_exp, typ)) c.captured_vars in Sil.Call (ret, proc_exp', par' @ par, loc, call_flags) - | _ -> - Sil.Call (ret, exp', par, loc, call_flags) in + | _ + -> Sil.Call (ret, exp', par, loc, call_flags) + in instr' - | _ -> _instr in - let skip_call ?(is_objc_instance_method=false) prop path callee_pname ret_annots loc ret_id + | _ + -> _instr + in + let skip_call ?(is_objc_instance_method= false) prop path callee_pname ret_annots loc ret_id ret_typ_opt actual_args = let skip_res () = let exn = Exceptions.Skip_function (Localise.desc_skip_function callee_pname) in - Reporting.log_info_deprecated current_pname exn; + Reporting.log_info_deprecated current_pname exn ; L.d_strln - ("Undefined function " ^ Typ.Procname.to_string callee_pname - ^ ", returning undefined value."); - (match Specs.get_summary current_pname with - | None -> () - | Some summary -> - Specs.CallStats.trace - summary.Specs.stats.Specs.call_stats callee_pname loc - (Specs.CallStats.CR_skip) !Config.footprint); - unknown_or_scan_call ~is_scan:false ret_typ_opt ret_annots Builtin.{ - pdesc= current_pdesc; instr; tenv; prop_= prop; path; ret_id; args= actual_args; - proc_name= callee_pname; loc; } in + ( "Undefined function " ^ Typ.Procname.to_string callee_pname + ^ ", returning undefined value." ) ; + ( match Specs.get_summary current_pname with + | None + -> () + | Some summary + -> Specs.CallStats.trace summary.Specs.stats.Specs.call_stats callee_pname loc + Specs.CallStats.CR_skip !Config.footprint ) ; + unknown_or_scan_call ~is_scan:false ret_typ_opt ret_annots + (Builtin. + { pdesc= current_pdesc + ; instr + ; tenv + ; prop_= prop + ; path + ; ret_id + ; args= actual_args + ; proc_name= callee_pname + ; loc }) + in if is_objc_instance_method then - handle_objc_instance_method_call_or_skip - current_pdesc tenv actual_args path callee_pname prop ret_id skip_res - else skip_res () in - let call_args prop_ proc_name args ret_id loc = { - Builtin.pdesc = current_pdesc; instr; tenv; prop_; path; ret_id; args; proc_name; loc; } in + handle_objc_instance_method_call_or_skip current_pdesc tenv actual_args path callee_pname + prop ret_id skip_res + else skip_res () + in + let call_args prop_ proc_name args ret_id loc = + {Builtin.pdesc= current_pdesc; instr; tenv; prop_; path; ret_id; args; proc_name; loc} + in match instr with - | Sil.Load (id, rhs_exp, typ, loc) -> - execute_load current_pname current_pdesc tenv id rhs_exp typ loc prop_ - |> ret_old_path - | Sil.Store (lhs_exp, typ, rhs_exp, loc) -> - execute_store current_pname current_pdesc tenv lhs_exp typ rhs_exp loc prop_ - |> ret_old_path - | Sil.Prune (cond, loc, true_branch, ik) -> - let prop__ = Attribute.nullify_exp_with_objc_null tenv prop_ cond in + | Sil.Load (id, rhs_exp, typ, loc) + -> execute_load current_pname current_pdesc tenv id rhs_exp typ loc prop_ |> ret_old_path + | Sil.Store (lhs_exp, typ, rhs_exp, loc) + -> execute_store current_pname current_pdesc tenv lhs_exp typ rhs_exp loc prop_ |> ret_old_path + | Sil.Prune (cond, loc, true_branch, ik) + -> let prop__ = Attribute.nullify_exp_with_objc_null tenv prop_ cond in let check_condition_always_true_false () = - if !Config.curr_language <> Config.Clang || - Config.report_condition_always_true_in_clang then + if !Config.curr_language <> Config.Clang || Config.report_condition_always_true_in_clang + then let report_condition_always_true_false i = - let skip_loop = match ik with - | Sil.Ik_while | Sil.Ik_for -> - not (IntLit.iszero i) (* skip wile(1) and for (;1;) *) - | Sil.Ik_dowhile -> - true (* skip do..while *) - | Sil.Ik_land_lor -> - true (* skip subpart of a condition obtained from compilation of && and || *) - | _ -> false in - true_branch && not skip_loop in + let skip_loop = + match ik with + | Sil.Ik_while | Sil.Ik_for + -> not (IntLit.iszero i) (* skip wile(1) and for (;1;) *) + | Sil.Ik_dowhile + -> true (* skip do..while *) + | Sil.Ik_land_lor + -> true (* skip subpart of a condition obtained from compilation of && and || *) + | _ + -> false + in + true_branch && not skip_loop + in match Prop.exp_normalize_prop tenv Prop.prop_emp cond with - | Exp.Const (Const.Cint i) when report_condition_always_true_false i -> - let node = State.get_node () in + | Exp.Const Const.Cint i when report_condition_always_true_false i + -> let node = State.get_node () in let desc = Errdesc.explain_condition_always_true_false tenv i cond node loc in let exn = - Exceptions.Condition_always_true_false (desc, not (IntLit.iszero i), __POS__) in + Exceptions.Condition_always_true_false (desc, not (IntLit.iszero i), __POS__) + in Reporting.log_warning_deprecated current_pname exn - | _ -> () in - if not Config.tracing then - check_already_dereferenced tenv current_pname cond prop__; - check_condition_always_true_false (); + | _ + -> () + in + if not Config.tracing then check_already_dereferenced tenv current_pname cond prop__ ; + check_condition_always_true_false () ; let n_cond, prop = check_arith_norm_exp tenv current_pname cond prop__ in ret_old_path (Propset.to_proplist (prune tenv ~positive:true n_cond prop)) - | Sil.Call (ret_id, Exp.Const (Const.Cfun callee_pname), actual_params, loc, call_flags) -> ( - match Builtin.get callee_pname with - | Some exec_builtin -> exec_builtin (call_args prop_ callee_pname actual_params ret_id loc) - | None -> ( - match callee_pname with - | Java callee_pname_java when Config.dynamic_dispatch = `Lazy -> - let norm_prop, norm_args' = normalize_params tenv current_pname prop_ actual_params in - let norm_args = call_constructor_url_update_args callee_pname norm_args' in - let exec_skip_call skipped_pname ret_annots ret_type = - skip_call norm_prop path skipped_pname ret_annots loc ret_id - (Some ret_type) norm_args in - let resolved_pname, resolved_summary_opt = resolve_and_analyze tenv current_pdesc - norm_prop norm_args callee_pname call_flags in - begin - match resolved_summary_opt with - | None -> - let ret_typ = Typ.java_proc_return_typ callee_pname_java in - let ret_annots = load_ret_annots callee_pname in - exec_skip_call resolved_pname ret_annots ret_typ - | Some resolved_summary when call_should_be_skipped resolved_summary -> - let proc_attrs = resolved_summary.Specs.attributes in - let ret_annots, _ = proc_attrs.ProcAttributes.method_annotation in - exec_skip_call resolved_pname ret_annots proc_attrs.ProcAttributes.ret_type - | Some resolved_summary -> - proc_call resolved_summary (call_args prop_ callee_pname norm_args ret_id loc) - end - | Java callee_pname_java -> - let norm_prop, norm_args = normalize_params tenv current_pname prop_ actual_params in - let url_handled_args = - call_constructor_url_update_args callee_pname norm_args in - let resolved_pnames = - resolve_virtual_pname tenv norm_prop url_handled_args callee_pname call_flags in - let exec_one_pname pname = - let exec_skip_call ret_annots ret_type = skip_call norm_prop path pname ret_annots - loc ret_id (Some ret_type) url_handled_args in - match Ondemand.analyze_proc_name ~propagate_exceptions:true current_pdesc pname with - | None -> - let ret_typ = Typ.java_proc_return_typ callee_pname_java in - let ret_annots = load_ret_annots callee_pname in - exec_skip_call ret_annots ret_typ - | Some callee_summary when call_should_be_skipped callee_summary -> - let proc_attrs = callee_summary.Specs.attributes in - let ret_annots, _ = proc_attrs.ProcAttributes.method_annotation in - exec_skip_call ret_annots proc_attrs.ProcAttributes.ret_type - | Some callee_summary -> - let handled_args = call_args norm_prop pname url_handled_args ret_id loc in - proc_call callee_summary handled_args in - List.fold - ~f:(fun acc pname -> exec_one_pname pname @ acc) - ~init:[] - resolved_pnames - | _ -> (* Generic fun call with known name *) - let (prop_r, n_actual_params) = - normalize_params tenv current_pname prop_ actual_params in - let resolved_pname = - match resolve_virtual_pname tenv prop_r n_actual_params callee_pname call_flags with - | resolved_pname :: _ -> resolved_pname - | [] -> callee_pname in - let resolved_summary_opt = - Ondemand.analyze_proc_name - ~propagate_exceptions:true current_pdesc resolved_pname in - let callee_pdesc_opt = Ondemand.get_proc_desc resolved_pname in - let ret_typ_opt = Option.map ~f:Procdesc.get_ret_type callee_pdesc_opt in - let sentinel_result = - if Config.curr_language_is Config.Clang then - check_variadic_sentinel_if_present - (call_args prop_r callee_pname actual_params ret_id loc) - else [(prop_r, path)] in - let do_call (prop, path) = - if Option.value_map ~f:call_should_be_skipped ~default:true resolved_summary_opt then - (* If it's an ObjC getter or setter, call the builtin rather than skipping *) - let attrs_opt = - let attr_opt = Option.map ~f:Procdesc.get_attributes callee_pdesc_opt in - match attr_opt, resolved_pname with - | Some attrs, Typ.Procname.ObjC_Cpp _ -> - Some attrs - | None, Typ.Procname.ObjC_Cpp _ -> - AttributesTable.load_attributes ~cache:true resolved_pname - | _ -> None in - let objc_property_accessor_ret_typ_opt = + | Sil.Call (ret_id, Exp.Const Const.Cfun callee_pname, actual_params, loc, call_flags) -> ( + match Builtin.get callee_pname with + | Some exec_builtin + -> exec_builtin (call_args prop_ callee_pname actual_params ret_id loc) + | None -> + match callee_pname with + | Java callee_pname_java when Config.dynamic_dispatch = `Lazy + -> ( + let norm_prop, norm_args' = normalize_params tenv current_pname prop_ actual_params in + let norm_args = call_constructor_url_update_args callee_pname norm_args' in + let exec_skip_call skipped_pname ret_annots ret_type = + skip_call norm_prop path skipped_pname ret_annots loc ret_id (Some ret_type) norm_args + in + let resolved_pname, resolved_summary_opt = + resolve_and_analyze tenv current_pdesc norm_prop norm_args callee_pname call_flags + in + match resolved_summary_opt with + | None + -> let ret_typ = Typ.java_proc_return_typ callee_pname_java in + let ret_annots = load_ret_annots callee_pname in + exec_skip_call resolved_pname ret_annots ret_typ + | Some resolved_summary when call_should_be_skipped resolved_summary + -> let proc_attrs = resolved_summary.Specs.attributes in + let ret_annots, _ = proc_attrs.ProcAttributes.method_annotation in + exec_skip_call resolved_pname ret_annots proc_attrs.ProcAttributes.ret_type + | Some resolved_summary + -> proc_call resolved_summary (call_args prop_ callee_pname norm_args ret_id loc) ) + | Java callee_pname_java + -> let norm_prop, norm_args = normalize_params tenv current_pname prop_ actual_params in + let url_handled_args = call_constructor_url_update_args callee_pname norm_args in + let resolved_pnames = + resolve_virtual_pname tenv norm_prop url_handled_args callee_pname call_flags + in + let exec_one_pname pname = + let exec_skip_call ret_annots ret_type = + skip_call norm_prop path pname ret_annots loc ret_id (Some ret_type) url_handled_args + in + match Ondemand.analyze_proc_name ~propagate_exceptions:true current_pdesc pname with + | None + -> let ret_typ = Typ.java_proc_return_typ callee_pname_java in + let ret_annots = load_ret_annots callee_pname in + exec_skip_call ret_annots ret_typ + | Some callee_summary when call_should_be_skipped callee_summary + -> let proc_attrs = callee_summary.Specs.attributes in + let ret_annots, _ = proc_attrs.ProcAttributes.method_annotation in + exec_skip_call ret_annots proc_attrs.ProcAttributes.ret_type + | Some callee_summary + -> let handled_args = call_args norm_prop pname url_handled_args ret_id loc in + proc_call callee_summary handled_args + in + List.fold ~f:(fun acc pname -> exec_one_pname pname @ acc) ~init:[] resolved_pnames + | _ + -> (* Generic fun call with known name *) + let prop_r, n_actual_params = normalize_params tenv current_pname prop_ actual_params in + let resolved_pname = + match resolve_virtual_pname tenv prop_r n_actual_params callee_pname call_flags with + | resolved_pname :: _ + -> resolved_pname + | [] + -> callee_pname + in + let resolved_summary_opt = + Ondemand.analyze_proc_name ~propagate_exceptions:true current_pdesc resolved_pname + in + let callee_pdesc_opt = Ondemand.get_proc_desc resolved_pname in + let ret_typ_opt = Option.map ~f:Procdesc.get_ret_type callee_pdesc_opt in + let sentinel_result = + if Config.curr_language_is Config.Clang then + check_variadic_sentinel_if_present + (call_args prop_r callee_pname actual_params ret_id loc) + else [(prop_r, path)] + in + let do_call (prop, path) = + if Option.value_map ~f:call_should_be_skipped ~default:true resolved_summary_opt then + (* If it's an ObjC getter or setter, call the builtin rather than skipping *) + let attrs_opt = + let attr_opt = Option.map ~f:Procdesc.get_attributes callee_pdesc_opt in + match (attr_opt, resolved_pname) with + | Some attrs, Typ.Procname.ObjC_Cpp _ + -> Some attrs + | None, Typ.Procname.ObjC_Cpp _ + -> AttributesTable.load_attributes ~cache:true resolved_pname + | _ + -> None + in + let objc_property_accessor_ret_typ_opt = + match attrs_opt with + | Some attrs -> ( + match attrs.ProcAttributes.objc_accessor with + | Some objc_accessor + -> Some (objc_accessor, attrs.ProcAttributes.ret_type) + | None + -> None ) + | None + -> None + in + match objc_property_accessor_ret_typ_opt with + | Some (objc_property_accessor, ret_typ) + -> handle_objc_instance_method_call n_actual_params n_actual_params prop tenv ret_id + current_pdesc callee_pname loc path + (sym_exec_objc_accessor objc_property_accessor ret_typ) + | None + -> let ret_annots = + match resolved_summary_opt with + | Some summ + -> let ret_annots, _ = + summ.Specs.attributes.ProcAttributes.method_annotation + in + ret_annots + | None + -> load_ret_annots resolved_pname + in + let is_objc_instance_method = match attrs_opt with - | Some attrs -> - (match attrs.ProcAttributes.objc_accessor with - | Some objc_accessor -> Some (objc_accessor, attrs.ProcAttributes.ret_type) - | None -> None) - | None -> None in - match objc_property_accessor_ret_typ_opt with - | Some (objc_property_accessor, ret_typ) -> - handle_objc_instance_method_call - n_actual_params n_actual_params prop tenv ret_id - current_pdesc callee_pname loc path - (sym_exec_objc_accessor objc_property_accessor ret_typ) - | None -> - let ret_annots = match resolved_summary_opt with - | Some summ -> - let ret_annots, _ = - summ.Specs.attributes.ProcAttributes.method_annotation in - ret_annots - | None -> - load_ret_annots resolved_pname in - let is_objc_instance_method = - match attrs_opt with - | Some attrs -> attrs.ProcAttributes.is_objc_instance_method - | None -> false in - skip_call ~is_objc_instance_method prop path resolved_pname ret_annots - loc ret_id ret_typ_opt n_actual_params - else - proc_call (Option.value_exn resolved_summary_opt) - (call_args prop resolved_pname n_actual_params ret_id loc) in - List.concat_map ~f:do_call sentinel_result - ) - ) - | Sil.Call (ret_id, fun_exp, actual_params, loc, call_flags) -> (* Call via function pointer *) - let (prop_r, n_actual_params) = normalize_params tenv current_pname prop_ actual_params in - if call_flags.CallFlags.cf_is_objc_block && - not (Rearrange.is_only_pt_by_fld_or_param_nonnull current_pdesc tenv prop_r fun_exp) then - Rearrange.check_call_to_objc_block_error tenv current_pdesc prop_r fun_exp loc; - Rearrange.check_dereference_error tenv current_pdesc prop_r fun_exp loc; - if call_flags.CallFlags.cf_noreturn then begin - L.d_str "Unknown function pointer with noreturn attribute "; - Sil.d_exp fun_exp; L.d_strln ", diverging."; - diverge prop_r path - end else begin - L.d_str "Unknown function pointer "; Sil.d_exp fun_exp; - L.d_strln ", returning undefined value."; + | Some attrs + -> attrs.ProcAttributes.is_objc_instance_method + | None + -> false + in + skip_call ~is_objc_instance_method prop path resolved_pname ret_annots loc ret_id + ret_typ_opt n_actual_params + else + proc_call (Option.value_exn resolved_summary_opt) + (call_args prop resolved_pname n_actual_params ret_id loc) + in + List.concat_map ~f:do_call sentinel_result ) + | Sil.Call (ret_id, fun_exp, actual_params, loc, call_flags) + -> (* Call via function pointer *) + let prop_r, n_actual_params = normalize_params tenv current_pname prop_ actual_params in + if call_flags.CallFlags.cf_is_objc_block + && not (Rearrange.is_only_pt_by_fld_or_param_nonnull current_pdesc tenv prop_r fun_exp) + then Rearrange.check_call_to_objc_block_error tenv current_pdesc prop_r fun_exp loc ; + Rearrange.check_dereference_error tenv current_pdesc prop_r fun_exp loc ; + if call_flags.CallFlags.cf_noreturn then ( + L.d_str "Unknown function pointer with noreturn attribute " ; + Sil.d_exp fun_exp ; + L.d_strln ", diverging." ; + diverge prop_r path ) + else ( + L.d_str "Unknown function pointer " ; + Sil.d_exp fun_exp ; + L.d_strln ", returning undefined value." ; let callee_pname = Typ.Procname.from_string_c_fun "__function_pointer__" in - unknown_or_scan_call ~is_scan:false None Annot.Item.empty Builtin.{ - pdesc= current_pdesc; instr; tenv; prop_= prop_r; path; ret_id; args= n_actual_params; - proc_name= callee_pname; loc; } - end - | Sil.Nullify (pvar, _) -> - begin - let eprop = Prop.expose prop_ in - match List.partition_tf - ~f:(function - | Sil.Hpointsto (Exp.Lvar pvar', _, _) -> Pvar.equal pvar pvar' - | _ -> false) eprop.Prop.sigma with - | [Sil.Hpointsto(e, se, typ)], sigma' -> - let sigma'' = - let se' = execute_nullify_se se in - Sil.Hpointsto(e, se', typ):: sigma' in - let eprop_res = Prop.set eprop ~sigma:sigma'' in - ret_old_path [Prop.normalize tenv eprop_res] - | [], _ -> - ret_old_path [prop_] - | _ -> - L.internal_error "Pvar %a appears on the LHS of >1 heap predicate!@." - (Pvar.pp Pp.text) pvar; - assert false - end - | Sil.Abstract _ -> - let node = State.get_node () in + unknown_or_scan_call ~is_scan:false None Annot.Item.empty + (Builtin. + { pdesc= current_pdesc + ; instr + ; tenv + ; prop_= prop_r + ; path + ; ret_id + ; args= n_actual_params + ; proc_name= callee_pname + ; loc }) ) + | Sil.Nullify (pvar, _) + -> ( + let eprop = Prop.expose prop_ in + match + List.partition_tf + ~f:(function + | Sil.Hpointsto (Exp.Lvar pvar', _, _) -> Pvar.equal pvar pvar' | _ -> false) + eprop.Prop.sigma + with + | [(Sil.Hpointsto (e, se, typ))], sigma' + -> let sigma'' = + let se' = execute_nullify_se se in + Sil.Hpointsto (e, se', typ) :: sigma' + in + let eprop_res = Prop.set eprop ~sigma:sigma'' in + ret_old_path [Prop.normalize tenv eprop_res] + | [], _ + -> ret_old_path [prop_] + | _ + -> L.internal_error "Pvar %a appears on the LHS of >1 heap predicate!@." (Pvar.pp Pp.text) + pvar ; + assert false ) + | Sil.Abstract _ + -> let node = State.get_node () in let blocks_nullified = get_blocks_nullified node in - List.iter ~f:(check_block_retain_cycle tenv current_pname prop_) blocks_nullified; - if Prover.check_inconsistency tenv prop_ - then - ret_old_path [] + List.iter ~f:(check_block_retain_cycle tenv current_pname prop_) blocks_nullified ; + if Prover.check_inconsistency tenv prop_ then ret_old_path [] else ret_old_path - [Abs.remove_redundant_array_elements current_pname tenv - (Abs.abstract current_pname tenv prop_)] - | Sil.Remove_temps (temps, _) -> - ret_old_path [Prop.exist_quantify tenv (Sil.fav_from_list temps) prop_] - | Sil.Declare_locals (ptl, _) -> - let sigma_locals = + [ Abs.remove_redundant_array_elements current_pname tenv + (Abs.abstract current_pname tenv prop_) ] + | Sil.Remove_temps (temps, _) + -> ret_old_path [Prop.exist_quantify tenv (Sil.fav_from_list temps) prop_] + | Sil.Declare_locals (ptl, _) + -> let sigma_locals = let add_None (x, typ) = - (x, Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact}, None) in + (x, Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact}, None) + in let sigma_locals () = - List.map - ~f:(Prop.mk_ptsto_lvar tenv Prop.Fld_init Sil.inst_initial) - (List.map ~f:add_None ptl) in + List.map ~f:(Prop.mk_ptsto_lvar tenv Prop.Fld_init Sil.inst_initial) + (List.map ~f:add_None ptl) + in Config.run_in_re_execution_mode (* no footprint vars for locals *) - sigma_locals () in + sigma_locals () + in let sigma' = prop_.Prop.sigma @ sigma_locals in let prop' = Prop.normalize tenv (Prop.set prop_ ~sigma:sigma') in ret_old_path [prop'] + and diverge prop path = - State.add_diverging_states (Paths.PathSet.from_renamed_list [(prop, path)]); (* diverge *) + State.add_diverging_states (Paths.PathSet.from_renamed_list [(prop, path)]) ; + (* diverge *) [] (** Symbolic execution of a sequence of instructions. If errors occur and [mask_errors] is true, just treat as skip. *) -and instrs ?(mask_errors=false) tenv pdesc instrs ppl = +and instrs ?(mask_errors= false) tenv pdesc instrs ppl = let exe_instr instr (p, path) = - L.d_str "Executing Generated Instruction "; Sil.d_instr instr; L.d_ln (); + L.d_str "Executing Generated Instruction " ; + Sil.d_instr instr ; + L.d_ln () ; try sym_exec tenv pdesc instr p path with exn when SymOp.exn_not_failure exn && mask_errors -> - let err_name, _, ml_source, _ , _, _, _ = Exceptions.recognize_exception exn in - let loc = (match ml_source with - | Some ml_loc -> "at " ^ (L.ml_loc_to_string ml_loc) - | None -> "") in - L.d_warning - ("Generated Instruction Failed with: " ^ - (Localise.to_issue_id err_name)^loc ); L.d_ln(); - [(p, path)] in + let err_name, _, ml_source, _, _, _, _ = Exceptions.recognize_exception exn in + let loc = + match ml_source with Some ml_loc -> "at " ^ L.ml_loc_to_string ml_loc | None -> "" + in + L.d_warning ("Generated Instruction Failed with: " ^ Localise.to_issue_id err_name ^ loc) ; + L.d_ln () ; + [(p, path)] + in let f plist instr = List.concat_map ~f:(exe_instr instr) plist in List.fold ~f ~init:ppl instrs @@ -1285,51 +1436,57 @@ and add_constraints_on_actuals_by_ref tenv prop actuals_by_ref callee_pname call let sigma' = List.map ~f:(function - | Sil.Hpointsto (lhs, _, _) when Exp.equal lhs actual_var -> new_hpred - | hpred -> hpred) - prop.Prop.sigma in - Prop.normalize tenv (Prop.set prop ~sigma:sigma') in + | Sil.Hpointsto (lhs, _, _) when Exp.equal lhs actual_var -> new_hpred | hpred -> hpred) + prop.Prop.sigma + in + Prop.normalize tenv (Prop.set prop ~sigma:sigma') + in let add_actual_by_ref_to_footprint prop (actual, actual_typ, _) = - let abduced = match actual with - | Exp.Lvar actual_pv -> - Pvar.mk_abduced_ref_param callee_pname actual_pv callee_loc - | Exp.Var actual_id -> - Pvar.mk_abduced_ref_param_val callee_pname actual_id callee_loc - | _ -> assert false + let abduced = + match actual with + | Exp.Lvar actual_pv + -> Pvar.mk_abduced_ref_param callee_pname actual_pv callee_loc + | Exp.Var actual_id + -> Pvar.mk_abduced_ref_param_val callee_pname actual_id callee_loc + | _ + -> assert false in let already_has_abduced_retval p = List.exists - ~f:(fun hpred -> match hpred with - | Sil.Hpointsto (Exp.Lvar pv, _, _) -> Pvar.equal pv abduced - | _ -> false) - p.Prop.sigma_fp in + ~f:(fun hpred -> + match hpred with + | Sil.Hpointsto (Exp.Lvar pv, _, _) + -> Pvar.equal pv abduced + | _ + -> false) + p.Prop.sigma_fp + in (* prevent introducing multiple abduced retvals for a single call site in a loop *) if already_has_abduced_retval prop then prop - else - if !Config.footprint then + else if !Config.footprint then let prop', abduced_strexp = match actual_typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct _} as typ, _) -> - (* for struct types passed by reference, do abduction on the fields of the + | Typ.Tptr (({desc= Tstruct _} as typ), _) + -> (* for struct types passed by reference, do abduction on the fields of the struct *) add_struct_value_to_footprint tenv abduced typ prop - | Typ.Tptr (typ, _) -> - (* for pointer types passed by reference, do abduction directly on the pointer *) - let (prop', fresh_fp_var) = - add_to_footprint tenv abduced typ prop in - prop', Sil.Eexp (fresh_fp_var, Sil.Inone) - | _ -> - failwith - ("No need for abduction on non-pointer type " ^ - (Typ.to_string actual_typ)) in + | Typ.Tptr (typ, _) + -> (* for pointer types passed by reference, do abduction directly on the pointer *) + let prop', fresh_fp_var = add_to_footprint tenv abduced typ prop in + (prop', Sil.Eexp (fresh_fp_var, Sil.Inone)) + | _ + -> failwith ("No need for abduction on non-pointer type " ^ Typ.to_string actual_typ) + in (* replace [actual] |-> _ with [actual] |-> [fresh_fp_var] *) let filtered_sigma = List.map ~f:(function - | Sil.Hpointsto (lhs, _, typ_exp) when Exp.equal lhs actual -> - Sil.Hpointsto (lhs, abduced_strexp, typ_exp) - | hpred -> hpred) - prop'.Prop.sigma in + | Sil.Hpointsto (lhs, _, typ_exp) when Exp.equal lhs actual + -> Sil.Hpointsto (lhs, abduced_strexp, typ_exp) + | hpred + -> hpred) + prop'.Prop.sigma + in Prop.normalize tenv (Prop.set prop' ~sigma:filtered_sigma) else (* bind actual passed by ref to the abduced value pointed to by the synthetic pvar *) @@ -1337,170 +1494,181 @@ and add_constraints_on_actuals_by_ref tenv prop actuals_by_ref callee_pname call let filtered_sigma = List.filter ~f:(function - | Sil.Hpointsto (lhs, _, _) when Exp.equal lhs actual -> - false - | _ -> true) - prop.Prop.sigma in - Prop.normalize tenv (Prop.set prop ~sigma:filtered_sigma) in + | Sil.Hpointsto (lhs, _, _) when Exp.equal lhs actual -> false | _ -> true) + prop.Prop.sigma + in + Prop.normalize tenv (Prop.set prop ~sigma:filtered_sigma) + in List.fold ~f:(fun p hpred -> - match hpred with - | Sil.Hpointsto (Exp.Lvar pv, rhs, texp) when Pvar.equal pv abduced -> - let new_hpred = Sil.Hpointsto (actual, rhs, texp) in - Prop.normalize tenv (Prop.set p ~sigma:(new_hpred :: prop'.Prop.sigma)) - | _ -> p) - ~init:prop' - prop'.Prop.sigma + match hpred with + | Sil.Hpointsto (Exp.Lvar pv, rhs, texp) when Pvar.equal pv abduced + -> let new_hpred = Sil.Hpointsto (actual, rhs, texp) in + Prop.normalize tenv (Prop.set p ~sigma:(new_hpred :: prop'.Prop.sigma)) + | _ + -> p) + ~init:prop' prop'.Prop.sigma in (* non-angelic mode; havoc each var passed by reference by assigning it to a fresh id *) let havoc_actual_by_ref prop (actual, actual_typ, _) = let actual_pt_havocd_var = let havocd_var = Exp.Var (Ident.create_fresh Ident.kprimed) in - let sizeof_exp = Exp.Sizeof {typ=Typ.strip_ptr actual_typ; nbytes=None; - dynamic_length=None; subtype=Subtype.subtypes} in - Prop.mk_ptsto tenv actual (Sil.Eexp (havocd_var, Sil.Inone)) sizeof_exp in - replace_actual_hpred actual actual_pt_havocd_var prop in + let sizeof_exp = + Exp.Sizeof + { typ= Typ.strip_ptr actual_typ + ; nbytes= None + ; dynamic_length= None + ; subtype= Subtype.subtypes } + in + Prop.mk_ptsto tenv actual (Sil.Eexp (havocd_var, Sil.Inone)) sizeof_exp + in + replace_actual_hpred actual actual_pt_havocd_var prop + in let do_actual_by_ref = - if Config.angelic_execution then add_actual_by_ref_to_footprint - else havoc_actual_by_ref in + if Config.angelic_execution then add_actual_by_ref_to_footprint else havoc_actual_by_ref + in let non_const_actuals_by_ref = let is_not_const (e, _, i) = match AttributesTable.load_attributes ~cache:true callee_pname with - | Some attrs -> - let is_const = List.mem ~equal:Int.equal attrs.ProcAttributes.const_formals i in + | Some attrs + -> let is_const = List.mem ~equal:Int.equal attrs.ProcAttributes.const_formals i in if is_const then ( - L.d_str (Printf.sprintf "Not havocing const argument number %d: " i); - Sil.d_exp e; - L.d_ln () - ); + L.d_str (Printf.sprintf "Not havocing const argument number %d: " i) ; + Sil.d_exp e ; + L.d_ln () ) ; not is_const - | None -> - true in - List.filter ~f:is_not_const actuals_by_ref in + | None + -> true + in + List.filter ~f:is_not_const actuals_by_ref + in List.fold ~f:do_actual_by_ref ~init:prop non_const_actuals_by_ref and check_untainted tenv exp taint_kind caller_pname callee_pname prop = match Attribute.get_taint tenv prop exp with - | Some (Apred (Ataint taint_info, _)) -> - let err_desc = - Errdesc.explain_tainted_value_reaching_sensitive_function - prop - exp - taint_info - callee_pname - (State.get_loc ()) in - let exn = - Exceptions.Tainted_value_reaching_sensitive_function - (err_desc, __POS__) in - Reporting.log_warning_deprecated caller_pname exn; + | Some Apred (Ataint taint_info, _) + -> let err_desc = + Errdesc.explain_tainted_value_reaching_sensitive_function prop exp taint_info callee_pname + (State.get_loc ()) + in + let exn = Exceptions.Tainted_value_reaching_sensitive_function (err_desc, __POS__) in + Reporting.log_warning_deprecated caller_pname exn ; Attribute.add_or_replace tenv prop (Apred (Auntaint taint_info, [exp])) - | _ -> - if !Config.footprint then - let taint_info = { PredSymb.taint_source = callee_pname; taint_kind; } in + | _ + -> if !Config.footprint then + let taint_info = {PredSymb.taint_source= callee_pname; taint_kind} in (* add untained(n_lexp) to the footprint *) Attribute.add tenv ~footprint:true prop (Auntaint taint_info) [exp] else prop (** execute a call for an unknown or scan function *) and unknown_or_scan_call ~is_scan ret_type_option ret_annots - { Builtin.tenv; pdesc; prop_= pre; path; ret_id; - args; proc_name= callee_pname; loc; instr; } = + {Builtin.tenv; pdesc; prop_= pre; path; ret_id; args; proc_name= callee_pname; loc; instr} = let remove_file_attribute prop = let do_exp p (e, _) = let do_attribute q atom = match atom with - | Sil.Apred ((Aresource {ra_res = Rfile} as res), _) -> Attribute.remove_for_attr tenv q res - | _ -> q in - List.fold ~f:do_attribute ~init:p (Attribute.get_for_exp tenv p e) in + | Sil.Apred ((Aresource {ra_res= Rfile} as res), _) + -> Attribute.remove_for_attr tenv q res + | _ + -> q + in + List.fold ~f:do_attribute ~init:p (Attribute.get_for_exp tenv p e) + in let filtered_args = - match args, instr with - | _:: other_args, Sil.Call (_, _, _, _, { CallFlags.cf_virtual }) when cf_virtual -> - (* Do not remove the file attribute on the reciver for virtual calls *) + match (args, instr) with + | _ :: other_args, Sil.Call (_, _, _, _, {CallFlags.cf_virtual}) when cf_virtual + -> (* Do not remove the file attribute on the reciver for virtual calls *) other_args - | _ -> args in - List.fold ~f:do_exp ~init:prop filtered_args in + | _ + -> args + in + List.fold ~f:do_exp ~init:prop filtered_args + in let add_tainted_pre prop actuals caller_pname callee_pname = if Config.taint_analysis then match Taint.accepts_sensitive_params callee_pname None with - | [] -> prop - | param_nums -> - let check_taint_if_nums_match (prop_acc, param_num) (actual_exp, _actual_typ) = + | [] + -> prop + | param_nums + -> let check_taint_if_nums_match (prop_acc, param_num) (actual_exp, _actual_typ) = let prop_acc' = match List.find ~f:(fun (num, _) -> Int.equal num param_num) param_nums with - | Some (_, taint_kind) -> - check_untainted tenv actual_exp taint_kind caller_pname callee_pname prop_acc - | None -> prop_acc in - prop_acc', param_num + 1 in - List.fold - ~f:check_taint_if_nums_match - ~init:(prop, 0) - actuals - |> fst - else prop in + | Some (_, taint_kind) + -> check_untainted tenv actual_exp taint_kind caller_pname callee_pname prop_acc + | None + -> prop_acc + in + (prop_acc', param_num + 1) + in + List.fold ~f:check_taint_if_nums_match ~init:(prop, 0) actuals |> fst + else prop + in let should_abduce_param_value pname = let open Typ.Procname in match pname with - | Java _ -> - (* FIXME (T19882766): we need to disable this for Java because it breaks too many tests *) + | Java _ + -> (* FIXME (T19882766): we need to disable this for Java because it breaks too many tests *) false - | ObjC_Cpp _ -> - (* FIXME: we need to work around a frontend hack for std::shared_ptr + | ObjC_Cpp _ + -> (* FIXME: we need to work around a frontend hack for std::shared_ptr * to silent some of the uninitialization warnings *) - if String.is_suffix ~suffix:"_std__shared_ptr" - (Typ.Procname.to_string callee_pname) then + if String.is_suffix ~suffix:"_std__shared_ptr" (Typ.Procname.to_string callee_pname) then false - else - true - | _ -> true + else true + | _ + -> true in let actuals_by_ref = List.filter_mapi - ~f:(fun i actual -> match actual with - | (Exp.Lvar _ as e, ({Typ.desc=Tptr _} as t)) -> Some (e, t, i) - | (Exp.Var _ as e, ({Typ.desc=Tptr _} as t)) - when should_abduce_param_value callee_pname -> - Some (e, t, i) - | _ -> None) - args in + ~f:(fun i actual -> + match actual with + | (Exp.Lvar _ as e), ({Typ.desc= Tptr _} as t) + -> Some (e, t, i) + | (Exp.Var _ as e), ({Typ.desc= Tptr _} as t) when should_abduce_param_value callee_pname + -> Some (e, t, i) + | _ + -> None) + args + in let has_nullable_annot = Annotations.ia_is_nullable ret_annots in let pre_final = (* in Java, assume that skip functions close resources passed as params *) - let pre_1 = - if Typ.Procname.is_java callee_pname - then remove_file_attribute pre - else pre in - let pre_2 = match ret_id, ret_type_option with - | Some (ret_id, _), Some ret_typ -> - (* TODO(jjb): Should this use the type of ret_id, or ret_type from the procedure type? *) - add_constraints_on_retval tenv - pdesc pre_1 (Exp.Var ret_id) ret_typ ~has_nullable_annot callee_pname loc - | _ -> - pre_1 in + let pre_1 = if Typ.Procname.is_java callee_pname then remove_file_attribute pre else pre in + let pre_2 = + match (ret_id, ret_type_option) with + | Some (ret_id, _), Some ret_typ + -> (* TODO(jjb): Should this use the type of ret_id, or ret_type from the procedure type? *) + add_constraints_on_retval tenv pdesc pre_1 (Exp.Var ret_id) ret_typ ~has_nullable_annot + callee_pname loc + | _ + -> pre_1 + in let pre_3 = add_constraints_on_actuals_by_ref tenv pre_2 actuals_by_ref callee_pname loc in let caller_pname = Procdesc.get_proc_name pdesc in - add_tainted_pre pre_3 args caller_pname callee_pname in - if is_scan (* if scan function, don't mark anything with undef attributes *) - then [(Tabulation.remove_constant_string_class tenv pre_final, path)] + add_tainted_pre pre_3 args caller_pname callee_pname + in + if is_scan (* if scan function, don't mark anything with undef attributes *) then + [(Tabulation.remove_constant_string_class tenv pre_final, path)] else (* otherwise, add undefined attribute to retvals and actuals passed by ref *) let exps_to_mark = let ret_exps = Option.value_map ~f:(fun (id, _) -> [Exp.Var id]) ~default:[] ret_id in List.fold ~f:(fun exps_to_mark (exp, _, _) -> exp :: exps_to_mark) - ~init:ret_exps - actuals_by_ref in + ~init:ret_exps actuals_by_ref + in let prop_with_undef_attr = let path_pos = State.get_path_pos () in - Attribute.mark_vars_as_undefined tenv - pre_final exps_to_mark callee_pname ret_annots loc path_pos in + Attribute.mark_vars_as_undefined tenv pre_final exps_to_mark callee_pname ret_annots loc + path_pos + in let reason = "function or method not found" in let skip_path = Paths.Path.add_skipped_call path callee_pname reason in [(prop_with_undef_attr, skip_path)] -and check_variadic_sentinel - ?(fails_on_nil = false) n_formals (sentinel, null_pos) - { Builtin.pdesc; tenv; prop_; path; args; proc_name; loc; } - = +and check_variadic_sentinel ?(fails_on_nil= false) n_formals (sentinel, null_pos) + {Builtin.pdesc; tenv; prop_; path; args; proc_name; loc} = (* from clang's lib/Sema/SemaExpr.cpp: *) (* "nullPos" is the number of formal parameters at the end which *) (* effectively count as part of the variadic arguments. This is *) @@ -1511,208 +1679,239 @@ and check_variadic_sentinel (* sentinels start counting from the last argument to the function *) let sentinel_pos = nargs - sentinel - 1 in let mk_non_terminal_argsi (acc, i) a = - if i < first_var_arg_pos || i >= sentinel_pos then (acc, i +1) - else ((a, i):: acc, i +1) in + if i < first_var_arg_pos || i >= sentinel_pos then (acc, i + 1) else ((a, i) :: acc, i + 1) + in (* fold_left reverses the arguments *) let non_terminal_argsi = fst (List.fold ~f:mk_non_terminal_argsi ~init:([], 0) args) in let check_allocated result ((lexp, typ), i) = (* simulate a Load for [lexp] *) let tmp_id_deref = Ident.create_fresh Ident.kprimed in let load_instr = Sil.Load (tmp_id_deref, lexp, typ, loc) in - try - instrs tenv pdesc [load_instr] result + try instrs tenv pdesc [load_instr] result with e when SymOp.exn_not_failure e -> if not fails_on_nil then let deref_str = Localise.deref_str_nil_argument_in_variadic_method proc_name nargs i in let err_desc = - Errdesc.explain_dereference tenv ~use_buckets: true ~is_premature_nil: true - deref_str prop_ loc in + Errdesc.explain_dereference tenv ~use_buckets:true ~is_premature_nil:true deref_str prop_ + loc + in raise (Exceptions.Premature_nil_termination (err_desc, __POS__)) - else - raise e in + else raise e + in (* fold_left reverses the arguments back so that we report an *) (* error on the first premature nil argument *) List.fold ~f:check_allocated ~init:[(prop_, path)] non_terminal_argsi -and check_variadic_sentinel_if_present - ({ Builtin.prop_; path; proc_name; } as builtin_args) = +and check_variadic_sentinel_if_present ({Builtin.prop_; path; proc_name} as builtin_args) = match Specs.proc_resolve_attributes proc_name with - | None -> - [(prop_, path)] + | None + -> [(prop_, path)] | Some callee_attributes -> - match PredSymb.get_sentinel_func_attribute_value - callee_attributes.ProcAttributes.func_attributes with - | None -> [(prop_, path)] - | Some sentinel_arg -> - let formals = callee_attributes.ProcAttributes.formals in - check_variadic_sentinel (List.length formals) sentinel_arg builtin_args + match + PredSymb.get_sentinel_func_attribute_value callee_attributes.ProcAttributes.func_attributes + with + | None + -> [(prop_, path)] + | Some sentinel_arg + -> let formals = callee_attributes.ProcAttributes.formals in + check_variadic_sentinel (List.length formals) sentinel_arg builtin_args and sym_exec_objc_getter field_name ret_typ tenv ret_id pdesc pname loc args prop = - L.d_strln ("No custom getter found. Executing the ObjC builtin getter with ivar "^ - (Typ.Fieldname.to_string field_name)^"."); - let ret_id = - match ret_id with - | Some (ret_id, _) -> ret_id - | None -> assert false in + L.d_strln + ( "No custom getter found. Executing the ObjC builtin getter with ivar " + ^ Typ.Fieldname.to_string field_name ^ "." ) ; + let ret_id = match ret_id with Some (ret_id, _) -> ret_id | None -> assert false in match args with - | [(lexp, ({Typ.desc=Tstruct _} as typ | {desc=Tptr ({desc=Tstruct _} as typ, _)}))] -> - let field_access_exp = Exp.Lfield (lexp, field_name, typ) in - execute_load - ~report_deref_errors:false pname pdesc tenv ret_id field_access_exp ret_typ loc prop - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | [(lexp, ({Typ.desc= Tstruct _} as typ | {desc= Tptr (({desc= Tstruct _} as typ), _)}))] + -> let field_access_exp = Exp.Lfield (lexp, field_name, typ) in + execute_load ~report_deref_errors:false pname pdesc tenv ret_id field_access_exp ret_typ loc + prop + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) and sym_exec_objc_setter field_name _ tenv _ pdesc pname loc args prop = - L.d_strln ("No custom setter found. Executing the ObjC builtin setter with ivar "^ - (Typ.Fieldname.to_string field_name)^"."); + L.d_strln + ( "No custom setter found. Executing the ObjC builtin setter with ivar " + ^ Typ.Fieldname.to_string field_name ^ "." ) ; match args with - | (lexp1, ({Typ.desc=Tstruct _} as typ1 | {Typ.desc=Tptr (typ1, _)})) :: (lexp2, typ2) :: _ -> - let field_access_exp = Exp.Lfield (lexp1, field_name, typ1) in - execute_store ~report_deref_errors:false pname pdesc tenv field_access_exp typ2 lexp2 loc prop - | _ -> raise (Exceptions.Wrong_argument_number __POS__) + | (lexp1, ({Typ.desc= Tstruct _} as typ1 | {Typ.desc= Tptr (typ1, _)})) :: (lexp2, typ2) :: _ + -> let field_access_exp = Exp.Lfield (lexp1, field_name, typ1) in + execute_store ~report_deref_errors:false pname pdesc tenv field_access_exp typ2 lexp2 loc + prop + | _ + -> raise (Exceptions.Wrong_argument_number __POS__) and sym_exec_objc_accessor property_accesor ret_typ tenv ret_id pdesc _ loc args prop path - : Builtin.ret_typ = + : Builtin.ret_typ = let f_accessor = match property_accesor with - | ProcAttributes.Objc_getter field_name -> sym_exec_objc_getter field_name - | ProcAttributes.Objc_setter field_name -> sym_exec_objc_setter field_name in + | ProcAttributes.Objc_getter field_name + -> sym_exec_objc_getter field_name + | ProcAttributes.Objc_setter field_name + -> sym_exec_objc_setter field_name + in (* we want to execute in the context of the current procedure, not in the context of callee_pname, since this is the procname of the setter/getter method *) let cur_pname = Procdesc.get_proc_name pdesc in - f_accessor ret_typ tenv ret_id pdesc cur_pname loc args prop - |> List.map ~f:(fun p -> (p, path)) + f_accessor ret_typ tenv ret_id pdesc cur_pname loc args prop |> List.map ~f:(fun p -> (p, path)) (** Perform symbolic execution for a function call *) -and proc_call - callee_summary {Builtin.pdesc; tenv; prop_= pre; path; ret_id; args= actual_pars; loc; } = +and proc_call callee_summary + {Builtin.pdesc; tenv; prop_= pre; path; ret_id; args= actual_pars; loc} = let caller_pname = Procdesc.get_proc_name pdesc in let callee_pname = Specs.get_proc_name callee_summary in let ret_typ = Specs.get_ret_type callee_summary in let check_return_value_ignored () = (* check if the return value of the call is ignored, and issue a warning *) - let is_ignored = match ret_typ.Typ.desc, ret_id with - | Typ.Tvoid, _ -> false - | _, None -> true - | _, Some (id, _) -> Errdesc.id_is_assigned_then_dead (State.get_node ()) id in - if is_ignored - && is_none (Specs.get_flag callee_summary ProcAttributes.proc_flag_ignore_return) then + let is_ignored = + match (ret_typ.Typ.desc, ret_id) with + | Typ.Tvoid, _ + -> false + | _, None + -> true + | _, Some (id, _) + -> Errdesc.id_is_assigned_then_dead (State.get_node ()) id + in + if is_ignored && is_none (Specs.get_flag callee_summary ProcAttributes.proc_flag_ignore_return) + then let err_desc = Localise.desc_return_value_ignored callee_pname loc in - let exn = (Exceptions.Return_value_ignored (err_desc, __POS__)) in - Reporting.log_warning_deprecated caller_pname exn in - check_inherently_dangerous_function caller_pname callee_pname; - begin - let formal_types = List.map ~f:snd (Specs.get_formals callee_summary) in - let rec comb actual_pars formal_types = - match actual_pars, formal_types with - | [], [] -> actual_pars - | (e, t_e):: etl', _:: tl' -> - (e, t_e) :: comb etl' tl' - | _,[] -> - Errdesc.warning_err - (State.get_loc ()) - "likely use of variable-arguments function, or function prototype missing@."; - L.d_warning - "likely use of variable-arguments function, or function prototype missing"; - L.d_ln(); - L.d_str "actual parameters: "; Sil.d_exp_list (List.map ~f:fst actual_pars); L.d_ln (); - L.d_str "formal parameters: "; Typ.d_list formal_types; L.d_ln (); - actual_pars - | [], _ -> - L.d_str ("**** ERROR: Procedure " ^ Typ.Procname.to_string callee_pname); - L.d_strln (" mismatch in the number of parameters ****"); - L.d_str "actual parameters: "; Sil.d_exp_list (List.map ~f:fst actual_pars); L.d_ln (); - L.d_str "formal parameters: "; Typ.d_list formal_types; L.d_ln (); - raise (Exceptions.Wrong_argument_number __POS__) in - let actual_params = comb actual_pars formal_types in - (* Actual parameters are associated to their formal + let exn = Exceptions.Return_value_ignored (err_desc, __POS__) in + Reporting.log_warning_deprecated caller_pname exn + in + check_inherently_dangerous_function caller_pname callee_pname ; + let formal_types = List.map ~f:snd (Specs.get_formals callee_summary) in + let rec comb actual_pars formal_types = + match (actual_pars, formal_types) with + | [], [] + -> actual_pars + | (e, t_e) :: etl', _ :: tl' + -> (e, t_e) :: comb etl' tl' + | _, [] + -> Errdesc.warning_err (State.get_loc ()) + "likely use of variable-arguments function, or function prototype missing@." ; + L.d_warning "likely use of variable-arguments function, or function prototype missing" ; + L.d_ln () ; + L.d_str "actual parameters: " ; + Sil.d_exp_list (List.map ~f:fst actual_pars) ; + L.d_ln () ; + L.d_str "formal parameters: " ; + Typ.d_list formal_types ; + L.d_ln () ; + actual_pars + | [], _ + -> L.d_str ("**** ERROR: Procedure " ^ Typ.Procname.to_string callee_pname) ; + L.d_strln " mismatch in the number of parameters ****" ; + L.d_str "actual parameters: " ; + Sil.d_exp_list (List.map ~f:fst actual_pars) ; + L.d_ln () ; + L.d_str "formal parameters: " ; + Typ.d_list formal_types ; + L.d_ln () ; + raise (Exceptions.Wrong_argument_number __POS__) + in + let actual_params = comb actual_pars formal_types in + (* Actual parameters are associated to their formal parameter type if there are enough formal parameters, and to their actual type otherwise. The latter case happens with variable - arguments functions *) - check_return_value_ignored (); - (* In case we call an objc instance method we add and extra spec *) - (* were the receiver is null and the semantics of the call is nop*) - (* let callee_attrs = Specs.get_attributes callee_summary in *) - if (!Config.curr_language <> Config.Java) && - (Specs.get_attributes callee_summary).ProcAttributes.is_objc_instance_method then - handle_objc_instance_method_call actual_pars actual_params pre tenv ret_id pdesc - callee_pname loc path (Tabulation.exe_function_call callee_summary) - else (* non-objective-c method call. Standard tabulation *) - Tabulation.exe_function_call - callee_summary tenv ret_id pdesc callee_pname loc actual_params pre path - end + check_return_value_ignored () ; + (* In case we call an objc instance method we add and extra spec *) + (* were the receiver is null and the semantics of the call is nop*) + (* let callee_attrs = Specs.get_attributes callee_summary in *) + if !Config.curr_language <> Config.Java + && (Specs.get_attributes callee_summary).ProcAttributes.is_objc_instance_method + then + handle_objc_instance_method_call actual_pars actual_params pre tenv ret_id pdesc callee_pname + loc path (Tabulation.exe_function_call callee_summary) + else + (* non-objective-c method call. Standard tabulation *) + Tabulation.exe_function_call callee_summary tenv ret_id pdesc callee_pname loc actual_params + pre path (** perform symbolic execution for a single prop, and check for junk *) and sym_exec_wrapper handle_exn tenv pdesc instr ((prop: Prop.normal Prop.t), path) - : Paths.PathSet.t = + : Paths.PathSet.t = let pname = Procdesc.get_proc_name pdesc in - let prop_primed_to_normal p = (* Rename primed vars with fresh normal vars, and return them *) + let prop_primed_to_normal p = + (* Rename primed vars with fresh normal vars, and return them *) let fav = Prop.prop_fav p in - Sil.fav_filter_ident fav Ident.is_primed; + Sil.fav_filter_ident fav Ident.is_primed ; let ids_primed = Sil.fav_to_list fav in let ids_primed_normal = - List.map ~f:(fun id -> (id, Ident.create_fresh Ident.knormal)) ids_primed in + List.map ~f:(fun id -> (id, Ident.create_fresh Ident.knormal)) ids_primed + in let ren_sub = - Sil.subst_of_list (List.map - ~f:(fun (id1, id2) -> (id1, Exp.Var id2)) ids_primed_normal) in + Sil.subst_of_list (List.map ~f:(fun (id1, id2) -> (id1, Exp.Var id2)) ids_primed_normal) + in let p' = Prop.normalize tenv (Prop.prop_sub ren_sub p) in let fav_normal = Sil.fav_from_list (List.map ~f:snd ids_primed_normal) in - p', fav_normal in - let prop_normal_to_primed fav_normal p = (* rename given normal vars to fresh primed *) - if List.is_empty (Sil.fav_to_list fav_normal) then p - else Prop.exist_quantify tenv fav_normal p in + (p', fav_normal) + in + let prop_normal_to_primed fav_normal p = + (* rename given normal vars to fresh primed *) + if List.is_empty (Sil.fav_to_list fav_normal) then p else Prop.exist_quantify tenv fav_normal p + in try let pre_process_prop p = let p', fav = - if Sil.instr_is_auxiliary instr - then p, Sil.fav_new () - else prop_primed_to_normal p in + if Sil.instr_is_auxiliary instr then (p, Sil.fav_new ()) else prop_primed_to_normal p + in let p'' = - let map_res_action e ra = (* update the vpath in resource attributes *) + let map_res_action e ra = + (* update the vpath in resource attributes *) let vpath, _ = Errdesc.vpath_find tenv p' e in - { ra with PredSymb.ra_vpath = vpath } in - Attribute.map_resource tenv p' map_res_action in - p'', fav in + {ra with PredSymb.ra_vpath= vpath} + in + Attribute.map_resource tenv p' map_res_action + in + (p'', fav) + in let post_process_result fav_normal p path = let p' = prop_normal_to_primed fav_normal p in - State.set_path path None; + State.set_path path None ; let node_has_abstraction node = - let instr_is_abstraction = function - | Sil.Abstract _ -> true - | _ -> false in - List.exists ~f:instr_is_abstraction (Procdesc.Node.get_instrs node) in + let instr_is_abstraction = function Sil.Abstract _ -> true | _ -> false in + List.exists ~f:instr_is_abstraction (Procdesc.Node.get_instrs node) + in let curr_node = State.get_node () in match Procdesc.Node.get_kind curr_node with - | Procdesc.Node.Prune_node _ when not (node_has_abstraction curr_node) -> - (* don't check for leaks in prune nodes, unless there is abstraction anyway,*) + | Procdesc.Node.Prune_node _ when not (node_has_abstraction curr_node) + -> (* don't check for leaks in prune nodes, unless there is abstraction anyway,*) (* but force them into either branch *) p' - | _ -> - check_deallocate_static_memory (Abs.abstract_junk ~original_prop: p pname tenv p') in - L.d_str "Instruction "; Sil.d_instr instr; L.d_ln (); + | _ + -> check_deallocate_static_memory (Abs.abstract_junk ~original_prop:p pname tenv p') + in + L.d_str "Instruction " ; + Sil.d_instr instr ; + L.d_ln () ; let prop', fav_normal = pre_process_prop prop in let res_list = - Config.run_with_abs_val_equal_zero (* no exp abstraction during sym exe *) - (fun () -> sym_exec tenv pdesc instr prop' path) - () in + Config.run_with_abs_val_equal_zero + (* no exp abstraction during sym exe *) + (fun () -> sym_exec tenv pdesc instr prop' path) + () + in let res_list_nojunk = - List.map - ~f:(fun (p, path) -> (post_process_result fav_normal p path, path)) - res_list in + List.map ~f:(fun (p, path) -> (post_process_result fav_normal p path, path)) res_list + in let results = List.map ~f:(fun (p, path) -> (Prop.prop_rename_primed_footprint_vars tenv p, path)) - res_list_nojunk in - L.d_strln "Instruction Returns"; - Propgraph.d_proplist prop (List.map ~f:fst results); L.d_ln (); - State.mark_instr_ok (); + res_list_nojunk + in + L.d_strln "Instruction Returns" ; + Propgraph.d_proplist prop (List.map ~f:fst results) ; + L.d_ln () ; + State.mark_instr_ok () ; Paths.PathSet.from_renamed_list results with exn when Exceptions.handle_exception exn && !Config.footprint -> - handle_exn exn; (* calls State.mark_instr_fail *) - Paths.PathSet.empty + handle_exn exn ; (* calls State.mark_instr_fail *) + Paths.PathSet.empty (** {2 Lifted Abstract Transfer Functions} *) -let node handle_exn tenv pdesc node (pset : Paths.PathSet.t) : Paths.PathSet.t = +let node handle_exn tenv pdesc node (pset: Paths.PathSet.t) : Paths.PathSet.t = let pname = Procdesc.get_proc_name pdesc in let exe_instr_prop instr p tr (pset1: Paths.PathSet.t) = let pset2 = @@ -1720,13 +1919,16 @@ let node handle_exn tenv pdesc node (pset : Paths.PathSet.t) : Paths.PathSet.t = && Procdesc.Node.get_kind node <> Procdesc.Node.exn_handler_kind (* skip normal instructions if an exception was thrown, unless this is an exception handler node *) - then - begin - L.d_str "Skipping instr "; Sil.d_instr instr; L.d_strln " due to exception"; - Paths.PathSet.from_renamed_list [(p, tr)] - end - else sym_exec_wrapper handle_exn tenv pdesc instr (p, tr) in - Paths.PathSet.union pset2 pset1 in + then ( + L.d_str "Skipping instr " ; + Sil.d_instr instr ; + L.d_strln " due to exception" ; + Paths.PathSet.from_renamed_list [(p, tr)] ) + else sym_exec_wrapper handle_exn tenv pdesc instr (p, tr) + in + Paths.PathSet.union pset2 pset1 + in let exe_instr_pset pset instr = - Paths.PathSet.fold (exe_instr_prop instr) pset Paths.PathSet.empty in + Paths.PathSet.fold (exe_instr_prop instr) pset Paths.PathSet.empty + in List.fold ~f:exe_instr_pset ~init:pset (Procdesc.Node.get_instrs node) diff --git a/infer/src/backend/symExec.mli b/infer/src/backend/symExec.mli index 146567fdd..2974d16ad 100644 --- a/infer/src/backend/symExec.mli +++ b/infer/src/backend/symExec.mli @@ -12,18 +12,18 @@ open! IStd (** Symbolic Execution *) -(** Symbolic execution of the instructions of a node, lifted to sets of propositions. *) val node : (exn -> unit) -> Tenv.t -> Procdesc.t -> Procdesc.Node.t -> Paths.PathSet.t -> Paths.PathSet.t +(** Symbolic execution of the instructions of a node, lifted to sets of propositions. *) +val instrs : + ?mask_errors:bool -> Tenv.t -> Procdesc.t -> Sil.instr list + -> (Prop.normal Prop.t * Paths.Path.t) list -> (Prop.normal Prop.t * Paths.Path.t) list (** Symbolic execution of a sequence of instructions. If errors occur and [mask_errors] is true, just treat as skip. *) -val instrs : - ?mask_errors:bool -> Tenv.t -> Procdesc.t -> Sil.instr list -> - (Prop.normal Prop.t * Paths.Path.t) list -> (Prop.normal Prop.t * Paths.Path.t) list -(** Symbolic execution of the divergent pure computation. *) val diverge : Prop.normal Prop.t -> Paths.Path.t -> (Prop.normal Prop.t * Paths.Path.t) list +(** Symbolic execution of the divergent pure computation. *) val proc_call : Specs.summary -> Builtin.t @@ -32,17 +32,17 @@ val unknown_or_scan_call : is_scan:bool -> Typ.t option -> Annot.Item.t -> Built val check_variadic_sentinel : ?fails_on_nil:bool -> int -> int * int -> Builtin.t val check_untainted : - Tenv.t -> Exp.t -> PredSymb.taint_kind -> Typ.Procname.t -> Typ.Procname.t -> Prop.normal Prop.t -> - Prop.normal Prop.t + Tenv.t -> Exp.t -> PredSymb.taint_kind -> Typ.Procname.t -> Typ.Procname.t -> Prop.normal Prop.t + -> Prop.normal Prop.t -(** Check for arithmetic problems and normalize an expression. *) val check_arith_norm_exp : Tenv.t -> Typ.Procname.t -> Exp.t -> Prop.normal Prop.t -> Exp.t * Prop.normal Prop.t +(** Check for arithmetic problems and normalize an expression. *) val prune : Tenv.t -> positive:bool -> Exp.t -> Prop.normal Prop.t -> Propset.t +val resolve_method : Tenv.t -> Typ.Name.t -> Typ.Procname.t -> Typ.Procname.t (** OO method resolution: given a class name and a method name, climb the class hierarchy to find the procname that the method name will actually resolve to at runtime. For example, if we have a procname like Foo.toString() and Foo does not override toString(), we must resolve the call to toString(). We will end up with Super.toString() where Super is some superclass of Foo. *) -val resolve_method : Tenv.t -> Typ.Name.t -> Typ.Procname.t -> Typ.Procname.t diff --git a/infer/src/backend/tabulation.ml b/infer/src/backend/tabulation.ml index 1371174ec..e6e6478c3 100644 --- a/infer/src/backend/tabulation.ml +++ b/infer/src/backend/tabulation.ml @@ -15,47 +15,38 @@ open! IStd module L = Logging module F = Format -type splitting = { - sub: Sil.subst; - frame: Sil.hpred list; - missing_pi: Sil.atom list; - missing_sigma: Sil.hpred list; - frame_fld : Sil.hpred list; - missing_fld : Sil.hpred list; - frame_typ : (Exp.t * Exp.t) list; - missing_typ : (Exp.t * Exp.t) list; -} +type splitting = + { sub: Sil.subst + ; frame: Sil.hpred list + ; missing_pi: Sil.atom list + ; missing_sigma: Sil.hpred list + ; frame_fld: Sil.hpred list + ; missing_fld: Sil.hpred list + ; frame_typ: (Exp.t * Exp.t) list + ; missing_typ: (Exp.t * Exp.t) list } type deref_error = - | Deref_freed of PredSymb.res_action (** dereference a freed pointer *) - | Deref_minusone (** dereference -1 *) - | Deref_null of PredSymb.path_pos (** dereference null *) + | Deref_freed of PredSymb.res_action (** dereference a freed pointer *) + | Deref_minusone (** dereference -1 *) + | Deref_null of PredSymb.path_pos (** dereference null *) | Deref_undef of Typ.Procname.t * Location.t * PredSymb.path_pos - (** dereference a value coming from the given undefined function *) - | Deref_undef_exp (** dereference an undefined expression *) + (** dereference a value coming from the given undefined function *) + | Deref_undef_exp (** dereference an undefined expression *) type invalid_res = | Dereference_error of deref_error * Localise.error_desc * Paths.Path.t option - (** dereference error and description *) - - | Prover_checks of Prover.check list - (** the abduction prover failed some checks *) - - | Cannot_combine - (** cannot combine actual pre with splitting and post *) - - | Missing_fld_not_empty - (** missing_fld not empty in re-execution mode *) - - | Missing_sigma_not_empty - (** missing sigma not empty in re-execution mode *) + (** dereference error and description *) + | Prover_checks of Prover.check list (** the abduction prover failed some checks *) + | Cannot_combine (** cannot combine actual pre with splitting and post *) + | Missing_fld_not_empty (** missing_fld not empty in re-execution mode *) + | Missing_sigma_not_empty (** missing sigma not empty in re-execution mode *) type valid_res = - { incons_pre_missing : bool; (** whether the actual pre is consistent with the missing part *) - vr_pi: Sil.atom list; (** missing pi *) - vr_sigma: Sil.hpred list; (** missing sigma *) - vr_cons_res : (Prop.normal Prop.t * Paths.Path.t) list; (** consistent result props *) - vr_incons_res : (Prop.normal Prop.t * Paths.Path.t) list; (** inconsistent result props *) } + { incons_pre_missing: bool (** whether the actual pre is consistent with the missing part *) + ; vr_pi: Sil.atom list (** missing pi *) + ; vr_sigma: Sil.hpred list (** missing sigma *) + ; vr_cons_res: (Prop.normal Prop.t * Paths.Path.t) list (** consistent result props *) + ; vr_incons_res: (Prop.normal Prop.t * Paths.Path.t) list (** inconsistent result props *) } (** Result of (bi)-abduction on a single spec. A result is invalid if no splitting was found, @@ -63,48 +54,60 @@ type valid_res = part of the splitting is not empty. A valid result contains the missing pi ans sigma, as well as the resulting props. *) type abduction_res = - | Valid_res of valid_res (** valid result for a function cal *) - | Invalid_res of invalid_res (** reason for invalid result *) + | Valid_res of valid_res (** valid result for a function cal *) + | Invalid_res of invalid_res (** reason for invalid result *) (**************** printing functions ****************) let d_splitting split = - L.d_strln "Actual splitting"; - L.d_increase_indent 1; - L.d_strln "------------------------------------------------------------"; - L.d_strln "SUB = "; Prop.d_sub split.sub; L.d_ln (); - L.d_strln "FRAME ="; Prop.d_sigma split.frame; L.d_ln (); - L.d_strln "MISSING ="; Prop.d_pi_sigma split.missing_pi split.missing_sigma; L.d_ln (); - L.d_strln "FRAME FLD = "; Prop.d_sigma split.frame_fld; L.d_ln (); - L.d_strln "MISSING FLD = "; Prop.d_sigma split.missing_fld; L.d_ln (); - if split.frame_typ <> [] - then L.d_strln "FRAME TYP = "; Prover.d_typings split.frame_typ; L.d_ln (); - if split.missing_typ <> [] - then L.d_strln "MISSING TYP = "; Prover.d_typings split.missing_typ; L.d_ln (); - L.d_strln "------------------------------------------------------------"; + L.d_strln "Actual splitting" ; + L.d_increase_indent 1 ; + L.d_strln "------------------------------------------------------------" ; + L.d_strln "SUB = " ; + Prop.d_sub split.sub ; + L.d_ln () ; + L.d_strln "FRAME =" ; + Prop.d_sigma split.frame ; + L.d_ln () ; + L.d_strln "MISSING =" ; + Prop.d_pi_sigma split.missing_pi split.missing_sigma ; + L.d_ln () ; + L.d_strln "FRAME FLD = " ; + Prop.d_sigma split.frame_fld ; + L.d_ln () ; + L.d_strln "MISSING FLD = " ; + Prop.d_sigma split.missing_fld ; + L.d_ln () ; + if split.frame_typ <> [] then L.d_strln "FRAME TYP = " ; + Prover.d_typings split.frame_typ ; + L.d_ln () ; + if split.missing_typ <> [] then L.d_strln "MISSING TYP = " ; + Prover.d_typings split.missing_typ ; + L.d_ln () ; + L.d_strln "------------------------------------------------------------" ; L.d_decrease_indent 1 let print_results tenv actual_pre results = - L.d_strln "***** RESULTS FUNCTION CALL *******"; - Propset.d actual_pre (Propset.from_proplist tenv results); + L.d_strln "***** RESULTS FUNCTION CALL *******" ; + Propset.d actual_pre (Propset.from_proplist tenv results) ; L.d_strln "***** END RESULTS FUNCTION CALL *******" + (***************) (** Rename the variables in the spec. *) let spec_rename_vars pname spec = let prop_add_callee_suffix p = - let f = function - | Exp.Lvar pv -> - Exp.Lvar (Pvar.to_callee pname pv) - | e -> e in - Prop.prop_expmap f p in + let f = function Exp.Lvar pv -> Exp.Lvar (Pvar.to_callee pname pv) | e -> e in + Prop.prop_expmap f p + in let jprop_add_callee_suffix = function - | Specs.Jprop.Prop (n, p) -> - Specs.Jprop.Prop (n, prop_add_callee_suffix p) - | Specs.Jprop.Joined (n, p, jp1, jp2) -> - Specs.Jprop.Joined (n, prop_add_callee_suffix p, jp1, jp2) in + | Specs.Jprop.Prop (n, p) + -> Specs.Jprop.Prop (n, prop_add_callee_suffix p) + | Specs.Jprop.Joined (n, p, jp1, jp2) + -> Specs.Jprop.Joined (n, prop_add_callee_suffix p, jp1, jp2) + in let fav = Sil.fav_new () in - Specs.Jprop.fav_add fav spec.Specs.pre; - List.iter ~f:(fun (p, _) -> Prop.prop_fav_add fav p) spec.Specs.posts; + Specs.Jprop.fav_add fav spec.Specs.pre ; + List.iter ~f:(fun (p, _) -> Prop.prop_fav_add fav p) spec.Specs.posts ; let ids = Sil.fav_to_list fav in let ids' = List.map ~f:(fun i -> (i, Ident.create_fresh Ident.kprimed)) ids in let ren_sub = Sil.subst_of_list (List.map ~f:(fun (i, i') -> (i, Exp.Var i')) ids') in @@ -112,177 +115,197 @@ let spec_rename_vars pname spec = let posts' = List.map ~f:(fun (p, path) -> (Prop.prop_sub ren_sub p, path)) spec.Specs.posts in let pre'' = jprop_add_callee_suffix pre' in let posts'' = List.map ~f:(fun (p, path) -> (prop_add_callee_suffix p, path)) posts' in - { Specs.pre = pre''; Specs.posts = posts''; Specs.visited = spec.Specs.visited } + {Specs.pre= pre''; Specs.posts= posts''; Specs.visited= spec.Specs.visited} (** Find and number the specs for [proc_name], after renaming their vars, and also return the parameters *) -let spec_find_rename trace_call summary - : (int * Prop.exposed Specs.spec) list * Pvar.t list = +let spec_find_rename trace_call summary : (int * Prop.exposed Specs.spec) list * Pvar.t list = let proc_name = Specs.get_proc_name summary in try let count = ref 0 in - let f spec = - incr count; (!count, spec_rename_vars proc_name spec) in + let f spec = incr count ; (!count, spec_rename_vars proc_name spec) in let specs = Specs.get_specs_from_payload summary in let formals = Specs.get_formals summary in - if List.is_empty specs then - begin - trace_call Specs.CallStats.CR_not_found; - raise (Exceptions.Precondition_not_found - (Localise.verbatim_desc (Typ.Procname.to_string proc_name), __POS__)) - end; - let formal_parameters = - List.map ~f:(fun (x, _) -> Pvar.mk_callee x proc_name) formals in - List.map ~f specs, formal_parameters - with Not_found -> begin - L.d_strln - ("ERROR: found no entry for procedure " ^ - Typ.Procname.to_string proc_name ^ - ". Give up..."); - raise (Exceptions.Precondition_not_found - (Localise.verbatim_desc (Typ.Procname.to_string proc_name), __POS__)) - end + if List.is_empty specs then ( + trace_call Specs.CallStats.CR_not_found ; + raise + (Exceptions.Precondition_not_found + (Localise.verbatim_desc (Typ.Procname.to_string proc_name), __POS__)) ) ; + let formal_parameters = List.map ~f:(fun (x, _) -> Pvar.mk_callee x proc_name) formals in + (List.map ~f specs, formal_parameters) + with Not_found -> + L.d_strln + ("ERROR: found no entry for procedure " ^ Typ.Procname.to_string proc_name ^ ". Give up...") ; + raise + (Exceptions.Precondition_not_found + (Localise.verbatim_desc (Typ.Procname.to_string proc_name), __POS__)) (** Process a splitting coming straight from a call to the prover: change the instantiating substitution so that it returns primed vars, except for vars occurring in the missing part, where it returns footprint vars. *) -let process_splitting - actual_pre sub1 sub2 frame missing_pi missing_sigma - frame_fld missing_fld frame_typ missing_typ = - +let process_splitting actual_pre sub1 sub2 frame missing_pi missing_sigma frame_fld missing_fld + frame_typ missing_typ = let hpred_has_only_footprint_vars hpred = let fav = Sil.fav_new () in - Sil.hpred_fav_add fav hpred; - Sil.fav_for_all fav Ident.is_footprint in + Sil.hpred_fav_add fav hpred ; Sil.fav_for_all fav Ident.is_footprint + in let sub = Sil.sub_join sub1 sub2 in let sub1_inverse = let sub1_list = Sil.sub_to_list sub1 in - let sub1_list' = List.filter ~f:(function (_, Exp.Var _) -> true | _ -> false) sub1_list in + let sub1_list' = List.filter ~f:(function _, Exp.Var _ -> true | _ -> false) sub1_list in let sub1_inverse_list = - List.map - ~f:(function (id, Exp.Var id') -> (id', Exp.Var id) | _ -> assert false) - sub1_list' - in Sil.exp_subst_of_list_duplicates sub1_inverse_list in + List.map ~f:(function id, Exp.Var id' -> (id', Exp.Var id) | _ -> assert false) sub1_list' + in + Sil.exp_subst_of_list_duplicates sub1_inverse_list + in let fav_actual_pre = - let fav_sub2 = (* vars which represent expansions of fields *) + let fav_sub2 = + (* vars which represent expansions of fields *) let fav = Sil.fav_new () in - List.iter ~f:(Sil.exp_fav_add fav) (Sil.sub_range sub2); + List.iter ~f:(Sil.exp_fav_add fav) (Sil.sub_range sub2) ; let filter id = Int.equal (Ident.get_stamp id) (-1) in - Sil.fav_filter_ident fav filter; - fav in + Sil.fav_filter_ident fav filter ; fav + in let fav_pre = Prop.prop_fav actual_pre in - Sil.ident_list_fav_add (Sil.fav_to_list fav_sub2) fav_pre; - fav_pre in - + Sil.ident_list_fav_add (Sil.fav_to_list fav_sub2) fav_pre ; + fav_pre + in let fav_missing = Prop.sigma_fav (Prop.sigma_sub (`Exp sub) missing_sigma) in - Prop.pi_fav_add fav_missing (Prop.pi_sub (`Exp sub) missing_pi); + Prop.pi_fav_add fav_missing (Prop.pi_sub (`Exp sub) missing_pi) ; let fav_missing_primed = - let filter id = Ident.is_primed id && not (Sil.fav_mem fav_actual_pre id) - in Sil.fav_copy_filter_ident fav_missing filter in + let filter id = Ident.is_primed id && not (Sil.fav_mem fav_actual_pre id) in + Sil.fav_copy_filter_ident fav_missing filter + in let fav_missing_fld = Prop.sigma_fav (Prop.sigma_sub (`Exp sub) missing_fld) in - let map_var_to_pre_var_or_fresh id = match Sil.exp_sub (`Exp sub1_inverse) (Exp.Var id) with - | Exp.Var id' -> - if Sil.fav_mem fav_actual_pre id' || Ident.is_path id' - (* a path id represents a position in the pre *) + | Exp.Var id' + -> if Sil.fav_mem fav_actual_pre id' || Ident.is_path id' + (* a path id represents a position in the pre *) then Exp.Var id' else Exp.Var (Ident.create_fresh Ident.kprimed) - | _ -> assert false in - + | _ + -> assert false + in let sub_list = Sil.sub_to_list sub in let fav_sub_list = let fav_sub = Sil.fav_new () in - List.iter ~f:(fun (_, e) -> Sil.exp_fav_add fav_sub e) sub_list; - Sil.fav_to_list fav_sub in + List.iter ~f:(fun (_, e) -> Sil.exp_fav_add fav_sub e) sub_list ; + Sil.fav_to_list fav_sub + in let sub1 = let f id = if Sil.fav_mem fav_actual_pre id then (id, Exp.Var id) else if Ident.is_normal id then (id, map_var_to_pre_var_or_fresh id) else if Sil.fav_mem fav_missing_fld id then (id, Exp.Var id) else if Ident.is_footprint id then (id, Exp.Var id) - else begin + else let dom1 = Sil.sub_domain sub1 in let rng1 = Sil.sub_range sub1 in let dom2 = Sil.sub_domain sub2 in let rng2 = Sil.sub_range sub2 in - let vars_actual_pre = List.map ~f:(fun id -> Exp.Var id) (Sil.fav_to_list fav_actual_pre) in - L.d_str "fav_actual_pre: "; Sil.d_exp_list vars_actual_pre; L.d_ln (); - L.d_str "Dom(Sub1): "; Sil.d_exp_list (List.map ~f:(fun id -> Exp.Var id) dom1); L.d_ln (); - L.d_str "Ran(Sub1): "; Sil.d_exp_list rng1; L.d_ln (); - L.d_str "Dom(Sub2): "; Sil.d_exp_list (List.map ~f:(fun id -> Exp.Var id) dom2); L.d_ln (); - L.d_str "Ran(Sub2): "; Sil.d_exp_list rng2; L.d_ln (); - L.d_str "Don't know about id: "; Sil.d_exp (Exp.Var id); L.d_ln (); - assert false; - end - in Sil.subst_of_list (List.map ~f fav_sub_list) in + let vars_actual_pre = + List.map ~f:(fun id -> Exp.Var id) (Sil.fav_to_list fav_actual_pre) + in + L.d_str "fav_actual_pre: " ; + Sil.d_exp_list vars_actual_pre ; + L.d_ln () ; + L.d_str "Dom(Sub1): " ; + Sil.d_exp_list (List.map ~f:(fun id -> Exp.Var id) dom1) ; + L.d_ln () ; + L.d_str "Ran(Sub1): " ; + Sil.d_exp_list rng1 ; + L.d_ln () ; + L.d_str "Dom(Sub2): " ; + Sil.d_exp_list (List.map ~f:(fun id -> Exp.Var id) dom2) ; + L.d_ln () ; + L.d_str "Ran(Sub2): " ; + Sil.d_exp_list rng2 ; + L.d_ln () ; + L.d_str "Don't know about id: " ; + Sil.d_exp (Exp.Var id) ; + L.d_ln () ; + assert false + in + Sil.subst_of_list (List.map ~f fav_sub_list) + in let sub2_list = - let f id = (id, Exp.Var (Ident.create_fresh Ident.kfootprint)) - in List.map ~f (Sil.fav_to_list fav_missing_primed) in - let sub_list' = - List.map ~f:(fun (id, e) -> (id, Sil.exp_sub sub1 e)) sub_list in + let f id = (id, Exp.Var (Ident.create_fresh Ident.kfootprint)) in + List.map ~f (Sil.fav_to_list fav_missing_primed) + in + let sub_list' = List.map ~f:(fun (id, e) -> (id, Sil.exp_sub sub1 e)) sub_list in let sub' = Sil.subst_of_list (sub2_list @ sub_list') in (* normalize everything w.r.t sub' *) let norm_missing_pi = Prop.pi_sub sub' missing_pi in let norm_missing_sigma = Prop.sigma_sub sub' missing_sigma in let norm_frame_fld = Prop.sigma_sub sub' frame_fld in let norm_frame_typ = - List.map ~f:(fun (e, te) -> Sil.exp_sub sub' e, Sil.exp_sub sub' te) frame_typ in + List.map ~f:(fun (e, te) -> (Sil.exp_sub sub' e, Sil.exp_sub sub' te)) frame_typ + in let norm_missing_typ = - List.map ~f:(fun (e, te) -> Sil.exp_sub sub' e, Sil.exp_sub sub' te) missing_typ in + List.map ~f:(fun (e, te) -> (Sil.exp_sub sub' e, Sil.exp_sub sub' te)) missing_typ + in let norm_missing_fld = let sigma = Prop.sigma_sub sub' missing_fld in let filter hpred = - if not (hpred_has_only_footprint_vars hpred) then - begin - L.d_warning "Missing fields hpred has non-footprint vars: "; Sil.d_hpred hpred; L.d_ln (); - false - end - else match hpred with - | Sil.Hpointsto(Exp.Var _, _, _) -> true - | Sil.Hpointsto(Exp.Lvar pvar, _, _) -> Pvar.is_global pvar - | _ -> - L.d_warning "Missing fields in complex pred: "; Sil.d_hpred hpred; L.d_ln (); - false in - List.filter ~f:filter sigma in + if not (hpred_has_only_footprint_vars hpred) then ( + L.d_warning "Missing fields hpred has non-footprint vars: " ; + Sil.d_hpred hpred ; + L.d_ln () ; + false ) + else + match hpred with + | Sil.Hpointsto (Exp.Var _, _, _) + -> true + | Sil.Hpointsto (Exp.Lvar pvar, _, _) + -> Pvar.is_global pvar + | _ + -> L.d_warning "Missing fields in complex pred: " ; Sil.d_hpred hpred ; L.d_ln () ; false + in + List.filter ~f:filter sigma + in let norm_frame = Prop.sigma_sub sub' frame in - { sub = sub'; - frame = norm_frame; - missing_pi = norm_missing_pi; - missing_sigma = norm_missing_sigma; - frame_fld = norm_frame_fld; - missing_fld = norm_missing_fld; - frame_typ = norm_frame_typ; - missing_typ = norm_missing_typ; } + { sub= sub' + ; frame= norm_frame + ; missing_pi= norm_missing_pi + ; missing_sigma= norm_missing_sigma + ; frame_fld= norm_frame_fld + ; missing_fld= norm_missing_fld + ; frame_typ= norm_frame_typ + ; missing_typ= norm_missing_typ } (** Check whether an inst represents a dereference without null check, and return the line number and path position *) let find_dereference_without_null_check_in_inst = function - | Sil.Iupdate (Some true, _, n, pos) - | Sil.Irearrange (Some true, _, n, pos) -> Some (n, pos) - | _ -> None + | Sil.Iupdate (Some true, _, n, pos) | Sil.Irearrange (Some true, _, n, pos) + -> Some (n, pos) + | _ + -> None (** Check whether a sexp contains a dereference without null check, and return the line number and path position *) let rec find_dereference_without_null_check_in_sexp = function - | Sil.Eexp (_, inst) -> find_dereference_without_null_check_in_inst inst - | Sil.Estruct (fsel, inst) -> - let res = find_dereference_without_null_check_in_inst inst in - if is_none res then - find_dereference_without_null_check_in_sexp_list (List.map ~f:snd fsel) + | Sil.Eexp (_, inst) + -> find_dereference_without_null_check_in_inst inst + | Sil.Estruct (fsel, inst) + -> let res = find_dereference_without_null_check_in_inst inst in + if is_none res then find_dereference_without_null_check_in_sexp_list (List.map ~f:snd fsel) else res - | Sil.Earray (_, esel, inst) -> - let res = find_dereference_without_null_check_in_inst inst in - if is_none res then - find_dereference_without_null_check_in_sexp_list (List.map ~f:snd esel) + | Sil.Earray (_, esel, inst) + -> let res = find_dereference_without_null_check_in_inst inst in + if is_none res then find_dereference_without_null_check_in_sexp_list (List.map ~f:snd esel) else res + and find_dereference_without_null_check_in_sexp_list = function - | [] -> None - | se:: sel -> - (match find_dereference_without_null_check_in_sexp se with - | None -> find_dereference_without_null_check_in_sexp_list sel - | Some x -> Some x) + | [] + -> None + | se :: sel -> + match find_dereference_without_null_check_in_sexp se with + | None + -> find_dereference_without_null_check_in_sexp_list sel + | Some x + -> Some x (** Check dereferences implicit in the spec pre. In case of dereference error, return [Some(deref_error, description)], otherwise [None] *) @@ -291,70 +314,83 @@ let check_dereferences tenv callee_pname actual_pre sub spec_pre formal_params = let e_sub = Sil.exp_sub sub e in let desc use_buckets deref_str = let error_desc = - Errdesc.explain_dereference_as_caller_expression tenv - ~use_buckets - deref_str actual_pre spec_pre e (State.get_node ()) (State.get_loc ()) formal_params in - (L.d_strln_color Red) "found error in dereference"; - L.d_strln "spec_pre:"; Prop.d_prop spec_pre; L.d_ln(); - L.d_str "exp "; Sil.d_exp e; - L.d_strln (" desc: " ^ (F.asprintf "%a" Localise.pp_error_desc error_desc)); - error_desc in + Errdesc.explain_dereference_as_caller_expression tenv ~use_buckets deref_str actual_pre + spec_pre e (State.get_node ()) (State.get_loc ()) formal_params + in + L.d_strln_color Red "found error in dereference" ; + L.d_strln "spec_pre:" ; + Prop.d_prop spec_pre ; + L.d_ln () ; + L.d_str "exp " ; + Sil.d_exp e ; + L.d_strln (" desc: " ^ F.asprintf "%a" Localise.pp_error_desc error_desc) ; + error_desc + in let deref_no_null_check_pos = if Exp.equal e_sub Exp.zero then match find_dereference_without_null_check_in_sexp sexp with - | Some (_, pos) -> Some pos - | None -> None - else None in - if deref_no_null_check_pos <> None - then + | Some (_, pos) + -> Some pos + | None + -> None + else None + in + if deref_no_null_check_pos <> None then (* only report a dereference null error if we know there was a dereference without null check *) match deref_no_null_check_pos with - | Some pos -> Some (Deref_null pos, desc true (Localise.deref_str_null (Some callee_pname))) - | None -> assert false + | Some pos + -> Some (Deref_null pos, desc true (Localise.deref_str_null (Some callee_pname))) + | None + -> assert false + else if (* Check if the dereferenced expr has the dangling uninitialized attribute. *) + (* In that case it raise a dangling pointer dereferece *) + Attribute.has_dangling_uninit tenv spec_pre e + then Some (Deref_undef_exp, desc false (Localise.deref_str_dangling (Some PredSymb.DAuninit))) + else if Exp.equal e_sub Exp.minus_one then + Some (Deref_minusone, desc true (Localise.deref_str_dangling None)) else - (* Check if the dereferenced expr has the dangling uninitialized attribute. *) - (* In that case it raise a dangling pointer dereferece *) - if Attribute.has_dangling_uninit tenv spec_pre e then - Some (Deref_undef_exp, desc false (Localise.deref_str_dangling (Some PredSymb.DAuninit)) ) - else if Exp.equal e_sub Exp.minus_one - then Some (Deref_minusone, desc true (Localise.deref_str_dangling None)) - else match Attribute.get_resource tenv actual_pre e_sub with - | Some (Apred (Aresource ({ ra_kind = Rrelease } as ra), _)) -> - Some (Deref_freed ra, desc true (Localise.deref_str_freed ra)) + match Attribute.get_resource tenv actual_pre e_sub with + | Some Apred (Aresource ({ra_kind= Rrelease} as ra), _) + -> Some (Deref_freed ra, desc true (Localise.deref_str_freed ra)) | _ -> - (match Attribute.get_undef tenv actual_pre e_sub with - | Some (Apred (Aundef (s, _, loc, pos), _)) -> - Some (Deref_undef (s, loc, pos), desc false (Localise.deref_str_undef (s, loc))) - | _ -> None) in + match Attribute.get_undef tenv actual_pre e_sub with + | Some Apred (Aundef (s, _, loc, pos), _) + -> Some (Deref_undef (s, loc, pos), desc false (Localise.deref_str_undef (s, loc))) + | _ + -> None + in let check_hpred = function - | Sil.Hpointsto (lexp, se, _) -> - check_dereference (Exp.root_of_lexp lexp) se - | _ -> None in + | Sil.Hpointsto (lexp, se, _) + -> check_dereference (Exp.root_of_lexp lexp) se + | _ + -> None + in let deref_err_list = List.fold - ~f:(fun deref_errs hpred -> match check_hpred hpred with - | Some reason -> reason :: deref_errs - | None -> deref_errs) - ~init:[] - spec_pre.Prop.sigma in + ~f:(fun deref_errs hpred -> + match check_hpred hpred with Some reason -> reason :: deref_errs | None -> deref_errs) + ~init:[] spec_pre.Prop.sigma + in match deref_err_list with - | [] -> None - | deref_err :: _ -> - if Config.angelic_execution then + | [] + -> None + | deref_err :: _ + -> if Config.angelic_execution then (* In angelic mode, prefer to report Deref_null over other kinds of deref errors. this * makes sure we report a NULL_DEREFERENCE instead of a less interesting PRECONDITION_NOT_MET * whenever possible *) (* TOOD (t4893533): use this trick outside of angelic mode and in other parts of the code *) - (match - List.find - ~f:(fun err -> match err with - | (Deref_null _, _) -> true - | _ -> false ) - deref_err_list with - | Some x -> Some x - | None -> Some deref_err) + match + List.find + ~f:(fun err -> match err with Deref_null _, _ -> true | _ -> false) + deref_err_list + with + | Some x + -> Some x + | None + -> Some deref_err else Some deref_err let post_process_sigma tenv (sigma: Sil.hpred list) loc : Sil.hpred list = @@ -367,532 +403,615 @@ let post_process_sigma tenv (sigma: Sil.hpred list) loc : Sil.hpred list = let check_path_errors_in_post tenv caller_pname post post_path = let check_attr atom = match atom with - | Sil.Apred (Adiv0 path_pos, [e]) -> - if Prover.check_zero tenv e then - let desc = Errdesc.explain_divide_by_zero tenv e (State.get_node ()) (State.get_loc ()) in + | Sil.Apred (Adiv0 path_pos, [e]) + -> if Prover.check_zero tenv e then + let desc = + Errdesc.explain_divide_by_zero tenv e (State.get_node ()) (State.get_loc ()) + in let new_path, path_pos_opt = let current_path, _ = State.get_path () in - if Paths.Path.contains_position post_path path_pos - then post_path, Some path_pos - else current_path, None (* position not found, only use the path up to the callee *) in - State.set_path new_path path_pos_opt; + if Paths.Path.contains_position post_path path_pos then (post_path, Some path_pos) + else (current_path, None) + (* position not found, only use the path up to the callee *) + in + State.set_path new_path path_pos_opt ; let exn = Exceptions.Divide_by_zero (desc, __POS__) in Reporting.log_warning_deprecated caller_pname exn - | _ -> () in + | _ + -> () + in List.iter ~f:check_attr (Attribute.get_all post) (** Post process the instantiated post after the function call so that x.f |-> se becomes x |-> \{ f: se \}. Also, update any Aresource attributes to refer to the caller *) -let post_process_post tenv - caller_pname callee_pname loc actual_pre ((post: Prop.exposed Prop.t), post_path) = - let actual_pre_has_freed_attribute e = match Attribute.get_resource tenv actual_pre e with - | Some (Apred (Aresource ({ ra_kind = Rrelease }), _)) -> true - | _ -> false in +let post_process_post tenv caller_pname callee_pname loc actual_pre + ((post: Prop.exposed Prop.t), post_path) = + let actual_pre_has_freed_attribute e = + match Attribute.get_resource tenv actual_pre e with + | Some Apred (Aresource {ra_kind= Rrelease}, _) + -> true + | _ + -> false + in let atom_update_alloc_attribute = function | Sil.Apred (Aresource ra, [e]) - when not (PredSymb.equal_res_act_kind ra.ra_kind PredSymb.Rrelease && - actual_pre_has_freed_attribute e) -> - (* unless it was already freed before the call *) + when not + ( PredSymb.equal_res_act_kind ra.ra_kind PredSymb.Rrelease + && actual_pre_has_freed_attribute e ) + -> (* unless it was already freed before the call *) let vpath, _ = Errdesc.vpath_find tenv post e in - let ra' = { ra with ra_pname = callee_pname; ra_loc = loc; ra_vpath = vpath } in + let ra' = {ra with ra_pname= callee_pname; ra_loc= loc; ra_vpath= vpath} in Sil.Apred (Aresource ra', [e]) - | a -> a in + | a + -> a + in let prop' = Prop.set post ~sigma:(post_process_sigma tenv post.Prop.sigma loc) in let pi' = List.map ~f:atom_update_alloc_attribute prop'.Prop.pi in (* update alloc attributes to refer to the caller *) let post' = Prop.set prop' ~pi:pi' in - check_path_errors_in_post tenv caller_pname post' post_path; - post', post_path - -let hpred_lhs_compare hpred1 hpred2 = match hpred1, hpred2 with - | Sil.Hpointsto(e1, _, _), Sil.Hpointsto(e2, _, _) -> Exp.compare e1 e2 - | Sil.Hpointsto _, _ -> - 1 - | _, Sil.Hpointsto _ -> 1 - | hpred1, hpred2 -> Sil.compare_hpred hpred1 hpred2 + check_path_errors_in_post tenv caller_pname post' post_path ; (post', post_path) + +let hpred_lhs_compare hpred1 hpred2 = + match (hpred1, hpred2) with + | Sil.Hpointsto (e1, _, _), Sil.Hpointsto (e2, _, _) + -> Exp.compare e1 e2 + | Sil.Hpointsto _, _ + -> -1 + | _, Sil.Hpointsto _ + -> 1 + | hpred1, hpred2 + -> Sil.compare_hpred hpred1 hpred2 (** set the inst everywhere in a sexp *) let rec sexp_set_inst inst = function - | Sil.Eexp (e, _) -> - Sil.Eexp (e, inst) - | Sil.Estruct (fsel, _) -> - Sil.Estruct ((List.map ~f:(fun (f, se) -> (f, sexp_set_inst inst se)) fsel), inst) - | Sil.Earray (len, esel, _) -> - Sil.Earray (len, List.map ~f:(fun (e, se) -> (e, sexp_set_inst inst se)) esel, inst) - -let rec fsel_star_fld fsel1 fsel2 = match fsel1, fsel2 with - | [], fsel2 -> fsel2 - | fsel1,[] -> fsel1 - | (f1, se1):: fsel1', (f2, se2):: fsel2' -> - (match Typ.Fieldname.compare f1 f2 with - | 0 -> (f1, sexp_star_fld se1 se2) :: fsel_star_fld fsel1' fsel2' - | n when n < 0 -> (f1, se1) :: fsel_star_fld fsel1' fsel2 - | _ -> (f2, se2) :: fsel_star_fld fsel1 fsel2') + | Sil.Eexp (e, _) + -> Sil.Eexp (e, inst) + | Sil.Estruct (fsel, _) + -> Sil.Estruct (List.map ~f:(fun (f, se) -> (f, sexp_set_inst inst se)) fsel, inst) + | Sil.Earray (len, esel, _) + -> Sil.Earray (len, List.map ~f:(fun (e, se) -> (e, sexp_set_inst inst se)) esel, inst) + +let rec fsel_star_fld fsel1 fsel2 = + match (fsel1, fsel2) with + | [], fsel2 + -> fsel2 + | fsel1, [] + -> fsel1 + | (f1, se1) :: fsel1', (f2, se2) :: fsel2' -> + match Typ.Fieldname.compare f1 f2 with + | 0 + -> (f1, sexp_star_fld se1 se2) :: fsel_star_fld fsel1' fsel2' + | n when n < 0 + -> (f1, se1) :: fsel_star_fld fsel1' fsel2 + | _ + -> (f2, se2) :: fsel_star_fld fsel1 fsel2' and array_content_star se1 se2 = - try sexp_star_fld se1 se2 with - | exn when SymOp.exn_not_failure exn -> se1 (* let postcondition override *) - -and esel_star_fld esel1 esel2 = match esel1, esel2 with - | [], esel2 -> (* don't know whether element is read or written in fun call with array *) + try sexp_star_fld se1 se2 + with exn when SymOp.exn_not_failure exn -> se1 + +(* let postcondition override *) +and esel_star_fld esel1 esel2 = + match (esel1, esel2) with + | [], esel2 + -> (* don't know whether element is read or written in fun call with array *) List.map ~f:(fun (e, se) -> (e, sexp_set_inst Sil.Inone se)) esel2 - | esel1,[] -> esel1 - | (e1, se1):: esel1', (e2, se2):: esel2' -> - (match Exp.compare e1 e2 with - | 0 -> (e1, array_content_star se1 se2) :: esel_star_fld esel1' esel2' - | n when n < 0 -> (e1, se1) :: esel_star_fld esel1' esel2 - | _ -> - let se2' = sexp_set_inst Sil.Inone se2 in - (* don't know whether element is read or written in fun call with array *) - (e2, se2') :: esel_star_fld esel1 esel2') + | esel1, [] + -> esel1 + | (e1, se1) :: esel1', (e2, se2) :: esel2' -> + match Exp.compare e1 e2 with + | 0 + -> (e1, array_content_star se1 se2) :: esel_star_fld esel1' esel2' + | n when n < 0 + -> (e1, se1) :: esel_star_fld esel1' esel2 + | _ + -> let se2' = sexp_set_inst Sil.Inone se2 in + (* don't know whether element is read or written in fun call with array *) + (e2, se2') + :: esel_star_fld esel1 esel2' and sexp_star_fld se1 se2 : Sil.strexp = (* L.d_str "sexp_star_fld "; Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_ln (); *) - match se1, se2 with - | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, inst2) -> - Sil.Estruct (fsel_star_fld fsel1 fsel2, inst2) - | Sil.Earray (len1, esel1, _), Sil.Earray (_, esel2, inst2) -> - Sil.Earray (len1, esel_star_fld esel1 esel2, inst2) - | Sil.Eexp (_, inst1), Sil.Earray (len2, esel2, _) -> - let esel1 = [(Exp.zero, se1)] in + match (se1, se2) with + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, inst2) + -> Sil.Estruct (fsel_star_fld fsel1 fsel2, inst2) + | Sil.Earray (len1, esel1, _), Sil.Earray (_, esel2, inst2) + -> Sil.Earray (len1, esel_star_fld esel1 esel2, inst2) + | Sil.Eexp (_, inst1), Sil.Earray (len2, esel2, _) + -> let esel1 = [(Exp.zero, se1)] in Sil.Earray (len2, esel_star_fld esel1 esel2, inst1) - | _ -> - L.d_str "cannot star "; - Sil.d_sexp se1; L.d_str " and "; Sil.d_sexp se2; - L.d_ln (); + | _ + -> L.d_str "cannot star " ; + Sil.d_sexp se1 ; + L.d_str " and " ; + Sil.d_sexp se2 ; + L.d_ln () ; assert false let texp_star tenv texp1 texp2 = - let rec ftal_sub ftal1 ftal2 = match ftal1, ftal2 with - | [], _ -> true - | _, [] -> false - | (f1, _, _):: ftal1', (f2, _, _):: ftal2' -> - begin match Typ.Fieldname.compare f1 f2 with - | n when n < 0 -> false - | 0 -> ftal_sub ftal1' ftal2' - | _ -> ftal_sub ftal1 ftal2' end in + let rec ftal_sub ftal1 ftal2 = + match (ftal1, ftal2) with + | [], _ + -> true + | _, [] + -> false + | (f1, _, _) :: ftal1', (f2, _, _) :: ftal2' -> + match Typ.Fieldname.compare f1 f2 with + | n when n < 0 + -> false + | 0 + -> ftal_sub ftal1' ftal2' + | _ + -> ftal_sub ftal1 ftal2' + in let typ_star (t1: Typ.t) (t2: Typ.t) = - match t1.desc, t2.desc with - | Tstruct name1, Tstruct name2 - when Typ.Name.is_same_type name1 name2 -> ( - match Tenv.lookup tenv name1, Tenv.lookup tenv name2 with - | Some { fields = fields1 }, Some { fields = fields2 } when ftal_sub fields1 fields2 -> - t2 - | _ -> - t1 - ) - | _ -> - t1 in - match texp1, texp2 with - | Exp.Sizeof ({typ=t1; subtype=st1} as sizeof1), Exp.Sizeof {typ=t2; subtype=st2} -> - Exp.Sizeof {sizeof1 with typ=typ_star t1 t2; subtype=Subtype.join st1 st2} - | _ -> - texp1 - -let hpred_star_fld tenv (hpred1 : Sil.hpred) (hpred2 : Sil.hpred) : Sil.hpred = - match hpred1, hpred2 with - | Sil.Hpointsto(e1, se1, t1), Sil.Hpointsto(_, se2, t2) -> - (* L.d_str "hpred_star_fld t1: "; Sil.d_texp_full t1; L.d_str " t2: "; Sil.d_texp_full t2; + match (t1.desc, t2.desc) with + | Tstruct name1, Tstruct name2 when Typ.Name.is_same_type name1 name2 -> ( + match (Tenv.lookup tenv name1, Tenv.lookup tenv name2) with + | Some {fields= fields1}, Some {fields= fields2} when ftal_sub fields1 fields2 + -> t2 + | _ + -> t1 ) + | _ + -> t1 + in + match (texp1, texp2) with + | Exp.Sizeof ({typ= t1; subtype= st1} as sizeof1), Exp.Sizeof {typ= t2; subtype= st2} + -> Exp.Sizeof {sizeof1 with typ= typ_star t1 t2; subtype= Subtype.join st1 st2} + | _ + -> texp1 + +let hpred_star_fld tenv (hpred1: Sil.hpred) (hpred2: Sil.hpred) : Sil.hpred = + match (hpred1, hpred2) with + | Sil.Hpointsto (e1, se1, t1), Sil.Hpointsto (_, se2, t2) + -> (* L.d_str "hpred_star_fld t1: "; Sil.d_texp_full t1; L.d_str " t2: "; Sil.d_texp_full t2; L.d_str " se1: "; Sil.d_sexp se1; L.d_str " se2: "; Sil.d_sexp se2; L.d_ln (); *) - Sil.Hpointsto(e1, sexp_star_fld se1 se2, texp_star tenv t1 t2) - | _ -> assert false + Sil.Hpointsto (e1, sexp_star_fld se1 se2, texp_star tenv t1 t2) + | _ + -> assert false (** Implementation of [*] for the field-splitting model *) -let sigma_star_fld tenv (sigma1 : Sil.hpred list) (sigma2 : Sil.hpred list) : Sil.hpred list = +let sigma_star_fld tenv (sigma1: Sil.hpred list) (sigma2: Sil.hpred list) : Sil.hpred list = let sigma1 = List.stable_sort ~cmp:hpred_lhs_compare sigma1 in let sigma2 = List.stable_sort ~cmp:hpred_lhs_compare sigma2 in (* L.out "@.@. computing %a@.STAR @.%a@.@." pp_sigma sigma1 pp_sigma sigma2; *) let rec star sg1 sg2 : Sil.hpred list = - match sg1, sg2 with - | [], _ -> [] - | sigma1,[] -> sigma1 - | hpred1:: sigma1', hpred2:: sigma2' -> - begin - match hpred_lhs_compare hpred1 hpred2 with - | 0 -> hpred_star_fld tenv hpred1 hpred2 :: star sigma1' sigma2' - | n when n < 0 -> hpred1 :: star sigma1' sg2 - | _ -> star sg1 sigma2' - end + match (sg1, sg2) with + | [], _ + -> [] + | sigma1, [] + -> sigma1 + | hpred1 :: sigma1', hpred2 :: sigma2' -> + match hpred_lhs_compare hpred1 hpred2 with + | 0 + -> hpred_star_fld tenv hpred1 hpred2 :: star sigma1' sigma2' + | n when n < 0 + -> hpred1 :: star sigma1' sg2 + | _ + -> star sg1 sigma2' in try star sigma1 sigma2 with exn when SymOp.exn_not_failure exn -> - L.d_str "cannot star "; - Prop.d_sigma sigma1; L.d_str " and "; Prop.d_sigma sigma2; - L.d_ln (); + L.d_str "cannot star " ; + Prop.d_sigma sigma1 ; + L.d_str " and " ; + Prop.d_sigma sigma2 ; + L.d_ln () ; raise (Exceptions.Cannot_star __POS__) -let hpred_typing_lhs_compare hpred1 (e2, _) = match hpred1 with - | Sil.Hpointsto(e1, _, _) -> Exp.compare e1 e2 - | _ -> - 1 +let hpred_typing_lhs_compare hpred1 (e2, _) = + match hpred1 with Sil.Hpointsto (e1, _, _) -> Exp.compare e1 e2 | _ -> -1 -let hpred_star_typing (hpred1 : Sil.hpred) (_, te2) : Sil.hpred = +let hpred_star_typing (hpred1: Sil.hpred) (_, te2) : Sil.hpred = match hpred1 with - | Sil.Hpointsto(e1, se1, _) -> Sil.Hpointsto (e1, se1, te2) - | _ -> assert false + | Sil.Hpointsto (e1, se1, _) + -> Sil.Hpointsto (e1, se1, te2) + | _ + -> assert false (** Implementation of [*] between predicates and typings *) -let sigma_star_typ - (sigma1 : Sil.hpred list) (typings2 : (Exp.t * Exp.t) list) : Sil.hpred list = +let sigma_star_typ (sigma1: Sil.hpred list) (typings2: (Exp.t * Exp.t) list) : Sil.hpred list = let typing_lhs_compare (e1, _) (e2, _) = Exp.compare e1 e2 in let sigma1 = List.stable_sort ~cmp:hpred_lhs_compare sigma1 in let typings2 = List.stable_sort ~cmp:typing_lhs_compare typings2 in let rec star sg1 typ2 : Sil.hpred list = - match sg1, typ2 with - | [], _ -> [] - | sigma1,[] -> sigma1 - | hpred1:: sigma1', typing2:: typings2' -> - begin - match hpred_typing_lhs_compare hpred1 typing2 with - | 0 -> hpred_star_typing hpred1 typing2 :: star sigma1' typings2' - | n when n < 0 -> hpred1 :: star sigma1' typ2 - | _ -> star sg1 typings2' - end in + match (sg1, typ2) with + | [], _ + -> [] + | sigma1, [] + -> sigma1 + | hpred1 :: sigma1', typing2 :: typings2' -> + match hpred_typing_lhs_compare hpred1 typing2 with + | 0 + -> hpred_star_typing hpred1 typing2 :: star sigma1' typings2' + | n when n < 0 + -> hpred1 :: star sigma1' typ2 + | _ + -> star sg1 typings2' + in try star sigma1 typings2 with exn when SymOp.exn_not_failure exn -> - L.d_str "cannot star "; - Prop.d_sigma sigma1; L.d_str " and "; Prover.d_typings typings2; - L.d_ln (); + L.d_str "cannot star " ; + Prop.d_sigma sigma1 ; + L.d_str " and " ; + Prover.d_typings typings2 ; + L.d_ln () ; raise (Exceptions.Cannot_star __POS__) (** [prop_footprint_add_pi_sigma_starfld_sigma prop pi sigma missing_fld] extends the footprint of [prop] with [pi,sigma] and extends the fields of |-> with [missing_fld] *) -let prop_footprint_add_pi_sigma_starfld_sigma tenv - (prop : 'a Prop.t) pi_new sigma_new missing_fld missing_typ : Prop.normal Prop.t option = - let rec extend_sigma current_sigma new_sigma = match new_sigma with - | [] -> Some current_sigma - | hpred :: new_sigma' -> - let fav = Prop.sigma_fav [hpred] in +let prop_footprint_add_pi_sigma_starfld_sigma tenv (prop: 'a Prop.t) pi_new sigma_new missing_fld + missing_typ : Prop.normal Prop.t option = + let rec extend_sigma current_sigma new_sigma = + match new_sigma with + | [] + -> Some current_sigma + | hpred :: new_sigma' + -> let fav = Prop.sigma_fav [hpred] in (* TODO (t4893479): make this check less angelic *) - if Sil.fav_exists fav - (fun id -> not (Ident.is_footprint id) && not Config.angelic_execution) - then begin - L.d_warning "found hpred with non-footprint variable, dropping the spec"; - L.d_ln (); Sil.d_hpred hpred; L.d_ln (); - None - end - else extend_sigma (hpred :: current_sigma) new_sigma' in - let rec extend_pi current_pi new_pi = match new_pi with - | [] -> current_pi - | a :: new_pi' -> - let fav = Prop.pi_fav [a] in - if Sil.fav_exists fav (fun id -> not (Ident.is_footprint id)) - then begin - L.d_warning "dropping atom with non-footprint variable"; - L.d_ln (); Sil.d_atom a; L.d_ln (); - extend_pi current_pi new_pi' - end - else extend_pi (a :: current_pi) new_pi' in + if Sil.fav_exists fav (fun id -> + not (Ident.is_footprint id) && not Config.angelic_execution ) + then ( + L.d_warning "found hpred with non-footprint variable, dropping the spec" ; + L.d_ln () ; + Sil.d_hpred hpred ; + L.d_ln () ; + None ) + else extend_sigma (hpred :: current_sigma) new_sigma' + in + let rec extend_pi current_pi new_pi = + match new_pi with + | [] + -> current_pi + | a :: new_pi' + -> let fav = Prop.pi_fav [a] in + if Sil.fav_exists fav (fun id -> not (Ident.is_footprint id)) then ( + L.d_warning "dropping atom with non-footprint variable" ; + L.d_ln () ; + Sil.d_atom a ; + L.d_ln () ; + extend_pi current_pi new_pi' ) + else extend_pi (a :: current_pi) new_pi' + in let pi_fp' = extend_pi prop.Prop.pi_fp pi_new in match extend_sigma prop.Prop.sigma_fp sigma_new with - | None -> None - | Some sigma' -> - let sigma_fp' = sigma_star_fld tenv sigma' missing_fld in + | None + -> None + | Some sigma' + -> let sigma_fp' = sigma_star_fld tenv sigma' missing_fld in let sigma_fp'' = sigma_star_typ sigma_fp' missing_typ in let pi' = pi_new @ prop.Prop.pi in Some (Prop.normalize tenv (Prop.set prop ~pi:pi' ~pi_fp:pi_fp' ~sigma_fp:sigma_fp'')) (** Check if the attribute change is a mismatch between a kind of allocation and a different kind of deallocation *) -let check_attr_dealloc_mismatch att_old att_new = match att_old, att_new with - | PredSymb.Aresource ({ ra_kind = Racquire; ra_res = Rmemory mk_old } as ra_old), - PredSymb.Aresource ({ ra_kind = Rrelease; ra_res = Rmemory mk_new } as ra_new) - when PredSymb.compare_mem_kind mk_old mk_new <> 0 -> - let desc = Errdesc.explain_allocation_mismatch ra_old ra_new in +let check_attr_dealloc_mismatch att_old att_new = + match (att_old, att_new) with + | ( PredSymb.Aresource ({ra_kind= Racquire; ra_res= Rmemory mk_old} as ra_old) + , PredSymb.Aresource ({ra_kind= Rrelease; ra_res= Rmemory mk_new} as ra_new) ) + when PredSymb.compare_mem_kind mk_old mk_new <> 0 + -> let desc = Errdesc.explain_allocation_mismatch ra_old ra_new in raise (Exceptions.Deallocation_mismatch (desc, __POS__)) - | _ -> () + | _ + -> () (** [prop_copy_footprint p1 p2] copies the footprint and pure part of [p1] into [p2] *) let prop_copy_footprint_pure tenv p1 p2 = - let p2' = - Prop.set p2 ~pi_fp:p1.Prop.pi_fp ~sigma_fp:p1.Prop.sigma_fp in + let p2' = Prop.set p2 ~pi_fp:p1.Prop.pi_fp ~sigma_fp:p1.Prop.sigma_fp in let pi2 = p2'.Prop.pi in let pi2_attr, pi2_noattr = List.partition_tf ~f:Attribute.is_pred pi2 in let res_noattr = Prop.set p2' ~pi:(Prop.get_pure p1 @ pi2_noattr) in - let replace_attr prop atom = (* call replace_atom_attribute which deals with existing attibutes *) + let replace_attr prop atom = + (* call replace_atom_attribute which deals with existing attibutes *) (* if [atom] represents an attribute [att], add the attribure to [prop] *) if Attribute.is_pred atom then Attribute.add_or_replace_check_changed tenv check_attr_dealloc_mismatch prop atom - else - prop in + else prop + in List.fold ~f:replace_attr ~init:(Prop.normalize tenv res_noattr) pi2_attr (** check if an expression is an exception *) -let exp_is_exn = function - | Exp.Exn _ -> true - | _ -> false +let exp_is_exn = function Exp.Exn _ -> true | _ -> false (** check if a prop is an exception *) let prop_is_exn pname prop = let ret_pvar = Exp.Lvar (Pvar.get_ret_pvar pname) in let is_exn = function - | Sil.Hpointsto (e1, Sil.Eexp(e2, _), _) when Exp.equal e1 ret_pvar -> - exp_is_exn e2 - | _ -> false in + | Sil.Hpointsto (e1, Sil.Eexp (e2, _), _) when Exp.equal e1 ret_pvar + -> exp_is_exn e2 + | _ + -> false + in List.exists ~f:is_exn prop.Prop.sigma (** when prop is an exception, return the exception name *) let prop_get_exn_name pname prop = let ret_pvar = Exp.Lvar (Pvar.get_ret_pvar pname) in let rec search_exn e = function - | [] -> None - | Sil.Hpointsto (e1, _, Sizeof {typ={desc=Tstruct name}}) :: _ - when Exp.equal e1 e -> - Some name - | _ :: tl -> search_exn e tl in + | [] + -> None + | (Sil.Hpointsto (e1, _, Sizeof {typ= {desc= Tstruct name}})) :: _ when Exp.equal e1 e + -> Some name + | _ :: tl + -> search_exn e tl + in let rec find_exn_name hpreds = function - | [] -> None - | Sil.Hpointsto (e1, Sil.Eexp (Exp.Exn e2, _), _) :: _ - when Exp.equal e1 ret_pvar -> - search_exn e2 hpreds - | _ :: tl -> find_exn_name hpreds tl in + | [] + -> None + | (Sil.Hpointsto (e1, Sil.Eexp (Exp.Exn e2, _), _)) :: _ when Exp.equal e1 ret_pvar + -> search_exn e2 hpreds + | _ :: tl + -> find_exn_name hpreds tl + in let hpreds = prop.Prop.sigma in find_exn_name hpreds hpreds - (** search in prop for some assignment of global errors *) let lookup_custom_errors prop = let rec search_error = function - | [] -> None - | Sil.Hpointsto (Exp.Lvar var, Sil.Eexp (Exp.Const (Const.Cstr error_str), _), _) :: _ - when Pvar.equal var Sil.custom_error -> Some error_str - | _ :: tl -> search_error tl in + | [] + -> None + | (Sil.Hpointsto (Exp.Lvar var, Sil.Eexp (Exp.Const Const.Cstr error_str, _), _)) :: _ + when Pvar.equal var Sil.custom_error + -> Some error_str + | _ :: tl + -> search_error tl + in search_error prop.Prop.sigma (** set a prop to an exception sexp *) let prop_set_exn tenv pname prop se_exn = let ret_pvar = Exp.Lvar (Pvar.get_ret_pvar pname) in let map_hpred = function - | Sil.Hpointsto (e, _, t) when Exp.equal e ret_pvar -> - Sil.Hpointsto(e, se_exn, t) - | hpred -> hpred in + | Sil.Hpointsto (e, _, t) when Exp.equal e ret_pvar + -> Sil.Hpointsto (e, se_exn, t) + | hpred + -> hpred + in let sigma' = List.map ~f:map_hpred prop.Prop.sigma in Prop.normalize tenv (Prop.set prop ~sigma:sigma') (** Include a subtrace for a procedure call if the callee is not a model. *) let include_subtrace callee_pname = match Specs.proc_resolve_attributes callee_pname with - | Some attrs -> - not attrs.ProcAttributes.is_model - && (SourceFile.is_under_project_root attrs.ProcAttributes.loc.Location.file) - | None -> false + | Some attrs + -> not attrs.ProcAttributes.is_model + && SourceFile.is_under_project_root attrs.ProcAttributes.loc.Location.file + | None + -> false (** combine the spec's post with a splitting and actual precondition *) -let combine tenv - ret_id (posts: ('a Prop.t * Paths.Path.t) list) - actual_pre path_pre split +let combine tenv ret_id (posts: ('a Prop.t * Paths.Path.t) list) actual_pre path_pre split caller_pdesc callee_pname loc = let caller_pname = Procdesc.get_proc_name caller_pdesc in let instantiated_post = let posts' = - if !Config.footprint && List.is_empty posts - then (* in case of divergence, produce a prop *) + if !Config.footprint && List.is_empty posts then + (* in case of divergence, produce a prop *) (* with updated footprint and inconsistent current *) [(Prop.set Prop.prop_emp ~pi:[Sil.Aneq (Exp.zero, Exp.zero)], path_pre)] else List.map ~f:(fun (p, path_post) -> - (p, - Paths.Path.add_call - (include_subtrace callee_pname) - path_pre - callee_pname - path_post)) - posts in + (p, Paths.Path.add_call (include_subtrace callee_pname) path_pre callee_pname path_post)) + posts + in List.map ~f:(fun (p, path) -> - (post_process_post tenv - caller_pname callee_pname loc actual_pre (Prop.prop_sub split.sub p, path))) - posts' in - L.d_increase_indent 1; - L.d_strln "New footprint:"; Prop.d_pi_sigma split.missing_pi split.missing_sigma; L.d_ln (); - L.d_strln "Frame fld:"; Prop.d_sigma split.frame_fld; L.d_ln (); - if split.frame_typ <> [] - then - begin L.d_strln "Frame typ:"; - Prover.d_typings split.frame_typ; L.d_ln () end; - L.d_strln "Missing fld:"; Prop.d_sigma split.missing_fld; L.d_ln (); - if split.missing_typ <> [] - then - begin L.d_strln "Missing typ:"; - Prover.d_typings split.missing_typ; L.d_ln (); end; - L.d_strln "Instantiated frame:"; Prop.d_sigma split.frame; L.d_ln (); - L.d_strln "Instantiated post:"; - Propgraph.d_proplist Prop.prop_emp (List.map ~f:fst instantiated_post); - L.d_decrease_indent 1; L.d_ln (); + post_process_post tenv caller_pname callee_pname loc actual_pre + (Prop.prop_sub split.sub p, path)) + posts' + in + L.d_increase_indent 1 ; + L.d_strln "New footprint:" ; + Prop.d_pi_sigma split.missing_pi split.missing_sigma ; + L.d_ln () ; + L.d_strln "Frame fld:" ; + Prop.d_sigma split.frame_fld ; + L.d_ln () ; + if split.frame_typ <> [] then ( + L.d_strln "Frame typ:" ; Prover.d_typings split.frame_typ ; L.d_ln () ) ; + L.d_strln "Missing fld:" ; + Prop.d_sigma split.missing_fld ; + L.d_ln () ; + if split.missing_typ <> [] then ( + L.d_strln "Missing typ:" ; Prover.d_typings split.missing_typ ; L.d_ln () ) ; + L.d_strln "Instantiated frame:" ; + Prop.d_sigma split.frame ; + L.d_ln () ; + L.d_strln "Instantiated post:" ; + Propgraph.d_proplist Prop.prop_emp (List.map ~f:fst instantiated_post) ; + L.d_decrease_indent 1 ; + L.d_ln () ; let compute_result post_p = let post_p' = let post_sigma = sigma_star_fld tenv post_p.Prop.sigma split.frame_fld in let post_sigma' = sigma_star_typ post_sigma split.frame_typ in - Prop.set post_p ~sigma:post_sigma' in + Prop.set post_p ~sigma:post_sigma' + in let post_p1 = - Prop.prop_sigma_star (prop_copy_footprint_pure tenv actual_pre post_p') split.frame in - + Prop.prop_sigma_star (prop_copy_footprint_pure tenv actual_pre post_p') split.frame + in let handle_null_case_analysis sigma = let id_assigned_to_null id = let filter = function - | Sil.Aeq (Exp.Var id', Exp.Const (Const.Cint i)) -> - Ident.equal id id' && IntLit.isnull i - | _ -> false in - List.exists ~f:filter split.missing_pi in - let f (e, inst_opt) = match e, inst_opt with - | Exp.Var id, Some inst when id_assigned_to_null id -> - let inst' = Sil.inst_set_null_case_flag inst in + | Sil.Aeq (Exp.Var id', Exp.Const Const.Cint i) + -> Ident.equal id id' && IntLit.isnull i + | _ + -> false + in + List.exists ~f:filter split.missing_pi + in + let f (e, inst_opt) = + match (e, inst_opt) with + | Exp.Var id, Some inst when id_assigned_to_null id + -> let inst' = Sil.inst_set_null_case_flag inst in (e, Some inst') - | _ -> (e, inst_opt) in - Sil.hpred_list_expmap f sigma in - + | _ + -> (e, inst_opt) + in + Sil.hpred_list_expmap f sigma + in let post_p2 = let post_p1_sigma = post_p1.Prop.sigma in let post_p1_sigma' = handle_null_case_analysis post_p1_sigma in let post_p1' = Prop.set post_p1 ~sigma:post_p1_sigma' in - Prop.normalize tenv (Prop.set post_p1' ~pi:(post_p1.Prop.pi @ split.missing_pi)) in - - let post_p3 = (* replace [result|callee] with an aux variable dedicated to this proc *) + Prop.normalize tenv (Prop.set post_p1' ~pi:(post_p1.Prop.pi @ split.missing_pi)) + in + let post_p3 = + (* replace [result|callee] with an aux variable dedicated to this proc *) let callee_ret_pvar = - Exp.Lvar (Pvar.to_callee callee_pname (Pvar.get_ret_pvar callee_pname)) in + Exp.Lvar (Pvar.to_callee callee_pname (Pvar.get_ret_pvar callee_pname)) + in match Prop.prop_iter_create post_p2 with - | None -> post_p2 - | Some iter -> - let filter = function - | Sil.Hpointsto (e, _, _) when Exp.equal e callee_ret_pvar -> Some () - | _ -> None in + | None + -> post_p2 + | Some iter + -> let filter = function + | Sil.Hpointsto (e, _, _) when Exp.equal e callee_ret_pvar + -> Some () + | _ + -> None + in match Prop.prop_iter_find iter filter with - | None -> post_p2 + | None + -> post_p2 | Some iter' -> - match fst (Prop.prop_iter_current tenv iter'), ret_id with - | Sil.Hpointsto (_, Sil.Eexp (e', inst), _), _ when exp_is_exn e' -> - (* resuls is an exception: set in caller *) - let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in - prop_set_exn tenv caller_pname p (Sil.Eexp (e', inst)) - | Sil.Hpointsto (_, Sil.Eexp (e', _), _), Some (id, _) -> - let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in - Prop.conjoin_eq tenv e' (Exp.Var id) p - | Sil.Hpointsto (_, Sil.Estruct (ftl, _), _), _ - when Int.equal (List.length ftl) (if is_none ret_id then 0 else 1) -> - (* TODO(jjb): Is this case dead? *) - let rec do_ftl_ids p = function - | [], None -> p - | (_, Sil.Eexp (e', _)) :: ftl', Some (ret_id, _) -> - let p' = Prop.conjoin_eq tenv e' (Exp.Var ret_id) p in - do_ftl_ids p' (ftl', None) - | _ -> p in - let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in - do_ftl_ids p (ftl, ret_id) - | Sil.Hpointsto _, _ -> - (* returning nothing or unexpected sexp, turning into nondet *) - Prop.prop_iter_remove_curr_then_to_prop tenv iter' - | _ -> assert false in + match (fst (Prop.prop_iter_current tenv iter'), ret_id) with + | Sil.Hpointsto (_, Sil.Eexp (e', inst), _), _ when exp_is_exn e' + -> (* resuls is an exception: set in caller *) + let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in + prop_set_exn tenv caller_pname p (Sil.Eexp (e', inst)) + | Sil.Hpointsto (_, Sil.Eexp (e', _), _), Some (id, _) + -> let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in + Prop.conjoin_eq tenv e' (Exp.Var id) p + | Sil.Hpointsto (_, Sil.Estruct (ftl, _), _), _ + when Int.equal (List.length ftl) (if is_none ret_id then 0 else 1) + -> (* TODO(jjb): Is this case dead? *) + let rec do_ftl_ids p = function + | [], None + -> p + | (_, Sil.Eexp (e', _)) :: ftl', Some (ret_id, _) + -> let p' = Prop.conjoin_eq tenv e' (Exp.Var ret_id) p in + do_ftl_ids p' (ftl', None) + | _ + -> p + in + let p = Prop.prop_iter_remove_curr_then_to_prop tenv iter' in + do_ftl_ids p (ftl, ret_id) + | Sil.Hpointsto _, _ + -> (* returning nothing or unexpected sexp, turning into nondet *) + Prop.prop_iter_remove_curr_then_to_prop tenv iter' + | _ + -> assert false + in let post_p4 = - if !Config.footprint - then - prop_footprint_add_pi_sigma_starfld_sigma tenv - post_p3 - split.missing_pi - split.missing_sigma - split.missing_fld - split.missing_typ - else Some post_p3 in - post_p4 in + if !Config.footprint then + prop_footprint_add_pi_sigma_starfld_sigma tenv post_p3 split.missing_pi split.missing_sigma + split.missing_fld split.missing_typ + else Some post_p3 + in + post_p4 + in let _results = List.map ~f:(fun (p, path) -> (compute_result p, path)) instantiated_post in if List.exists ~f:(fun (x, _) -> is_none x) _results then (* at least one combine failed *) None else let results = - List.map ~f:(function (Some x, path) -> (x, path) | (None, _) -> assert false) - _results in - print_results tenv actual_pre (List.map ~f:fst results); + List.map ~f:(function Some x, path -> (x, path) | None, _ -> assert false) _results + in + print_results tenv actual_pre (List.map ~f:fst results) ; Some results (* Add Auntaint attribute to a callee_pname precondition *) let mk_pre tenv pre formal_params callee_pname callee_attrs = - if Config.taint_analysis - then + if Config.taint_analysis then match Taint.accepts_sensitive_params callee_pname (Some callee_attrs) with - | [] -> pre - | tainted_param_nums -> - Taint.get_params_to_taint tainted_param_nums formal_params + | [] + -> pre + | tainted_param_nums + -> Taint.get_params_to_taint tainted_param_nums formal_params |> List.fold - ~f:(fun prop_acc (param, taint_kind) -> - let attr = PredSymb.Auntaint { taint_source = callee_pname; taint_kind; } in - Taint.add_tainting_attribute tenv attr param prop_acc) - ~init:(Prop.normalize tenv pre) + ~f:(fun prop_acc (param, taint_kind) -> + let attr = PredSymb.Auntaint {taint_source= callee_pname; taint_kind} in + Taint.add_tainting_attribute tenv attr param prop_acc) + ~init:(Prop.normalize tenv pre) |> Prop.expose else pre let report_taint_error e taint_info callee_pname caller_pname calling_prop = - let err_desc = - Errdesc.explain_tainted_value_reaching_sensitive_function - calling_prop - e - taint_info - callee_pname - (State.get_loc ()) in - let exn = - Exceptions.Tainted_value_reaching_sensitive_function - (err_desc, __POS__) in + let err_desc = + Errdesc.explain_tainted_value_reaching_sensitive_function calling_prop e taint_info + callee_pname (State.get_loc ()) + in + let exn = Exceptions.Tainted_value_reaching_sensitive_function (err_desc, __POS__) in Reporting.log_warning_deprecated caller_pname exn let check_taint_on_variadic_function tenv callee_pname caller_pname actual_params calling_prop = - let rec n_tail lst n = (* return the tail of a list from element n *) - if Int.equal n 1 then lst - else match lst with - | [] -> [] - | _::lst' -> n_tail lst' (n-1) in + let rec n_tail lst n = + (* return the tail of a list from element n *) + if Int.equal n 1 then lst else match lst with [] -> [] | _ :: lst' -> n_tail lst' (n - 1) + in let tainted_params = Taint.accepts_sensitive_params callee_pname None in match tainted_params with - | [(tp, _)] when tp < 0 -> - (* All actual params from abs(tp) should not be tainted. If we find one we give the warning *) + | [(tp, _)] when tp < 0 + -> (* All actual params from abs(tp) should not be tainted. If we find one we give the warning *) let tp_abs = abs tp in L.d_strln - ("Checking tainted actual parameters from parameter number " ^ - (string_of_int tp_abs) ^ - " onwards."); + ( "Checking tainted actual parameters from parameter number " ^ string_of_int tp_abs + ^ " onwards." ) ; let actual_params' = n_tail actual_params tp_abs in - L.d_str "Paramters to be checked: [ "; - List.iter ~f:(fun (e,_) -> - L.d_str (" " ^ (Exp.to_string e) ^ " "); + L.d_str "Paramters to be checked: [ " ; + List.iter + ~f:(fun (e, _) -> + L.d_str (" " ^ Exp.to_string e ^ " ") ; match Attribute.get_taint tenv calling_prop e with - | Some (Apred (Ataint taint_info, _)) -> - report_taint_error e taint_info callee_pname caller_pname calling_prop - | _ -> ()) actual_params'; - L.d_strln" ]" - | _ -> () + | Some Apred (Ataint taint_info, _) + -> report_taint_error e taint_info callee_pname caller_pname calling_prop + | _ + -> ()) + actual_params' ; + L.d_strln " ]" + | _ + -> () (** Construct the actual precondition: add to the current state a copy of the (callee's) formal parameters instantiated with the actual parameters. *) let mk_actual_precondition tenv prop actual_params formal_params = let formals_actuals = - let rec comb fpars apars = match fpars, apars with - | f:: fpars', a:: apars' -> (f, a) :: comb fpars' apars' - | [], _ -> - if apars <> [] then - begin + let rec comb fpars apars = + match (fpars, apars) with + | f :: fpars', a :: apars' + -> (f, a) :: comb fpars' apars' + | [], _ + -> ( if apars <> [] then let str = - "more actual pars than formal pars in fun call (" ^ - string_of_int (List.length actual_params) ^ - " vs " ^ - string_of_int (List.length formal_params) ^ - ")" in - L.d_warning str; L.d_ln () - end; + "more actual pars than formal pars in fun call (" + ^ string_of_int (List.length actual_params) ^ " vs " + ^ string_of_int (List.length formal_params) ^ ")" + in + L.d_warning str ; L.d_ln () ) ; [] - | _:: _,[] -> raise (Exceptions.Wrong_argument_number __POS__) in - comb formal_params actual_params in + | _ :: _, [] + -> raise (Exceptions.Wrong_argument_number __POS__) + in + comb formal_params actual_params + in let mk_instantiation (formal_var, (actual_e, actual_t)) = - Prop.mk_ptsto tenv - (Exp.Lvar formal_var) - (Sil.Eexp (actual_e, Sil.inst_actual_precondition)) - (Exp.Sizeof {typ=actual_t; nbytes=None; dynamic_length=None; subtype=Subtype.exact}) in + Prop.mk_ptsto tenv (Exp.Lvar formal_var) (Sil.Eexp (actual_e, Sil.inst_actual_precondition)) + (Exp.Sizeof {typ= actual_t; nbytes= None; dynamic_length= None; subtype= Subtype.exact}) + in let instantiated_formals = List.map ~f:mk_instantiation formals_actuals in let actual_pre = Prop.prop_sigma_star prop instantiated_formals in Prop.normalize tenv actual_pre let mk_posts tenv ret_id prop callee_pname callee_attrs posts = match ret_id with - | Some (ret_id, _) -> - let mk_getter_idempotent posts = + | Some (ret_id, _) + -> let mk_getter_idempotent posts = (* if we have seen a previous call to the same function, only use specs whose return value is consistent with constraints on the return value of the previous call w.r.t to nullness. meant to eliminate false NPE warnings from the common @@ -900,47 +1019,59 @@ let mk_posts tenv ret_id prop callee_pname callee_attrs posts = let last_call_ret_non_null = List.exists ~f:(function - | Sil.Apred (Aretval (pname, _), [exp]) when Typ.Procname.equal callee_pname pname -> - Prover.check_disequal tenv prop exp Exp.zero - | _ -> false) - (Attribute.get_all prop) in + | Sil.Apred (Aretval (pname, _), [exp]) when Typ.Procname.equal callee_pname pname + -> Prover.check_disequal tenv prop exp Exp.zero + | _ + -> false) + (Attribute.get_all prop) + in if last_call_ret_non_null then let returns_null prop = List.exists ~f:(function - | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp (e, _), _) when Pvar.is_return pvar -> - Prover.check_equal tenv (Prop.normalize tenv prop) e Exp.zero - | _ -> false) - prop.Prop.sigma in + | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp (e, _), _) when Pvar.is_return pvar + -> Prover.check_equal tenv (Prop.normalize tenv prop) e Exp.zero + | _ + -> false) + prop.Prop.sigma + in List.filter ~f:(fun (prop, _) -> not (returns_null prop)) posts - else posts in + else posts + in let mk_retval_tainted posts = match Taint.returns_tainted callee_pname (Some callee_attrs) with - | Some taint_kind -> - let taint_retval (prop, path) = + | Some taint_kind + -> let taint_retval (prop, path) = let prop_normal = Prop.normalize tenv prop in let prop' = Attribute.add_or_replace tenv prop_normal - (Apred (Ataint { taint_source = callee_pname; taint_kind; }, [Exp.Var ret_id])) - |> Prop.expose in - (prop', path) in + (Apred (Ataint {taint_source= callee_pname; taint_kind}, [Exp.Var ret_id])) + |> Prop.expose + in + (prop', path) + in List.map ~f:taint_retval posts - | None -> posts in + | None + -> posts + in let posts' = - if Config.idempotent_getters && Config.curr_language_is Config.Java - then mk_getter_idempotent posts - else posts in + if Config.idempotent_getters && Config.curr_language_is Config.Java then + mk_getter_idempotent posts + else posts + in if Config.taint_analysis then mk_retval_tainted posts' else posts' - | _ -> posts + | _ + -> posts (** Check if actual_pre * missing_footprint |- false *) let inconsistent_actualpre_missing tenv actual_pre split_opt = match split_opt with - | Some split -> - let prop'= Prop.normalize tenv (Prop.prop_sigma_star actual_pre split.missing_sigma) in - let prop''= List.fold ~f:(Prop.prop_atom_and tenv) ~init:prop' split.missing_pi in + | Some split + -> let prop' = Prop.normalize tenv (Prop.prop_sigma_star actual_pre split.missing_sigma) in + let prop'' = List.fold ~f:(Prop.prop_atom_and tenv) ~init:prop' split.missing_pi in Prover.check_inconsistency tenv prop'' - | None -> false + | None + -> false (* perform the taint analysis check by comparing the taint atoms in [calling_pi] with the untaint atoms required by the [missing_pi] computed during abduction *) @@ -953,161 +1084,201 @@ let do_taint_check tenv caller_pname callee_pname calling_prop missing_pi sub ac attrs (we will flag errors on those exprs) *) let collect_taint_untaint_exprs acc_map (atom: Sil.atom) = match atom with - | Apred (Ataint _, [e]) -> - let taint_atoms, untaint_atoms = try Exp.Map.find e acc_map with Not_found -> ([], []) in + | Apred (Ataint _, [e]) + -> let taint_atoms, untaint_atoms = + try Exp.Map.find e acc_map + with Not_found -> ([], []) + in Exp.Map.add e (atom :: taint_atoms, untaint_atoms) acc_map - | Apred (Auntaint _, [e]) -> - let taint_atoms, untaint_atoms = try Exp.Map.find e acc_map with Not_found -> ([], []) in + | Apred (Auntaint _, [e]) + -> let taint_atoms, untaint_atoms = + try Exp.Map.find e acc_map + with Not_found -> ([], []) + in Exp.Map.add e (taint_atoms, atom :: untaint_atoms) acc_map - | _ -> acc_map in + | _ + -> acc_map + in let taint_untaint_exp_map = - List.fold - ~f:collect_taint_untaint_exprs - ~init:Exp.Map.empty - combined_pi - |> Exp.Map.filter (fun _ (taint, untaint) -> taint <> [] && untaint <> []) in + List.fold ~f:collect_taint_untaint_exprs ~init:Exp.Map.empty combined_pi + |> Exp.Map.filter (fun _ (taint, untaint) -> taint <> [] && untaint <> []) + in (* TODO: in the future, we will have a richer taint domain that will require making sure that the "kind" (e.g. security, privacy) of the taint and untaint match, but for now we don't look at the untaint atoms *) let report_taint_errors e (taint_atoms, _untaint_atoms) = let report_one_error (taint_atom: Sil.atom) = - let taint_info = match taint_atom with - | Apred (Ataint taint_info, _) -> taint_info - | _ -> failwith "Expected to get taint attr on atom" in - report_taint_error e taint_info callee_pname caller_pname calling_prop in - List.iter ~f:report_one_error taint_atoms in - Exp.Map.iter report_taint_errors taint_untaint_exp_map; + let taint_info = + match taint_atom with + | Apred (Ataint taint_info, _) + -> taint_info + | _ + -> failwith "Expected to get taint attr on atom" + in + report_taint_error e taint_info callee_pname caller_pname calling_prop + in + List.iter ~f:report_one_error taint_atoms + in + Exp.Map.iter report_taint_errors taint_untaint_exp_map ; (* filter out UNTAINT(e) atoms from [missing_pi] such that we have already reported a taint error on e. without doing this, we will get PRECONDITION_NOT_MET (and failed spec inference), which is bad. instead, what this does is effectively assume that the UNTAINT(e) precondition was met, and continue with the analysis under this assumption. this makes sense because we are reporting the taint error, but propagating a *safe* postcondition w.r.t to tainting. *) - let not_untaint_atom atom = not + let not_untaint_atom atom = + not (Exp.Map.exists (fun _ (_, untaint_atoms) -> - List.exists - ~f:(fun a -> Sil.equal_atom atom a) - untaint_atoms) - taint_untaint_exp_map) in - check_taint_on_variadic_function tenv callee_pname caller_pname actual_params calling_prop; + List.exists ~f:(fun a -> Sil.equal_atom atom a) untaint_atoms) + taint_untaint_exp_map) + in + check_taint_on_variadic_function tenv callee_pname caller_pname actual_params calling_prop ; List.filter ~f:not_untaint_atom missing_pi_sub let class_cast_exn tenv pname_opt texp1 texp2 exp ml_loc = let desc = - Errdesc.explain_class_cast_exception tenv - pname_opt texp1 texp2 exp (State.get_node ()) (State.get_loc ()) in + Errdesc.explain_class_cast_exception tenv pname_opt texp1 texp2 exp (State.get_node ()) + (State.get_loc ()) + in Exceptions.Class_cast_exception (desc, ml_loc) let create_cast_exception tenv ml_loc pname_opt texp1 texp2 exp = class_cast_exn tenv pname_opt texp1 texp2 exp ml_loc -let get_check_exn tenv check callee_pname loc ml_loc = match check with - | Prover.Bounds_check -> - let desc = Localise.desc_precondition_not_met (Some Localise.Pnm_bounds) callee_pname loc in +let get_check_exn tenv check callee_pname loc ml_loc = + match check with + | Prover.Bounds_check + -> let desc = Localise.desc_precondition_not_met (Some Localise.Pnm_bounds) callee_pname loc in Exceptions.Precondition_not_met (desc, ml_loc) - | Prover.Class_cast_check (texp1, texp2, exp) -> - class_cast_exn tenv (Some callee_pname) texp1 texp2 exp ml_loc + | Prover.Class_cast_check (texp1, texp2, exp) + -> class_cast_exn tenv (Some callee_pname) texp1 texp2 exp ml_loc let check_uninitialize_dangling_deref tenv callee_pname actual_pre sub formal_params props = - List.iter ~f:(fun (p, _ ) -> + List.iter + ~f:(fun (p, _) -> match check_dereferences tenv callee_pname actual_pre sub p formal_params with - | Some (Deref_undef_exp, desc) -> - raise (Exceptions.Dangling_pointer_dereference (Some PredSymb.DAuninit, desc, __POS__)) - | _ -> ()) props + | Some (Deref_undef_exp, desc) + -> raise (Exceptions.Dangling_pointer_dereference (Some PredSymb.DAuninit, desc, __POS__)) + | _ + -> ()) + props (** Perform symbolic execution for a single spec *) -let exe_spec - tenv ret_id (n, nspecs) caller_pdesc callee_pname callee_attrs loc prop path_pre - (spec : Prop.exposed Specs.spec) actual_params formal_params : abduction_res = +let exe_spec tenv ret_id (n, nspecs) caller_pdesc callee_pname callee_attrs loc prop path_pre + (spec: Prop.exposed Specs.spec) actual_params formal_params : abduction_res = let caller_pname = Procdesc.get_proc_name caller_pdesc in let posts = mk_posts tenv ret_id prop callee_pname callee_attrs spec.Specs.posts in let actual_pre = mk_actual_precondition tenv prop actual_params formal_params in let spec_pre = - mk_pre tenv (Specs.Jprop.to_prop spec.Specs.pre) formal_params callee_pname callee_attrs in - L.d_strln ("EXECUTING SPEC " ^ string_of_int n ^ "/" ^ string_of_int nspecs); - L.d_strln "ACTUAL PRECONDITION ="; - L.d_increase_indent 1; Prop.d_prop actual_pre; L.d_decrease_indent 1; L.d_ln (); - L.d_strln "SPEC ="; - L.d_increase_indent 1; Specs.d_spec spec; L.d_decrease_indent 1; L.d_ln (); - SymOp.pay(); (* pay one symop *) + mk_pre tenv (Specs.Jprop.to_prop spec.Specs.pre) formal_params callee_pname callee_attrs + in + L.d_strln ("EXECUTING SPEC " ^ string_of_int n ^ "/" ^ string_of_int nspecs) ; + L.d_strln "ACTUAL PRECONDITION =" ; + L.d_increase_indent 1 ; + Prop.d_prop actual_pre ; + L.d_decrease_indent 1 ; + L.d_ln () ; + L.d_strln "SPEC =" ; + L.d_increase_indent 1 ; + Specs.d_spec spec ; + L.d_decrease_indent 1 ; + L.d_ln () ; + SymOp.pay () ; + (* pay one symop *) match Prover.check_implication_for_footprint caller_pname tenv actual_pre spec_pre with - | Prover.ImplFail checks -> Invalid_res (Prover_checks checks) + | Prover.ImplFail checks + -> Invalid_res (Prover_checks checks) | Prover.ImplOK - (checks, sub1, sub2, frame, missing_pi, missing_sigma, - frame_fld, missing_fld, frame_typ, missing_typ) -> - let log_check_exn check = + ( checks + , sub1 + , sub2 + , frame + , missing_pi + , missing_sigma + , frame_fld + , missing_fld + , frame_typ + , missing_typ ) + -> let log_check_exn check = let exn = get_check_exn tenv check callee_pname loc __POS__ in - Reporting.log_warning_deprecated caller_pname exn in + Reporting.log_warning_deprecated caller_pname exn + in let do_split () = let missing_pi' = if Config.taint_analysis then do_taint_check tenv caller_pname callee_pname actual_pre missing_pi sub2 actual_params - else missing_pi in - process_splitting - actual_pre sub1 sub2 frame missing_pi' missing_sigma - frame_fld missing_fld frame_typ missing_typ in + else missing_pi + in + process_splitting actual_pre sub1 sub2 frame missing_pi' missing_sigma frame_fld + missing_fld frame_typ missing_typ + in let report_valid_res split = - match combine tenv - ret_id posts - actual_pre path_pre split - caller_pdesc callee_pname loc with - | None -> Invalid_res Cannot_combine - | Some results -> - (* After combining we check that we have not added + match + combine tenv ret_id posts actual_pre path_pre split caller_pdesc callee_pname loc + with + | None + -> Invalid_res Cannot_combine + | Some results + -> (* After combining we check that we have not added a points-to of initialized variables.*) - check_uninitialize_dangling_deref tenv - callee_pname actual_pre split.sub formal_params results; + check_uninitialize_dangling_deref tenv callee_pname actual_pre split.sub formal_params + results ; let inconsistent_results, consistent_results = - List.partition_tf ~f:(fun (p, _) -> Prover.check_inconsistency tenv p) results in + List.partition_tf ~f:(fun (p, _) -> Prover.check_inconsistency tenv p) results + in let incons_pre_missing = inconsistent_actualpre_missing tenv actual_pre (Some split) in - Valid_res { incons_pre_missing = incons_pre_missing; - vr_pi = split.missing_pi; - vr_sigma = split.missing_sigma; - vr_cons_res = consistent_results; - vr_incons_res = inconsistent_results } in - begin - List.iter ~f:log_check_exn checks; - let subbed_pre = (Prop.prop_sub (`Exp sub1) actual_pre) in - match check_dereferences tenv callee_pname subbed_pre (`Exp sub2) spec_pre formal_params with - | Some (Deref_undef _, _) when Config.angelic_execution -> - let split = do_split () in - report_valid_res split - | Some (deref_error, desc) -> - let rec join_paths = function - | [] -> None - | (_, p):: l -> - (match join_paths l with - | None -> Some p - | Some p' -> Some (Paths.Path.join p p')) in - let pjoin = join_paths posts in (* join the paths from the posts *) - Invalid_res (Dereference_error (deref_error, desc, pjoin)) - | None -> - let split = do_split () in - (* check if a missing_fld hpred is about a hidden field *) - let hpred_missing_hidden = function - | Sil.Hpointsto (_, Sil.Estruct ([(fld, _)], _), _) -> Typ.Fieldname.is_hidden fld - | _ -> false in - (* missing fields minus hidden fields *) - let missing_fld_nohidden = - List.filter ~f:(fun hp -> not (hpred_missing_hidden hp)) missing_fld in - if not !Config.footprint && split.missing_sigma <> [] then - begin - L.d_strln "Implication error: missing_sigma not empty in re-execution"; - Invalid_res Missing_sigma_not_empty - end - else if not !Config.footprint && missing_fld_nohidden <> [] then - begin - L.d_strln "Implication error: missing_fld not empty in re-execution"; - Invalid_res Missing_fld_not_empty - end - else report_valid_res split - end + Valid_res + { incons_pre_missing + ; vr_pi= split.missing_pi + ; vr_sigma= split.missing_sigma + ; vr_cons_res= consistent_results + ; vr_incons_res= inconsistent_results } + in + List.iter ~f:log_check_exn checks ; + let subbed_pre = Prop.prop_sub (`Exp sub1) actual_pre in + match check_dereferences tenv callee_pname subbed_pre (`Exp sub2) spec_pre formal_params with + | Some (Deref_undef _, _) when Config.angelic_execution + -> let split = do_split () in + report_valid_res split + | Some (deref_error, desc) + -> let rec join_paths = function + | [] + -> None + | (_, p) :: l -> + match join_paths l with None -> Some p | Some p' -> Some (Paths.Path.join p p') + in + let pjoin = join_paths posts in + (* join the paths from the posts *) + Invalid_res (Dereference_error (deref_error, desc, pjoin)) + | None + -> let split = do_split () in + (* check if a missing_fld hpred is about a hidden field *) + let hpred_missing_hidden = function + | Sil.Hpointsto (_, Sil.Estruct ([(fld, _)], _), _) + -> Typ.Fieldname.is_hidden fld + | _ + -> false + in + (* missing fields minus hidden fields *) + let missing_fld_nohidden = + List.filter ~f:(fun hp -> not (hpred_missing_hidden hp)) missing_fld + in + if not !Config.footprint && split.missing_sigma <> [] then ( + L.d_strln "Implication error: missing_sigma not empty in re-execution" ; + Invalid_res Missing_sigma_not_empty ) + else if not !Config.footprint && missing_fld_nohidden <> [] then ( + L.d_strln "Implication error: missing_fld not empty in re-execution" ; + Invalid_res Missing_fld_not_empty ) + else report_valid_res split let remove_constant_string_class tenv prop = let filter = function - | Sil.Hpointsto (Exp.Const (Const.Cstr _ | Const.Cclass _), _, _) -> false - | _ -> true in + | Sil.Hpointsto (Exp.Const (Const.Cstr _ | Const.Cclass _), _, _) + -> false + | _ + -> true + in let sigma = List.filter ~f:filter prop.Prop.sigma in let sigmafp = List.filter ~f:filter prop.Prop.sigma_fp in let prop' = Prop.set prop ~sigma ~sigma_fp:sigmafp in @@ -1117,9 +1288,9 @@ let remove_constant_string_class tenv prop = by the prover to keep track of expansions of lhs paths and remove pointsto's whose lhs is a constant string *) let quantify_path_idents_remove_constant_strings tenv (prop: Prop.normal Prop.t) - : Prop.normal Prop.t = + : Prop.normal Prop.t = let fav = Prop.prop_fav prop in - Sil.fav_filter_ident fav Ident.is_path; + Sil.fav_filter_ident fav Ident.is_path ; remove_constant_string_class tenv (Prop.exist_quantify tenv fav prop) (** Strengthen the footprint by adding pure facts from the current part *) @@ -1128,197 +1299,200 @@ let prop_pure_to_footprint tenv (p: 'a Prop.t) : Prop.normal Prop.t = not (Attribute.is_pred a) && let a_fav = Sil.atom_fav a in - Sil.fav_for_all a_fav Ident.is_footprint in + Sil.fav_for_all a_fav Ident.is_footprint + in let pure = Prop.get_pure p in let new_footprint_atoms = List.filter ~f:is_footprint_atom_not_attribute pure in - if List.is_empty new_footprint_atoms - then p - else (* add pure fact to footprint *) - let filtered_pi_fp = List.filter (p.Prop.pi_fp @ new_footprint_atoms) ~f:(fun a -> - not (Sil.atom_has_local_addr a)) in + if List.is_empty new_footprint_atoms then p + else + (* add pure fact to footprint *) + let filtered_pi_fp = + List.filter (p.Prop.pi_fp @ new_footprint_atoms) ~f:(fun a -> not (Sil.atom_has_local_addr a)) + in Prop.normalize tenv (Prop.set p ~pi_fp:filtered_pi_fp) (** post-process the raw result of a function call *) let exe_call_postprocess tenv ret_id trace_call callee_pname callee_attrs loc results = - let filter_valid_res = function - | Invalid_res _ -> false - | Valid_res _ -> true in - let valid_res0, invalid_res0 = - List.partition_tf ~f:filter_valid_res results in + let filter_valid_res = function Invalid_res _ -> false | Valid_res _ -> true in + let valid_res0, invalid_res0 = List.partition_tf ~f:filter_valid_res results in let valid_res = - List.map ~f:(function Valid_res cr -> cr | Invalid_res _ -> assert false) valid_res0 in + List.map ~f:(function Valid_res cr -> cr | Invalid_res _ -> assert false) valid_res0 + in let invalid_res = - List.map ~f:(function Valid_res _ -> assert false | Invalid_res ir -> ir) invalid_res0 in + List.map ~f:(function Valid_res _ -> assert false | Invalid_res ir -> ir) invalid_res0 + in let valid_res_miss_pi, valid_res_no_miss_pi = - List.partition_tf ~f:(fun vr -> vr.vr_pi <> []) valid_res in + List.partition_tf ~f:(fun vr -> vr.vr_pi <> []) valid_res + in let _, valid_res_cons_pre_missing = - List.partition_tf ~f:(fun vr -> vr.incons_pre_missing) valid_res in + List.partition_tf ~f:(fun vr -> vr.incons_pre_missing) valid_res + in let deref_errors = - List.filter ~f:(function Dereference_error _ -> true | _ -> false) invalid_res in - let print_pi pi = - L.d_str "pi: "; Prop.d_pi pi; L.d_ln () in + List.filter ~f:(function Dereference_error _ -> true | _ -> false) invalid_res + in + let print_pi pi = L.d_str "pi: " ; Prop.d_pi pi ; L.d_ln () in let call_desc kind_opt = Localise.desc_precondition_not_met kind_opt callee_pname loc in let res_with_path_idents = if !Config.footprint then - begin - if List.is_empty valid_res_cons_pre_missing then - (* no valid results where actual pre and missing are consistent *) - begin - match deref_errors with - | error :: _ -> (* dereference error detected *) - let extend_path path_opt path_pos_opt = match path_opt with - | None -> () - | Some path_post -> - let old_path, _ = State.get_path () in - let new_path = - Paths.Path.add_call - (include_subtrace callee_pname) old_path callee_pname path_post in - State.set_path new_path path_pos_opt in - (match error with - | Dereference_error (Deref_minusone, desc, path_opt) -> - trace_call Specs.CallStats.CR_not_met; - extend_path path_opt None; - raise (Exceptions.Dangling_pointer_dereference - (Some PredSymb.DAminusone, desc, __POS__)) - | Dereference_error (Deref_undef_exp, desc, path_opt) -> - trace_call Specs.CallStats.CR_not_met; - extend_path path_opt None; - raise (Exceptions.Dangling_pointer_dereference - (Some PredSymb.DAuninit, desc, __POS__)) - | Dereference_error (Deref_null pos, desc, path_opt) -> - trace_call Specs.CallStats.CR_not_met; - extend_path path_opt (Some pos); - if Localise.is_parameter_not_null_checked_desc desc then - raise (Exceptions.Parameter_not_null_checked (desc, __POS__)) - else if Localise.is_field_not_null_checked_desc desc then - raise (Exceptions.Field_not_null_checked (desc, __POS__)) - else if Localise.is_double_lock_desc desc then - raise (Exceptions.Double_lock (desc, __POS__)) - else if Localise.is_empty_vector_access_desc desc then - raise (Exceptions.Empty_vector_access (desc, __POS__)) - else raise (Exceptions.Null_dereference (desc, __POS__)) - | Dereference_error (Deref_freed _, desc, path_opt) -> - trace_call Specs.CallStats.CR_not_met; - extend_path path_opt None; - raise (Exceptions.Use_after_free (desc, __POS__)) - | Dereference_error (Deref_undef (_, _, pos), desc, path_opt) -> - trace_call Specs.CallStats.CR_not_met; - extend_path path_opt (Some pos); - raise (Exceptions.Skip_pointer_dereference (desc, __POS__)) - | Prover_checks _ - | Cannot_combine - | Missing_sigma_not_empty - | Missing_fld_not_empty -> - trace_call Specs.CallStats.CR_not_met; - assert false) - | [] -> (* no dereference error detected *) - let desc = - if List.exists ~f:(function Cannot_combine -> true | _ -> false) invalid_res then - call_desc (Some Localise.Pnm_dangling) - else if List.exists ~f:(function - | Prover_checks (check :: _) -> - trace_call Specs.CallStats.CR_not_met; - let exn = get_check_exn tenv check callee_pname loc __POS__ in - raise exn - | _ -> false) invalid_res then - call_desc (Some Localise.Pnm_bounds) - else call_desc None in - trace_call Specs.CallStats.CR_not_met; - raise (Exceptions.Precondition_not_met (desc, __POS__)) - end - else (* combine the valid results, and store diverging states *) - let process_valid_res vr = - let save_diverging_states () = - if not vr.incons_pre_missing && List.is_empty vr.vr_cons_res - then (* no consistent results on one spec: divergence *) - let incons_res = - List.map - ~f:(fun (p, path) -> (prop_pure_to_footprint tenv p, path)) - vr.vr_incons_res in - State.add_diverging_states (Paths.PathSet.from_renamed_list incons_res) in - save_diverging_states (); - vr.vr_cons_res in - List.map - ~f:(fun (p, path) -> (prop_pure_to_footprint tenv p, path)) - (List.concat_map ~f:process_valid_res valid_res) - end + if List.is_empty valid_res_cons_pre_missing then + (* no valid results where actual pre and missing are consistent *) + match deref_errors with + | error :: _ + -> ( + (* dereference error detected *) + let extend_path path_opt path_pos_opt = + match path_opt with + | None + -> () + | Some path_post + -> let old_path, _ = State.get_path () in + let new_path = + Paths.Path.add_call (include_subtrace callee_pname) old_path callee_pname + path_post + in + State.set_path new_path path_pos_opt + in + match error with + | Dereference_error (Deref_minusone, desc, path_opt) + -> trace_call Specs.CallStats.CR_not_met ; + extend_path path_opt None ; + raise + (Exceptions.Dangling_pointer_dereference (Some PredSymb.DAminusone, desc, __POS__)) + | Dereference_error (Deref_undef_exp, desc, path_opt) + -> trace_call Specs.CallStats.CR_not_met ; + extend_path path_opt None ; + raise + (Exceptions.Dangling_pointer_dereference (Some PredSymb.DAuninit, desc, __POS__)) + | Dereference_error (Deref_null pos, desc, path_opt) + -> trace_call Specs.CallStats.CR_not_met ; + extend_path path_opt (Some pos) ; + if Localise.is_parameter_not_null_checked_desc desc then + raise (Exceptions.Parameter_not_null_checked (desc, __POS__)) + else if Localise.is_field_not_null_checked_desc desc then + raise (Exceptions.Field_not_null_checked (desc, __POS__)) + else if Localise.is_double_lock_desc desc then + raise (Exceptions.Double_lock (desc, __POS__)) + else if Localise.is_empty_vector_access_desc desc then + raise (Exceptions.Empty_vector_access (desc, __POS__)) + else raise (Exceptions.Null_dereference (desc, __POS__)) + | Dereference_error (Deref_freed _, desc, path_opt) + -> trace_call Specs.CallStats.CR_not_met ; + extend_path path_opt None ; + raise (Exceptions.Use_after_free (desc, __POS__)) + | Dereference_error (Deref_undef (_, _, pos), desc, path_opt) + -> trace_call Specs.CallStats.CR_not_met ; + extend_path path_opt (Some pos) ; + raise (Exceptions.Skip_pointer_dereference (desc, __POS__)) + | Prover_checks _ | Cannot_combine | Missing_sigma_not_empty | Missing_fld_not_empty + -> trace_call Specs.CallStats.CR_not_met ; + assert false ) + | [] + -> (* no dereference error detected *) + let desc = + if List.exists ~f:(function Cannot_combine -> true | _ -> false) invalid_res then + call_desc (Some Localise.Pnm_dangling) + else if List.exists + ~f:(function + | Prover_checks (check :: _) + -> trace_call Specs.CallStats.CR_not_met ; + let exn = get_check_exn tenv check callee_pname loc __POS__ in + raise exn + | _ + -> false) + invalid_res + then call_desc (Some Localise.Pnm_bounds) + else call_desc None + in + trace_call Specs.CallStats.CR_not_met ; + raise (Exceptions.Precondition_not_met (desc, __POS__)) + else + (* combine the valid results, and store diverging states *) + let process_valid_res vr = + let save_diverging_states () = + if not vr.incons_pre_missing && List.is_empty vr.vr_cons_res then + (* no consistent results on one spec: divergence *) + let incons_res = + List.map + ~f:(fun (p, path) -> (prop_pure_to_footprint tenv p, path)) + vr.vr_incons_res + in + State.add_diverging_states (Paths.PathSet.from_renamed_list incons_res) + in + save_diverging_states () ; vr.vr_cons_res + in + List.map + ~f:(fun (p, path) -> (prop_pure_to_footprint tenv p, path)) + (List.concat_map ~f:process_valid_res valid_res) else if valid_res_no_miss_pi <> [] then List.concat_map ~f:(fun vr -> vr.vr_cons_res) valid_res_no_miss_pi else if List.is_empty valid_res_miss_pi then raise (Exceptions.Precondition_not_met (call_desc None, __POS__)) - else - begin - L.d_strln "Missing pure facts for the function call:"; - List.iter ~f:print_pi (List.map ~f:(fun vr -> vr.vr_pi) valid_res_miss_pi); - match - Prover.find_minimum_pure_cover tenv - (List.map ~f:(fun vr -> (vr.vr_pi, vr.vr_cons_res)) valid_res_miss_pi) with - | None -> - trace_call Specs.CallStats.CR_not_met; - raise (Exceptions.Precondition_not_met (call_desc None, __POS__)) - | Some cover -> - L.d_strln "Found minimum cover"; - List.iter ~f:print_pi (List.map ~f:fst cover); - List.concat_map ~f:snd cover - end in - trace_call Specs.CallStats.CR_success; + else ( + L.d_strln "Missing pure facts for the function call:" ; + List.iter ~f:print_pi (List.map ~f:(fun vr -> vr.vr_pi) valid_res_miss_pi) ; + match + Prover.find_minimum_pure_cover tenv + (List.map ~f:(fun vr -> (vr.vr_pi, vr.vr_cons_res)) valid_res_miss_pi) + with + | None + -> trace_call Specs.CallStats.CR_not_met ; + raise (Exceptions.Precondition_not_met (call_desc None, __POS__)) + | Some cover + -> L.d_strln "Found minimum cover" ; + List.iter ~f:print_pi (List.map ~f:fst cover) ; + List.concat_map ~f:snd cover ) + in + trace_call Specs.CallStats.CR_success ; let res = List.map ~f:(fun (p, path) -> (quantify_path_idents_remove_constant_strings tenv p, path)) - res_with_path_idents in + res_with_path_idents + in let ret_annot, _ = callee_attrs.ProcAttributes.method_annotation in let returns_nullable ret_annot = Annotations.ia_is_nullable ret_annot in let should_add_ret_attr _ = let is_likely_getter = function - | Typ.Procname.Java pn_java -> - Int.equal (List.length (Typ.Procname.java_get_parameters pn_java)) 0 - | _ -> - false in - (Config.idempotent_getters && - Config.curr_language_is Config.Java && - is_likely_getter callee_pname) - || returns_nullable ret_annot in + | Typ.Procname.Java pn_java + -> Int.equal (List.length (Typ.Procname.java_get_parameters pn_java)) 0 + | _ + -> false + in + Config.idempotent_getters && Config.curr_language_is Config.Java + && is_likely_getter callee_pname + || returns_nullable ret_annot + in match ret_id with - | Some (ret_id, _) when should_add_ret_attr () -> - (* add attribute to remember what function call a return id came from *) + | Some (ret_id, _) when should_add_ret_attr () + -> (* add attribute to remember what function call a return id came from *) let ret_var = Exp.Var ret_id in let mark_id_as_retval (p, path) = let att_retval = PredSymb.Aretval (callee_pname, ret_annot) in - Attribute.add tenv p att_retval [ret_var], path in + (Attribute.add tenv p att_retval [ret_var], path) + in List.map ~f:mark_id_as_retval res - | _ -> res + | _ + -> res (** Execute the function call and return the list of results with return value *) -let exe_function_call - callee_summary tenv ret_id caller_pdesc callee_pname loc actual_params prop path = +let exe_function_call callee_summary tenv ret_id caller_pdesc callee_pname loc actual_params prop + path = let callee_attrs = Specs.get_attributes callee_summary in let caller_pname = Procdesc.get_proc_name caller_pdesc in let caller_summary = Specs.get_summary_unsafe "exe_function_call" caller_pname in let trace_call res = - Specs.CallStats.trace - caller_summary.Specs.stats.Specs.call_stats callee_pname loc res !Config.footprint in + Specs.CallStats.trace caller_summary.Specs.stats.Specs.call_stats callee_pname loc res + !Config.footprint + in let spec_list, formal_params = spec_find_rename trace_call callee_summary in let nspecs = List.length spec_list in L.d_strln - ("Found " ^ - string_of_int nspecs ^ - " specs for function " ^ - Typ.Procname.to_string callee_pname); - L.d_strln ("START EXECUTING SPECS FOR " ^ Typ.Procname.to_string callee_pname ^ " from state"); - Prop.d_prop prop; L.d_ln (); + ("Found " ^ string_of_int nspecs ^ " specs for function " ^ Typ.Procname.to_string callee_pname) ; + L.d_strln ("START EXECUTING SPECS FOR " ^ Typ.Procname.to_string callee_pname ^ " from state") ; + Prop.d_prop prop ; + L.d_ln () ; let exe_one_spec (n, spec) = - exe_spec - tenv - ret_id - (n, nspecs) - caller_pdesc - callee_pname - callee_attrs - loc - prop - path - spec - actual_params - formal_params in + exe_spec tenv ret_id (n, nspecs) caller_pdesc callee_pname callee_attrs loc prop path spec + actual_params formal_params + in let results = List.map ~f:exe_one_spec spec_list in exe_call_postprocess tenv ret_id trace_call callee_pname callee_attrs loc results diff --git a/infer/src/backend/tabulation.mli b/infer/src/backend/tabulation.mli index a1f7ae13d..e564e92be 100644 --- a/infer/src/backend/tabulation.mli +++ b/infer/src/backend/tabulation.mli @@ -15,35 +15,35 @@ open! IStd (** Frame and anti-frame *) type splitting -(** Remove constant string or class from a prop *) val remove_constant_string_class : Tenv.t -> 'a Prop.t -> Prop.normal Prop.t +(** Remove constant string or class from a prop *) +val check_attr_dealloc_mismatch : PredSymb.t -> PredSymb.t -> unit (** Check if the attribute change is a mismatch between a kind of allocation and a different kind of deallocation *) -val check_attr_dealloc_mismatch : PredSymb.t -> PredSymb.t -> unit +val find_dereference_without_null_check_in_sexp : Sil.strexp -> (int * PredSymb.path_pos) option (** Check whether a sexp contains a dereference without null check, and return the line number and path position *) -val find_dereference_without_null_check_in_sexp : Sil.strexp -> (int * PredSymb.path_pos) option -(** raise a cast exception *) val create_cast_exception : Tenv.t -> Logging.ml_loc -> Typ.Procname.t option -> Exp.t -> Exp.t -> Exp.t -> exn +(** raise a cast exception *) -(** check if a prop is an exception *) val prop_is_exn : Typ.Procname.t -> 'a Prop.t -> bool +(** check if a prop is an exception *) -(** when prop is an exception, return the exception name *) val prop_get_exn_name : Typ.Procname.t -> 'a Prop.t -> Typ.Name.t option +(** when prop is an exception, return the exception name *) -(** search in prop contains an error state *) val lookup_custom_errors : 'a Prop.t -> string option +(** search in prop contains an error state *) -(** Dump a splitting *) val d_splitting : splitting -> unit +(** Dump a splitting *) +val exe_function_call : + Specs.summary -> Tenv.t -> (Ident.t * Typ.t) option -> Procdesc.t -> Typ.Procname.t -> Location.t + -> (Exp.t * Typ.t) list -> Prop.normal Prop.t -> Paths.Path.t + -> (Prop.normal Prop.t * Paths.Path.t) list (** Execute the function call and return the list of results with return value *) -val exe_function_call: - Specs.summary -> Tenv.t -> (Ident.t * Typ.t) option -> Procdesc.t -> Typ.Procname.t -> - Location.t -> (Exp.t * Typ.t) list -> Prop.normal Prop.t -> Paths.Path.t -> - (Prop.normal Prop.t * Paths.Path.t) list diff --git a/infer/src/backend/taint.ml b/infer/src/backend/taint.ml index 7160c4300..17052af52 100644 --- a/infer/src/backend/taint.ml +++ b/infer/src/backend/taint.ml @@ -8,265 +8,236 @@ *) open! IStd - module L = Logging - open PatternMatch (* list of sources that return a tainted value *) -let sources0 = [ - (* for testing only *) - { - classname = "com.facebook.infer.builtins.InferTaint"; - method_name = "inferSecretSource"; - ret_type = "java.lang.Object"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Java; - }; - { - classname = "com.facebook.infer.builtins.InferTaint"; - method_name = "inferSecretSourceUndefined"; - ret_type = "java.lang.Object"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Java - }; - (* actual specs *) - { - classname = "android.content.SharedPreferences"; - method_name = "getString"; - ret_type = "java.lang.String"; - params = ["java.lang.String"; "java.lang.String"]; - is_static = false; - taint_kind = Tk_shared_preferences_data; - language = Config.Java - }; - (* === iOS === *) - { - classname = "NSHTTPCookie"; - method_name = "value"; - ret_type = "NSString *"; - params = []; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Clang - }; - -] @ FbTaint.sources +let sources0 = + [ (* for testing only *) + { classname= "com.facebook.infer.builtins.InferTaint" + ; method_name= "inferSecretSource" + ; ret_type= "java.lang.Object" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Java } + ; { classname= "com.facebook.infer.builtins.InferTaint" + ; method_name= "inferSecretSourceUndefined" + ; ret_type= "java.lang.Object" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Java } + ; (* actual specs *) + { classname= "android.content.SharedPreferences" + ; method_name= "getString" + ; ret_type= "java.lang.String" + ; params= ["java.lang.String"; "java.lang.String"] + ; is_static= false + ; taint_kind= Tk_shared_preferences_data + ; language= Config.Java } + ; (* === iOS === *) + { classname= "NSHTTPCookie" + ; method_name= "value" + ; ret_type= "NSString *" + ; params= [] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Clang } ] + @ FbTaint.sources (* list of (sensitive sinks, zero-indexed numbers of parameters that should not be tainted). note: index 0 means "the first non-this/self argument"; we currently don't have a way to say "this/self should not be tainted" with this form of specification *) -let sinks = [ - (* for testing only *) - ({ - classname = "com.facebook.infer.builtins.InferTaint"; - method_name = "inferSensitiveSink"; - ret_type = "void"; - params = ["java.lang.Object"]; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Java - }, [0]); - ({ - classname = "com.facebook.infer.builtins.InferTaint"; - method_name = "inferSensitiveSinkUndefined"; - ret_type = "void"; - params = ["java.lang.Object"]; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Java - }, [0]); - (* actual specs *) - ({ - classname = "android.util.Log"; - method_name = "d"; - ret_type = "int"; - params = ["java.lang.String"; "java.lang.String"]; - is_static = true; - taint_kind = Tk_privacy_annotation; - language = Config.Java - }, [0;1]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openInputStream"; - ret_type = "java.io.InputStream"; - params = ["android.net.Uri"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [1]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openOutputStream"; - ret_type = "java.io.OutputStream"; - params = ["android.net.Uri"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openOutputStream"; - ret_type = "java.io.OutputStream"; - params = ["android.net.Uri"; "java.lang.String"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openAssetFileDescriptor"; - ret_type = "android.content.res.AssetFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openAssetFileDescriptor"; - ret_type = "android.content.res.AssetFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"; "android.os.CancellationSignal"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openFileDescriptor"; - ret_type = "android.os.ParcelFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"; "android.os.CancellationSignal"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openFileDescriptor"; - ret_type = "android.os.ParcelFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openTypedAssetFileDescriptor"; - ret_type = "android.content.res.AssetFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"; "android.os.Bundle"; - "android.os.CancellationSignal"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - ({ - classname = "android.content.ContentResolver"; - method_name = "openTypedAssetFileDescriptor"; - ret_type = "android.content.res.AssetFileDescriptor"; - params = ["android.net.Uri"; "java.lang.String"; "android.os.Bundle"]; - is_static = false; - taint_kind = Tk_privacy_annotation; - language = Config.Java; - }, [0]); - - (* === iOS === *) - ({ - classname = "NSString"; - method_name = "stringWithFormat:"; - ret_type = "instancetype"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Clang; - }, [-2]); - ({ - classname = "NSString"; - method_name = "stringWithUTF8String:"; - ret_type = "instancetype"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Clang - }, [-2]); - ({ - classname = "NSString"; - method_name = "localizedStringWithFormat:"; - ret_type = "instancetype"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Clang - }, [-2]); - ({ - classname = "NSString"; - method_name = "initWithFormat:"; - ret_type = "instancetype"; - params = []; - is_static = false; - taint_kind = Tk_unknown; - language = Config.Clang - }, [-2]); - ({ - classname = "NSString"; - method_name = "stringWithString:"; - ret_type = "instancetype"; - params = []; - is_static = true; - taint_kind = Tk_unknown; - language = Config.Clang - }, [0]); +let sinks = + (* it's instance method *) + [ (* for testing only *) + ( { classname= "com.facebook.infer.builtins.InferTaint" + ; method_name= "inferSensitiveSink" + ; ret_type= "void" + ; params= ["java.lang.Object"] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Java } + , [0] ) + ; ( { classname= "com.facebook.infer.builtins.InferTaint" + ; method_name= "inferSensitiveSinkUndefined" + ; ret_type= "void" + ; params= ["java.lang.Object"] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Java } + , [0] ) + ; (* actual specs *) + ( { classname= "android.util.Log" + ; method_name= "d" + ; ret_type= "int" + ; params= ["java.lang.String"; "java.lang.String"] + ; is_static= true + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0; 1] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openInputStream" + ; ret_type= "java.io.InputStream" + ; params= ["android.net.Uri"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [1] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openOutputStream" + ; ret_type= "java.io.OutputStream" + ; params= ["android.net.Uri"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openOutputStream" + ; ret_type= "java.io.OutputStream" + ; params= ["android.net.Uri"; "java.lang.String"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openAssetFileDescriptor" + ; ret_type= "android.content.res.AssetFileDescriptor" + ; params= ["android.net.Uri"; "java.lang.String"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openAssetFileDescriptor" + ; ret_type= "android.content.res.AssetFileDescriptor" + ; params= ["android.net.Uri"; "java.lang.String"; "android.os.CancellationSignal"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openFileDescriptor" + ; ret_type= "android.os.ParcelFileDescriptor" + ; params= ["android.net.Uri"; "java.lang.String"; "android.os.CancellationSignal"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openFileDescriptor" + ; ret_type= "android.os.ParcelFileDescriptor" + ; params= ["android.net.Uri"; "java.lang.String"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openTypedAssetFileDescriptor" + ; ret_type= "android.content.res.AssetFileDescriptor" + ; params= + [ "android.net.Uri" + ; "java.lang.String" + ; "android.os.Bundle" + ; "android.os.CancellationSignal" ] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; ( { classname= "android.content.ContentResolver" + ; method_name= "openTypedAssetFileDescriptor" + ; ret_type= "android.content.res.AssetFileDescriptor" + ; params= ["android.net.Uri"; "java.lang.String"; "android.os.Bundle"] + ; is_static= false + ; taint_kind= Tk_privacy_annotation + ; language= Config.Java } + , [0] ) + ; (* === iOS === *) + ( { classname= "NSString" + ; method_name= "stringWithFormat:" + ; ret_type= "instancetype" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [-2] ) + ; ( { classname= "NSString" + ; method_name= "stringWithUTF8String:" + ; ret_type= "instancetype" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [-2] ) + ; ( { classname= "NSString" + ; method_name= "localizedStringWithFormat:" + ; ret_type= "instancetype" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [-2] ) + ; ( { classname= "NSString" + ; method_name= "initWithFormat:" + ; ret_type= "instancetype" + ; params= [] + ; is_static= false + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [-2] ) + ; ( { classname= "NSString" + ; method_name= "stringWithString:" + ; ret_type= "instancetype" + ; params= [] + ; is_static= true + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [0] ) + ; (* ==== iOS for testing only ==== *) + ( { classname= "ExampleViewController" + ; method_name= "loadURL:trackingCodes:" + ; ret_type= "void" + ; params= [] + ; is_static= false + ; taint_kind= Tk_unknown + ; language= Config.Clang } + , [1] ) ] + @ FbTaint.sinks - (* ==== iOS for testing only ==== *) - ({ - classname = "ExampleViewController"; - method_name = "loadURL:trackingCodes:"; - ret_type = "void"; - params = []; - is_static = false; - taint_kind = Tk_unknown; - language = Config.Clang; - }, [1]); (* it's instance method *) -] @ FbTaint.sinks - -let functions_with_tainted_params = [ - (* ==== iOS for testing only ==== *) - ({ - classname = "ExampleDelegate"; - method_name = "application:openURL:sourceApplication:annotation:"; - ret_type = "BOOL"; - params = []; - is_static = false; (* it's instance method *) - taint_kind = Tk_unknown; - language = Config.Clang; - }, [2]); - - (* actual specs *) - ({ (* This method is a source in iOS as it get as parameter +let functions_with_tainted_params = + [ (* ==== iOS for testing only ==== *) + ( { classname= "ExampleDelegate" + ; method_name= "application:openURL:sourceApplication:annotation:" + ; ret_type= "BOOL" + ; params= [] + ; is_static= false + ; (* it's instance method *) + taint_kind= Tk_unknown + ; language= Config.Clang } + , [2] ) + ; (* actual specs *) + ( { (* This method is a source in iOS as it get as parameter a non trusted URL (openURL). The method the passes it around and this URL may arrive unsanitized to loadURL:trackingCodes: of FBWebViewController which uses the URL. *) - classname = "AppDelegate"; - method_name = "application:openURL:sourceApplication:annotation:"; - ret_type = "BOOL"; - params = []; - is_static = false; (* it's instance method *) - taint_kind = Tk_integrity_annotation; - language = Config.Clang; - }, [2]); -] @ FbTaint.functions_with_tainted_params + classname= "AppDelegate" + ; method_name= "application:openURL:sourceApplication:annotation:" + ; ret_type= "BOOL" + ; params= [] + ; is_static= false + ; (* it's instance method *) + taint_kind= Tk_integrity_annotation + ; language= Config.Clang } + , [2] ) ] + @ FbTaint.functions_with_tainted_params (* turn string specificiation of Java method into a procname *) let java_method_to_procname java_method = Typ.Procname.Java - (Typ.Procname.java - (Typ.Name.Java.from_string java_method.classname) - (Some (Typ.Procname.split_classname java_method.ret_type)) - java_method.method_name + (Typ.Procname.java (Typ.Name.Java.from_string java_method.classname) + (Some (Typ.Procname.split_classname java_method.ret_type)) java_method.method_name (List.map ~f:Typ.Procname.split_classname java_method.params) (if java_method.is_static then Typ.Procname.Static else Typ.Procname.Non_Static)) @@ -275,48 +246,48 @@ let objc_method_to_procname objc_method = let method_kind = Typ.Procname.objc_method_kind_of_bool (not objc_method.is_static) in let typename = Typ.Name.Objc.from_string objc_method.classname in Typ.Procname.ObjC_Cpp - (Typ.Procname.objc_cpp typename objc_method.method_name method_kind - Typ.NoTemplate ~is_generic_model:false) + (Typ.Procname.objc_cpp typename objc_method.method_name method_kind Typ.NoTemplate + ~is_generic_model:false) let taint_spec_to_taint_info taint_spec = let taint_source = match taint_spec.language with - | Config.Clang -> objc_method_to_procname taint_spec - | Config.Java -> java_method_to_procname taint_spec in - { PredSymb.taint_source; taint_kind = taint_spec.taint_kind } + | Config.Clang + -> objc_method_to_procname taint_spec + | Config.Java + -> java_method_to_procname taint_spec + in + {PredSymb.taint_source= taint_source; taint_kind= taint_spec.taint_kind} -let sources = - List.map ~f:taint_spec_to_taint_info sources0 +let sources = List.map ~f:taint_spec_to_taint_info sources0 let mk_pname_param_num methods = - List.map - ~f:(fun (mname, param_num) -> taint_spec_to_taint_info mname, param_num) - methods + List.map ~f:(fun (mname, param_num) -> (taint_spec_to_taint_info mname, param_num)) methods -let taint_sinks = - mk_pname_param_num sinks +let taint_sinks = mk_pname_param_num sinks -let func_with_tainted_params = - mk_pname_param_num functions_with_tainted_params +let func_with_tainted_params = mk_pname_param_num functions_with_tainted_params let attrs_opt_get_annots = function - | Some attrs -> attrs.ProcAttributes.method_annotation - | None -> Annot.Method.empty + | Some attrs + -> attrs.ProcAttributes.method_annotation + | None + -> Annot.Method.empty (* TODO: return a taint kind *) + (** returns true if [callee_pname] returns a tainted value *) let returns_tainted callee_pname callee_attrs_opt = let procname_matches taint_info = - Typ.Procname.equal taint_info.PredSymb.taint_source callee_pname in + Typ.Procname.equal taint_info.PredSymb.taint_source callee_pname + in match List.find ~f:procname_matches sources with - | Some taint_info -> - Some taint_info.PredSymb.taint_kind - | None -> - let ret_annot, _ = attrs_opt_get_annots callee_attrs_opt in - if Annotations.ia_is_integrity_source ret_annot - then Some PredSymb.Tk_integrity_annotation - else if Annotations.ia_is_privacy_source ret_annot - then Some PredSymb.Tk_privacy_annotation + | Some taint_info + -> Some taint_info.PredSymb.taint_kind + | None + -> let ret_annot, _ = attrs_opt_get_annots callee_attrs_opt in + if Annotations.ia_is_integrity_source ret_annot then Some PredSymb.Tk_integrity_annotation + else if Annotations.ia_is_privacy_source ret_annot then Some PredSymb.Tk_privacy_annotation else None let find_callee taint_infos callee_pname = @@ -327,44 +298,56 @@ let find_callee taint_infos callee_pname = (** returns list of zero-indexed argument numbers of [callee_pname] that may be tainted *) let accepts_sensitive_params callee_pname callee_attrs_opt = match find_callee taint_sinks callee_pname with - | None -> - let _, param_annots = attrs_opt_get_annots callee_attrs_opt in + | None + -> let _, param_annots = attrs_opt_get_annots callee_attrs_opt in let offset = if Typ.Procname.java_is_static callee_pname then 0 else 1 in let indices_and_annots = - List.mapi ~f:(fun param_num attr -> param_num + offset, attr) param_annots in + List.mapi ~f:(fun param_num attr -> (param_num + offset, attr)) param_annots + in let tag_tainted_indices acc (index, attr) = - if Annotations.ia_is_integrity_sink attr - then (index, PredSymb.Tk_privacy_annotation) :: acc - else if Annotations.ia_is_privacy_sink attr - then (index, PredSymb.Tk_privacy_annotation) :: acc - else acc in + if Annotations.ia_is_integrity_sink attr then (index, PredSymb.Tk_privacy_annotation) + :: acc + else if Annotations.ia_is_privacy_sink attr then (index, PredSymb.Tk_privacy_annotation) + :: acc + else acc + in List.fold ~f:tag_tainted_indices ~init:[] indices_and_annots - | Some (taint_info, tainted_param_indices) -> - List.map ~f:(fun param_num -> param_num, taint_info.PredSymb.taint_kind) tainted_param_indices + | Some (taint_info, tainted_param_indices) + -> List.map + ~f:(fun param_num -> (param_num, taint_info.PredSymb.taint_kind)) + tainted_param_indices (** returns list of zero-indexed parameter numbers of [callee_pname] that should be considered tainted during symbolic execution *) let tainted_params callee_pname = match find_callee func_with_tainted_params callee_pname with - | Some (taint_info, tainted_param_indices) -> - List.map ~f:(fun param_num -> param_num, taint_info.PredSymb.taint_kind) tainted_param_indices - | None -> [] + | Some (taint_info, tainted_param_indices) + -> List.map + ~f:(fun param_num -> (param_num, taint_info.PredSymb.taint_kind)) + tainted_param_indices + | None + -> [] let has_taint_annotation fieldname (struct_typ: Typ.Struct.t) = let fld_has_taint_annot (fname, _, annot) = - Typ.Fieldname.equal fieldname fname && - (Annotations.ia_is_privacy_source annot || Annotations.ia_is_integrity_source annot) in - List.exists ~f:fld_has_taint_annot struct_typ.fields || - List.exists ~f:fld_has_taint_annot struct_typ.statics + Typ.Fieldname.equal fieldname fname + && (Annotations.ia_is_privacy_source annot || Annotations.ia_is_integrity_source annot) + in + List.exists ~f:fld_has_taint_annot struct_typ.fields + || List.exists ~f:fld_has_taint_annot struct_typ.statics (* add tainting attributes to a list of paramenters *) let get_params_to_taint tainted_param_nums formal_params = let get_taint_kind index = - List.find ~f:(fun (taint_index, _) -> Int.equal index taint_index) tainted_param_nums in + List.find ~f:(fun (taint_index, _) -> Int.equal index taint_index) tainted_param_nums + in let collect_params_to_taint params_to_taint_acc (index, param) = match get_taint_kind index with - | Some (_, taint_kind) -> (param, taint_kind) :: params_to_taint_acc - | None -> params_to_taint_acc in + | Some (_, taint_kind) + -> (param, taint_kind) :: params_to_taint_acc + | None + -> params_to_taint_acc + in let numbered_params = List.mapi ~f:(fun i param -> (i, param)) formal_params in List.fold ~f:collect_params_to_taint ~init:[] numbered_params @@ -372,12 +355,11 @@ let get_params_to_taint tainted_param_nums formal_params = let add_tainting_attribute tenv att pvar_param prop = List.fold ~f:(fun prop_acc hpred -> - match hpred with - | Sil.Hpointsto (Exp.Lvar pvar, (Sil.Eexp (rhs, _)), _) - when Pvar.equal pvar pvar_param -> - L.d_strln ("TAINT ANALYSIS: setting taint/untaint attribute of parameter " ^ - (Pvar.to_string pvar)); - Attribute.add_or_replace tenv prop_acc (Apred (att, [rhs])) - | _ -> prop_acc) - ~init:prop - prop.Prop.sigma + match hpred with + | Sil.Hpointsto (Exp.Lvar pvar, Sil.Eexp (rhs, _), _) when Pvar.equal pvar pvar_param + -> L.d_strln + ("TAINT ANALYSIS: setting taint/untaint attribute of parameter " ^ Pvar.to_string pvar) ; + Attribute.add_or_replace tenv prop_acc (Apred (att, [rhs])) + | _ + -> prop_acc) + ~init:prop prop.Prop.sigma diff --git a/infer/src/backend/taint.mli b/infer/src/backend/taint.mli index 7471004ee..934e2efab 100644 --- a/infer/src/backend/taint.mli +++ b/infer/src/backend/taint.mli @@ -9,21 +9,22 @@ open! IStd -(** returns true if [callee_pname] returns a tainted value *) val returns_tainted : Typ.Procname.t -> ProcAttributes.t option -> PredSymb.taint_kind option +(** returns true if [callee_pname] returns a tainted value *) -(** returns list of zero-indexed argument numbers of [callee_pname] that may be tainted *) val accepts_sensitive_params : Typ.Procname.t -> ProcAttributes.t option -> (int * PredSymb.taint_kind) list +(** returns list of zero-indexed argument numbers of [callee_pname] that may be tainted *) +val tainted_params : Typ.Procname.t -> (int * PredSymb.taint_kind) list (** returns list of zero-indexed parameter numbers of [callee_pname] that should be considered tainted during symbolic execution *) -val tainted_params : Typ.Procname.t -> (int * PredSymb.taint_kind) list -(** returns the taint_kind of [fieldname] if it has a taint source annotation *) val has_taint_annotation : Typ.Fieldname.t -> Typ.Struct.t -> bool +(** returns the taint_kind of [fieldname] if it has a taint source annotation *) -val add_tainting_attribute : Tenv.t -> PredSymb.t -> Pvar.t -> Prop.normal Prop.t -> Prop.normal Prop.t +val add_tainting_attribute : + Tenv.t -> PredSymb.t -> Pvar.t -> Prop.normal Prop.t -> Prop.normal Prop.t val get_params_to_taint : (int * PredSymb.taint_kind) list -> Pvar.t list -> (Pvar.t * PredSymb.taint_kind) list diff --git a/infer/src/backend/timeout.ml b/infer/src/backend/timeout.ml index 068080012..b9adf7a42 100644 --- a/infer/src/backend/timeout.ml +++ b/infer/src/backend/timeout.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging module F = Format @@ -16,72 +15,59 @@ module F = Format (** status of a timeout instance *) type status = - { - seconds_remaining : float; - (** Seconds remaining in the current timeout *) - - symop_state : SymOp.t - (** Internal State of SymOp *) - } + { seconds_remaining: float (** Seconds remaining in the current timeout *) + ; symop_state: SymOp.t (** Internal State of SymOp *) } (** stack of suspended timeout instances *) -type timeouts_stack = - status list ref +type timeouts_stack = status list ref module GlobalState = struct - let stack : timeouts_stack = - ref [] + let stack : timeouts_stack = ref [] let pop () = match !stack with - | top_status :: l -> - stack := l; + | top_status :: l + -> stack := l ; Some top_status - | [] -> - None + | [] + -> None - let push status = - stack := status :: !stack + let push status = stack := status :: !stack end let set_alarm nsecs = match Config.os_type with - | Config.Unix | Config.Cygwin -> - ignore (Unix.setitimer Unix.ITIMER_REAL - { Unix.it_interval = 3.0; (* try again after 3 seconds if the signal is lost *) - Unix.it_value = nsecs }) - | Config.Win32 -> - SymOp.set_wallclock_alarm nsecs + | Config.Unix | Config.Cygwin + -> ignore + (Unix.setitimer Unix.ITIMER_REAL + { Unix.it_interval= 3.0 + ; (* try again after 3 seconds if the signal is lost *) + Unix.it_value= nsecs }) + | Config.Win32 + -> SymOp.set_wallclock_alarm nsecs let unset_alarm () = match Config.os_type with - | Config.Unix | Config.Cygwin -> - set_alarm 0.0 - | Config.Win32 -> - SymOp.unset_wallclock_alarm () + | Config.Unix | Config.Cygwin + -> set_alarm 0.0 + | Config.Win32 + -> SymOp.unset_wallclock_alarm () let get_seconds_remaining () = match Config.os_type with - | Config.Unix | Config.Cygwin -> - (Unix.getitimer Unix.ITIMER_REAL).Unix.it_value - | Config.Win32 -> - SymOp.get_remaining_wallclock_time () + | Config.Unix | Config.Cygwin + -> (Unix.getitimer Unix.ITIMER_REAL).Unix.it_value + | Config.Win32 + -> SymOp.get_remaining_wallclock_time () let get_current_status ~keep_symop_total = let seconds_remaining = get_seconds_remaining () in let symop_state = SymOp.save_state ~keep_symop_total in - { - seconds_remaining; - symop_state; - } + {seconds_remaining; symop_state} -let set_status status = - SymOp.restore_state status.symop_state; - set_alarm status.seconds_remaining +let set_status status = SymOp.restore_state status.symop_state ; set_alarm status.seconds_remaining -let timeout_action _ = - unset_alarm (); - raise (SymOp.Analysis_failure_exe (FKtimeout)) +let timeout_action _ = unset_alarm () ; raise (SymOp.Analysis_failure_exe FKtimeout) let () = (* Can't use Core since it wraps signal handlers and alarms with catch-all exception handlers that @@ -89,23 +75,19 @@ let () = let module Gc = Caml.Gc in let module Sys = Caml.Sys in match Config.os_type with - | Config.Unix | Config.Cygwin -> - Sys.set_signal Sys.sigvtalrm (Sys.Signal_handle timeout_action); + | Config.Unix | Config.Cygwin + -> Sys.set_signal Sys.sigvtalrm (Sys.Signal_handle timeout_action) ; Sys.set_signal Sys.sigalrm (Sys.Signal_handle timeout_action) - | Config.Win32 -> - SymOp.set_wallclock_timeout_handler timeout_action; + | Config.Win32 + -> SymOp.set_wallclock_timeout_handler timeout_action ; (* use the Gc alarm for periodic timeout checks *) ignore (Gc.create_alarm SymOp.check_wallclock_alarm) -let unwind () = - unset_alarm (); - SymOp.unset_alarm (); - GlobalState.pop () +let unwind () = unset_alarm () ; SymOp.unset_alarm () ; GlobalState.pop () let suspend_existing_timeout ~keep_symop_total = let current_status = get_current_status ~keep_symop_total in - unset_alarm (); - GlobalState.push current_status + unset_alarm () ; GlobalState.push current_status let resume_previous_timeout () = let status_opt = unwind () in @@ -113,20 +95,15 @@ let resume_previous_timeout () = let exe_timeout f x = let suspend_existing_timeout_and_start_new_one () = - suspend_existing_timeout ~keep_symop_total:true; - Option.iter (SymOp.get_timeout_seconds ()) ~f:set_alarm; - SymOp.set_alarm () in - try - suspend_existing_timeout_and_start_new_one (); - f x; - resume_previous_timeout (); - None - with - | SymOp.Analysis_failure_exe kind -> - resume_previous_timeout (); - L.progressbar_timeout_event kind; - Errdesc.warning_err (State.get_loc ()) "TIMEOUT: %a@." SymOp.pp_failure_kind kind; + suspend_existing_timeout ~keep_symop_total:true ; + Option.iter (SymOp.get_timeout_seconds ()) ~f:set_alarm ; + SymOp.set_alarm () + in + try suspend_existing_timeout_and_start_new_one () ; f x ; resume_previous_timeout () ; None with + | SymOp.Analysis_failure_exe kind + -> resume_previous_timeout () ; + L.progressbar_timeout_event kind ; + Errdesc.warning_err (State.get_loc ()) "TIMEOUT: %a@." SymOp.pp_failure_kind kind ; Some kind - | exe -> - resume_previous_timeout (); - raise exe + | exe + -> resume_previous_timeout () ; raise exe diff --git a/infer/src/backend/timeout.mli b/infer/src/backend/timeout.mli index 2af674dfd..8d1c7448a 100644 --- a/infer/src/backend/timeout.mli +++ b/infer/src/backend/timeout.mli @@ -11,11 +11,11 @@ open! IStd (** Handle timeout events *) -(** Execute the function up to a given timeout. *) val exe_timeout : ('a -> unit) -> 'a -> SymOp.failure_kind option +(** Execute the function up to a given timeout. *) -(** Resume a previously suspended timeout. *) val resume_previous_timeout : unit -> unit +(** Resume a previously suspended timeout. *) -(** Suspend the current timeout. It must be resumed later. *) val suspend_existing_timeout : keep_symop_total:bool -> unit +(** Suspend the current timeout. It must be resumed later. *) diff --git a/infer/src/base/CommandDoc.ml b/infer/src/base/CommandDoc.ml index bf05f4086..f41f6e4dc 100644 --- a/infer/src/base/CommandDoc.ml +++ b/infer/src/base/CommandDoc.ml @@ -7,10 +7,9 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module CLOpt = CommandLineOption -type data = { name: string; command_doc: CLOpt.command_doc } +type data = {name: string; command_doc: CLOpt.command_doc} let inferconfig_env_var = "INFERCONFIG" @@ -19,60 +18,70 @@ let infer_exe_name = "infer" (** Name of the infer configuration file *) let inferconfig_file = ".inferconfig" -let command_to_name = CLOpt.[ - Analyze, "analyze"; Capture, "capture"; Compile, "compile"; Report, "report"; - ReportDiff, "reportdiff"; Run, "run"; - ] +let command_to_name = + let open CLOpt in + [ (Analyze, "analyze") + ; (Capture, "capture") + ; (Compile, "compile") + ; (Report, "report") + ; (ReportDiff, "reportdiff") + ; (Run, "run") ] -let name_of_command = - List.Assoc.find_exn ~equal:CLOpt.equal_command command_to_name +let name_of_command = List.Assoc.find_exn ~equal:CLOpt.equal_command command_to_name -let exe_name_of_command_name name = - Printf.sprintf "%s-%s" infer_exe_name name +let exe_name_of_command_name name = Printf.sprintf "%s-%s" infer_exe_name name -let exe_name_of_command cmd = - name_of_command cmd |> exe_name_of_command_name +let exe_name_of_command cmd = name_of_command cmd |> exe_name_of_command_name let command_of_exe_name exe_name = List.find_map command_to_name ~f:(fun (cmd, name) -> - if String.equal exe_name (exe_name_of_command_name name) then Some cmd - else None) + if String.equal exe_name (exe_name_of_command_name name) then Some cmd else None ) let mk_command_doc ~see_also:see_also_commands ?and_also ?environment:environment_opt ?files:files_opt ~synopsis = let section = 1 in let see_also = - let exe_names = List.map see_also_commands ~f:(fun cmd -> - let exe = exe_name_of_command cmd in - Printf.sprintf "$(b,%s)(%d)" (Cmdliner.Manpage.escape exe) section) in + let exe_names = + List.map see_also_commands ~f:(fun cmd -> + let exe = exe_name_of_command cmd in + Printf.sprintf "$(b,%s)(%d)" (Cmdliner.Manpage.escape exe) section ) + in let suffix = Option.value ~default:"" and_also in - [`P (String.concat ~sep:", " exe_names ^ suffix)] in - let environment = Option.value environment_opt - ~default:[`I (Printf.sprintf "$(b,%s), $(b,%s), $(b,%s)" - CLOpt.args_env_var inferconfig_env_var CLOpt.strict_mode_env_var, - Printf.sprintf "See the %s section in the manual of $(b,infer)(%d)." - Cmdliner.Manpage.s_environment section)] in - let files = Option.value files_opt - ~default:[`I (Printf.sprintf "$(b,%s)" inferconfig_file, - Printf.sprintf "See the %s section in the manual of $(b,infer)(%d)." - Cmdliner.Manpage.s_files section)] in + [`P (String.concat ~sep:", " exe_names ^ suffix)] + in + let environment = + Option.value environment_opt + ~default: + [ `I + ( Printf.sprintf "$(b,%s), $(b,%s), $(b,%s)" CLOpt.args_env_var inferconfig_env_var + CLOpt.strict_mode_env_var + , Printf.sprintf "See the %s section in the manual of $(b,infer)(%d)." + Cmdliner.Manpage.s_environment section ) ] + in + let files = + Option.value files_opt + ~default: + [ `I + ( Printf.sprintf "$(b,%s)" inferconfig_file + , Printf.sprintf "See the %s section in the manual of $(b,infer)(%d)." + Cmdliner.Manpage.s_files section ) ] + in CLOpt.mk_command_doc ~section ~version:Version.versionString - ~date:Version.man_pages_last_modify_date ~synopsis:[`Pre synopsis] ~environment ~files ~see_also + ~date:Version.man_pages_last_modify_date ~synopsis:[`Pre synopsis] ~environment ~files + ~see_also let analyze = - mk_command_doc ~title:"Infer Analysis" - ~short_description:"analyze the files captured by infer" - ~synopsis: -{|$(b,infer) $(b,analyze) $(i,[options]) + mk_command_doc ~title:"Infer Analysis" ~short_description:"analyze the files captured by infer" + ~synopsis:{|$(b,infer) $(b,analyze) $(i,[options]) $(b,infer) $(i,[options])|} ~description:[`P "Analyze the files captured in the project results directory and report."] - ~see_also:CLOpt.[Report; Run] + ~see_also:CLOpt.([Report; Run]) let capture = mk_command_doc ~title:"Infer Compilation Capture" ~short_description:"capture source files for later analysis" ~synopsis: -{|$(b,infer) $(b,capture) $(i,[options]) $(b,--) $(b,buck) $(i,...) + {|$(b,infer) $(b,capture) $(i,[options]) $(b,--) $(b,buck) $(i,...) $(b,infer) $(b,capture) $(b,--flavors) $(i,[options]) $(b,--) $(b,buck) $(i,...) $(b,infer) $(b,capture) $(b,--buck-compilation-database) $(i,[no-]deps) $(i,[options]) $(b,--) $(b,buck) $(i,...) $(b,infer) $(b,capture) $(i,[options]) $(b,--compilation-database) $(i,file) @@ -83,50 +92,44 @@ $(b,infer) $(b,capture) $(i,[options]) $(b,--) $(b,make)/$(b,clang)/$(b,gcc) $(i $(b,infer) $(b,capture) $(i,[options]) $(b,--) $(b,mvn)/$(b,mvnw) $(i,...) $(b,infer) $(b,capture) $(i,[options]) $(b,--) $(b,ndk-build) $(i,...) $(b,infer) $(b,capture) $(i,[--no-xcpretty]) $(i,[options]) $(b,--) $(b,xcodebuild) $(i,...)|} - ~description:[ - `P "Capture the build command or compilation database specified on the command line: infer \ - intercepts calls to the compiler to read source files, translate them into infer's \ - intermediate representation, and store the result of the translation in the results \ - directory."; - ] - ~see_also:CLOpt.[Analyze;Compile;Run] + ~description: + [ `P + "Capture the build command or compilation database specified on the command line: infer intercepts calls to the compiler to read source files, translate them into infer's intermediate representation, and store the result of the translation in the results directory." + ] + ~see_also:CLOpt.([Analyze; Compile; Run]) let compile = mk_command_doc ~title:"Infer Project Compilation" ~short_description:"compile project from within the infer environment" ~synopsis:"$(b,infer) $(b,compile) $(b,--) $(i,[compile command])" - ~description:[ - `P "Intercepts compilation commands similarly to $(b,infer-capture), but simply execute \ - these compilation commands and do not perform any translation of the source files. This \ - can be useful to configure build systems or for debugging purposes."; - ] - ~examples:[ - `P "$(b,cmake)(1) hardcodes the absolute paths to the compiler inside the Makefiles it \ - generates, which defeats the later capture of compilation commands by infer. Thus, to \ - capture a CMake project, one should configure the project from within the infer build \ - environment, for instance:"; - `Pre -{| mkdir build && cd build + ~description: + [ `P + "Intercepts compilation commands similarly to $(b,infer-capture), but simply execute these compilation commands and do not perform any translation of the source files. This can be useful to configure build systems or for debugging purposes." + ] + ~examples: + [ `P + "$(b,cmake)(1) hardcodes the absolute paths to the compiler inside the Makefiles it generates, which defeats the later capture of compilation commands by infer. Thus, to capture a CMake project, one should configure the project from within the infer build environment, for instance:" + ; `Pre {| mkdir build && cd build infer compile -- cmake .. - infer capture -- make|}; - `P "The same solution can be used for projects whose \"./configure\" script hardcodes the \ - paths to the compilers, for instance:"; - `Pre -{| infer compile -- ./configure - infer capture -- make|}; - `P "Another solution for CMake projects is to use CMake's compilation databases, for \ - instance:"; - `Pre -{| mkdir build && cd build + infer capture -- make|} + ; `P + "The same solution can be used for projects whose \"./configure\" script hardcodes the paths to the compilers, for instance:" + ; `Pre {| infer compile -- ./configure + infer capture -- make|} + ; `P + "Another solution for CMake projects is to use CMake's compilation databases, for instance:" + ; `Pre + {| mkdir build && cd build cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .. - infer capture --compilation-database compile_commands.json|}; - ] - ~see_also:CLOpt.[Capture] + infer capture --compilation-database compile_commands.json|} + ] + ~see_also:CLOpt.([Capture]) -let infer = mk_command_doc ~title:"Infer Static Analyzer" +let infer = + mk_command_doc ~title:"Infer Static Analyzer" ~short_description:"static analysis for Java and C/C++/Objective-C/Objective-C++" ~synopsis: -{|$(b,infer) $(b,analyze) $(i,[options]) + {|$(b,infer) $(b,analyze) $(i,[options]) $(b,infer) $(b,capture) $(i,[options]) $(b,infer) $(b,compile) $(i,[options]) $(b,infer) $(b,report) $(i,[options]) @@ -135,140 +138,129 @@ $(b,infer) $(b,run) $(i,[options]) $(b,infer) $(b,--compilation-database[-escaped]) $(i,file) $(i,[options]) $(b,infer) $(i,[options]) $(b,--) $(b,compile command) $(b,infer) $(i,[options])|} - ~description:[ - `P "Infer is a static analyzer. Given a collection of source files written in Java or in \ - languages of the C family, and a command to build them, infer produces a list of \ - potential issues."; - `P "Infer consists of a collection of tools referenced in the $(i,SEE ALSO) section of this \ - manual. See their respective manuals for more information about each."; - ] - ~options:(`Prepend [ - `P "If a compilation command is specified via the $(b,--) option or one of the \ - $(b,--clang-compilation-database[-escaped]) options, $(b,infer) behaves as \ - $(b,infer-run)(1). Otherwise, $(b,infer) behaves as $(b,infer-analyze)(1)."; - `P "Every infer command accepts the arguments from all the other infer commands. The same \ - option may affect and thus be list in the manual of several commands."; - `P (Printf.sprintf - "Options are read from the $(b,%s) file, then from the $(b,%s) environment variable, \ - then from the command line. Options in $(b,%s) take precedence over options in \ - $(b,%s), and options passed on the command line take precedence over options in \ - $(b,%s). See the $(i,%s) and $(i,%s) sections of this manual for more information." - inferconfig_file CLOpt.args_env_var - CLOpt.args_env_var - inferconfig_file - CLOpt.args_env_var Cmdliner.Manpage.s_environment Cmdliner.Manpage.s_files - ); - `P "Options can be specified inside an argument file $(i,file) by passing $(b,@)$(i,file) \ - as argument. The format is one option per line, and enclosing single \' and double \" \ - quotes are ignored."; - `P "See the manuals of individual infer commands for details about their supported \ - options. The following is a list of all the supported options (see also \ - $(b,--help-full) for options reserved for internal use)."; - ]) - ~environment:[ - `P (Printf.sprintf - "Extra arguments may be passed to all infer commands using the $(b,%s) \ - environment variable (see the $(i,%s) section). $(b,%s) is expected to contain a \ - string of %c-separated options. For instance, calling `%s=--debug^--print-logs infer` \ - is equivalent to calling `infer --debug --print-logs`." - CLOpt.args_env_var - Cmdliner.Manpage.s_options CLOpt.args_env_var - CLOpt.env_var_sep CLOpt.args_env_var - ); - `P (Printf.sprintf - "$(b,%s): Tells infer where to find the %s file. (See the %s section)" - inferconfig_env_var inferconfig_file Cmdliner.Manpage.s_files - ); - `P (Printf.sprintf - "If $(b,%s) is set to \"1\", then infer commands will exit with an error code in some \ - cases when otherwise a simple warning would be emitted on stderr, for instance if a \ - deprecated form of an option is used." - CLOpt.strict_mode_env_var - ); - ] - ~files:[ - `P (Printf.sprintf - "$(b,%s) can be used to store infer options. Its format is that of a JSON \ - record, where fields are infer long-form options, without their leading \"--\", and \ - values depend on the type of the option:" inferconfig_file); - `Noblank; - `P "- for switches options, the value is a JSON boolean (true or false, without quotes)"; - `Noblank; - `P "- for integers, the value is a JSON integer (without quotes)"; - `Noblank; - `P "- string options have string values"; - `Noblank; - `P (Printf.sprintf "- path options have string values, and are interpreted relative to the \ - location of the %s file" inferconfig_file); - `Noblank; - `P "- cumulative options are JSON arrays of the appropriate type"; - `P (Printf.sprintf "Infer will look for an $(b,%s) file in the current directory, then its \ - parent, etc., stopping at the first $(b,%s) file found." - inferconfig_file inferconfig_file); - `P "Example:"; - `Pre -{| { + ~description: + [ `P + "Infer is a static analyzer. Given a collection of source files written in Java or in languages of the C family, and a command to build them, infer produces a list of potential issues." + ; `P + "Infer consists of a collection of tools referenced in the $(i,SEE ALSO) section of this manual. See their respective manuals for more information about each." + ] + ~options: + (`Prepend + [ `P + "If a compilation command is specified via the $(b,--) option or one of the $(b,--clang-compilation-database[-escaped]) options, $(b,infer) behaves as $(b,infer-run)(1). Otherwise, $(b,infer) behaves as $(b,infer-analyze)(1)." + ; `P + "Every infer command accepts the arguments from all the other infer commands. The same option may affect and thus be list in the manual of several commands." + ; `P + (Printf.sprintf + "Options are read from the $(b,%s) file, then from the $(b,%s) environment variable, then from the command line. Options in $(b,%s) take precedence over options in $(b,%s), and options passed on the command line take precedence over options in $(b,%s). See the $(i,%s) and $(i,%s) sections of this manual for more information." + inferconfig_file CLOpt.args_env_var CLOpt.args_env_var inferconfig_file + CLOpt.args_env_var Cmdliner.Manpage.s_environment Cmdliner.Manpage.s_files) + ; `P + "Options can be specified inside an argument file $(i,file) by passing $(b,@)$(i,file) as argument. The format is one option per line, and enclosing single ' and double \" quotes are ignored." + ; `P + "See the manuals of individual infer commands for details about their supported options. The following is a list of all the supported options (see also $(b,--help-full) for options reserved for internal use)." + ]) + ~environment: + [ `P + (Printf.sprintf + "Extra arguments may be passed to all infer commands using the $(b,%s) environment variable (see the $(i,%s) section). $(b,%s) is expected to contain a string of %c-separated options. For instance, calling `%s=--debug^--print-logs infer` is equivalent to calling `infer --debug --print-logs`." + CLOpt.args_env_var Cmdliner.Manpage.s_options CLOpt.args_env_var CLOpt.env_var_sep + CLOpt.args_env_var) + ; `P + (Printf.sprintf "$(b,%s): Tells infer where to find the %s file. (See the %s section)" + inferconfig_env_var inferconfig_file Cmdliner.Manpage.s_files) + ; `P + (Printf.sprintf + "If $(b,%s) is set to \"1\", then infer commands will exit with an error code in some cases when otherwise a simple warning would be emitted on stderr, for instance if a deprecated form of an option is used." + CLOpt.strict_mode_env_var) ] + ~files: + [ `P + (Printf.sprintf + "$(b,%s) can be used to store infer options. Its format is that of a JSON record, where fields are infer long-form options, without their leading \"--\", and values depend on the type of the option:" + inferconfig_file) + ; `Noblank + ; `P "- for switches options, the value is a JSON boolean (true or false, without quotes)" + ; `Noblank + ; `P "- for integers, the value is a JSON integer (without quotes)" + ; `Noblank + ; `P "- string options have string values" + ; `Noblank + ; `P + (Printf.sprintf + "- path options have string values, and are interpreted relative to the location of the %s file" + inferconfig_file) + ; `Noblank + ; `P "- cumulative options are JSON arrays of the appropriate type" + ; `P + (Printf.sprintf + "Infer will look for an $(b,%s) file in the current directory, then its parent, etc., stopping at the first $(b,%s) file found." + inferconfig_file inferconfig_file) + ; `P "Example:" + ; `Pre + {| { "cxx": false, "infer-blacklist-files-containing": ["@generated","@Generated"] - }|}; - ] - ~see_also:(List.filter ~f:(function | CLOpt.Clang -> false | _ -> true) CLOpt.all_commands) - ~and_also:", $(b,inferTraceBugs)" - "infer" + }|} + ] + ~see_also:(List.filter ~f:(function CLOpt.Clang -> false | _ -> true) CLOpt.all_commands) + ~and_also:", $(b,inferTraceBugs)" "infer" let report = - mk_command_doc ~title:"Infer Reporting" - ~short_description:"compute and manipulate infer results" + mk_command_doc ~title:"Infer Reporting" ~short_description:"compute and manipulate infer results" ~synopsis:"$(b,infer) $(b,report) $(i,[options]) [$(i,file.specs)...]" - ~description:[ - `P "Read, convert, and print .specs files in the results directory. Each spec is printed to \ - standard output unless option -q is used."; - `P "If no specs file are passed on the command line, process all the .specs in the results \ - directory."; - ] - ~see_also:CLOpt.[ReportDiff; Run] + ~description: + [ `P + "Read, convert, and print .specs files in the results directory. Each spec is printed to standard output unless option -q is used." + ; `P + "If no specs file are passed on the command line, process all the .specs in the results directory." + ] + ~see_also:CLOpt.([ReportDiff; Run]) let reportdiff = mk_command_doc ~title:"Infer Report Difference" ~short_description:"compute the differences between two infer reports" - ~synopsis:"$(b,infer) $(b,reportdiff) $(b,--report-current) $(i,file) \ - $(b,--report-previous) $(i,file) $(i,[options])" - ~description:[ - `P "Given two infer reports $(i,previous) and $(i,current), compute the following three \ - reports and store them inside the \"differential/\" subdirectory of the results \ - directory:"; - `Noblank; `P "- $(b,introduced.json) contains the issues found in $(i,current) but not \ - $(i,previous);"; - `Noblank; `P "- $(b,fixed.json) contains the issues found in $(i,previous) but not \ - $(i,current);"; - `Noblank; `P "- $(b,preexisting.json) contains the issues found in both $(i,previous) and \ - $(i,current)."; - `P "All three files follow the same format as normal infer reports."; - ] - ~see_also:CLOpt.[Report] + ~synopsis: + "$(b,infer) $(b,reportdiff) $(b,--report-current) $(i,file) $(b,--report-previous) $(i,file) $(i,[options])" + ~description: + [ `P + "Given two infer reports $(i,previous) and $(i,current), compute the following three reports and store them inside the \"differential/\" subdirectory of the results directory:" + ; `Noblank + ; `P + "- $(b,introduced.json) contains the issues found in $(i,current) but not $(i,previous);" + ; `Noblank + ; `P "- $(b,fixed.json) contains the issues found in $(i,previous) but not $(i,current);" + ; `Noblank + ; `P + "- $(b,preexisting.json) contains the issues found in both $(i,previous) and $(i,current)." + ; `P "All three files follow the same format as normal infer reports." ] + ~see_also:CLOpt.([Report]) let run = mk_command_doc ~title:"Infer Analysis of a Project" ~short_description:"capture source files, analyze, and report" ~synopsis: -{|$(b,infer) $(b,run) $(i,[options]) + {|$(b,infer) $(b,run) $(i,[options]) $(b,infer) $(i,[options]) $(b,--) $(i,compile command)|} - ~description:[ - `P "Calling \"$(b,infer) $(b,run) $(i,[options])\" is equivalent to performing the following \ - sequence of commands:"; - `Pre -{|$(b,infer) $(b,capture) $(i,[options]) -$(b,infer) $(b,analyze) $(i,[options])|}; - ] - ~see_also:CLOpt.[Analyze; Capture; Report] + ~description: + [ `P + "Calling \"$(b,infer) $(b,run) $(i,[options])\" is equivalent to performing the following sequence of commands:" + ; `Pre {|$(b,infer) $(b,capture) $(i,[options]) +$(b,infer) $(b,analyze) $(i,[options])|} ] + ~see_also:CLOpt.([Analyze; Capture; Report]) let command_to_data = let mk cmd mk_doc = let name = name_of_command cmd in let command_doc = mk_doc (exe_name_of_command cmd) in - cmd, { name; command_doc } in - CLOpt.[mk Analyze analyze; mk Capture capture; mk Compile compile; - mk Report report; mk ReportDiff reportdiff; mk Run run] + (cmd, {name; command_doc}) + in + let open CLOpt in + [ mk Analyze analyze + ; mk Capture capture + ; mk Compile compile + ; mk Report report + ; mk ReportDiff reportdiff + ; mk Run run ] let data_of_command command = List.Assoc.find_exn ~equal:CLOpt.equal_command command_to_data command diff --git a/infer/src/base/CommandDoc.mli b/infer/src/base/CommandDoc.mli index 17bda0a9c..f6c5a24fa 100644 --- a/infer/src/base/CommandDoc.mli +++ b/infer/src/base/CommandDoc.mli @@ -6,19 +6,24 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) -open! IStd +open! IStd module CLOpt = CommandLineOption -type data = { name: string; command_doc: CLOpt.command_doc } +type data = {name: string; command_doc: CLOpt.command_doc} val infer_exe_name : string + val inferconfig_env_var : string + val inferconfig_file : string val name_of_command : CLOpt.command -> string + val exe_name_of_command : CLOpt.command -> string + val command_of_exe_name : string -> CLOpt.command option val infer : CLOpt.command_doc + val data_of_command : CLOpt.command -> data diff --git a/infer/src/base/CommandLineOption.ml b/infer/src/base/CommandLineOption.ml index 13db076a4..4f86e376f 100644 --- a/infer/src/base/CommandLineOption.ml +++ b/infer/src/base/CommandLineOption.ml @@ -10,27 +10,24 @@ (** Definition and parsing of command line arguments *) open! IStd - module F = Format module YBU = Yojson.Basic.Util -let (=) = String.equal +let ( = ) = String.equal let manpage_s_notes = "NOTES" -let is_env_var_set v = - Option.value (Option.map (Sys.getenv v) ~f:((=) "1")) ~default:false +let is_env_var_set v = Option.value (Option.map (Sys.getenv v) ~f:(( = ) "1")) ~default:false (** The working directory of the initial invocation of infer, to which paths passed as command line options are relative. *) let init_work_dir, is_originator = match Sys.getenv "INFER_CWD" with - | Some dir -> - (dir, false) - | None -> - let real_cwd = Utils.realpath (Sys.getcwd ()) in - Unix.putenv ~key:"INFER_CWD" ~data:real_cwd; - (real_cwd, true) + | Some dir + -> (dir, false) + | None + -> let real_cwd = Utils.realpath (Sys.getcwd ()) in + Unix.putenv ~key:"INFER_CWD" ~data:real_cwd ; (real_cwd, true) let strict_mode_env_var = "INFER_STRICT_MODE" @@ -51,197 +48,227 @@ type spec = | Rest of (string -> unit) let to_arg_spec = function - | Unit f -> Arg.Unit f - | String f -> Arg.String f - | Symbol (symbols, f) -> Arg.Symbol (symbols, f) - | Rest f -> Arg.Rest f + | Unit f + -> Arg.Unit f + | String f + -> Arg.String f + | Symbol (symbols, f) + -> Arg.Symbol (symbols, f) + | Rest f + -> Arg.Rest f let to_arg_spec_triple (x, spec, y) = (x, to_arg_spec spec, y) + let to_arg_speclist = List.map ~f:to_arg_spec_triple (* NOTE: All variants must be also added to `all_parse_modes` below *) type parse_mode = InferCommand | Javac | NoParse [@@deriving compare] + let equal_parse_mode = [%compare.equal : parse_mode] let all_parse_modes = [InferCommand; Javac; NoParse] -type anon_arg_action = { - parse_subcommands : bool; - parse_argfiles : bool; - on_unknown : [`Add | `Reject | `Skip]; -} +type anon_arg_action = + {parse_subcommands: bool; parse_argfiles: bool; on_unknown: [`Add | `Reject | `Skip]} let anon_arg_action_of_parse_mode parse_mode = - let parse_subcommands, parse_argfiles, on_unknown = match parse_mode with - | InferCommand -> true, true, `Reject - | Javac -> false, true, `Skip - | NoParse-> false, false, `Skip in + let parse_subcommands, parse_argfiles, on_unknown = + match parse_mode with + | InferCommand + -> (true, true, `Reject) + | Javac + -> (false, true, `Skip) + | NoParse + -> (false, false, `Skip) + in {parse_subcommands; parse_argfiles; on_unknown} (* NOTE: All variants must be also added to `all_commands` below *) type command = - | Analyze | Capture | Clang | Compile | Report | ReportDiff | Run -[@@deriving compare] + | Analyze + | Capture + | Clang + | Compile + | Report + | ReportDiff + | Run + [@@deriving compare] let equal_command = [%compare.equal : command] -let all_commands = [ - Analyze; Capture; Clang; Compile; Report; ReportDiff; Run -] - -type command_doc = { - title : Cmdliner.Manpage.title; - manual_before_options : Cmdliner.Manpage.block list; - manual_options : [ - | `Prepend of Cmdliner.Manpage.block list - | `Replace of Cmdliner.Manpage.block list - ]; - manual_after_options : Cmdliner.Manpage.block list; -} - -type desc = { - long: string; short: string; meta: string; doc: string; spec: spec; - (** how to go from an option in the json config file to a list of command-line options *) - decode_json: inferconfig_dir:string -> Yojson.Basic.json -> string list ; -} +let all_commands = [Analyze; Capture; Clang; Compile; Report; ReportDiff; Run] + +type command_doc = + { title: Cmdliner.Manpage.title + ; manual_before_options: Cmdliner.Manpage.block list + ; manual_options: + [`Prepend of Cmdliner.Manpage.block list | `Replace of Cmdliner.Manpage.block list] + ; manual_after_options: Cmdliner.Manpage.block list } + +type desc = + { long: string + ; short: string + ; meta: string + ; doc: string + ; spec: spec + (** how to go from an option in the json config file to a list of command-line options *) + ; decode_json: inferconfig_dir:string -> Yojson.Basic.json -> string list } let dashdash ?short long = - match long, short with - | "", (None | Some "") - | "--", _ -> long - | "", Some short -> "-" ^ short - | _ -> "--" ^ long + match (long, short) with + | "", (None | Some "") | "--", _ + -> long + | "", Some short + -> "-" ^ short + | _ + -> "--" ^ long let xdesc {long; short; spec} = let key long short = - match long, short with - | "", "" -> "" - | "--", _ -> "--" - | "", _ -> "-" ^ short - | _ -> "--" ^ long + match (long, short) with + | "", "" + -> "" + | "--", _ + -> "--" + | "", _ + -> "-" ^ short + | _ + -> "--" ^ long in let xspec = match spec with (* translate Symbol to String for better formatting of --help messages *) - | Symbol (symbols, action) -> - String (fun arg -> - if List.mem ~equal:String.equal symbols arg then - action arg + | Symbol (symbols, action) + -> String + (fun arg -> + if List.mem ~equal:String.equal symbols arg then action arg else - raise (Arg.Bad (F.sprintf "wrong argument '%s'; option '%s' expects one of: %s" - arg (dashdash ~short long) (String.concat ~sep:" | " symbols))) - ) - | _ -> - spec + raise + (Arg.Bad + (F.sprintf "wrong argument '%s'; option '%s' expects one of: %s" arg + (dashdash ~short long) (String.concat ~sep:" | " symbols)))) + | _ + -> spec in (* Arg doesn't need to know anything about documentation since we generate our own *) (key long short, xspec, "") let check_no_duplicates desc_list = let rec check_for_duplicates_ = function - | [] | [_] -> - true - | (x, _, _) :: (y, _, _) :: _ when x <> "" && x = y -> - failwith ("Multiple definitions of command line option: " ^ x) - | _ :: tl -> - check_for_duplicates_ tl + | [] | [_] + -> true + | (x, _, _) :: (y, _, _) :: _ when x <> "" && x = y + -> failwith ("Multiple definitions of command line option: " ^ x) + | _ :: tl + -> check_for_duplicates_ tl in check_for_duplicates_ (List.sort ~cmp:(fun (x, _, _) (y, _, _) -> String.compare x y) desc_list) - let parse_mode_desc_lists = List.map ~f:(fun parse_mode -> (parse_mode, ref [])) all_parse_modes module SectionMap = Caml.Map.Make (struct - type t = String.t - (* this must be the reverse of the order in which we want the sections to appear in the + type t = String.t + + (* this must be the reverse of the order in which we want the sections to appear in the manual *) - let compare s1 s2 = - if String.equal s1 s2 then - (* this simplifies the next two cases *) - 0 - else if String.equal s1 Cmdliner.Manpage.s_options then - (* ensure OPTIONS section is last (hence first in the manual) *) - 1 - else if String.equal s2 Cmdliner.Manpage.s_options then - (* same as above *) - -1 - else - (* reverse order *) - String.compare s2 s1 - end) + let compare s1 s2 = + if String.equal s1 s2 then (* this simplifies the next two cases *) + 0 + else if String.equal s1 Cmdliner.Manpage.s_options then + (* ensure OPTIONS section is last (hence first in the manual) *) + 1 + else if String.equal s2 Cmdliner.Manpage.s_options then (* same as above *) + -1 + else (* reverse order *) + String.compare s2 s1 +end) let help_sections_desc_lists = List.map all_commands ~f:(fun command -> (command, ref SectionMap.empty)) + let visible_descs_list = ref [] + let hidden_descs_list = ref [] (** add [desc] to the one relevant parse_tag_desc_lists for the purposes of parsing, and, in the case of InferCommand, include [desc] in --help only for the relevant sections. *) let add parse_mode sections desc = let desc_list = List.Assoc.find_exn ~equal:equal_parse_mode parse_mode_desc_lists parse_mode in - desc_list := desc :: !desc_list; + desc_list := desc :: !desc_list ; let add_to_section (command, section) = let sections = List.Assoc.find_exn ~equal:equal_command help_sections_desc_lists command in let prev_contents = try SectionMap.find section !sections - with Not_found -> [] in - sections := SectionMap.add section (desc::prev_contents) !sections in - List.iter sections ~f:add_to_section; - if List.is_empty sections then - hidden_descs_list := desc :: !hidden_descs_list - else - visible_descs_list := desc :: !visible_descs_list; + with Not_found -> [] + in + sections := SectionMap.add section (desc :: prev_contents) !sections + in + List.iter sections ~f:add_to_section ; + if List.is_empty sections then hidden_descs_list := desc :: !hidden_descs_list + else visible_descs_list := desc :: !visible_descs_list ; () let deprecate_desc parse_mode ~long ~short ~deprecated desc = - let warn () = match parse_mode with - | Javac | NoParse -> () - | InferCommand -> - warnf "WARNING: '-%s' is deprecated. Use '--%s'%s instead.@." - deprecated long (if short = "" then "" else Printf.sprintf " or '-%s'" short) in - let warn_then_f f x = warn (); f x in - let deprecated_spec = match desc.spec with - | Unit f -> Unit (warn_then_f f) - | String f -> String (warn_then_f f) - | Symbol (symbols, f) -> Symbol (symbols, warn_then_f f) - | Rest _ as spec -> spec in + let warn () = + match parse_mode with + | Javac | NoParse + -> () + | InferCommand + -> warnf "WARNING: '-%s' is deprecated. Use '--%s'%s instead.@." deprecated long + (if short = "" then "" else Printf.sprintf " or '-%s'" short) + in + let warn_then_f f x = warn () ; f x in + let deprecated_spec = + match desc.spec with + | Unit f + -> Unit (warn_then_f f) + | String f + -> String (warn_then_f f) + | Symbol (symbols, f) + -> Symbol (symbols, warn_then_f f) + | Rest _ as spec + -> spec + in let deprecated_decode_json ~inferconfig_dir j = - warnf "WARNING: in .inferconfig: '%s' is deprecated. Use '%s' instead.@." deprecated long; - desc.decode_json ~inferconfig_dir j in - { long = ""; short = deprecated; meta = ""; doc = ""; - spec = deprecated_spec; decode_json = deprecated_decode_json } - -let mk ?(deprecated=[]) ?(parse_mode=InferCommand) ?(in_help=[]) - ~long ?short:short0 ~default ~meta doc ~default_to_string ~decode_json ~mk_setter ~mk_spec = + warnf "WARNING: in .inferconfig: '%s' is deprecated. Use '%s' instead.@." deprecated long ; + desc.decode_json ~inferconfig_dir j + in + { long= "" + ; short= deprecated + ; meta= "" + ; doc= "" + ; spec= deprecated_spec + ; decode_json= deprecated_decode_json } + +let mk ?(deprecated= []) ?(parse_mode= InferCommand) ?(in_help= []) ~long ?short:short0 ~default + ~meta doc ~default_to_string ~decode_json ~mk_setter ~mk_spec = let variable = ref default in let closure = mk_setter variable in let setter str = try closure str with exc -> - raise (Arg.Bad ("bad value " ^ str ^ " for flag " ^ long - ^ " (" ^ (Exn.to_string exc) ^ ")")) in + raise (Arg.Bad ("bad value " ^ str ^ " for flag " ^ long ^ " (" ^ Exn.to_string exc ^ ")")) + in let spec = mk_spec setter in let doc = let default_string = default_to_string default in if default_string = "" then doc else let doc_default_sep = if String.is_suffix ~suffix:"\n" doc then "" else " " in - doc ^ doc_default_sep ^ "(default: $(i," ^ Cmdliner.Manpage.escape default_string ^ "))" in + doc ^ doc_default_sep ^ "(default: $(i," ^ Cmdliner.Manpage.escape default_string ^ "))" + in let short = match short0 with Some c -> String.of_char c | None -> "" in - let desc = {long; short=short; meta; doc; spec; decode_json} in + let desc = {long; short; meta; doc; spec; decode_json} in (* add desc for long option, with documentation (which includes any short option) for exes *) if long <> "" then add parse_mode in_help desc ; (* add desc for short option only for parsing, without documentation *) - if short <> "" then - add parse_mode [] {desc with long = ""; meta = ""; doc = ""} ; + if short <> "" then add parse_mode [] {desc with long= ""; meta= ""; doc= ""} ; (* add desc for deprecated options only for parsing, without documentation *) List.iter deprecated ~f:(fun deprecated -> - deprecate_desc parse_mode ~long ~short:short ~deprecated desc - |> add parse_mode []) ; + deprecate_desc parse_mode ~long ~short ~deprecated desc |> add parse_mode [] ) ; variable (* begin parsing state *) - (* arguments passed to Arg.parse_argv_dynamic, susceptible to be modified on the fly when parsing *) let args_to_parse : string array ref = ref (Array.of_list []) @@ -254,6 +281,7 @@ let curr_speclist : (Arg.key * Arg.spec * Arg.doc) list ref = ref [] let anon_arg_action = ref (anon_arg_action_of_parse_mode InferCommand) let subcommands = ref [] + let subcommand_actions = ref [] let rev_anon_args = ref [] @@ -264,83 +292,77 @@ let curr_command = ref None (* end parsing state *) type 'a t = - ?deprecated:string list -> long:Arg.key -> ?short:char -> - ?parse_mode:parse_mode -> ?in_help:(command * string) list -> ?meta:string -> Arg.doc -> - 'a + ?deprecated:string list -> long:Arg.key -> ?short:char -> ?parse_mode:parse_mode + -> ?in_help:(command * string) list -> ?meta:string -> Arg.doc -> 'a -let string_json_decoder ~long ~inferconfig_dir:_ json = - [dashdash long; YBU.to_string json] +let string_json_decoder ~long ~inferconfig_dir:_ json = [dashdash long; YBU.to_string json] let path_json_decoder ~long ~inferconfig_dir json = let abs_path = let path = YBU.to_string json in - if Filename.is_relative path then inferconfig_dir ^/ path - else path in + if Filename.is_relative path then inferconfig_dir ^/ path else path + in [dashdash long; abs_path] let list_json_decoder json_decoder ~inferconfig_dir json = List.concat (YBU.convert_each (json_decoder ~inferconfig_dir) json) -let mk_set var value ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="") doc = +let mk_set var value ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "") doc = let setter () = var := value in - ignore( - mk ~deprecated ~long ?short ~default:() ?parse_mode ?in_help ~meta doc - ~default_to_string:(fun () -> "") - ~decode_json:(string_json_decoder ~long) - ~mk_setter:(fun _ _ -> setter ()) - ~mk_spec:(fun _ -> Unit setter) ) - -let mk_option ?(default=None) ?(default_to_string=fun _ -> "") ~f - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="string") doc = - mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string - ~decode_json:(string_json_decoder ~long) - ~mk_setter:(fun var str -> var := f str) - ~mk_spec:(fun set -> String set) - -let mk_bool ?(deprecated_no=[]) ?(default=false) ?(f=fun b -> b) - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="") doc = + ignore + (mk ~deprecated ~long ?short ~default:() ?parse_mode ?in_help ~meta doc + ~default_to_string:(fun () -> "") ~decode_json:(string_json_decoder ~long) + ~mk_setter:(fun _ _ -> setter ()) ~mk_spec:(fun _ -> Unit setter )) + +let mk_option ?(default= None) ?(default_to_string= fun _ -> "") ~f ?(deprecated= []) ~long ?short + ?parse_mode ?in_help ?(meta= "string") doc = + mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc ~default_to_string + ~decode_json:(string_json_decoder ~long) ~mk_setter:(fun var str -> var := f str) ~mk_spec: + (fun set -> String set ) + +let mk_bool ?(deprecated_no= []) ?(default= false) ?(f= fun b -> b) ?(deprecated= []) ~long ?short + ?parse_mode ?in_help ?(meta= "") doc = let nolong = let len = String.length long in - if len > 3 && String.sub long ~pos:0 ~len:3 = "no-" then - String.sub long ~pos:3 ~len:(len - 3) - else - "no-" ^ long + if len > 3 && String.sub long ~pos:0 ~len:3 = "no-" then String.sub long ~pos:3 ~len:(len - 3) + else "no-" ^ long and noshort = - Option.map ~f:(fun short -> - if Char.is_lowercase short then Char.uppercase short - else Char.lowercase short - ) short + Option.map + ~f:(fun short -> + if Char.is_lowercase short then Char.uppercase short else Char.lowercase short) + short in let doc long short = match short with - | Some short -> doc ^ " (Conversely: $(b,--" ^ long ^ ") | $(b,-" ^ String.of_char short ^ "))" - | None -> doc ^ " (Conversely: $(b,--" ^ long ^ "))" + | Some short + -> doc ^ " (Conversely: $(b,--" ^ long ^ ") | $(b,-" ^ String.of_char short ^ "))" + | None + -> doc ^ " (Conversely: $(b,--" ^ long ^ "))" in let doc, nodoc = - if not default then - ("Activates: " ^ doc nolong noshort, "") - else - ("", "Deactivates: " ^ doc long short) in + if not default then ("Activates: " ^ doc nolong noshort, "") + else ("", "Deactivates: " ^ doc long short) + in let default_to_string _ = "" in let mk_spec set = Unit (fun () -> set "") in let var = - mk ~long ?short ~deprecated ~default ?parse_mode ?in_help - ~meta doc ~default_to_string ~mk_setter:(fun var _ -> var := f true) + mk ~long ?short ~deprecated ~default ?parse_mode ?in_help ~meta doc ~default_to_string + ~mk_setter:(fun var _ -> var := f true) ~decode_json:(fun ~inferconfig_dir:_ json -> - [dashdash (if YBU.to_bool json then long else nolong)]) - ~mk_spec in - ignore( - mk ~long:nolong ?short:noshort ~deprecated:deprecated_no ~default:(not default) - ?parse_mode ?in_help - ~meta nodoc ~default_to_string ~mk_setter:(fun _ _ -> var := f false) - ~decode_json:(fun ~inferconfig_dir:_ json -> - [dashdash (if YBU.to_bool json then nolong else long)]) - ~mk_spec ); + [dashdash (if YBU.to_bool json then long else nolong)]) + ~mk_spec + in + ignore + (mk ~long:nolong ?short:noshort ~deprecated:deprecated_no ~default:(not default) ?parse_mode + ?in_help ~meta nodoc ~default_to_string + ~mk_setter:(fun _ _ -> var := f false) + ~decode_json:(fun ~inferconfig_dir:_ json -> + [dashdash (if YBU.to_bool json then nolong else long)]) + ~mk_spec) ; var -let mk_bool_group ?(deprecated_no=[]) ?(default=false) ?f:(f0=Fn.id) - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?meta doc children no_children = +let mk_bool_group ?(deprecated_no= []) ?(default= false) ?f:(f0 = Fn.id) ?(deprecated= []) ~long + ?short ?parse_mode ?in_help ?meta doc children no_children = let f b = List.iter ~f:(fun child -> child := b) children ; List.iter ~f:(fun child -> child := not b) no_children ; @@ -348,218 +370,207 @@ let mk_bool_group ?(deprecated_no=[]) ?(default=false) ?f:(f0=Fn.id) in mk_bool ~deprecated ~deprecated_no ~default ~long ?short ~f ?parse_mode ?in_help ?meta doc -let mk_int ~default ?(f=Fn.id) - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="int") doc = +let mk_int ~default ?(f= Fn.id) ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "int") + doc = mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string:string_of_int - ~mk_setter:(fun var str -> var := f (int_of_string str)) - ~decode_json:(string_json_decoder ~long) - ~mk_spec:(fun set -> String set) + ~default_to_string:string_of_int ~mk_setter:(fun var str -> var := f (int_of_string str)) + ~decode_json:(string_json_decoder ~long) ~mk_spec:(fun set -> String set ) -let mk_int_opt ?default ?f:(f0=Fn.id) - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="int") doc = +let mk_int_opt ?default ?f:(f0 = Fn.id) ?(deprecated= []) ~long ?short ?parse_mode ?in_help + ?(meta= "int") doc = let default_to_string = function Some f -> string_of_int f | None -> "" in let f s = Some (f0 (int_of_string s)) in mk_option ~deprecated ~long ?short ~default ~default_to_string ~f ?parse_mode ?in_help ~meta doc -let mk_float ~default ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="float") doc = +let mk_float ~default ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "float") doc = mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string:string_of_float - ~mk_setter:(fun var str -> var := (float_of_string str)) - ~decode_json:(string_json_decoder ~long) - ~mk_spec:(fun set -> String set) + ~default_to_string:string_of_float ~mk_setter:(fun var str -> var := float_of_string str) + ~decode_json:(string_json_decoder ~long) ~mk_spec:(fun set -> String set ) -let mk_float_opt ?default ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="float") doc = +let mk_float_opt ?default ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "float") doc = let default_to_string = function Some f -> string_of_float f | None -> "" in let f s = Some (float_of_string s) in mk_option ~deprecated ~long ?short ~default ~default_to_string ~f ?parse_mode ?in_help ~meta doc -let mk_string ~default ?(f=fun s -> s) ?(deprecated=[]) ~long ?short ?parse_mode ?in_help - ?(meta="string") doc = +let mk_string ~default ?(f= fun s -> s) ?(deprecated= []) ~long ?short ?parse_mode ?in_help + ?(meta= "string") doc = mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string:(fun s -> s) - ~mk_setter:(fun var str -> var := f str) - ~decode_json:(string_json_decoder ~long) - ~mk_spec:(fun set -> String set) + ~default_to_string:(fun s -> s) ~mk_setter:(fun var str -> var := f str) + ~decode_json:(string_json_decoder ~long) ~mk_spec:(fun set -> String set ) -let mk_string_opt ?default ?(f=fun s -> s) ?(deprecated=[]) ~long ?short ?parse_mode ?in_help - ?(meta="string") doc = +let mk_string_opt ?default ?(f= fun s -> s) ?(deprecated= []) ~long ?short ?parse_mode ?in_help + ?(meta= "string") doc = let default_to_string = function Some s -> s | None -> "" in let f s = Some (f s) in mk_option ~deprecated ~long ?short ~default ~default_to_string ~f ?parse_mode ?in_help ~meta doc -let mk_string_list ?(default=[]) ?(f=fun s -> s) - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="+string") doc = +let mk_string_list ?(default= []) ?(f= fun s -> s) ?(deprecated= []) ~long ?short ?parse_mode + ?in_help ?(meta= "+string") doc = mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string:(String.concat ~sep:", ") - ~mk_setter:(fun var str -> var := (f str) :: !var) - ~decode_json:(list_json_decoder (string_json_decoder ~long)) - ~mk_spec:(fun set -> String set) + ~default_to_string:(String.concat ~sep:", ") ~mk_setter:(fun var str -> var := f str :: !var) + ~decode_json:(list_json_decoder (string_json_decoder ~long)) ~mk_spec:(fun set -> String set ) -let normalize_path_in_args_being_parsed ?(f=Fn.id) ~is_anon_arg str = - if Filename.is_relative str then ( +let normalize_path_in_args_being_parsed ?(f= Fn.id) ~is_anon_arg str = + if Filename.is_relative str then (* Replace relative paths with absolute ones on the fly in the args being parsed. This assumes that [!arg_being_parsed] points at either [str] (if [is_anon_arg]) or at the option name position in [!args_to_parse], as is the case e.g. when calling [Arg.parse_argv_dynamic ~current:arg_being_parsed !args_to_parse ...]. *) let root = Unix.getcwd () in let abs_path = Utils.filename_to_absolute ~root str in - (!args_to_parse).(!arg_being_parsed + if is_anon_arg then 0 else 1) <- f abs_path; + !args_to_parse.((!arg_being_parsed + if is_anon_arg then 0 else 1)) <- f abs_path ; abs_path - ) else - str + else str -let mk_path_helper ~setter ~default_to_string - ~default ~deprecated ~long ~short ~parse_mode ~in_help ~meta ~decode_json doc = - mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~decode_json ~default_to_string +let mk_path_helper ~setter ~default_to_string ~default ~deprecated ~long ~short ~parse_mode + ~in_help ~meta ~decode_json doc = + mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc ~decode_json + ~default_to_string ~mk_setter:(fun var str -> - let abs_path = normalize_path_in_args_being_parsed ~is_anon_arg:false str in - setter var abs_path) - ~mk_spec:(fun set -> String set) + let abs_path = normalize_path_in_args_being_parsed ~is_anon_arg:false str in + setter var abs_path) ~mk_spec:(fun set -> String set ) -let mk_path ~default ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="path") = +let mk_path ~default ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "path") = mk_path_helper ~setter:(fun var x -> var := x) ~decode_json:(path_json_decoder ~long) ~default_to_string:(fun s -> s) ~default ~deprecated ~long ~short ~parse_mode ~in_help ~meta -let mk_path_opt ?default ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="path") = +let mk_path_opt ?default ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "path") = mk_path_helper ~setter:(fun var x -> var := Some x) ~decode_json:(path_json_decoder ~long) ~default_to_string:(function Some s -> s | None -> "") ~default ~deprecated ~long ~short ~parse_mode ~in_help ~meta -let mk_path_list ?(default=[]) ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="+path") = +let mk_path_list ?(default= []) ?(deprecated= []) ~long ?short ?parse_mode ?in_help + ?(meta= "+path") = mk_path_helper ~setter:(fun var x -> var := x :: !var) ~decode_json:(list_json_decoder (path_json_decoder ~long)) - ~default_to_string:(String.concat ~sep:", ") - ~default ~deprecated ~long ~short ~parse_mode ~in_help ~meta + ~default_to_string:(String.concat ~sep:", ") ~default ~deprecated ~long ~short ~parse_mode + ~in_help ~meta let mk_symbols_meta symbols = let strings = List.map ~f:fst symbols in Printf.sprintf "{ %s }" (String.concat ~sep:" | " strings) -let mk_symbol ~default ~symbols ~eq ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?meta doc = +let mk_symbol ~default ~symbols ~eq ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?meta doc = let strings = List.map ~f:fst symbols in - let sym_to_str = List.map ~f:(fun (x,y) -> (y,x)) symbols in + let sym_to_str = List.map ~f:(fun (x, y) -> (y, x)) symbols in let of_string str = List.Assoc.find_exn ~equal:String.equal symbols str in let to_string sym = List.Assoc.find_exn ~equal:eq sym_to_str sym in let meta = Option.value meta ~default:(mk_symbols_meta symbols) in mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc - ~default_to_string:(fun s -> to_string s) - ~mk_setter:(fun var str -> var := of_string str) - ~decode_json:(string_json_decoder ~long) - ~mk_spec:(fun set -> Symbol (strings, set)) + ~default_to_string:(fun s -> to_string s) ~mk_setter:(fun var str -> var := of_string str) + ~decode_json:(string_json_decoder ~long) ~mk_spec:(fun set -> Symbol (strings, set) ) -let mk_symbol_opt ~symbols ?(f=Fn.id) ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?meta doc = +let mk_symbol_opt ~symbols ?(f= Fn.id) ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?meta + doc = let strings = List.map ~f:fst symbols in let of_string str = List.Assoc.find_exn ~equal:String.equal symbols str in let meta = Option.value meta ~default:(mk_symbols_meta symbols) in mk ~deprecated ~long ?short ~default:None ?parse_mode ?in_help ~meta doc - ~default_to_string:(fun _ -> "") - ~mk_setter:(fun var str -> var := Some (f (of_string str))) - ~decode_json:(string_json_decoder ~long) - ~mk_spec:(fun set -> Symbol (strings, set)) + ~default_to_string:(fun _ -> "") ~mk_setter:(fun var str -> var := Some (f (of_string str))) + ~decode_json:(string_json_decoder ~long) ~mk_spec:(fun set -> Symbol (strings, set) ) -let mk_symbol_seq ?(default=[]) ~symbols ~eq ?(deprecated=[]) ~long ?short ?parse_mode ?in_help +let mk_symbol_seq ?(default= []) ~symbols ~eq ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?meta doc = - let sym_to_str = List.map ~f:(fun (x,y) -> (y,x)) symbols in + let sym_to_str = List.map ~f:(fun (x, y) -> (y, x)) symbols in let of_string str = List.Assoc.find_exn ~equal:String.equal symbols str in let to_string sym = List.Assoc.find_exn ~equal:eq sym_to_str sym in let meta = Option.value meta ~default:(",-separated sequence of " ^ mk_symbols_meta symbols) in - mk ~deprecated ~long ?short ~default ?parse_mode ?in_help - ~meta doc + mk ~deprecated ~long ?short ~default ?parse_mode ?in_help ~meta doc ~default_to_string:(fun syms -> String.concat ~sep:" " (List.map ~f:to_string syms)) - ~mk_setter:(fun var str_seq -> - var := List.map ~f:of_string (String.split ~on:',' str_seq)) + ~mk_setter:(fun var str_seq -> var := List.map ~f:of_string (String.split ~on:',' str_seq)) ~decode_json:(fun ~inferconfig_dir:_ json -> - [dashdash long; - String.concat ~sep:"," (YBU.convert_each YBU.to_string json)]) - ~mk_spec:(fun set -> String set) - -let mk_set_from_json ~default ~default_to_string ~f - ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="json") doc = - mk ~deprecated ~long ?short ?parse_mode ?in_help ~meta doc - ~default ~default_to_string + [dashdash long; String.concat ~sep:"," (YBU.convert_each YBU.to_string json)]) ~mk_spec: + (fun set -> String set ) + +let mk_set_from_json ~default ~default_to_string ~f ?(deprecated= []) ~long ?short ?parse_mode + ?in_help ?(meta= "json") doc = + mk ~deprecated ~long ?short ?parse_mode ?in_help ~meta doc ~default ~default_to_string ~mk_setter:(fun var json -> var := f (Yojson.Basic.from_string json)) ~decode_json:(fun ~inferconfig_dir:_ json -> [dashdash long; Yojson.Basic.to_string json]) - ~mk_spec:(fun set -> String set) + ~mk_spec:(fun set -> String set ) -let mk_json ?(deprecated=[]) ~long ?short ?parse_mode ?in_help ?(meta="json") doc = - mk ~deprecated ~long ?short ?parse_mode ?in_help ~meta doc - ~default:(`List []) ~default_to_string:Yojson.Basic.to_string +let mk_json ?(deprecated= []) ~long ?short ?parse_mode ?in_help ?(meta= "json") doc = + mk ~deprecated ~long ?short ?parse_mode ?in_help ~meta doc ~default:(`List []) + ~default_to_string:Yojson.Basic.to_string ~mk_setter:(fun var json -> var := Yojson.Basic.from_string json) ~decode_json:(fun ~inferconfig_dir:_ json -> [dashdash long; Yojson.Basic.to_string json]) - ~mk_spec:(fun set -> String set) + ~mk_spec:(fun set -> String set ) (** [mk_anon] always return the same ref. Anonymous arguments are only accepted if [parse_action_accept_unknown_args] is true. *) let mk_anon () = rev_anon_args -let mk_rest ?(parse_mode=InferCommand) ?(in_help=[]) doc = +let mk_rest ?(parse_mode= InferCommand) ?(in_help= []) doc = let rest = ref [] in let spec = Rest (fun arg -> rest := arg :: !rest) in - add parse_mode in_help {long = "--"; short = ""; meta = ""; doc; spec; - decode_json = fun ~inferconfig_dir:_ _ -> []} ; + add parse_mode in_help + {long= "--"; short= ""; meta= ""; doc; spec; decode_json= (fun ~inferconfig_dir:_ _ -> [])} ; rest let normalize_desc_list speclist = let norm k = let remove_no s = let len = String.length k in - if len > 3 && String.sub s ~pos:0 ~len:3 = "no-" - then String.sub s ~pos:3 ~len:(len - 3) - else s in + if len > 3 && String.sub s ~pos:0 ~len:3 = "no-" then String.sub s ~pos:3 ~len:(len - 3) + else s + in let remove_weird_chars = - String.filter ~f:(function - | 'a'..'z' | '0'..'9' | '-' -> true - | _ -> false) in - remove_weird_chars @@ String.lowercase @@ remove_no k in - let compare_specs {long = x} {long = y} = - match x, y with - | "--", "--" -> 0 - | "--", _ -> 1 - | _, "--" -> -1 - | _ -> - let lower_norm s = String.lowercase @@ norm s in - String.compare (lower_norm x) (lower_norm y) in + String.filter ~f:(function 'a'..'z' | '0'..'9' | '-' -> true | _ -> false) + in + remove_weird_chars @@ String.lowercase @@ remove_no k + in + let compare_specs {long= x} {long= y} = + match (x, y) with + | "--", "--" + -> 0 + | "--", _ + -> 1 + | _, "--" + -> -1 + | _ + -> let lower_norm s = String.lowercase @@ norm s in + String.compare (lower_norm x) (lower_norm y) + in let sort speclist = List.sort ~cmp:compare_specs speclist in sort speclist let mk_command_doc ~title ~section ~version ~date ~short_description ~synopsis ~description - ?options ?exit_status ?environment ?files ?notes ?bugs ?examples ~see_also - command_str = - let add_if section blocks = match blocks with - | None -> `Blocks [] - | Some bs -> `Blocks (`S section :: bs) in - let manual_before_options = [ - `S Cmdliner.Manpage.s_name; - (* the format of the following line is mandated by man(7) *) - `Pre (Printf.sprintf "%s - %s" command_str short_description); - `S Cmdliner.Manpage.s_synopsis; - `Blocks synopsis; - `S Cmdliner.Manpage.s_description; - `Blocks description; - ] in + ?options ?exit_status ?environment ?files ?notes ?bugs ?examples ~see_also command_str = + let add_if section blocks = + match blocks with None -> `Blocks [] | Some bs -> `Blocks (`S section :: bs) + in + let manual_before_options = + [ `S Cmdliner.Manpage.s_name + ; (* the format of the following line is mandated by man(7) *) + `Pre (Printf.sprintf "%s - %s" command_str short_description) + ; `S Cmdliner.Manpage.s_synopsis + ; `Blocks synopsis + ; `S Cmdliner.Manpage.s_description + ; `Blocks description ] + in let manual_options = Option.value ~default:(`Prepend []) options in - let manual_after_options = [ - add_if Cmdliner.Manpage.s_exit_status exit_status; - add_if Cmdliner.Manpage.s_environment environment; - add_if Cmdliner.Manpage.s_files files; - add_if manpage_s_notes notes; - add_if Cmdliner.Manpage.s_bugs bugs; - add_if Cmdliner.Manpage.s_examples examples; - `S Cmdliner.Manpage.s_see_also; - `Blocks see_also; - ] in - let command_doc = { - title = command_str, section, date, version, title; - manual_before_options; manual_options; manual_after_options; - } in + let manual_after_options = + [ add_if Cmdliner.Manpage.s_exit_status exit_status + ; add_if Cmdliner.Manpage.s_environment environment + ; add_if Cmdliner.Manpage.s_files files + ; add_if manpage_s_notes notes + ; add_if Cmdliner.Manpage.s_bugs bugs + ; add_if Cmdliner.Manpage.s_examples examples + ; `S Cmdliner.Manpage.s_see_also + ; `Blocks see_also ] + in + let command_doc = + { title= (command_str, section, date, version, title) + ; manual_before_options + ; manual_options + ; manual_after_options } + in command_doc let set_curr_speclist_for_parse_mode ~usage parse_mode = @@ -572,59 +583,61 @@ let set_curr_speclist_for_parse_mode ~usage parse_mode = treatment *) let add_or_suppress_help speclist = let unknown opt = - (opt, Unit (fun () -> raise (Arg.Bad ("unknown option '" ^ opt ^ "'"))), "") in + (opt, Unit (fun () -> raise (Arg.Bad ("unknown option '" ^ opt ^ "'"))), "") + in let has_opt opt = List.exists ~f:(fun (o, _, _) -> String.equal opt o) speclist in let add_unknown opt = if not (has_opt opt) then List.cons (unknown opt) else Fn.id in add_unknown "-help" @@ add_unknown "--help" @@ speclist in let full_desc_list = - List.Assoc.find_exn ~equal:equal_parse_mode parse_mode_desc_lists parse_mode in - curr_speclist := normalize_desc_list !full_desc_list - |> List.map ~f:xdesc - |> add_or_suppress_help - |> to_arg_speclist; - assert( check_no_duplicates !curr_speclist ); + List.Assoc.find_exn ~equal:equal_parse_mode parse_mode_desc_lists parse_mode + in + curr_speclist + := normalize_desc_list !full_desc_list |> List.map ~f:xdesc |> add_or_suppress_help + |> to_arg_speclist ; + assert (check_no_duplicates !curr_speclist) ; curr_usage let select_parse_mode ~usage parse_mode = let print_usage = set_curr_speclist_for_parse_mode ~usage parse_mode in - anon_arg_action := anon_arg_action_of_parse_mode parse_mode; + anon_arg_action := anon_arg_action_of_parse_mode parse_mode ; print_usage let string_of_command command = - let (_, s, _) = List.Assoc.find_exn !subcommands ~equal:equal_command command in + let _, s, _ = List.Assoc.find_exn !subcommands ~equal:equal_command command in s -let mk_rest_actions ?(parse_mode=InferCommand) ?(in_help=[]) doc ~usage decode_action = +let mk_rest_actions ?(parse_mode= InferCommand) ?(in_help= []) doc ~usage decode_action = let rest = ref [] in - let spec = String (fun arg -> - rest := List.rev (Array.to_list (Array.slice !args_to_parse (!arg_being_parsed + 1) 0)) ; - select_parse_mode ~usage (decode_action arg) |> ignore) in - add parse_mode in_help {long = "--"; short = ""; meta = ""; doc; spec; - decode_json = fun ~inferconfig_dir:_ _ -> []} ; + let spec = + String + (fun arg -> + rest := List.rev (Array.to_list (Array.slice !args_to_parse (!arg_being_parsed + 1) 0)) ; + select_parse_mode ~usage (decode_action arg) |> ignore) + in + add parse_mode in_help + {long= "--"; short= ""; meta= ""; doc; spec; decode_json= (fun ~inferconfig_dir:_ _ -> [])} ; rest -let mk_subcommand command ?on_unknown_arg:(on_unknown=`Reject) ~name ?deprecated_long - ?parse_mode ?in_help command_doc = +let mk_subcommand command ?on_unknown_arg:(on_unknown = `Reject) ~name ?deprecated_long ?parse_mode + ?in_help command_doc = let switch () = - curr_command := Some command; - anon_arg_action := {!anon_arg_action with on_unknown} in - (match deprecated_long with - | Some long -> - ignore( - mk ~long ~default:() ?parse_mode ?in_help ~meta:"" "" - ~default_to_string:(fun () -> "") + curr_command := Some command ; + anon_arg_action := {(!anon_arg_action) with on_unknown} + in + ( match deprecated_long with + | Some long + -> ignore + (mk ~long ~default:() ?parse_mode ?in_help ~meta:"" "" ~default_to_string:(fun () -> "") ~decode_json:(fun ~inferconfig_dir:_ _ -> - raise (Arg.Bad ("Bad option in config file: " ^ long))) + raise (Arg.Bad ("Bad option in config file: " ^ long))) ~mk_setter:(fun _ _ -> - warnf "WARNING: '%s' is deprecated. Please use '%s' instead.@\n" - (dashdash long) name; - switch ()) - ~mk_spec:(fun set -> Unit (fun () -> set ""))) - | None -> () - ); - subcommands := (command, (command_doc, name, in_help))::!subcommands; - subcommand_actions := (name, switch)::!subcommand_actions + warnf "WARNING: '%s' is deprecated. Please use '%s' instead.@\n" (dashdash long) name ; + switch ()) ~mk_spec:(fun set -> Unit (fun () -> set "") )) + | None + -> () ) ; + subcommands := (command, (command_doc, name, in_help)) :: !subcommands ; + subcommand_actions := (name, switch) :: !subcommand_actions (* drop well-balanced first and last characters in [s] that satisfy the [drop] predicate; for instance, [lrstrip ~drop:(function | 'a' | 'x' -> true | _ -> false) "xaabax"] returns "ab" *) @@ -633,54 +646,57 @@ let rec lrstrip ~drop s = if n < 2 then s else let first = String.unsafe_get s 0 in - if Char.equal first (String.unsafe_get s (n-1)) && drop first then - lrstrip ~drop (String.slice s 1 (n-1)) + if Char.equal first (String.unsafe_get s (n - 1)) && drop first then + lrstrip ~drop (String.slice s 1 (n - 1)) else s let args_from_argfile arg = let abs_fname = let fname = String.slice arg 1 (String.length arg) in - normalize_path_in_args_being_parsed ~f:(fun s -> "@" ^ s) ~is_anon_arg:true fname in + normalize_path_in_args_being_parsed ~f:(fun s -> "@" ^ s) ~is_anon_arg:true fname + in match In_channel.read_lines abs_fname with - | lines -> - let strip = lrstrip ~drop:(function '"' | '\'' -> true | _ -> false) in + | lines + -> let strip = lrstrip ~drop:(function '"' | '\'' -> true | _ -> false) in List.map ~f:strip lines - | exception e -> - raise (Arg.Bad ("Error reading argument file '" ^ abs_fname ^ "': " ^ Exn.to_string e)) + | exception e + -> raise (Arg.Bad ("Error reading argument file '" ^ abs_fname ^ "': " ^ Exn.to_string e)) exception SubArguments of string list let anon_fun arg = - if !anon_arg_action.parse_argfiles - && String.is_prefix arg ~prefix:"@" then + if !anon_arg_action.parse_argfiles && String.is_prefix arg ~prefix:"@" then (* stop parsing the current args and go look in that argfile *) raise (SubArguments (args_from_argfile arg)) else if !anon_arg_action.parse_subcommands - && List.Assoc.mem !subcommand_actions ~equal:String.equal arg then + && List.Assoc.mem !subcommand_actions ~equal:String.equal arg + then let command_switch = List.Assoc.find_exn !subcommand_actions ~equal:String.equal arg in - match !curr_command, is_originator with - | None, _ | Some _, false -> - command_switch () - | Some command, true -> - raise (Arg.Bad - (Printf.sprintf "More than one subcommand specified: '%s', '%s'" - (string_of_command command) arg)) - else match !anon_arg_action.on_unknown with - | `Add -> - rev_anon_args := arg::!rev_anon_args - | `Skip -> - () - | `Reject -> - raise (Arg.Bad (Printf.sprintf "Unexpected anonymous argument: '%s'" arg)) - + match (!curr_command, is_originator) with + | None, _ | Some _, false + -> command_switch () + | Some command, true + -> raise + (Arg.Bad + (Printf.sprintf "More than one subcommand specified: '%s', '%s'" + (string_of_command command) arg)) + else + match !anon_arg_action.on_unknown with + | `Add + -> rev_anon_args := arg :: !rev_anon_args + | `Skip + -> () + | `Reject + -> raise (Arg.Bad (Printf.sprintf "Unexpected anonymous argument: '%s'" arg)) let decode_inferconfig_to_argv path = - let json = match Utils.read_json_file path with - | Ok json -> - json - | Error msg -> - warnf "WARNING: Could not read or parse Infer config in %s:@\n%s@." path msg ; - `Assoc [] in + let json = + match Utils.read_json_file path with + | Ok json + -> json + | Error msg + -> warnf "WARNING: Could not read or parse Infer config in %s:@\n%s@." path msg ; `Assoc [] + in let desc_list = List.Assoc.find_exn ~equal:equal_parse_mode parse_mode_desc_lists InferCommand in let json_config = YBU.to_assoc json in let inferconfig_dir = Filename.dirname path in @@ -689,34 +705,33 @@ let decode_inferconfig_to_argv path = let {decode_json} = List.find_exn ~f:(fun {long; short} -> - String.equal key long - || (* for deprecated options *) String.equal key short) - !desc_list in + String.equal key long || (* for deprecated options *) String.equal key short) + !desc_list + in decode_json ~inferconfig_dir json_val @ result with - | Not_found -> - warnf "WARNING: while reading config file %s:@\nUnknown option %s@." path key ; - result - | YBU.Type_error (msg, json) -> - warnf "WARNING: while reading config file %s:@\nIll-formed value %s for option %s: %s@." + | Not_found + -> warnf "WARNING: while reading config file %s:@\nUnknown option %s@." path key ; result + | YBU.Type_error (msg, json) + -> warnf "WARNING: while reading config file %s:@\nIll-formed value %s for option %s: %s@." path (Yojson.Basic.to_string json) key msg ; - result in + result + in List.fold ~f:one_config_item ~init:[] json_config - (** separator of argv elements when encoded into environment variables *) let env_var_sep = '^' let encode_argv_to_env argv = String.concat ~sep:(String.make 1 env_var_sep) - (List.filter ~f:(fun arg -> + (List.filter + ~f:(fun arg -> not (String.contains arg env_var_sep) - || ( - warnf "WARNING: Ignoring unsupported option containing '%c' character: %s@\n" - env_var_sep arg ; - false - ) - ) argv) + || + (warnf "WARNING: Ignoring unsupported option containing '%c' character: %s@\n" env_var_sep + arg ; + false)) + argv) let decode_env_to_argv env = String.split ~on:env_var_sep env |> List.filter ~f:(Fn.non String.is_empty) @@ -724,92 +739,94 @@ let decode_env_to_argv env = (** [prefix_before_rest (prefix @ ["--" :: rest])] is [prefix] where "--" is not in [prefix]. *) let rev_prefix_before_rest args = let rec rev_prefix_before_rest_ rev_keep = function - | [] | "--" :: _ -> rev_keep - | keep :: args -> rev_prefix_before_rest_ (keep :: rev_keep) args in + | [] | "--" :: _ + -> rev_keep + | keep :: args + -> rev_prefix_before_rest_ (keep :: rev_keep) args + in rev_prefix_before_rest_ [] args - (** environment variable use to pass arguments from parent to child processes *) let args_env_var = "INFER_ARGS" let extra_env_args = ref [] -let extend_env_args args = - extra_env_args := List.rev_append args !extra_env_args +let extend_env_args args = extra_env_args := List.rev_append args !extra_env_args let parse_args ~usage initial_action ?initial_command args = let exe_name = Sys.executable_name in - args_to_parse := Array.of_list (exe_name :: args); - arg_being_parsed := 0; + args_to_parse := Array.of_list (exe_name :: args) ; + arg_being_parsed := 0 ; let curr_usage = select_parse_mode ~usage initial_action in Option.iter initial_command ~f:(fun command -> - let switch = List.Assoc.find_exn !subcommand_actions ~equal:String.equal - (string_of_command command) in - switch ()); + let switch = + List.Assoc.find_exn !subcommand_actions ~equal:String.equal (string_of_command command) + in + switch () ) ; (* tests if msg indicates an unknown option, as opposed to a known option with bad argument *) let is_unknown msg = String.is_substring msg ~substring:": unknown option" in let rec parse_loop () = try - Arg.parse_argv_dynamic ~current:arg_being_parsed !args_to_parse curr_speclist - anon_fun usage + Arg.parse_argv_dynamic ~current:arg_being_parsed !args_to_parse curr_speclist anon_fun usage with - | SubArguments args -> - (* stop parsing the current arguments and parse [args] for a while *) + | SubArguments args + -> (* stop parsing the current arguments and parse [args] for a while *) let saved_args = !args_to_parse in let saved_current = !arg_being_parsed in - args_to_parse := Array.of_list (exe_name :: args); - arg_being_parsed := 0; - parse_loop (); + args_to_parse := Array.of_list (exe_name :: args) ; + arg_being_parsed := 0 ; + parse_loop () ; (* resume argument parsing *) - args_to_parse := saved_args; - arg_being_parsed := saved_current; + args_to_parse := saved_args ; + arg_being_parsed := saved_current ; parse_loop () - | Arg.Bad usage_msg -> - if !anon_arg_action.on_unknown <> `Reject && is_unknown usage_msg then ( - anon_fun !args_to_parse.(!arg_being_parsed); - parse_loop () - ) else ( - Pervasives.prerr_string usage_msg; - exit 2 - ) - | Arg.Help _ -> - (* we handle --help by ourselves and error on -help, so Arg has no way to raise Help + | Arg.Bad usage_msg + -> if !anon_arg_action.on_unknown <> `Reject && is_unknown usage_msg then ( + anon_fun !args_to_parse.(!arg_being_parsed) ; + parse_loop () ) + else ( Pervasives.prerr_string usage_msg ; exit 2 ) + | Arg.Help _ + -> (* we handle --help by ourselves and error on -help, so Arg has no way to raise Help anymore *) - assert false in - parse_loop (); - curr_usage + assert false + in + parse_loop () ; curr_usage let parse ?config_file ~usage action initial_command = let env_args = decode_env_to_argv (Option.value (Sys.getenv args_env_var) ~default:"") in let inferconfig_args = - Option.map ~f:decode_inferconfig_to_argv config_file |> Option.value ~default:[] in + Option.map ~f:decode_inferconfig_to_argv config_file |> Option.value ~default:[] + in let args_to_export = ref "" in let add_parsed_args_to_args_to_export () = (* reread args_to_parse instead of using all_args since mk_path_helper may have modified them *) let prog_args = List.rev_append (rev_prefix_before_rest (Array.to_list !args_to_parse)) - (List.rev !extra_env_args) in + (List.rev !extra_env_args) + in (* do not include program path in args passed via env var *) let args = Option.value (List.tl prog_args) ~default:[] in if not (List.is_empty args) then let arg_string = if String.equal !args_to_export "" then encode_argv_to_env args - else !args_to_export ^ String.of_char env_var_sep ^ encode_argv_to_env args in - args_to_export := arg_string in + else !args_to_export ^ String.of_char env_var_sep ^ encode_argv_to_env args + in + args_to_export := arg_string + in (* read .inferconfig first, then env vars, then command-line options *) - parse_args ~usage InferCommand inferconfig_args |> ignore; + parse_args ~usage InferCommand inferconfig_args |> ignore ; (* NOTE: do not add the contents of .inferconfig to INFER_ARGS. This helps avoid hitting the command line size limit. *) - parse_args ~usage InferCommand env_args |> ignore; - add_parsed_args_to_args_to_export (); + parse_args ~usage InferCommand env_args |> ignore ; + add_parsed_args_to_args_to_export () ; let curr_usage = let cl_args = match Array.to_list Sys.argv with _ :: tl -> tl | [] -> [] in let curr_usage = parse_args ~usage action ?initial_command cl_args in - add_parsed_args_to_args_to_export (); - curr_usage in - Unix.putenv ~key:args_env_var ~data:!args_to_export; - !curr_command, curr_usage + add_parsed_args_to_args_to_export () ; curr_usage + in + Unix.putenv ~key:args_env_var ~data:!args_to_export ; + (!curr_command, curr_usage) let wrap_line indent_string wrap_length line0 = let line = indent_string ^ line0 in @@ -820,82 +837,92 @@ let wrap_line indent_string wrap_length line0 = let add_word_to_paragraph (rev_lines, non_empty, line, line_length) word = let word_length = let len = String.length word in - if String.is_prefix ~prefix:"$(b," word || String.is_prefix ~prefix:"$(i," word then - len - 4 (* length of formatting tag prefix *) + if String.is_prefix ~prefix:"$(b," word || String.is_prefix ~prefix:"$(i," word then len - 4 + (* length of formatting tag prefix *) - 1 (* APPROXIMATION: closing parenthesis that will come after the word, or maybe later *) - else - len in - let new_length = line_length + (String.length word_sep_str) + word_length in + else len + in + let new_length = line_length + String.length word_sep_str + word_length in let new_non_empty = non_empty || word <> "" in if new_length > wrap_length && non_empty then - (line::rev_lines, true, indent_string ^ word, indent_length + word_length) + (line :: rev_lines, true, indent_string ^ word, indent_length + word_length) else let sep = if Int.equal line_length indent_length then "" else word_sep_str in let new_line = line ^ sep ^ word in if new_length > wrap_length && new_non_empty then - (new_line::rev_lines, false, indent_string, indent_length) - else - (rev_lines, new_non_empty, new_line, new_length) in - let (rev_lines, _, line, _) = - List.fold ~f:add_word_to_paragraph ~init:([], false, "", 0) words in - List.rev (line::rev_lines) + (new_line :: rev_lines, false, indent_string, indent_length) + else (rev_lines, new_non_empty, new_line, new_length) + in + let rev_lines, _, line, _ = List.fold ~f:add_word_to_paragraph ~init:([], false, "", 0) words in + List.rev (line :: rev_lines) let show_manual ?internal_section format default_doc command_opt = - let command_doc = match command_opt with - | None -> - default_doc + let command_doc = + match command_opt with + | None + -> default_doc | Some command -> - match List.Assoc.find_exn ~equal:equal_command !subcommands command with - | (Some command_doc, _, _) -> - command_doc - | (None, _, _) -> - invalid_argf "No manual for internal command %s" (string_of_command command) in - let pp_meta f meta = match meta with - | "" -> () - | meta -> F.fprintf f " $(i,%s)" (Cmdliner.Manpage.escape meta) in - let pp_short f = function - | "" -> () - | s -> Format.fprintf f ",$(b,-%s)" s in - let block_of_desc { long; meta; short; doc } = - if String.equal doc "" then - [] + match List.Assoc.find_exn ~equal:equal_command !subcommands command with + | Some command_doc, _, _ + -> command_doc + | None, _, _ + -> invalid_argf "No manual for internal command %s" (string_of_command command) + in + let pp_meta f meta = + match meta with "" -> () | meta -> F.fprintf f " $(i,%s)" (Cmdliner.Manpage.escape meta) + in + let pp_short f = function "" -> () | s -> Format.fprintf f ",$(b,-%s)" s in + let block_of_desc {long; meta; short; doc} = + if String.equal doc "" then [] else - let doc_first_line, doc_other_lines = match String.split ~on:'\n' doc with - | first::other -> first, other - | [] -> "", [] in + let doc_first_line, doc_other_lines = + match String.split ~on:'\n' doc with first :: other -> (first, other) | [] -> ("", []) + in (* Cmdline.Manpage does not format multi-paragraph documentation strings correctly for `I blocks, so we do a bit of formatting by hand *) let indent_string = " " in - let width = 77 (* Cmdliner.Manpage width limit it seems *) - - 7 (* base indentation of documentation strings *) in - `I (Format.asprintf "$(b,%s)%a%a" (dashdash long) pp_short short pp_meta meta, - doc_first_line) - :: List.concat_map (List.concat_map ~f:(wrap_line indent_string width) doc_other_lines) - ~f:(fun s -> [`Noblank; `Pre s]) in - let option_blocks = match command_doc.manual_options with - | `Replace blocks -> - `S Cmdliner.Manpage.s_options :: blocks - | `Prepend blocks -> - let hidden = + let width = + 77 (* Cmdliner.Manpage width limit it seems *) + - 7 + (* base indentation of documentation strings *) + in + `I (Format.asprintf "$(b,%s)%a%a" (dashdash long) pp_short short pp_meta meta, doc_first_line) + :: List.concat_map (List.concat_map ~f:(wrap_line indent_string width) doc_other_lines) ~f: + (fun s -> [`Noblank; `Pre s] ) + in + let option_blocks = + match command_doc.manual_options with + | `Replace blocks + -> `S Cmdliner.Manpage.s_options :: blocks + | `Prepend blocks + -> let hidden = match internal_section with - | Some section -> - `S section :: `P "Use at your own risk." - :: List.concat_map ~f:block_of_desc (normalize_desc_list !hidden_descs_list) - | None -> - [] in + | Some section + -> `S section + :: `P "Use at your own risk." + :: List.concat_map ~f:block_of_desc (normalize_desc_list !hidden_descs_list) + | None + -> [] + in match command_opt with - | Some command -> - let sections = List.Assoc.find_exn ~equal:equal_command help_sections_desc_lists command in - SectionMap.fold (fun section descs result -> - `S section :: - (if String.equal section Cmdliner.Manpage.s_options then blocks else []) @ - List.concat_map ~f:block_of_desc (normalize_desc_list descs) @ result) + | Some command + -> let sections = + List.Assoc.find_exn ~equal:equal_command help_sections_desc_lists command + in + SectionMap.fold + (fun section descs result -> + `S section + :: (if String.equal section Cmdliner.Manpage.s_options then blocks else []) + @ List.concat_map ~f:block_of_desc (normalize_desc_list descs) @ result) !sections hidden - | None -> - `S Cmdliner.Manpage.s_options :: blocks @ - List.concat_map ~f:block_of_desc (normalize_desc_list !visible_descs_list) @ - hidden in - let blocks = [`Blocks command_doc.manual_before_options; `Blocks option_blocks; - `Blocks command_doc.manual_after_options] in - Cmdliner.Manpage.print format Format.std_formatter (command_doc.title, blocks); + | None + -> `S Cmdliner.Manpage.s_options :: blocks + @ List.concat_map ~f:block_of_desc (normalize_desc_list !visible_descs_list) @ hidden + in + let blocks = + [ `Blocks command_doc.manual_before_options + ; `Blocks option_blocks + ; `Blocks command_doc.manual_after_options ] + in + Cmdliner.Manpage.print format Format.std_formatter (command_doc.title, blocks) ; () diff --git a/infer/src/base/CommandLineOption.mli b/infer/src/base/CommandLineOption.mli index 0c037cd94..4e3fbe08b 100644 --- a/infer/src/base/CommandLineOption.mli +++ b/infer/src/base/CommandLineOption.mli @@ -11,29 +11,32 @@ open! IStd -(** Print to stderr in case of error, fails in strict mode *) val warnf : ('a, Format.formatter, unit) format -> 'a +(** Print to stderr in case of error, fails in strict mode *) type parse_mode = - | InferCommand (** parse arguments as arguments for infer *) - | Javac (** parse arguments passed to the Java compiler *) - | NoParse (** all arguments are anonymous arguments, no parsing is attempted *) -[@@deriving compare] + | InferCommand (** parse arguments as arguments for infer *) + | Javac (** parse arguments passed to the Java compiler *) + | NoParse (** all arguments are anonymous arguments, no parsing is attempted *) + [@@deriving compare] (** Main modes of operation for infer *) type command = - | Analyze (** analyze previously captured source files *) - | Capture (** capture compilation commands and translate source files into infer's intermediate + | Analyze (** analyze previously captured source files *) + | Capture + (** capture compilation commands and translate source files into infer's intermediate language *) - | Clang (** run and accept the same arguments as the clang compiler, may also capture the source + | Clang + (** run and accept the same arguments as the clang compiler, may also capture the source files compiled, and may also not actually compile the files depending on other options *) - | Compile (** set up the infer environment then run the compilation commands without capturing the + | Compile + (** set up the infer environment then run the compilation commands without capturing the source files *) - | Report (** post-process infer results and reports *) - | ReportDiff (** compute the difference of two infer reports *) - | Run (** orchestrate the capture, analysis, and reporting of a compilation command *) -[@@deriving compare] + | Report (** post-process infer results and reports *) + | ReportDiff (** compute the difference of two infer reports *) + | Run (** orchestrate the capture, analysis, and reporting of a compilation command *) + [@@deriving compare] val equal_command : command -> command -> bool @@ -62,30 +65,29 @@ val init_work_dir : string - a documentation string *) type 'a t = - ?deprecated:string list -> long:string -> ?short:char -> - ?parse_mode:parse_mode -> ?in_help:(command * string) list -> ?meta:string -> string -> - 'a + ?deprecated:string list -> long:string -> ?short:char -> ?parse_mode:parse_mode + -> ?in_help:(command * string) list -> ?meta:string -> string -> 'a -(** [mk_set variable value] defines a command line option which sets [variable] to [value]. *) val mk_set : 'a ref -> 'a -> unit t +(** [mk_set variable value] defines a command line option which sets [variable] to [value]. *) val mk_option : - ?default:'a option -> ?default_to_string:('a option -> string) -> f:(string -> 'a option) -> - 'a option ref t + ?default:'a option -> ?default_to_string:('a option -> string) -> f:(string -> 'a option) + -> 'a option ref t +val mk_bool : ?deprecated_no:string list -> ?default:bool -> ?f:(bool -> bool) -> bool ref t (** [mk_bool long short doc] defines a [bool ref] set by the command line flag [--long] (and [-s]), and cleared by the flag [--no-long] (and [-S]). If [long] already has a "no-" prefix, or [s] is capital, then the existing prefixes will instead be removed. The default value is [false] unless overridden by [~default:true]. The [doc] string will be prefixed with either "Activates:" or "Deactivates:", so should be phrased accordingly. *) -val mk_bool : ?deprecated_no:string list -> ?default:bool -> ?f:(bool -> bool) -> bool ref t +val mk_bool_group : + ?deprecated_no:string list -> ?default:bool -> ?f:(bool -> bool) + -> (bool ref list -> bool ref list -> bool ref) t (** [mk_bool_group children not_children] behaves as [mk_bool] with the addition that all the [children] are also set and the [no_children] are unset. A child can be unset by including "--no-child" later in the arguments. *) -val mk_bool_group : - ?deprecated_no:string list -> ?default:bool -> ?f:(bool -> bool) -> - (bool ref list -> bool ref list -> bool ref) t val mk_int : default:int -> ?f:(int -> int) -> int ref t @@ -99,51 +101,52 @@ val mk_string : default:string -> ?f:(string -> string) -> string ref t val mk_string_opt : ?default:string -> ?f:(string -> string) -> string option ref t +val mk_string_list : ?default:string list -> ?f:(string -> string) -> string list ref t (** [mk_string_list] defines a [string list ref], initialized to [[]] unless overridden by [~default]. Each argument of an occurrence of the option will be prepended to the list, so the final value will be in the reverse order they appeared on the command line. *) -val mk_string_list : - ?default:string list -> ?f:(string -> string) -> string list ref t +val mk_path : default:string -> string ref t (** like [mk_string] but will resolve the string into an absolute path so that children processes agree on the absolute path that the option represents *) -val mk_path : default:string -> string ref t -(** analogous of [mk_string_opt] with the extra feature of [mk_path] *) val mk_path_opt : ?default:string -> string option ref t +(** analogous of [mk_string_opt] with the extra feature of [mk_path] *) -(** analogous of [mk_string_list] with the extra feature of [mk_path] *) val mk_path_list : ?default:string list -> string list ref t +(** analogous of [mk_string_list] with the extra feature of [mk_path] *) +val mk_symbol : default:'a -> symbols:(string * 'a) list -> eq:('a -> 'a -> bool) -> 'a ref t (** [mk_symbol long symbols] defines a command line flag [--long ] where [(,_)] is an element of [symbols]. *) -val mk_symbol : default:'a -> symbols:(string * 'a) list -> eq:('a -> 'a -> bool) -> 'a ref t -(** [mk_symbol_opt] is similar to [mk_symbol] but defaults to [None]. *) val mk_symbol_opt : symbols:(string * 'a) list -> ?f:('a -> 'a) -> 'a option ref t +(** [mk_symbol_opt] is similar to [mk_symbol] but defaults to [None]. *) +val mk_symbol_seq : + ?default:'a list -> symbols:(string * 'a) list -> eq:('a -> 'a -> bool) -> 'a list ref t (** [mk_symbol_seq long symbols] defines a command line flag [--long ] where [] is a comma-separated sequence of []s such that [(,_)] is an element of [symbols]. *) -val mk_symbol_seq : - ?default:'a list -> symbols:(string * 'a) list -> eq:('a -> 'a -> bool) -> 'a list ref t -val mk_set_from_json : default:'a -> default_to_string:('a -> string) - -> f:(Yojson.Basic.json -> 'a) -> 'a ref t +val mk_set_from_json : + default:'a -> default_to_string:('a -> string) -> f:(Yojson.Basic.json -> 'a) -> 'a ref t val mk_json : Yojson.Basic.json ref t +val mk_anon : unit -> string list ref (** [mk_anon ()] defines a [string list ref] of the anonymous command line arguments, in the reverse order they appeared on the command line. *) -val mk_anon : unit -> string list ref +val mk_rest : + ?parse_mode:parse_mode -> ?in_help:(command * string) list -> string -> string list ref (** [mk_rest doc] defines a [string list ref] of the command line arguments following ["--"], in the reverse order they appeared on the command line. For example, calling [mk_rest] and parsing [exe -opt1 -opt2 -- arg1 arg2] will result in the returned ref containing [arg2; arg1]. *) -val mk_rest : - ?parse_mode:parse_mode-> ?in_help:(command * string) list -> string -> - string list ref +val mk_rest_actions : + ?parse_mode:parse_mode -> ?in_help:(command * string) list -> string -> usage:string + -> (string -> parse_mode) -> string list ref (** [mk_rest_actions doc ~usage command_to_parse_mode] defines a [string list ref] of the command line arguments following ["--"], in the reverse order they appeared on the command line. [usage] is the usage message in case of parse errors or if --help is passed. For example, calling @@ -151,13 +154,17 @@ val mk_rest : containing [arg2; arg1]. Additionally, the first arg following ["--"] is passed to [command_to_parse_mode] to obtain the parse action that will be used to parse the remaining arguments. *) -val mk_rest_actions : - ?parse_mode:parse_mode -> ?in_help:(command * string) list -> string -> - usage:string -> (string -> parse_mode) - -> string list ref type command_doc +val mk_command_doc : + title:string -> section:int -> version:string -> date:string -> short_description:string + -> synopsis:Cmdliner.Manpage.block list -> description:Cmdliner.Manpage.block list + -> ?options:[`Prepend of Cmdliner.Manpage.block list | `Replace of Cmdliner.Manpage.block list] + -> ?exit_status:Cmdliner.Manpage.block list -> ?environment:Cmdliner.Manpage.block list + -> ?files:Cmdliner.Manpage.block list -> ?notes:Cmdliner.Manpage.block list + -> ?bugs:Cmdliner.Manpage.block list -> ?examples:Cmdliner.Manpage.block list + -> see_also:Cmdliner.Manpage.block list -> string -> command_doc (** [mk_command_doc ~title ~section ~version ~short_description ~synopsis ~description ~see_also command_exe] records information about a command that is used to create its man page. A lot of the concepts are taken from man-pages(7). @@ -174,40 +181,31 @@ type command_doc - All the other [section_name] options correspond to the contents of the section [section_name]. Some are mandatory and some are not. *) -val mk_command_doc : title:string -> section:int -> version:string -> date:string -> - short_description:string -> synopsis:Cmdliner.Manpage.block list -> - description:Cmdliner.Manpage.block list -> - ?options:[`Prepend of Cmdliner.Manpage.block list | `Replace of Cmdliner.Manpage.block list] -> - ?exit_status:Cmdliner.Manpage.block list -> - ?environment:Cmdliner.Manpage.block list -> - ?files:Cmdliner.Manpage.block list -> - ?notes:Cmdliner.Manpage.block list -> - ?bugs:Cmdliner.Manpage.block list -> - ?examples:Cmdliner.Manpage.block list -> - see_also:Cmdliner.Manpage.block list -> - string -> command_doc +val mk_subcommand : + command -> ?on_unknown_arg:[`Add | `Skip | `Reject] -> name:string -> ?deprecated_long:string + -> ?parse_mode:parse_mode -> ?in_help:(command * string) list -> command_doc option -> unit (** [mk_subcommand command ~long command_doc] defines the subcommand [command]. A subcommand is activated by passing [name], and by passing [--deprecated_long] if specified. A man page is automatically generated for [command] based on the information in [command_doc], if available (otherwise the command is considered internal). [on_unknown_arg] is the action taken on unknown anonymous arguments; it is `Reject by default. *) -val mk_subcommand : command -> ?on_unknown_arg:[`Add | `Skip | `Reject] -> - name:string -> ?deprecated_long:string -> ?parse_mode:parse_mode -> - ?in_help:(command * string) list -> command_doc option -> unit -(** environment variable use to pass arguments from parent to child processes *) val args_env_var : string +(** environment variable use to pass arguments from parent to child processes *) val strict_mode_env_var : string -(** separator of argv elements when encoded into environment variables *) val env_var_sep : char +(** separator of argv elements when encoded into environment variables *) -(** [extend_env_args args] appends [args] to those passed via [args_env_var] *) val extend_env_args : string list -> unit +(** [extend_env_args args] appends [args] to those passed via [args_env_var] *) +val parse : + ?config_file:string -> usage:Arg.usage_msg -> parse_mode -> command option + -> command option * (int -> 'a) (** [parse ~usage parse_mode command] parses command line arguments as specified by preceding calls to the [mk_*] functions, and returns: - the command selected by the user on the command line, except if [command] is not None in which @@ -225,14 +223,12 @@ val extend_env_args : string list -> unit WARNING: An argument will be interpreted as many times as it appears in all of the config file, the environment variable, and the command line. The [args_env_var] is set to the set of options parsed in [args_env_var] and on the command line. *) -val parse : ?config_file:string -> usage:Arg.usage_msg -> parse_mode -> command option - -> command option * (int -> 'a) -(** [is_env_var_set var] is true if $[var]=1 *) val is_env_var_set : string -> bool +(** [is_env_var_set var] is true if $[var]=1 *) +val show_manual : + ?internal_section:string -> Cmdliner.Manpage.format -> command_doc -> command option -> unit (** Display the manual of [command] to the user, or [command_doc] if [command] is None. [format] is used as for [Cmdliner.Manpage.print]. If [internal_section] is specified, add a section titled [internal_section] about internal (hidden) options. *) -val show_manual : ?internal_section:string -> Cmdliner.Manpage.format -> command_doc - -> command option -> unit diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index ab1f0d399..3e572d87e 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -18,68 +18,69 @@ module CLOpt = CommandLineOption module F = Format type analyzer = - | BiAbduction | CaptureOnly | CompileOnly | Eradicate | Checkers | Crashcontext | Linters -[@@deriving compare] + | BiAbduction + | CaptureOnly + | CompileOnly + | Eradicate + | Checkers + | Crashcontext + | Linters + [@@deriving compare] let equal_analyzer = [%compare.equal : analyzer] -let string_to_analyzer = [ - "capture", CaptureOnly; "checkers", Checkers; "compile", CompileOnly; - "crashcontext", Crashcontext; "eradicate", Eradicate; "infer", BiAbduction; "linters", Linters; -] +let string_to_analyzer = + [ ("capture", CaptureOnly) + ; ("checkers", Checkers) + ; ("compile", CompileOnly) + ; ("crashcontext", Crashcontext) + ; ("eradicate", Eradicate) + ; ("infer", BiAbduction) + ; ("linters", Linters) ] let string_of_analyzer a = List.find_exn ~f:(fun (_, a') -> equal_analyzer a a') string_to_analyzer |> fst -let clang_frontend_action_symbols = [ - ("lint", `Lint); - ("capture", `Capture); - ("lint_and_capture", `Lint_and_capture); -] +let clang_frontend_action_symbols = + [("lint", `Lint); ("capture", `Capture); ("lint_and_capture", `Lint_and_capture)] type language = Clang | Java [@@deriving compare] let equal_language = [%compare.equal : language] -let string_of_language = function - | Java -> "Java" - | Clang -> "C_CPP" - - -let ml_bucket_symbols = [ - ("all", `MLeak_all); - ("cf", `MLeak_cf); - ("arc", `MLeak_arc); - ("narc", `MLeak_no_arc); - ("cpp", `MLeak_cpp); - ("unknown_origin", `MLeak_unknown); -] - -let issues_fields_symbols = [ - ("bug_class", `Issue_field_bug_class); - ("kind", `Issue_field_kind); - ("bug_type", `Issue_field_bug_type); - ("qualifier", `Issue_field_qualifier); - ("severity", `Issue_field_severity); - ("visibility", `Issue_field_visibility); - ("line", `Issue_field_line); - ("column", `Issue_field_column); - ("procedure", `Issue_field_procedure); - ("procedure_id", `Issue_field_procedure_id); - ("procedure_start_line", `Issue_field_procedure_start_line); - ("file", `Issue_field_file); - ("bug_trace", `Issue_field_bug_trace); - ("key", `Issue_field_key); - ("hash", `Issue_field_hash); - ("line_offset", `Issue_field_line_offset); - ("procedure_id_without_crc", `Issue_field_procedure_id_without_crc); - ("qualifier_contains_potential_exception_note", - `Issue_field_qualifier_contains_potential_exception_note); -] +let string_of_language = function Java -> "Java" | Clang -> "C_CPP" + +let ml_bucket_symbols = + [ ("all", `MLeak_all) + ; ("cf", `MLeak_cf) + ; ("arc", `MLeak_arc) + ; ("narc", `MLeak_no_arc) + ; ("cpp", `MLeak_cpp) + ; ("unknown_origin", `MLeak_unknown) ] + +let issues_fields_symbols = + [ ("bug_class", `Issue_field_bug_class) + ; ("kind", `Issue_field_kind) + ; ("bug_type", `Issue_field_bug_type) + ; ("qualifier", `Issue_field_qualifier) + ; ("severity", `Issue_field_severity) + ; ("visibility", `Issue_field_visibility) + ; ("line", `Issue_field_line) + ; ("column", `Issue_field_column) + ; ("procedure", `Issue_field_procedure) + ; ("procedure_id", `Issue_field_procedure_id) + ; ("procedure_start_line", `Issue_field_procedure_start_line) + ; ("file", `Issue_field_file) + ; ("bug_trace", `Issue_field_bug_trace) + ; ("key", `Issue_field_key) + ; ("hash", `Issue_field_hash) + ; ("line_offset", `Issue_field_line_offset) + ; ("procedure_id_without_crc", `Issue_field_procedure_id_without_crc) + ; ( "qualifier_contains_potential_exception_note" + , `Issue_field_qualifier_contains_potential_exception_note ) ] type os_type = Unix | Win32 | Cygwin - (** Constant configuration values *) (** If true, a precondition with e.g. index 3 in an array does not require the caller to @@ -110,9 +111,7 @@ let buck_results_dir_name = "infer" let captured_dir_name = "captured" -let checks_disabled_by_default = [ - "GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL"; -] +let checks_disabled_by_default = ["GLOBAL_VARIABLE_INITIALIZED_WITH_FUNCTION_OR_METHOD_CALL"] let clang_initializer_prefix = "__infer_globals_initializer_" @@ -153,26 +152,43 @@ let lint_issues_dir_name = "lint_issues" (** letters used in the analysis output *) let log_analysis_file = "F" + let log_analysis_procedure = "." + let log_analysis_wallclock_timeout = "T" + let log_analysis_symops_timeout = "S" + let log_analysis_recursion_timeout = "R" + let log_analysis_crash = "C" let log_dir_name = "log" let manual_buck_compilation_db = "BUCK COMPILATION DATABASE OPTIONS" + let manual_buck_flavors = "BUCK FLAVORS OPTIONS" + let manual_buck_java = "BUCK FOR JAVA OPTIONS" + let manual_buffer_overrun = "BUFFER OVERRUN OPTIONS" + let manual_clang = "CLANG OPTIONS" + let manual_clang_linters = "CLANG LINTERS OPTIONS" + let manual_crashcontext = "CRASHCONTEXT OPTIONS" + let manual_generic = Cmdliner.Manpage.s_options + let manual_internal = "INTERNAL OPTIONS" + let manual_java = "JAVA OPTIONS" + let manual_quandary = "QUANDARY CHECKER OPTIONS" + let manual_siof = "SIOF CHECKER OPTIONS" + let manual_threadsafety = "THREADSAFETY CHECKER OPTIONS" (** Maximum level of recursion during the analysis, after which a timeout is generated *) @@ -231,27 +247,17 @@ let use_jar_cache = true let weak = "<\"Weak\">" -let whitelisted_cpp_methods = [ - "std::move"; - "std::forward"; - "std::min"; - "std::max"; - "std::swap"; - "google::CheckNotNull"; -] - -let whitelisted_cpp_classes = [ - "std::__less"; - "std::__wrap_iter"; (* libc++ internal name of vector iterator *) - "__gnu_cxx::__normal_iterator"; (* libstdc++ internal name of vector iterator *) -] - -type dynamic_dispatch_policy = [ - | `None - | `Interface - | `Sound - | `Lazy -] +let whitelisted_cpp_methods = + ["std::move"; "std::forward"; "std::min"; "std::max"; "std::swap"; "google::CheckNotNull"] + +let whitelisted_cpp_classes = + (* libstdc++ internal name of vector iterator *) + [ "std::__less" + ; "std::__wrap_iter" + ; (* libc++ internal name of vector iterator *) + "__gnu_cxx::__normal_iterator" ] + +type dynamic_dispatch_policy = [`None | `Interface | `Sound | `Lazy] (** Compile time configuration values *) @@ -267,70 +273,67 @@ let version_string = F.asprintf "%a" pp_version () Unix.time *) let initial_analysis_time = Unix.time () -let clang_exe_aliases = [ - (* this must be kept in sync with the clang-like symlinks in [wrappers_dir] (see below) *) - "c++"; "cc"; "clang"; "clang++"; "g++"; "gcc"; -] +let clang_exe_aliases = + [ (* this must be kept in sync with the clang-like symlinks in [wrappers_dir] (see below) *) + "c++" + ; "cc" + ; "clang" + ; "clang++" + ; "g++" + ; "gcc" ] let initial_command = (* Sys.executable_name tries to do clever things which we must avoid, use argv[0] instead *) let exe_basename = Filename.basename Sys.argv.(0) in let is_clang = List.mem ~equal:String.equal clang_exe_aliases in match CommandDoc.command_of_exe_name exe_basename with - | Some _ as command -> command - | None when is_clang exe_basename -> Some CLOpt.Clang - | None -> None + | Some _ as command + -> command + | None when is_clang exe_basename + -> Some CLOpt.Clang + | None + -> None let bin_dir = (* Resolve symlinks to get to the real executable, which is located in [bin_dir]. *) Filename.dirname (Utils.realpath Sys.executable_name) -let lib_dir = - bin_dir ^/ Filename.parent_dir_name ^/ "lib" +let lib_dir = bin_dir ^/ Filename.parent_dir_name ^/ "lib" -let etc_dir = - bin_dir ^/ Filename.parent_dir_name ^/ "etc" +let etc_dir = bin_dir ^/ Filename.parent_dir_name ^/ "etc" (** Path to lib/specs to retrieve the default models *) -let models_dir = - lib_dir ^/ specs_dir_name +let models_dir = lib_dir ^/ specs_dir_name -let models_jar = - lib_dir ^/ "java" ^/ "models.jar" +let models_jar = lib_dir ^/ "java" ^/ "models.jar" let models_src_dir = let root = Unix.getcwd () in let dir = bin_dir ^/ Filename.parent_dir_name ^/ "models" in - Utils.filename_to_absolute ~root dir (* Normalize the path *) + Utils.filename_to_absolute ~root dir + +(* Normalize the path *) let relative_cpp_extra_include_dir = "cpp" ^/ "include" let cpp_extra_include_dir = models_src_dir ^/ relative_cpp_extra_include_dir -let relative_cpp_models_dir = - relative_cpp_extra_include_dir ^/ "infer_model" +let relative_cpp_models_dir = relative_cpp_extra_include_dir ^/ "infer_model" let linters_def_dir = lib_dir ^/ "linter_rules" let linters_def_default_file = linters_def_dir ^/ "linters.al" -let wrappers_dir = - lib_dir ^/ "wrappers" +let wrappers_dir = lib_dir ^/ "wrappers" let ncpu = try - Utils.with_process_in - "getconf _NPROCESSORS_ONLN 2>/dev/null" - (fun chan -> Scanf.bscanf (Scanf.Scanning.from_channel chan) "%d" (fun n -> n)) + Utils.with_process_in "getconf _NPROCESSORS_ONLN 2>/dev/null" (fun chan -> + Scanf.bscanf (Scanf.Scanning.from_channel chan) "%d" (fun n -> n) ) |> fst - with _ -> - 1 - -let os_type = match Sys.os_type with - | "Win32" -> Win32 - | "Cygwin" -> Cygwin - | _ -> Unix + with _ -> 1 +let os_type = match Sys.os_type with "Win32" -> Win32 | "Cygwin" -> Cygwin | _ -> Unix (** Resolve relative paths passed as command line options, i.e., with respect to the working directory of the initial invocation of infer. *) @@ -340,7 +343,7 @@ let infer_inside_maven_env_var = "INFER_INSIDE_MAVEN" let maven = CLOpt.is_env_var_set infer_inside_maven_env_var -let env_inside_maven = `Extend [infer_inside_maven_env_var, "1"] +let env_inside_maven = `Extend [(infer_inside_maven_env_var, "1")] let infer_is_javac = maven @@ -348,20 +351,26 @@ let startup_action = let open CLOpt in if infer_is_javac then Javac else if !Sys.interactive then NoParse - else match initial_command with - | Some Clang -> - NoParse - | None | Some (Analyze | Capture | Compile | Report | ReportDiff | Run) -> - InferCommand + else + match initial_command with + | Some Clang + -> NoParse + | None | Some (Analyze | Capture | Compile | Report | ReportDiff | Run) + -> InferCommand let exe_usage = - let exe_command_name = match initial_command with - | Some CLOpt.Clang -> None (* users cannot see this *) - | Some command -> Some (CommandDoc.name_of_command command) - | None -> None in + let exe_command_name = + match initial_command with + | Some CLOpt.Clang + -> None (* users cannot see this *) + | Some command + -> Some (CommandDoc.name_of_command command) + | None + -> None + in Printf.sprintf "%s\nUsage: infer %s [options]\nSee `infer%s --help` for more information." version_string (Option.value ~default:"command" exe_command_name) - (Option.value_map ~default:"" ~f:((^) " ") exe_command_name) + (Option.value_map ~default:"" ~f:(( ^ ) " ") exe_command_name) (** Command Line options *) @@ -394,103 +403,104 @@ let exe_usage = let anon_args = CLOpt.mk_anon () let () = - let on_unknown_arg_from_command (cmd: CLOpt.command) = match cmd with - | Clang -> assert false (* filtered out *) - | Report -> `Add - | Analyze | Capture | Compile | ReportDiff | Run -> `Reject in + let on_unknown_arg_from_command (cmd: CLOpt.command) = + match cmd with + | Clang + -> assert false (* filtered out *) + | Report + -> `Add + | Analyze | Capture | Compile | ReportDiff | Run + -> `Reject + in (* make sure we generate doc for all the commands we know about *) - List.filter ~f:(Fn.non (CLOpt.(equal_command Clang))) CLOpt.all_commands + List.filter ~f:(Fn.non CLOpt.(equal_command Clang)) CLOpt.all_commands |> List.iter ~f:(fun cmd -> - let { CommandDoc.name; command_doc } = CommandDoc.data_of_command cmd in - let on_unknown_arg = on_unknown_arg_from_command cmd in - let deprecated_long = if CLOpt.(equal_command ReportDiff) cmd then Some "diff" else None in - CLOpt.mk_subcommand cmd ~name ?deprecated_long ~on_unknown_arg (Some command_doc)) + let {CommandDoc.name; command_doc} = CommandDoc.data_of_command cmd in + let on_unknown_arg = on_unknown_arg_from_command cmd in + let deprecated_long = + if CLOpt.(equal_command ReportDiff) cmd then Some "diff" else None + in + CLOpt.mk_subcommand cmd ~name ?deprecated_long ~on_unknown_arg (Some command_doc) ) -let () = - CLOpt.mk_subcommand CLOpt.Clang ~name:"clang" ~on_unknown_arg:`Skip None +let () = CLOpt.mk_subcommand CLOpt.Clang ~name:"clang" ~on_unknown_arg:`Skip None let abs_struct = - CLOpt.mk_int ~deprecated:["absstruct"] ~long:"abs-struct" ~default:1 - ~meta:"int" -{|Specify abstraction level for fields of structs: + CLOpt.mk_int ~deprecated:["absstruct"] ~long:"abs-struct" ~default:1 ~meta:"int" + {|Specify abstraction level for fields of structs: - 0 = no - 1 = forget some fields during matching (and so lseg abstraction) |} and abs_val = - CLOpt.mk_int ~deprecated:["absval"] ~long:"abs-val" ~default:2 - ~meta:"int" -{|Specify abstraction level for expressions: + CLOpt.mk_int ~deprecated:["absval"] ~long:"abs-val" ~default:2 ~meta:"int" + {|Specify abstraction level for expressions: - 0 = no abstraction - 1 = evaluate all expressions abstractly - 2 = 1 + abstract constant integer values during join |} - and allow_leak = - CLOpt.mk_bool ~deprecated:["leak"] ~long:"allow-leak" - "Forget leaked memory during abstraction" + CLOpt.mk_bool ~deprecated:["leak"] ~long:"allow-leak" "Forget leaked memory during abstraction" and allow_specs_cleanup = CLOpt.mk_bool ~deprecated:["allow_specs_cleanup"] ~long:"allow-specs-cleanup" ~default:true "Allow to remove existing specs before running analysis when it's not incremental" -and ( - analysis_blacklist_files_containing_options, - analysis_path_regex_blacklist_options, - analysis_path_regex_whitelist_options, - analysis_suppress_errors_options) = - let mk_filtering_options ~suffix ?(deprecated_suffix=[]) ~help ~meta = +and ( analysis_blacklist_files_containing_options + , analysis_path_regex_blacklist_options + , analysis_path_regex_whitelist_options + , analysis_suppress_errors_options ) = + let mk_filtering_options ~suffix ?(deprecated_suffix= []) ~help ~meta = let mk_option analyzer_name = let long = Printf.sprintf "%s-%s" analyzer_name suffix in - let deprecated = - List.map ~f:(Printf.sprintf "%s_%s" analyzer_name) deprecated_suffix in + let deprecated = List.map ~f:(Printf.sprintf "%s_%s" analyzer_name) deprecated_suffix in (* empty doc to hide the options from --help since there are many redundant ones *) - CLOpt.mk_string_list ~deprecated ~long ~meta "" in - ignore ( - let long = "-" ^ suffix in - CLOpt.mk_string_list ~long ~meta ~f:(fun _ -> raise (Arg.Bad "invalid option")) - ~in_help:CLOpt.[Report, manual_generic; Run, manual_generic] - help - ); - List.map ~f:(fun (name, analyzer) -> (analyzer, mk_option name)) string_to_analyzer in - ( - mk_filtering_options - ~suffix:"blacklist-files-containing" + CLOpt.mk_string_list ~deprecated ~long ~meta "" + in + ignore + (let long = "-" ^ suffix in + CLOpt.mk_string_list ~long ~meta + ~f:(fun _ -> raise (Arg.Bad "invalid option")) + ~in_help:CLOpt.([(Report, manual_generic); (Run, manual_generic)]) + help) ; + List.map ~f:(fun (name, analyzer) -> (analyzer, mk_option name)) string_to_analyzer + in + ( mk_filtering_options ~suffix:"blacklist-files-containing" ~deprecated_suffix:["blacklist_files_containing"] - ~help:"blacklist files containing the specified string for the given analyzer (see \ - $(b,--analyzer) for valid values)" - ~meta:"string", - mk_filtering_options - ~suffix:"blacklist-path-regex" - ~deprecated_suffix:["blacklist"] - ~help:"blacklist the analysis of files whose relative path matches the specified OCaml-style \ - regex (to whitelist: $(b,---whitelist-path-regex))" - ~meta:"path regex", - mk_filtering_options - ~suffix:"whitelist-path-regex" - ~deprecated_suffix:["whitelist"] - ~help:"" - ~meta:"path regex", - mk_filtering_options - ~suffix:"suppress-errors" - ~deprecated_suffix:["suppress_errors"] - ~help:"do not report a type of errors" - ~meta:"error name") + ~help: + "blacklist files containing the specified string for the given analyzer (see $(b,--analyzer) for valid values)" + ~meta:"string" + , mk_filtering_options ~suffix:"blacklist-path-regex" ~deprecated_suffix:["blacklist"] + ~help: + "blacklist the analysis of files whose relative path matches the specified OCaml-style regex (to whitelist: $(b,---whitelist-path-regex))" + ~meta:"path regex" + , mk_filtering_options ~suffix:"whitelist-path-regex" ~deprecated_suffix:["whitelist"] ~help:"" + ~meta:"path regex" + , mk_filtering_options ~suffix:"suppress-errors" ~deprecated_suffix:["suppress_errors"] + ~help:"do not report a type of errors" ~meta:"error name" ) and analysis_stops = CLOpt.mk_bool ~deprecated:["analysis_stops"] ~long:"analysis-stops" "Issue a warning when the analysis stops" and analyzer = - let () = match BiAbduction with - (* NOTE: if compilation fails here, it means you have added a new analyzer without updating the + let () = + match BiAbduction + with + | (* NOTE: if compilation fails here, it means you have added a new analyzer without updating the documentation of this option *) - | BiAbduction | CaptureOnly | CompileOnly | Eradicate | Checkers | Crashcontext - | Linters -> () in + BiAbduction + | CaptureOnly + | CompileOnly + | Eradicate + | Checkers + | Crashcontext + | Linters + -> () + in CLOpt.mk_symbol_opt ~deprecated:["analyzer"] ~long:"analyzer" ~short:'a' - ~in_help:CLOpt.[Analyze, manual_generic; Run, manual_generic] -{|Specify which analyzer to run (only one at a time is supported): + ~in_help:CLOpt.([(Analyze, manual_generic); (Run, manual_generic)]) + {|Specify which analyzer to run (only one at a time is supported): - $(b,infer): run the bi-abduction based checker, in particular to check for memory errors (activated by default) - $(b,checkers), $(b,eradicate): run the specified analysis - $(b,capture): similar to specifying the $(b,capture) subcommand (DEPRECATED) @@ -498,14 +508,17 @@ and analyzer = - $(b,crashcontext): experimental (see $(b,--crashcontext)) - $(b,linters): run linters based on the ast only (Objective-C and Objective-C++ only, activated by default)|} ~f:(function - | CaptureOnly | CompileOnly as x -> - let analyzer_str = List.find_map_exn string_to_analyzer - ~f:(fun (s, y) -> if equal_analyzer x y then Some s else None) in + | CaptureOnly | CompileOnly as x + -> let analyzer_str = + List.find_map_exn string_to_analyzer ~f:(fun (s, y) -> + if equal_analyzer x y then Some s else None ) + in CLOpt.warnf - "WARNING: The analyzer '%s' is deprecated, use the '%s' subcommand instead:@\n\ - @\n infer %s ..." analyzer_str analyzer_str analyzer_str; + "WARNING: The analyzer '%s' is deprecated, use the '%s' subcommand instead:@\n@\n infer %s ..." + analyzer_str analyzer_str analyzer_str ; x - | _ as x -> x) + | _ as x + -> x) ~symbols:string_to_analyzer and android_harness = @@ -516,254 +529,234 @@ and angelic_execution = CLOpt.mk_bool ~deprecated:["angelic_execution"] ~long:"angelic-execution" ~default:true "Angelic execution, where the analysis ignores errors caused by unknown procedure calls" -and (annotation_reachability, - biabduction, - bufferoverrun, - crashcontext, - default_checkers, - eradicate, - fragment_retains_view, - immutable_cast, - printf_args, - quandary, - repeated_calls, - siof, - threadsafety, - suggest_nullable) = +and ( annotation_reachability + , biabduction + , bufferoverrun + , crashcontext + , default_checkers + , eradicate + , fragment_retains_view + , immutable_cast + , printf_args + , quandary + , repeated_calls + , siof + , threadsafety + , suggest_nullable ) = let annotation_reachability = - CLOpt.mk_bool ~long:"annotation-reachability" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"annotation-reachability" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~default:true - "the annotation reachability checker. Given a pair of source and sink annotation, e.g. \ - @PerformanceCritical and @Expensive, this checker will warn whenever some method annotated \ - with @PerformanceCritical calls, directly or indirectly, another method annotated with \ - @Expensive" - + "the annotation reachability checker. Given a pair of source and sink annotation, e.g. @PerformanceCritical and @Expensive, this checker will warn whenever some method annotated with @PerformanceCritical calls, directly or indirectly, another method annotated with @Expensive" and biabduction = - CLOpt.mk_bool ~long:"biabduction" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"biabduction" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "the separation logic based bi-abduction analysis using the checkers framework" - and bufferoverrun = - CLOpt.mk_bool ~long:"bufferoverrun" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"bufferoverrun" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "the buffer overrun analysis" - and crashcontext = - CLOpt.mk_bool ~long:"crashcontext" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"crashcontext" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "the crashcontext checker for Java stack trace context reconstruction" - and eradicate = - CLOpt.mk_bool ~long:"eradicate" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"eradicate" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "the eradicate @Nullable checker for Java annotations" - and fragment_retains_view = - CLOpt.mk_bool ~long:"fragment-retains-view" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"fragment-retains-view" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~default:true "detects when Android fragments are not explicitly nullified before becoming unreabable" - and immutable_cast = - CLOpt.mk_bool ~long:"immutable-cast" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"immutable-cast" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~default:true - "the detection of object cast from immutable type to mutable type. \ - For instance, it will detect cast from ImmutableList to List, ImmutableMap to Map, \ - and ImmutableSet to Set." - + "the detection of object cast from immutable type to mutable type. For instance, it will detect cast from ImmutableList to List, ImmutableMap to Map, and ImmutableSet to Set." and printf_args = - CLOpt.mk_bool ~long:"printf-args" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"printf-args" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~default:true - "the detection of mismatch between the Java printf format strings and the argument types \ - For, example, this checker will warn about the type error in \ - `printf(\"Hello %d\", \"world\")`" - + "the detection of mismatch between the Java printf format strings and the argument types For, example, this checker will warn about the type error in `printf(\"Hello %d\", \"world\")`" and repeated_calls = - CLOpt.mk_bool ~long:"repeated-calls" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"repeated-calls" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "check for repeated calls" - and quandary = - CLOpt.mk_bool ~long:"quandary" ~in_help:CLOpt.[Analyze, manual_generic] ~default:true - "the quandary taint analysis" - + CLOpt.mk_bool ~long:"quandary" + ~in_help:CLOpt.([(Analyze, manual_generic)]) + ~default:true "the quandary taint analysis" and siof = - CLOpt.mk_bool ~long:"siof" ~in_help:CLOpt.[Analyze, manual_generic] ~default:true - "the Static Initialization Order Fiasco analysis (C++ only)" - + CLOpt.mk_bool ~long:"siof" + ~in_help:CLOpt.([(Analyze, manual_generic)]) + ~default:true "the Static Initialization Order Fiasco analysis (C++ only)" and threadsafety = - CLOpt.mk_bool ~long:"threadsafety" ~in_help:CLOpt.[Analyze, manual_generic] ~default:true - "the thread safety analysis" - + CLOpt.mk_bool ~long:"threadsafety" + ~in_help:CLOpt.([(Analyze, manual_generic)]) + ~default:true "the thread safety analysis" and suggest_nullable = CLOpt.mk_bool ~long:"suggest-nullable" ~default:false - "Nullable annotation sugesstions analysis (experimental)" in - + "Nullable annotation sugesstions analysis (experimental)" + in (* IMPORTANT: keep in sync with the checkers that have ~default:true above *) let default_checkers = - CLOpt.mk_bool_group ~long:"default-checkers" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool_group ~long:"default-checkers" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~default:true - "Default checkers: $(b,--annotation-reachability), $(b,--fragments-retains-view), \ - $(b,--immutable-cast), $(b,--printf-args), $(b,--quandary), $(b,--siof), $(b,--threadsafety)" - [annotation_reachability; fragment_retains_view; immutable_cast; printf_args; quandary; - siof; threadsafety] - [] in - - (annotation_reachability, - biabduction, - bufferoverrun, - crashcontext, - default_checkers, - eradicate, - fragment_retains_view, - immutable_cast, - printf_args, - quandary, - repeated_calls, - siof, - threadsafety, - suggest_nullable) + "Default checkers: $(b,--annotation-reachability), $(b,--fragments-retains-view), $(b,--immutable-cast), $(b,--printf-args), $(b,--quandary), $(b,--siof), $(b,--threadsafety)" + [ annotation_reachability + ; fragment_retains_view + ; immutable_cast + ; printf_args + ; quandary + ; siof + ; threadsafety ] [] + in + ( annotation_reachability + , biabduction + , bufferoverrun + , crashcontext + , default_checkers + , eradicate + , fragment_retains_view + , immutable_cast + , printf_args + , quandary + , repeated_calls + , siof + , threadsafety + , suggest_nullable ) and annotation_reachability_custom_pairs = CLOpt.mk_json ~long:"annotation-reachability-custom-pairs" - ~in_help:CLOpt.[Analyze, manual_java] -{|Specify custom sources/sink for the annotation reachability checker + ~in_help:CLOpt.([(Analyze, manual_java)]) + {|Specify custom sources/sink for the annotation reachability checker Example format: for custom annotations com.my.annotation.{Source1,Source2,Sink1} { "sources" : ["Source1", "Source2"], "sink" : "Sink1" }|} and array_level = - CLOpt.mk_int ~deprecated:["arraylevel"] ~long:"array-level" ~default:0 - ~meta:"int" -{|Level of treating the array indexing and pointer arithmetic: + CLOpt.mk_int ~deprecated:["arraylevel"] ~long:"array-level" ~default:0 ~meta:"int" + {|Level of treating the array indexing and pointer arithmetic: - 0 = treats both features soundly - 1 = assumes that the size of every array is infinite - 2 = assumes that all heap dereferences via array indexing and pointer arithmetic are correct |} + and ast_file = - CLOpt.mk_path_opt ~deprecated:["ast"] ~long:"ast-file" - ~meta:"file" "AST file for the translation" + CLOpt.mk_path_opt ~deprecated:["ast"] ~long:"ast-file" ~meta:"file" + "AST file for the translation" and blacklist = - CLOpt.mk_string_opt ~deprecated:["-blacklist-regex";"-blacklist"] ~long:"buck-blacklist" - ~in_help:CLOpt.[Run, manual_buck_flavors; Capture, manual_buck_flavors] + CLOpt.mk_string_opt ~deprecated:["-blacklist-regex"; "-blacklist"] ~long:"buck-blacklist" + ~in_help:CLOpt.([(Run, manual_buck_flavors); (Capture, manual_buck_flavors)]) ~meta:"regex" "Skip analysis of files matched by the specified regular expression" and bootclasspath = CLOpt.mk_string_opt ~long:"bootclasspath" - ~in_help:CLOpt.[Capture, manual_java] + ~in_help:CLOpt.([(Capture, manual_java)]) "Specify the Java bootclasspath" (** Automatically set when running from within Buck *) -and buck = - CLOpt.mk_bool ~long:"buck" - "" +and buck = CLOpt.mk_bool ~long:"buck" "" and buck_build_args = CLOpt.mk_string_list ~long:"Xbuck" - ~in_help:CLOpt.[Capture, manual_buck_flavors] + ~in_help:CLOpt.([(Capture, manual_buck_flavors)]) "Pass values as command-line arguments to invocations of $(i,`buck build`)" and buck_compilation_database = CLOpt.mk_symbol_opt ~long:"buck-compilation-database" ~deprecated:["-use-compilation-database"] - ~in_help:CLOpt.[Capture, manual_buck_compilation_db] + ~in_help:CLOpt.([(Capture, manual_buck_compilation_db)]) "Buck integration using the compilation database, with or without dependencies." ~symbols:[("deps", `Deps); ("no-deps", `NoDeps)] and buck_out = CLOpt.mk_path_opt ~long:"buck-out" - ~in_help:CLOpt.[Capture, manual_buck_java] ~meta:"dir" "Specify the root directory of buck-out" + ~in_help:CLOpt.([(Capture, manual_buck_java)]) + ~meta:"dir" "Specify the root directory of buck-out" and bugs_csv = CLOpt.mk_path_opt ~deprecated:["bugs"] ~long:"issues-csv" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~meta:"file" "Write a list of issues in CSV format to a file" and bugs_json = CLOpt.mk_path_opt ~deprecated:["bugs_json"] ~long:"issues-json" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~meta:"file" "Write a list of issues in JSON format to a file" and bugs_tests = CLOpt.mk_path_opt ~long:"issues-tests" - ~in_help:CLOpt.[Report, manual_generic] - ~meta:"file" - "Write a list of issues in a format suitable for tests to a file" + ~in_help:CLOpt.([(Report, manual_generic)]) + ~meta:"file" "Write a list of issues in a format suitable for tests to a file" and bugs_txt = CLOpt.mk_path_opt ~deprecated:["bugs_txt"] ~long:"issues-txt" - ~in_help:CLOpt.[Report, manual_generic] - ~meta:"file" - "Write a list of issues in TXT format to a file" + ~in_help:CLOpt.([(Report, manual_generic)]) + ~meta:"file" "Write a list of issues in TXT format to a file" and calls_csv = CLOpt.mk_path_opt ~deprecated:["calls"] ~long:"calls-csv" - ~in_help:CLOpt.[Report, manual_generic] - ~meta:"file" - "Write individual calls in CSV format to a file" + ~in_help:CLOpt.([(Report, manual_generic)]) + ~meta:"file" "Write individual calls in CSV format to a file" and changed_files_index = - CLOpt.mk_path_opt ~long:"changed-files-index" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_path_opt ~long:"changed-files-index" + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~meta:"file" - "Specify the file containing the list of source files from which reactive analysis should \ - start. Source files should be specified relative to project root or be absolute" + "Specify the file containing the list of source files from which reactive analysis should start. Source files should be specified relative to project root or be absolute" and clang_biniou_file = CLOpt.mk_path_opt ~long:"clang-biniou-file" - ~in_help:CLOpt.[Capture, manual_clang] ~meta:"file" - "Specify a file containing the AST of the program, in biniou format" + ~in_help:CLOpt.([(Capture, manual_clang)]) + ~meta:"file" "Specify a file containing the AST of the program, in biniou format" and clang_compilation_dbs = ref [] and clang_frontend_action = CLOpt.mk_symbol_opt ~long:"clang-frontend-action" - ~in_help:CLOpt.[Capture, manual_clang; Run, manual_clang] + ~in_help:CLOpt.([(Capture, manual_clang); (Run, manual_clang)]) "Specify whether the clang frontend should capture or lint or both." ~symbols:clang_frontend_action_symbols and clang_include_to_override_regex = CLOpt.mk_string_opt ~long:"clang-include-to-override-regex" - ~deprecated:["-clang-include-to-override"] - ~meta:"dir OCaml regex" - "Use this option in the uncommon case where the normal compilation process overrides the \ - location of internal compiler headers. This option should specify regular expression with \ - the path to those headers so that infer can use its own clang internal headers instead." + ~deprecated:["-clang-include-to-override"] ~meta:"dir OCaml regex" + "Use this option in the uncommon case where the normal compilation process overrides the location of internal compiler headers. This option should specify regular expression with the path to those headers so that infer can use its own clang internal headers instead." and clang_ignore_regex = - CLOpt.mk_string_opt ~long:"clang-ignore-regex" - ~meta:"dir OCaml regex" - "The files in this regex will be ignored in the compilation process and \ - an empty file will be passed to clang instead. This is to be used with the buck flavour \ - infer-capture-all to work around missing generated files." + CLOpt.mk_string_opt ~long:"clang-ignore-regex" ~meta:"dir OCaml regex" + "The files in this regex will be ignored in the compilation process and an empty file will be passed to clang instead. This is to be used with the buck flavour infer-capture-all to work around missing generated files." -and classpath = - CLOpt.mk_string_opt ~long:"classpath" - "Specify the Java classpath" +and classpath = CLOpt.mk_string_opt ~long:"classpath" "Specify the Java classpath" and cluster = - CLOpt.mk_path_opt ~deprecated:["cluster"] ~long:"cluster" - ~meta:"file" "Specify a .cluster file to be analyzed" + CLOpt.mk_path_opt ~deprecated:["cluster"] ~long:"cluster" ~meta:"file" + "Specify a .cluster file to be analyzed" and compilation_database = - CLOpt.mk_path_list ~long:"compilation-database" - ~deprecated:["-clang-compilation-db-files"] - ~in_help:CLOpt.[Capture, manual_clang] + CLOpt.mk_path_list ~long:"compilation-database" ~deprecated:["-clang-compilation-db-files"] + ~in_help:CLOpt.([(Capture, manual_clang)]) "File that contain compilation commands (can be specified multiple times)" and compilation_database_escaped = CLOpt.mk_path_list ~long:"compilation-database-escaped" ~deprecated:["-clang-compilation-db-files-escaped"] - ~in_help:CLOpt.[Capture, manual_clang] - "File that contain compilation commands where all entries are escaped for the shell, eg coming \ - from Xcode (can be specified multiple times)" + ~in_help:CLOpt.([(Capture, manual_clang)]) + "File that contain compilation commands where all entries are escaped for the shell, eg coming from Xcode (can be specified multiple times)" and compute_analytics = - CLOpt.mk_bool ~long:"compute-analytics" - ~default:false - ~in_help:CLOpt.[Capture, manual_clang; Run, manual_clang] - "Emit analytics as info-level issues, like component kit line count and \ - component kit file cyclomatic complexity" + CLOpt.mk_bool ~long:"compute-analytics" ~default:false + ~in_help:CLOpt.([(Capture, manual_clang); (Run, manual_clang)]) + "Emit analytics as info-level issues, like component kit line count and component kit file cyclomatic complexity" (** Continue the capture for reactive mode: If a procedure was changed beforehand, keep the changed marking. *) and continue = CLOpt.mk_bool ~deprecated:["continue"] ~long:"continue" - ~in_help:CLOpt.[Analyze, manual_generic] - "Continue the capture for the reactive analysis, increasing the changed files/procedures. (If \ - a procedure was changed beforehand, keep the changed marking.)" + ~in_help:CLOpt.([(Analyze, manual_generic)]) + "Continue the capture for the reactive analysis, increasing the changed files/procedures. (If a procedure was changed beforehand, keep the changed marking.)" and copy_propagation = CLOpt.mk_bool ~deprecated:["copy-propagation"] ~long:"copy-propagation" @@ -771,346 +764,287 @@ and copy_propagation = and cxx, cxx_infer_headers = let cxx_infer_headers = - CLOpt.mk_bool ~long:"cxx-infer-headers" - ~default:true - ~in_help:CLOpt.[Capture, manual_clang] - "Include C++ header models during compilation, set by $(b,--cxx). Infer swaps some C++ \ - headers for its own in order to get a better model of, eg, the standard library. This \ - can sometimes cause compilation failures." in - let cxx = CLOpt.mk_bool_group ~long:"cxx" - ~default:true - ~in_help:CLOpt.[Capture, manual_clang] - "Analyze C++ methods" - [cxx_infer_headers] [] in - cxx, cxx_infer_headers - - -and ( - bo_debug, - developer_mode, - debug, - debug_exceptions, - debug_level_analysis, - debug_level_capture, - debug_level_linters, - default_linters, - failures_allowed, - filtering, - frontend_tests, - linters_developer_mode, - only_cheap_debug, - print_buckets, - print_logs, - print_types, - reports_include_ml_loc, - stats, - trace_error, - write_html, - write_html_whitelist_regex, - write_dotty -) = + CLOpt.mk_bool ~long:"cxx-infer-headers" ~default:true + ~in_help:CLOpt.([(Capture, manual_clang)]) + "Include C++ header models during compilation, set by $(b,--cxx). Infer swaps some C++ headers for its own in order to get a better model of, eg, the standard library. This can sometimes cause compilation failures." + in + let cxx = + CLOpt.mk_bool_group ~long:"cxx" ~default:true + ~in_help:CLOpt.([(Capture, manual_clang)]) + "Analyze C++ methods" [cxx_infer_headers] [] + in + (cxx, cxx_infer_headers) + +and ( bo_debug + , developer_mode + , debug + , debug_exceptions + , debug_level_analysis + , debug_level_capture + , debug_level_linters + , default_linters + , failures_allowed + , filtering + , frontend_tests + , linters_developer_mode + , only_cheap_debug + , print_buckets + , print_logs + , print_types + , reports_include_ml_loc + , stats + , trace_error + , write_html + , write_html_whitelist_regex + , write_dotty ) = let all_generic_manuals = - List.filter_map CLOpt.all_commands - ~f:(fun cmd -> - if CLOpt.(equal_command cmd Clang) then None - else Some (cmd, manual_generic)) in - + List.filter_map CLOpt.all_commands ~f:(fun cmd -> + if CLOpt.(equal_command cmd Clang) then None else Some (cmd, manual_generic) ) + in let bo_debug = CLOpt.mk_int ~default:0 ~long:"bo-debug" - ~in_help:CLOpt.[Analyze, manual_buffer_overrun] "Debug level for buffer-overrun checker (0-4)" - + ~in_help:CLOpt.([(Analyze, manual_buffer_overrun)]) + "Debug level for buffer-overrun checker (0-4)" and debug_level_analysis = - CLOpt.mk_int ~long:"debug-level-analysis" ~default:0 - ~in_help:all_generic_manuals + CLOpt.mk_int ~long:"debug-level-analysis" ~default:0 ~in_help:all_generic_manuals "Debug level for the analysis. See $(b,--debug-level) for accepted values." - and debug_level_capture = - CLOpt.mk_int ~long:"debug-level-capture" ~default:0 - ~in_help:all_generic_manuals + CLOpt.mk_int ~long:"debug-level-capture" ~default:0 ~in_help:all_generic_manuals "Debug level for the capture. See $(b,--debug-level) for accepted values." - and debug_level_linters = CLOpt.mk_int ~long:"debug-level-linters" ~default:0 - ~in_help:(CLOpt.(Capture, manual_clang_linters)::all_generic_manuals) + ~in_help:(CLOpt.((Capture, manual_clang_linters)) :: all_generic_manuals) "Debug level for the linters. See $(b,--debug-level) for accepted values." - and developer_mode = CLOpt.mk_bool ~long:"developer-mode" - ~default:(Option.value_map ~default:false ~f:(CLOpt.(equal_command Report)) initial_command) + ~default:(Option.value_map ~default:false ~f:CLOpt.(equal_command Report) initial_command) "Show internal exceptions" - and failures_allowed = CLOpt.mk_bool ~deprecated_no:["-no_failures_allowed"] ~long:"failures-allowed" ~default:true "Fail if at least one of the translations fails (clang only)" - and filtering = CLOpt.mk_bool ~deprecated_no:["nf"] ~long:"filtering" ~short:'f' ~default:true - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) "Do not show the results from experimental and blacklisted checks" - and only_cheap_debug = - CLOpt.mk_bool ~long:"only-cheap-debug" - ~default:true - "Disable expensive debugging output" - + CLOpt.mk_bool ~long:"only-cheap-debug" ~default:true "Disable expensive debugging output" and print_buckets = CLOpt.mk_bool ~long:"print-buckets" "Show the internal bucket of Infer reports in their textual description" - and print_types = - CLOpt.mk_bool ~long:"print-types" ~default:false - "Print types in symbolic heaps" - + CLOpt.mk_bool ~long:"print-types" ~default:false "Print types in symbolic heaps" and reports_include_ml_loc = CLOpt.mk_bool ~deprecated:["with_infer_src_loc"] ~long:"reports-include-ml-loc" "Include the location in the Infer source code from where reports are generated" - and trace_error = - CLOpt.mk_bool ~long:"trace-error" - "Detailed tracing information during error explanation" - + CLOpt.mk_bool ~long:"trace-error" "Detailed tracing information during error explanation" and write_html = - CLOpt.mk_bool ~long:"write-html" - "Produce hmtl debug output in the results directory" - + CLOpt.mk_bool ~long:"write-html" "Produce hmtl debug output in the results directory" and write_html_whitelist_regex = CLOpt.mk_string_list ~long:"write-html-whitelist-regex" "whitelist files that will have its html debug output printed" - and write_dotty = - CLOpt.mk_bool ~long:"write-dotty" - "Produce dotty files for specs in the results directory" + CLOpt.mk_bool ~long:"write-dotty" "Produce dotty files for specs in the results directory" in - let set_debug_level level = - bo_debug := level; - debug_level_analysis := level; - debug_level_capture := level; - debug_level_linters := level in - + bo_debug := level ; + debug_level_analysis := level ; + debug_level_capture := level ; + debug_level_linters := level + in let debug = - CLOpt.mk_bool_group ~deprecated:["debug"] ~long:"debug" ~short:'g' - ~in_help:all_generic_manuals - "Debug mode (also sets $(b,--debug-level 2), $(b,--developer-mode), $(b,--no-filtering), \ - $(b,--print-buckets), $(b,--print-types), $(b,--reports-include-ml-loc), \ - $(b,--no-only-cheap-debug), $(b,--trace-error), $(b,--write-dotty), $(b,--write-html))" - ~f:(fun debug -> if debug then set_debug_level 2 else set_debug_level 0; debug) - [developer_mode; print_buckets; print_types; reports_include_ml_loc; trace_error; write_html; - write_dotty] - [filtering; only_cheap_debug] - + CLOpt.mk_bool_group ~deprecated:["debug"] ~long:"debug" ~short:'g' ~in_help:all_generic_manuals + "Debug mode (also sets $(b,--debug-level 2), $(b,--developer-mode), $(b,--no-filtering), $(b,--print-buckets), $(b,--print-types), $(b,--reports-include-ml-loc), $(b,--no-only-cheap-debug), $(b,--trace-error), $(b,--write-dotty), $(b,--write-html))" + ~f:(fun debug -> + if debug then set_debug_level 2 else set_debug_level 0 ; + debug) + [ developer_mode + ; print_buckets + ; print_types + ; reports_include_ml_loc + ; trace_error + ; write_html + ; write_dotty ] [filtering; only_cheap_debug] and _ : int option ref = - CLOpt.mk_int_opt ~long:"debug-level" - ~in_help:all_generic_manuals ~meta:"level" - ~f:(fun level -> set_debug_level level; level) -{|Debug level (sets $(b,--bo-debug) $(i,level), $(b,--debug-level-analysis) $(i,level), $(b,--debug-level-capture) $(i,level), $(b,--debug-level-linters) $(i,level)): + CLOpt.mk_int_opt ~long:"debug-level" ~in_help:all_generic_manuals ~meta:"level" + ~f:(fun level -> set_debug_level level ; level) + {|Debug level (sets $(b,--bo-debug) $(i,level), $(b,--debug-level-analysis) $(i,level), $(b,--debug-level-capture) $(i,level), $(b,--debug-level-linters) $(i,level)): - 0: only basic debugging enabled - 1: verbose debugging enabled - 2: very verbose debugging enabled|} - and debug_exceptions = CLOpt.mk_bool_group ~long:"debug-exceptions" - "Generate lightweight debugging information: just print the internal exceptions during \ - analysis (also sets $(b,--developer-mode), $(b,--no-filtering), $(b,--print-buckets), \ - $(b,--reports-include-ml-loc))" - [developer_mode; print_buckets; reports_include_ml_loc] - [filtering] - + "Generate lightweight debugging information: just print the internal exceptions during analysis (also sets $(b,--developer-mode), $(b,--no-filtering), $(b,--print-buckets), $(b,--reports-include-ml-loc))" + [developer_mode; print_buckets; reports_include_ml_loc] [filtering] and default_linters = - CLOpt.mk_bool ~long:"default-linters" ~in_help:CLOpt.[Capture, manual_clang_linters] - ~default:true - "Use the default linters for the analysis." - + CLOpt.mk_bool ~long:"default-linters" + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) + ~default:true "Use the default linters for the analysis." and frontend_tests = CLOpt.mk_bool_group ~long:"frontend-tests" - ~in_help:CLOpt.[Capture, manual_clang] - "Save filename.ext.test.dot with the cfg in dotty format for frontend tests (also sets \ - $(b,--print-types))" + ~in_help:CLOpt.([(Capture, manual_clang)]) + "Save filename.ext.test.dot with the cfg in dotty format for frontend tests (also sets $(b,--print-types))" [print_types] [] - and print_logs = CLOpt.mk_bool ~long:"print-logs" - ~in_help:CLOpt.[Analyze, manual_generic; Capture, manual_generic; Run, manual_generic; - Report, manual_generic] + ~in_help: + (CLOpt.( + [ (Analyze, manual_generic) + ; (Capture, manual_generic) + ; (Run, manual_generic) + ; (Report, manual_generic) ])) "Also log messages to stdout and stderr" - and stats = - CLOpt.mk_bool ~deprecated:["stats"] ~long:"stats" "Stats mode (debugging)" - ~f:(fun stats -> if stats then set_debug_level 1 else set_debug_level 0; stats) - + CLOpt.mk_bool ~deprecated:["stats"] ~long:"stats" "Stats mode (debugging)" ~f:(fun stats -> + if stats then set_debug_level 1 else set_debug_level 0 ; + stats ) in let linters_developer_mode = CLOpt.mk_bool_group ~long:"linters-developer-mode" - ~in_help:CLOpt.[Capture, manual_clang_linters] - "Debug mode for developing new linters. (Sets the analyzer to $(b,linters); also sets \ - $(b,--debug), $(b,--debug-level-linters 2), $(b,--developer-mode), and \ - unsets $(b,--allowed-failures) and $(b,--default-linters)." - ~f:(fun debug -> debug_level_linters := if debug then 2 else 0; debug) + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) + "Debug mode for developing new linters. (Sets the analyzer to $(b,linters); also sets $(b,--debug), $(b,--debug-level-linters 2), $(b,--developer-mode), and unsets $(b,--allowed-failures) and $(b,--default-linters)." + ~f:(fun debug -> + debug_level_linters := if debug then 2 else 0 ; + debug) [debug; developer_mode] [failures_allowed; default_linters] - - in ( - bo_debug, - developer_mode, - debug, - debug_exceptions, - debug_level_analysis, - debug_level_capture, - debug_level_linters, - default_linters, - failures_allowed, - filtering, - frontend_tests, - linters_developer_mode, - only_cheap_debug, - print_buckets, - print_logs, - print_types, - reports_include_ml_loc, - stats, - trace_error, - write_html, - write_html_whitelist_regex, - write_dotty - ) - + in + ( bo_debug + , developer_mode + , debug + , debug_exceptions + , debug_level_analysis + , debug_level_capture + , debug_level_linters + , default_linters + , failures_allowed + , filtering + , frontend_tests + , linters_developer_mode + , only_cheap_debug + , print_buckets + , print_logs + , print_types + , reports_include_ml_loc + , stats + , trace_error + , write_html + , write_html_whitelist_regex + , write_dotty ) and dependencies = CLOpt.mk_bool ~deprecated:["dependencies"] ~long:"dependencies" - ~in_help:CLOpt.[Capture, manual_java] - "Translate all the dependencies during the capture. The classes in the given jar file will be \ - translated. No sources needed." + ~in_help:CLOpt.([(Capture, manual_java)]) + "Translate all the dependencies during the capture. The classes in the given jar file will be translated. No sources needed." and differential_filter_files = - CLOpt.mk_string_opt - ~long:"differential-filter-files" ~in_help:CLOpt.[Report, manual_generic] - "Specify the file containing the list of source files for which a differential report \ - is desired. Source files should be specified relative to project root or be absolute" + CLOpt.mk_string_opt ~long:"differential-filter-files" + ~in_help:CLOpt.([(Report, manual_generic)]) + "Specify the file containing the list of source files for which a differential report is desired. Source files should be specified relative to project root or be absolute" and differential_filter_set = - CLOpt.mk_symbol_seq ~long:"differential-filter-set" ~eq:PVariant.(=) - "Specify which set of the differential results is filtered with the modified files provided \ - through the $(b,--differential-modified-files) argument. By default it is applied to all sets \ - ($(b,introduced), $(b,fixed), and $(b,preexisting))" + CLOpt.mk_symbol_seq ~long:"differential-filter-set" ~eq:PVariant.( = ) + "Specify which set of the differential results is filtered with the modified files provided through the $(b,--differential-modified-files) argument. By default it is applied to all sets ($(b,introduced), $(b,fixed), and $(b,preexisting))" ~symbols:[("introduced", `Introduced); ("fixed", `Fixed); ("preexisting", `Preexisting)] ~default:[`Introduced; `Fixed; `Preexisting] and disable_checks = CLOpt.mk_string_list ~deprecated:["disable_checks"] ~long:"disable-checks" ~meta:"error name" - ~in_help:CLOpt.[Report, manual_generic] - ~default: [ - "ANALYSIS_STOPS"; - "ARRAY_OUT_OF_BOUNDS_L1"; - "ARRAY_OUT_OF_BOUNDS_L2"; - "ARRAY_OUT_OF_BOUNDS_L3"; - "CLASS_CAST_EXCEPTION"; - "CONDITION_ALWAYS_FALSE"; - "CONDITION_ALWAYS_TRUE"; - "DANGLING_POINTER_DEREFERENCE"; - "DIVIDE_BY_ZERO"; - "NULL_TEST_AFTER_DEREFERENCE"; - "RETAIN_CYCLE"; - "RETURN_VALUE_IGNORED"; - "STACK_VARIABLE_ADDRESS_ESCAPE"; - "UNARY_MINUS_APPLIED_TO_UNSIGNED_EXPRESSION"; - "UNINITIALIZED_VALUE"; - ] - "Do not show reports coming from this type of errors. This option has lower precedence than \ - $(b,--no-filtering) and $(b,--enable-checks)" + ~in_help:CLOpt.([(Report, manual_generic)]) + ~default: + [ "ANALYSIS_STOPS" + ; "ARRAY_OUT_OF_BOUNDS_L1" + ; "ARRAY_OUT_OF_BOUNDS_L2" + ; "ARRAY_OUT_OF_BOUNDS_L3" + ; "CLASS_CAST_EXCEPTION" + ; "CONDITION_ALWAYS_FALSE" + ; "CONDITION_ALWAYS_TRUE" + ; "DANGLING_POINTER_DEREFERENCE" + ; "DIVIDE_BY_ZERO" + ; "NULL_TEST_AFTER_DEREFERENCE" + ; "RETAIN_CYCLE" + ; "RETURN_VALUE_IGNORED" + ; "STACK_VARIABLE_ADDRESS_ESCAPE" + ; "UNARY_MINUS_APPLIED_TO_UNSIGNED_EXPRESSION" + ; "UNINITIALIZED_VALUE" ] + "Do not show reports coming from this type of errors. This option has lower precedence than $(b,--no-filtering) and $(b,--enable-checks)" and dotty_cfg_libs = CLOpt.mk_bool ~deprecated:["dotty_no_cfg_libs"] ~long:"dotty-cfg-libs" ~default:true "Print the cfg of the code coming from the libraries" and dump_duplicate_symbols = - CLOpt.mk_bool ~long:"dump-duplicate-symbols" ~in_help:CLOpt.[Capture, manual_clang] + CLOpt.mk_bool ~long:"dump-duplicate-symbols" + ~in_help:CLOpt.([(Capture, manual_clang)]) "Dump all symbols with the same name that are defined in more than one file." and dynamic_dispatch = CLOpt.mk_symbol_opt ~long:"dynamic-dispatch" - "Specify treatment of dynamic dispatch in Java code: 'none' treats dynamic dispatch as a call \ - to unknown code, 'lazy' follows the JVM semantics and creates procedure descriptions during \ - symbolic execution using the type information found in the abstract state; 'sound' is \ - significantly more computationally expensive" + "Specify treatment of dynamic dispatch in Java code: 'none' treats dynamic dispatch as a call to unknown code, 'lazy' follows the JVM semantics and creates procedure descriptions during symbolic execution using the type information found in the abstract state; 'sound' is significantly more computationally expensive" ~symbols:[("none", `None); ("interface", `Interface); ("sound", `Sound); ("lazy", `Lazy)] and enable_checks = CLOpt.mk_string_list ~deprecated:["enable_checks"] ~long:"enable-checks" ~meta:"error name" - "Show reports coming from this type of errors. This option has higher precedence than \ - $(b,--disable-checks)" + "Show reports coming from this type of errors. This option has higher precedence than $(b,--disable-checks)" and eradicate_condition_redundant = - CLOpt.mk_bool ~long:"eradicate-condition-redundant" - "Condition redundant warnings" + CLOpt.mk_bool ~long:"eradicate-condition-redundant" "Condition redundant warnings" and eradicate_field_not_mutable = - CLOpt.mk_bool ~long:"eradicate-field-not-mutable" - "Field not mutable warnings" + CLOpt.mk_bool ~long:"eradicate-field-not-mutable" "Field not mutable warnings" and eradicate_field_over_annotated = - CLOpt.mk_bool ~long:"eradicate-field-over-annotated" - "Field over-annotated warnings" + CLOpt.mk_bool ~long:"eradicate-field-over-annotated" "Field over-annotated warnings" and eradicate_optional_present = - CLOpt.mk_bool ~long:"eradicate-optional-present" - "Check for @Present annotations" + CLOpt.mk_bool ~long:"eradicate-optional-present" "Check for @Present annotations" and eradicate_propagate_return_nullable = CLOpt.mk_bool ~long:"eradicate-propagate-return-nullable" "Propagation of nullable to the return value" and eradicate_return_over_annotated = - CLOpt.mk_bool ~long:"eradicate-return-over-annotated" - "Return over-annotated warning" + CLOpt.mk_bool ~long:"eradicate-return-over-annotated" "Return over-annotated warning" and eradicate_debug = - CLOpt.mk_bool ~long:"eradicate-debug" - "Print debug info when errors are found" + CLOpt.mk_bool ~long:"eradicate-debug" "Print debug info when errors are found" and eradicate_verbose = - CLOpt.mk_bool ~long:"eradicate-verbose" - "Print initial and final typestates" + CLOpt.mk_bool ~long:"eradicate-verbose" "Print initial and final typestates" and fail_on_bug = CLOpt.mk_bool ~deprecated:["-fail-on-bug"] ~long:"fail-on-issue" ~default:false - ~in_help:CLOpt.[Run, manual_generic] + ~in_help:CLOpt.([(Run, manual_generic)]) (Printf.sprintf "Exit with error code %d if Infer found something to report" fail_on_issue_exit_code) and fcp_apple_clang = - CLOpt.mk_path_opt ~long:"fcp-apple-clang" - ~meta:"path" "Specify the path to Apple Clang" + CLOpt.mk_path_opt ~long:"fcp-apple-clang" ~meta:"path" "Specify the path to Apple Clang" -and fcp_syntax_only = - CLOpt.mk_bool ~long:"fcp-syntax-only" - "Skip creation of object files" +and fcp_syntax_only = CLOpt.mk_bool ~long:"fcp-syntax-only" "Skip creation of object files" and file_renamings = - CLOpt.mk_path_opt - ~long:"file-renamings" ~in_help:CLOpt.[ReportDiff, manual_generic] + CLOpt.mk_path_opt ~long:"file-renamings" + ~in_help:CLOpt.([(ReportDiff, manual_generic)]) "JSON with a list of file renamings to use while computing differential reports" and filter_paths = - CLOpt.mk_bool ~long:"filter-paths" ~default:true - "Filters specified in .inferconfig" + CLOpt.mk_bool ~long:"filter-paths" ~default:true "Filters specified in .inferconfig" and flavors = CLOpt.mk_bool ~deprecated:["-use-flavors"] ~long:"flavors" - ~in_help:CLOpt.[Capture, manual_buck_flavors] - "Buck integration using Buck flavors (clang only), eg $(i,`infer --flavors -- buck build \ - //foo:bar#infer`)" + ~in_help:CLOpt.([(Capture, manual_buck_flavors)]) + "Buck integration using Buck flavors (clang only), eg $(i,`infer --flavors -- buck build //foo:bar#infer`)" and from_json_report = CLOpt.mk_path_opt ~long:"from-json-report" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~meta:"report.json" - "Load analysis results from a report file (default is to load the results from the specs \ - files generated by the analysis)." + "Load analysis results from a report file (default is to load the results from the specs files generated by the analysis)." and frontend_debug = CLOpt.mk_bool ~deprecated:["fd"] ~deprecated_no:["nfd"] ~long:"frontend-debug" - ~in_help:CLOpt.[Capture, manual_clang] - "Emit debug info to *.o.astlog and a script *.o.sh that replays the command used to run clang \ - with the plugin attached, piped to the infer frontend" + ~in_help:CLOpt.([(Capture, manual_clang)]) + "Emit debug info to *.o.astlog and a script *.o.sh that replays the command used to run clang with the plugin attached, piped to the infer frontend" and frontend_stats = CLOpt.mk_bool ~deprecated:["fs"] ~deprecated_no:["nfs"] ~long:"frontend-stats" @@ -1118,209 +1052,192 @@ and frontend_stats = and generated_classes = CLOpt.mk_path_opt ~long:"generated-classes" - ~in_help:CLOpt.[Capture, manual_java] + ~in_help:CLOpt.([(Capture, manual_java)]) "Specify where to load the generated class files" and headers = CLOpt.mk_bool ~deprecated:["headers"; "hd"] ~deprecated_no:["no_headers"; "nhd"] ~long:"headers" - ~in_help:CLOpt.[Capture, manual_clang] + ~in_help:CLOpt.([(Capture, manual_clang)]) "Analyze code in header files" and help = let var = ref `None in CLOpt.mk_set var `Help ~long:"help" - ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> command, manual_generic)) + ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> (command, manual_generic))) "Show this manual" ; CLOpt.mk_set var `HelpFull ~long:"help-full" - ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> command, manual_generic)) - (Printf.sprintf "Show this manual with all internal options in the %s section" manual_internal); + ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> (command, manual_generic))) + (Printf.sprintf "Show this manual with all internal options in the %s section" manual_internal) ; var and help_format = CLOpt.mk_symbol ~long:"help-format" ~symbols:[("auto", `Auto); ("groff", `Groff); ("pager", `Pager); ("plain", `Plain)] - ~eq:PVariant.(=) ~default:`Auto - ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> command, manual_generic)) - "Show this help in the specified format. $(b,auto) sets the format to $(b,plain) if the \ - environment variable $(b,TERM) is \"dumb\" or undefined, and to $(b,pager) otherwise." + ~eq:PVariant.( = ) ~default:`Auto + ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> (command, manual_generic))) + "Show this help in the specified format. $(b,auto) sets the format to $(b,plain) if the environment variable $(b,TERM) is \"dumb\" or undefined, and to $(b,pager) otherwise." and icfg_dotty_outfile = CLOpt.mk_path_opt ~long:"icfg-dotty-outfile" ~meta:"path" - "If set, specifies path where .dot file should be written, it overrides the path for all \ - other options that would generate icfg file otherwise" + "If set, specifies path where .dot file should be written, it overrides the path for all other options that would generate icfg file otherwise" -and ignore_trivial_traces = CLOpt.mk_bool - ~long:"ignore-trivial-traces" ~default:true "Ignore traces whose length is at most 1" +and ignore_trivial_traces = + CLOpt.mk_bool ~long:"ignore-trivial-traces" ~default:true + "Ignore traces whose length is at most 1" and infer_cache = - CLOpt.mk_path_opt ~deprecated:["infer_cache"; "-infer_cache"] ~long:"infer-cache" - ~meta:"dir" "Select a directory to contain the infer cache (Buck and Java only)" + CLOpt.mk_path_opt ~deprecated:["infer_cache"; "-infer_cache"] ~long:"infer-cache" ~meta:"dir" + "Select a directory to contain the infer cache (Buck and Java only)" and iphoneos_target_sdk_version = CLOpt.mk_string_opt ~long:"iphoneos-target-sdk-version" - ~in_help:CLOpt.[Capture, manual_clang_linters] + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) "Specify the target SDK version to use for iphoneos" and iphoneos_target_sdk_version_skip_path = CLOpt.mk_string_list ~long:"iphoneos-target-sdk-version-skip-path" - ~in_help:CLOpt.[Capture, manual_clang_linters] + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) ~meta:"path prefix OCaml regex" - "To be used together with iphoneos-target-sdk-version, \ - to disable that flag in a particular path (can be specified multiple times)" + "To be used together with iphoneos-target-sdk-version, to disable that flag in a particular path (can be specified multiple times)" and issues_fields = CLOpt.mk_symbol_seq ~long:"issues-fields" - ~in_help:CLOpt.[Report, manual_generic] - ~default:[ - `Issue_field_file; - `Issue_field_procedure; - `Issue_field_line_offset; - `Issue_field_bug_type; - `Issue_field_bug_trace; - ] - ~symbols:issues_fields_symbols ~eq:PVariant.(=) + ~in_help:CLOpt.([(Report, manual_generic)]) + ~default: + [ `Issue_field_file + ; `Issue_field_procedure + ; `Issue_field_line_offset + ; `Issue_field_bug_type + ; `Issue_field_bug_trace ] ~symbols:issues_fields_symbols ~eq:PVariant.( = ) "Fields to emit with $(b,--issues-tests)" and iterations = - CLOpt.mk_int ~deprecated:["iterations"] ~long:"iterations" ~default:1 - ~meta:"int" - "Specify the maximum number of operations for each function, expressed as a multiple of \ - symbolic operations and a multiple of seconds of elapsed time" + CLOpt.mk_int ~deprecated:["iterations"] ~long:"iterations" ~default:1 ~meta:"int" + "Specify the maximum number of operations for each function, expressed as a multiple of symbolic operations and a multiple of seconds of elapsed time" and java_jar_compiler = - CLOpt.mk_path_opt - ~long:"java-jar-compiler" - ~in_help:CLOpt.[Capture, manual_java] + CLOpt.mk_path_opt ~long:"java-jar-compiler" + ~in_help:CLOpt.([(Capture, manual_java)]) ~meta:"path" "Specify the Java compiler jar used to generate the bytecode" and jobs = CLOpt.mk_int ~deprecated:["-multicore"] ~long:"jobs" ~short:'j' ~default:ncpu - ~in_help:CLOpt.[Analyze, manual_generic] + ~in_help:CLOpt.([(Analyze, manual_generic)]) ~meta:"int" "Run the specified number of analysis jobs simultaneously" and join_cond = - CLOpt.mk_int ~deprecated:["join_cond"] ~long:"join-cond" ~default:1 - ~meta:"int" -{|Set the strength of the final information-loss check used by the join: + CLOpt.mk_int ~deprecated:["join_cond"] ~long:"join-cond" ~default:1 ~meta:"int" + {|Set the strength of the final information-loss check used by the join: - 0 = use the most aggressive join for preconditions - 1 = use the least aggressive join for preconditions |} and latex = - CLOpt.mk_path_opt ~deprecated:["latex"] ~long:"latex" - ~meta:"file" + CLOpt.mk_path_opt ~deprecated:["latex"] ~long:"latex" ~meta:"file" "Write a latex report of the analysis results to a file" and log_file = - CLOpt.mk_string ~deprecated:["out_file"; "-out-file"] ~long:"log-file" - ~meta:"file" ~default:"logs" "Specify the file to use for logging" + CLOpt.mk_string ~deprecated:["out_file"; "-out-file"] ~long:"log-file" ~meta:"file" + ~default:"logs" "Specify the file to use for logging" and linter = - CLOpt.mk_string_opt ~long:"linter" ~in_help:CLOpt.[Capture, manual_clang_linters] - "From the linters available, only run this one linter. \ - (Useful together with $(b,--linters-developer-mode))" + CLOpt.mk_string_opt ~long:"linter" + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) + "From the linters available, only run this one linter. (Useful together with $(b,--linters-developer-mode))" and linters_def_file = - CLOpt.mk_path_list ~default:[] - ~long:"linters-def-file" ~in_help:CLOpt.[Capture, manual_clang_linters] + CLOpt.mk_path_list ~default:[] ~long:"linters-def-file" + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) ~meta:"file" "Specify the file containing linters definition (e.g. 'linters.al')" and linters_def_folder = CLOpt.mk_path_list ~default:[] ~long:"linters-def-folder" - ~in_help:CLOpt.[Capture, manual_clang_linters] + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) ~meta:"dir" "Specify the folder containing linters files with extension .al" and linters_ignore_clang_failures = CLOpt.mk_bool ~long:"linters-ignore-clang-failures" - ~in_help:CLOpt.[Capture, manual_clang_linters] - ~default:false - "Continue linting files even if some compilation fails." + ~in_help:CLOpt.([(Capture, manual_clang_linters)]) + ~default:false "Continue linting files even if some compilation fails." and load_average = CLOpt.mk_float_opt ~long:"load-average" ~short:'l' - ~in_help:CLOpt.[Capture, manual_generic] ~meta:"float" - "Do not start new parallel jobs if the load average is greater than that specified (Buck and \ - make only)" + ~in_help:CLOpt.([(Capture, manual_generic)]) + ~meta:"float" + "Do not start new parallel jobs if the load average is greater than that specified (Buck and make only)" and load_results = CLOpt.mk_path_opt ~deprecated:["load_results"] ~long:"load-results" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~meta:"file.iar" "Load analysis results from Infer Analysis Results file file.iar" (** name of the makefile to create with clusters and dependencies *) -and makefile = - CLOpt.mk_path ~deprecated:["makefile"] ~long:"makefile" ~default:"" - ~meta:"file" "" +and makefile = CLOpt.mk_path ~deprecated:["makefile"] ~long:"makefile" ~default:"" ~meta:"file" "" and margin = - CLOpt.mk_int ~deprecated:["set_pp_margin"] ~long:"margin" ~default:100 - ~meta:"int" "Set right margin for the pretty printing functions" + CLOpt.mk_int ~deprecated:["set_pp_margin"] ~long:"margin" ~default:100 ~meta:"int" + "Set right margin for the pretty printing functions" and merge = CLOpt.mk_bool ~deprecated:["merge"] ~long:"merge" - ~in_help:CLOpt.[Analyze, manual_buck_flavors] + ~in_help:CLOpt.([(Analyze, manual_buck_flavors)]) "Merge the captured results directories specified in the dependency file" and ml_buckets = CLOpt.mk_symbol_seq ~deprecated:["ml_buckets"; "-ml_buckets"] ~long:"ml-buckets" ~default:[`MLeak_cf] - ~in_help:CLOpt.[Analyze, manual_clang] -{|Specify the memory leak buckets to be checked in Objective-C/C++: + ~in_help:CLOpt.([(Analyze, manual_clang)]) + {|Specify the memory leak buckets to be checked in Objective-C/C++: - $(b,cf) checks leaks from Core Foundation (activated by default), - $(b,arc) from code compiled in ARC mode, - $(b,narc) from code not compiled in ARC mode, - $(b,cpp) from C++ code |} - ~symbols:ml_bucket_symbols ~eq:PVariant.(=) + ~symbols:ml_bucket_symbols ~eq:PVariant.( = ) and models_mode = CLOpt.mk_bool ~deprecated:["models_mode"; "-models_mode"] ~long:"models-mode" "Mode for analyzing the models" and modified_targets = - CLOpt.mk_path_opt ~deprecated:["modified_targets"] ~long:"modified-targets" - ~meta:"file" "Read the file of Buck targets modified since the last analysis" + CLOpt.mk_path_opt ~deprecated:["modified_targets"] ~long:"modified-targets" ~meta:"file" + "Read the file of Buck targets modified since the last analysis" and monitor_prop_size = CLOpt.mk_bool ~deprecated:["monitor_prop_size"] ~long:"monitor-prop-size" "Monitor size of props, and print every time the current max is exceeded" -and nelseg = - CLOpt.mk_bool ~deprecated:["nelseg"] ~long:"nelseg" - "Use only nonempty lsegs" +and nelseg = CLOpt.mk_bool ~deprecated:["nelseg"] ~long:"nelseg" "Use only nonempty lsegs" (* TODO: document *) and objc_memory_model = - CLOpt.mk_bool ~deprecated:["objcm"] ~long:"objc-memory-model" - "Use ObjC memory model" + CLOpt.mk_bool ~deprecated:["objcm"] ~long:"objc-memory-model" "Use ObjC memory model" and only_footprint = - CLOpt.mk_bool ~deprecated:["only_footprint"] ~long:"only-footprint" - "Skip the re-execution phase" + CLOpt.mk_bool ~deprecated:["only_footprint"] ~long:"only-footprint" "Skip the re-execution phase" and passthroughs = CLOpt.mk_bool ~long:"passthroughs" ~default:false - "In error traces, show intermediate steps that propagate data. When false, error traces are \ - shorter and show only direct flow via souces/sinks" + "In error traces, show intermediate steps that propagate data. When false, error traces are shorter and show only direct flow via souces/sinks" and patterns_modeled_expensive = let long = "modeled-expensive" in - (long, - CLOpt.mk_json ~deprecated:["modeled_expensive"] ~long - "Matcher or list of matchers for methods that should be considered expensive by the \ - performance critical checker.") + ( long + , CLOpt.mk_json ~deprecated:["modeled_expensive"] ~long + "Matcher or list of matchers for methods that should be considered expensive by the performance critical checker." + ) and patterns_never_returning_null = let long = "never-returning-null" in - (long, - CLOpt.mk_json ~deprecated:["never_returning_null"] ~long - "Matcher or list of matchers for functions that never return $(i,null).") + ( long + , CLOpt.mk_json ~deprecated:["never_returning_null"] ~long + "Matcher or list of matchers for functions that never return $(i,null)." ) and patterns_skip_translation = let long = "skip-translation" in - (long, - CLOpt.mk_json ~deprecated:["skip_translation"] ~long - "Matcher or list of matchers for names of files that should not be analyzed at all.") + ( long + , CLOpt.mk_json ~deprecated:["skip_translation"] ~long + "Matcher or list of matchers for names of files that should not be analyzed at all." ) and per_procedure_parallelism = CLOpt.mk_bool ~long:"per-procedure-parallelism" ~default:true @@ -1328,7 +1245,7 @@ and per_procedure_parallelism = and pmd_xml = CLOpt.mk_bool ~long:"pmd-xml" - ~in_help:CLOpt.[Run, manual_generic] + ~in_help:CLOpt.([(Run, manual_generic)]) "Output issues in (PMD) XML format" and precondition_stats = @@ -1336,7 +1253,8 @@ and precondition_stats = "Print stats about preconditions to standard output" and print_active_checkers = - CLOpt.mk_bool ~long:"print-active-checkers" ~in_help:CLOpt.[Analyze, manual_generic] + CLOpt.mk_bool ~long:"print-active-checkers" + ~in_help:CLOpt.([(Analyze, manual_generic)]) "Print the active checkers before starting the analysis" and print_builtins = @@ -1349,63 +1267,62 @@ and print_using_diff = and procedures_per_process = CLOpt.mk_int ~long:"procedures-per-process" ~default:1000 ~meta:"int" - "Specify the number of procedures to analyze per process when using \ - $(b,--per-procedure-parallelism). If 0 is specified, each file is divided into $(b,--jobs) \ - groups of procedures." + "Specify the number of procedures to analyze per process when using $(b,--per-procedure-parallelism). If 0 is specified, each file is divided into $(b,--jobs) groups of procedures." and procs_csv = - CLOpt.mk_path_opt ~deprecated:["procs"] ~long:"procs-csv" - ~meta:"file" "Write statistics for each procedure in CSV format to a file" + CLOpt.mk_path_opt ~deprecated:["procs"] ~long:"procs-csv" ~meta:"file" + "Write statistics for each procedure in CSV format to a file" and procs_xml = - CLOpt.mk_path_opt ~deprecated:["procs_xml"] ~long:"procs-xml" - ~meta:"file" - "Write statistics for each procedure in XML format to a file (as a path relative to \ - $(b,--results-dir))" + CLOpt.mk_path_opt ~deprecated:["procs_xml"] ~long:"procs-xml" ~meta:"file" + "Write statistics for each procedure in XML format to a file (as a path relative to $(b,--results-dir))" and progress_bar = CLOpt.mk_bool ~deprecated:["pb"] ~deprecated_no:["no_progress_bar"; "npb"] ~short:'p' ~long:"progress-bar" ~default:true - ~in_help:CLOpt.[Run, manual_generic] + ~in_help:CLOpt.([(Run, manual_generic)]) "Show a progress bar" and project_root = CLOpt.mk_path ~deprecated:["project_root"; "-project_root"; "pr"] ~long:"project-root" ~short:'C' ~default:CLOpt.init_work_dir - ~in_help:CLOpt.[Analyze, manual_generic; Capture, manual_generic; Run, manual_generic; - Report, manual_generic] + ~in_help: + (CLOpt.( + [ (Analyze, manual_generic) + ; (Capture, manual_generic) + ; (Run, manual_generic) + ; (Report, manual_generic) ])) ~meta:"dir" "Specify the root directory of the project" and quandary_endpoints = CLOpt.mk_json ~long:"quandary-endpoints" - ~in_help:CLOpt.[Analyze, manual_quandary] + ~in_help:CLOpt.([(Analyze, manual_quandary)]) "Specify endpoint classes for Quandary" and quandary_sanitizers = CLOpt.mk_json ~long:"quandary-sanitizers" - ~in_help:CLOpt.[Analyze, manual_quandary] + ~in_help:CLOpt.([(Analyze, manual_quandary)]) "Specify custom sanitizers for Quandary" and quandary_sources = CLOpt.mk_json ~long:"quandary-sources" - ~in_help:CLOpt.[Analyze, manual_quandary] + ~in_help:CLOpt.([(Analyze, manual_quandary)]) "Specify custom sources for Quandary" and quandary_sinks = CLOpt.mk_json ~long:"quandary-sinks" - ~in_help:CLOpt.[Analyze, manual_quandary] + ~in_help:CLOpt.([(Analyze, manual_quandary)]) "Specify custom sinks for Quandary" and quiet = CLOpt.mk_bool ~long:"quiet" ~short:'q' ~default:false - ~in_help:CLOpt.[Analyze, manual_generic; Report, manual_generic] + ~in_help:CLOpt.([(Analyze, manual_generic); (Report, manual_generic)]) "Do not print specs on standard output (default: only print for the $(b,report) command)" and reactive = CLOpt.mk_bool ~deprecated:["reactive"] ~long:"reactive" ~short:'r' - ~in_help:CLOpt.[Analyze, manual_generic] - "Reactive mode: the analysis starts from the files captured since the $(i,infer) command \ - started" + ~in_help:CLOpt.([(Analyze, manual_generic)]) + "Reactive mode: the analysis starts from the files captured since the $(i,infer) command started" and reactive_capture = CLOpt.mk_bool ~long:"reactive-capture" @@ -1413,69 +1330,62 @@ and reactive_capture = and report = CLOpt.mk_bool ~long:"report" ~default:true - ~in_help:CLOpt.[Analyze, manual_generic; Run, manual_generic] + ~in_help:CLOpt.([(Analyze, manual_generic); (Run, manual_generic)]) "Run the reporting phase once the analysis has completed" and report_current = - CLOpt.mk_path_opt ~long:"report-current" ~in_help:CLOpt.[ReportDiff, manual_generic] + CLOpt.mk_path_opt ~long:"report-current" + ~in_help:CLOpt.([(ReportDiff, manual_generic)]) "report of the latest revision" -and report_custom_error = - CLOpt.mk_bool ~long:"report-custom-error" - "" +and report_custom_error = CLOpt.mk_bool ~long:"report-custom-error" "" and report_formatter = CLOpt.mk_symbol ~long:"report-formatter" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~default:`Phabricator_formatter - ~symbols:[ - ("none", `No_formatter); - ("phabricator", `Phabricator_formatter); - ] ~eq:PVariant.(=) + ~symbols:[("none", `No_formatter); ("phabricator", `Phabricator_formatter)] ~eq:PVariant.( = ) "Which formatter to use when emitting the report" and report_hook = CLOpt.mk_string_opt ~long:"report-hook" ~default:(lib_dir ^/ "python" ^/ "report.py") ~meta:"script" - "Specify a script to be executed after the analysis results are written. This script will be \ - passed $(b,--issues-csv), $(b,--issues-json), $(b,--issues-txt), $(b,--issues-xml), \ - $(b,--project-root), and $(b,--results-dir)." + "Specify a script to be executed after the analysis results are written. This script will be passed $(b,--issues-csv), $(b,--issues-json), $(b,--issues-txt), $(b,--issues-xml), $(b,--project-root), and $(b,--results-dir)." and report_previous = - CLOpt.mk_path_opt ~long:"report-previous" ~in_help:CLOpt.[ReportDiff, manual_generic] + CLOpt.mk_path_opt ~long:"report-previous" + ~in_help:CLOpt.([(ReportDiff, manual_generic)]) "Report of the base revision to use for comparison" and resource_leak = - CLOpt.mk_bool ~long:"resource-leak" ~default:false - "the resource leak analysis (experimental)" + CLOpt.mk_bool ~long:"resource-leak" ~default:false "the resource leak analysis (experimental)" and resolve_infer_eradicate_conflict = - CLOpt.mk_bool ~long:"resolve-infer-eradicate-conflict" - ~default:false ~in_help:CLOpt.[ReportDiff, manual_generic] + CLOpt.mk_bool ~long:"resolve-infer-eradicate-conflict" ~default:false + ~in_help:CLOpt.([(ReportDiff, manual_generic)]) "Filter out Null Dereferences reported by Infer if Eradicate is enabled" and rest = - CLOpt.mk_rest_actions - ~in_help:CLOpt.[Capture, manual_generic; Run, manual_generic] - "Stop argument processing, use remaining arguments as a build command" - ~usage:exe_usage + CLOpt.mk_rest_actions ~in_help:CLOpt.([(Capture, manual_generic); (Run, manual_generic)]) + "Stop argument processing, use remaining arguments as a build command" ~usage:exe_usage (fun build_exe -> - match Filename.basename build_exe with - | "java" | "javac" -> CLOpt.Javac - | _ -> CLOpt.NoParse - ) + match Filename.basename build_exe with "java" | "javac" -> CLOpt.Javac | _ -> CLOpt.NoParse ) and results_dir = CLOpt.mk_path ~deprecated:["results_dir"; "-out"] ~long:"results-dir" ~short:'o' ~default:(CLOpt.init_work_dir ^/ "infer-out") - ~in_help:CLOpt.[Analyze, manual_generic; Capture, manual_generic; Run, manual_generic; - Report, manual_generic] + ~in_help: + (CLOpt.( + [ (Analyze, manual_generic) + ; (Capture, manual_generic) + ; (Run, manual_generic) + ; (Report, manual_generic) ])) ~meta:"dir" "Write results and internal files in the specified directory" and save_results = CLOpt.mk_path_opt ~deprecated:["save_results"] ~long:"save-results" - ~in_help:CLOpt.[Report, manual_generic] + ~in_help:CLOpt.([(Report, manual_generic)]) ~meta:"file.iar" "Save analysis results to Infer Analysis Results file file.iar" and seconds_per_iteration = @@ -1484,105 +1394,97 @@ and seconds_per_iteration = and siof_safe_methods = CLOpt.mk_string_list ~long:"siof-safe-methods" - ~in_help:CLOpt.[Analyze, manual_siof] - "Methods that are SIOF-safe; \"foo::bar\" will match \"foo::bar()\", \"foo::bar()\", \ - etc. (can be specified multiple times)" + ~in_help:CLOpt.([(Analyze, manual_siof)]) + "Methods that are SIOF-safe; \"foo::bar\" will match \"foo::bar()\", \"foo::bar()\", etc. (can be specified multiple times)" and skip_analysis_in_path = CLOpt.mk_string_list ~deprecated:["-skip-clang-analysis-in-path"] ~long:"skip-analysis-in-path" - ~in_help:CLOpt.[Capture, manual_generic; Run, manual_generic] + ~in_help:CLOpt.([(Capture, manual_generic); (Run, manual_generic)]) ~meta:"path prefix OCaml regex" "Ignore files whose path matches the given prefix (can be specified multiple times)" and skip_analysis_in_path_skips_compilation = CLOpt.mk_bool ~long:"skip-analysis-in-path-skips-compilation" - ~in_help:CLOpt.[Report, manual_generic] - ~default:false - "Whether paths in --skip-analysis-in-path should be compiled or not" + ~in_help:CLOpt.([(Report, manual_generic)]) + ~default:false "Whether paths in --skip-analysis-in-path should be compiled or not" and skip_duplicated_types = CLOpt.mk_bool ~long:"skip-duplicated-types" ~default:true - ~in_help:CLOpt.[ReportDiff, manual_generic] + ~in_help:CLOpt.([(ReportDiff, manual_generic)]) "Skip fixed-then-introduced duplicated types while computing differential reports" and skip_translation_headers = CLOpt.mk_string_list ~deprecated:["skip_translation_headers"] ~long:"skip-translation-headers" - ~in_help:CLOpt.[Capture, manual_clang] + ~in_help:CLOpt.([(Capture, manual_clang)]) ~meta:"path prefix" "Ignore headers whose path matches the given prefix" -and sources = - CLOpt.mk_string_list ~long:"sources" - "Specify the list of source files" +and sources = CLOpt.mk_string_list ~long:"sources" "Specify the list of source files" -and sourcepath = - CLOpt.mk_string_opt ~long:"sourcepath" - "Specify the sourcepath" +and sourcepath = CLOpt.mk_string_opt ~long:"sourcepath" "Specify the sourcepath" and spec_abs_level = - CLOpt.mk_int ~deprecated:["spec_abs_level"] ~long:"spec-abs-level" ~default:1 - ~meta:"int" -{|Set the level of abstracting the postconditions of discovered specs: + CLOpt.mk_int ~deprecated:["spec_abs_level"] ~long:"spec-abs-level" ~default:1 ~meta:"int" + {|Set the level of abstracting the postconditions of discovered specs: - 0 = nothing special - 1 = filter out redundant posts implied by other posts |} and specs_library = let specs_library = - CLOpt.mk_path_list ~deprecated:["lib"] ~long:"specs-library" ~short:'L' - ~meta:"dir|jar" "Search for .spec files in given directory or jar file" in + CLOpt.mk_path_list ~deprecated:["lib"] ~long:"specs-library" ~short:'L' ~meta:"dir|jar" + "Search for .spec files in given directory or jar file" + in let _ = (* Given a filename with a list of paths, convert it into a list of string iff they are absolute *) let read_specs_dir_list_file fname = let validate_path path = if Filename.is_relative path then - failwith ("Failing because path " ^ path ^ " is not absolute") in + failwith ("Failing because path " ^ path ^ " is not absolute") + in match Utils.read_file (resolve fname) with - | Ok pathlist -> - List.iter ~f:validate_path pathlist; - pathlist - | Error error -> - failwithf "cannot read file '%s' from cwd '%s': %s" fname (Sys.getcwd ()) error + | Ok pathlist + -> List.iter ~f:validate_path pathlist ; pathlist + | Error error + -> failwithf "cannot read file '%s' from cwd '%s': %s" fname (Sys.getcwd ()) error in (* Add the newline-separated directories listed in to the list of directories to be searched for .spec files *) CLOpt.mk_string ~deprecated:["specs-dir-list-file"; "-specs-dir-list-file"] - ~long:"specs-library-index" - ~default:"" - ~f:(fun file -> specs_library := (read_specs_dir_list_file file) @ !specs_library; "") - ~in_help:CLOpt.[Analyze, manual_generic] ~meta:"file" - "" in + ~long:"specs-library-index" ~default:"" + ~f:(fun file -> + specs_library := read_specs_dir_list_file file @ !specs_library ; + "") + ~in_help:CLOpt.([(Analyze, manual_generic)]) + ~meta:"file" "" + in specs_library and stacktrace = CLOpt.mk_path_opt ~deprecated:["st"] ~long:"stacktrace" - ~in_help:CLOpt.[Analyze, manual_crashcontext] - ~meta:"file" "File path containing a json-encoded Java crash stacktrace. Used to guide the \ - analysis (only with '-a crashcontext'). See \ - tests/codetoanalyze/java/crashcontext/*.json for examples of the expected format." + ~in_help:CLOpt.([(Analyze, manual_crashcontext)]) + ~meta:"file" + "File path containing a json-encoded Java crash stacktrace. Used to guide the analysis (only with '-a crashcontext'). See tests/codetoanalyze/java/crashcontext/*.json for examples of the expected format." and stacktraces_dir = CLOpt.mk_path_opt ~long:"stacktraces-dir" - ~in_help:CLOpt.[Analyze, manual_crashcontext] - ~meta:"dir" "Directory path containing multiple json-encoded Java crash stacktraces. \ - Used to guide the analysis (only with '-a crashcontext'). See \ - tests/codetoanalyze/java/crashcontext/*.json for examples of the expected format." + ~in_help:CLOpt.([(Analyze, manual_crashcontext)]) + ~meta:"dir" + "Directory path containing multiple json-encoded Java crash stacktraces. Used to guide the analysis (only with '-a crashcontext'). See tests/codetoanalyze/java/crashcontext/*.json for examples of the expected format." and stats_report = - CLOpt.mk_path_opt ~long:"stats-report" - ~meta:"file" "Write a report of the analysis results to a file" + CLOpt.mk_path_opt ~long:"stats-report" ~meta:"file" + "Write a report of the analysis results to a file" and subtype_multirange = CLOpt.mk_bool ~deprecated:["subtype_multirange"] ~long:"subtype-multirange" ~default:true "Use the multirange subtyping domain" -and svg = - CLOpt.mk_bool ~deprecated:["svg"] ~long:"svg" - "Generate .dot and .svg files from specs" +and svg = CLOpt.mk_bool ~deprecated:["svg"] ~long:"svg" "Generate .dot and .svg files from specs" and symops_per_iteration = - CLOpt.mk_int_opt ~deprecated:["symops_per_iteration"] ~long:"symops-per-iteration" - ~meta:"int" "Set the number of symbolic operations per iteration (see $(b,--iterations))" + CLOpt.mk_int_opt ~deprecated:["symops_per_iteration"] ~long:"symops-per-iteration" ~meta:"int" + "Set the number of symbolic operations per iteration (see $(b,--iterations))" and test_filtering = CLOpt.mk_bool ~deprecated:["test_filtering"] ~long:"test-filtering" @@ -1595,15 +1497,14 @@ and testing_mode = and threadsafe_aliases = CLOpt.mk_json ~long:"threadsafe-aliases" - ~in_help:CLOpt.[Analyze, manual_threadsafety] + ~in_help:CLOpt.([(Analyze, manual_threadsafety)]) "Specify custom annotations that should be considered aliases of @ThreadSafe" and trace_join = CLOpt.mk_bool ~deprecated:["trace_join"] ~long:"trace-join" "Detailed tracing information during prop join operations" -and trace_ondemand = - CLOpt.mk_bool ~long:"trace-ondemand" "" +and trace_ondemand = CLOpt.mk_bool ~long:"trace-ondemand" "" and trace_rearrange = CLOpt.mk_bool ~deprecated:["trace_rearrange"] ~long:"trace-rearrange" @@ -1611,12 +1512,11 @@ and trace_rearrange = and tracing = CLOpt.mk_bool ~deprecated:["tracing"] ~long:"tracing" - "Report error traces for runtime exceptions (Java only): generate preconditions for runtime\ - exceptions in Java and report errors for public methods which throw runtime exceptions" + "Report error traces for runtime exceptions (Java only): generate preconditions for runtimeexceptions in Java and report errors for public methods which throw runtime exceptions" and tv_limit = - CLOpt.mk_int ~long:"tv-limit" ~default:100 - ~meta:"int" "The maximum number of traces to submit to Traceview" + CLOpt.mk_int ~long:"tv-limit" ~default:100 ~meta:"int" + "The maximum number of traces to submit to Traceview" and type_size = CLOpt.mk_bool ~deprecated:["type_size"] ~long:"type-size" @@ -1624,29 +1524,26 @@ and type_size = and unsafe_malloc = CLOpt.mk_bool ~long:"unsafe-malloc" - ~in_help:CLOpt.[Analyze, manual_clang] + ~in_help:CLOpt.([(Analyze, manual_clang)]) "Assume that malloc(3) never returns null." (** Set the path to the javac verbose output *) and verbose_out = - CLOpt.mk_path ~deprecated:["verbose_out"] ~long:"verbose-out" ~default:"" - ~meta:"file" "" + CLOpt.mk_path ~deprecated:["verbose_out"] ~long:"verbose-out" ~default:"" ~meta:"file" "" and version = let var = ref `None in CLOpt.mk_set var `Full ~deprecated:["version"] ~long:"version" - ~in_help:CLOpt.[Run, manual_generic] + ~in_help:CLOpt.([(Run, manual_generic)]) "Print version information and exit" ; CLOpt.mk_set var `Json ~deprecated:["version_json"] ~long:"version-json" - ~in_help:CLOpt.[Run, manual_generic] + ~in_help:CLOpt.([(Run, manual_generic)]) "Print version information in json format and exit" ; - CLOpt.mk_set var `Vcs ~long:"version-vcs" - "Print version control system commit and exit" ; + CLOpt.mk_set var `Vcs ~long:"version-vcs" "Print version control system commit and exit" ; var and whole_seconds = - CLOpt.mk_bool ~deprecated:["whole_seconds"] ~long:"whole-seconds" - "Print whole seconds only" + CLOpt.mk_bool ~deprecated:["whole_seconds"] ~long:"whole-seconds" "Print whole seconds only" (** visit mode for the worklist: 0 depth - fist visit @@ -1654,8 +1551,7 @@ and whole_seconds = 2 least visited first *) and worklist_mode = let var = ref 0 in - CLOpt.mk_set var 2 ~long:"coverage" - "analysis mode to maximize coverage (can take longer)" ; + CLOpt.mk_set var 2 ~long:"coverage" "analysis mode to maximize coverage (can take longer)" ; CLOpt.mk_set var 1 ~long:"exit-node-bias" ~deprecated:["exit_node_bias"] "nodes nearest the exit node are analyzed first" ; CLOpt.mk_set var 2 ~long:"visits-bias" ~deprecated:["visits_bias"] @@ -1664,16 +1560,13 @@ and worklist_mode = and xcode_developer_dir = CLOpt.mk_path_opt ~long:"xcode-developer-dir" - ~in_help:CLOpt.[Capture, manual_buck_flavors] + ~in_help:CLOpt.([(Capture, manual_buck_flavors)]) ~meta:"XCODE_DEVELOPER_DIR" "Specify the path to Xcode developer directory" and xcpretty = - CLOpt.mk_bool ~long:"xcpretty" - ~default:true - ~in_help:CLOpt.[Capture, manual_clang] - "Infer will use xcpretty together with xcodebuild to analyze an iOS app. xcpretty just needs \ - to be in the path, infer command is still just $(i,`infer -- `). \ - (Recommended)" + CLOpt.mk_bool ~long:"xcpretty" ~default:true + ~in_help:CLOpt.([(Capture, manual_clang)]) + "Infer will use xcpretty together with xcodebuild to analyze an iOS app. xcpretty just needs to be in the path, infer command is still just $(i,`infer -- `). (Recommended)" and xml_specs = CLOpt.mk_bool ~deprecated:["xml"] ~long:"xml-specs" @@ -1683,386 +1576,560 @@ and xml_specs = are allowed to refer to the other arg variables. *) let javac_classes_out = - CLOpt.mk_string_opt ~parse_mode:CLOpt.Javac - ~deprecated:["classes_out"] ~long:"" ~short:'d' + CLOpt.mk_string_opt ~parse_mode:CLOpt.Javac ~deprecated:["classes_out"] ~long:"" ~short:'d' ~f:(fun classes_out -> - if !buck then ( + ( if !buck then let classes_out_infer = resolve classes_out ^/ buck_results_dir_name in (* extend env var args to pass args to children that do not receive the rest args *) CLOpt.extend_env_args ["--results-dir"; classes_out_infer] ; - results_dir := classes_out_infer; - ); - classes_out) + results_dir := classes_out_infer ) ; + classes_out) "" and _ = - CLOpt.mk_string_opt ~parse_mode:CLOpt.Javac - ~deprecated:["classpath";"cp"] ~long:"" + CLOpt.mk_string_opt ~parse_mode:CLOpt.Javac ~deprecated:["classpath"; "cp"] ~long:"" ~f:(fun classpath -> - if !buck then ( + ( if !buck then let paths = String.split classpath ~on:':' in let files = List.filter paths ~f:(fun path -> Sys.is_file path = `Yes) in CLOpt.extend_env_args (List.concat_map files ~f:(fun file -> ["--specs-library"; file])) ; - specs_library := List.rev_append files !specs_library - ); - classpath) + specs_library := List.rev_append files !specs_library ) ; + classpath) "" - -and () = - CLOpt.mk_set ~parse_mode:CLOpt.Javac version - ~deprecated:["version"] ~long:"" `Javac - "" +and () = CLOpt.mk_set ~parse_mode:CLOpt.Javac version ~deprecated:["version"] ~long:"" `Javac "" (** Parse Command Line Args *) let post_parsing_initialization command_opt = - (match !version with - | `Full -> - (* TODO(11791235) change back to stdout once buck integration is fixed *) - prerr_endline version_string - | `Javac when !buck -> - (* print buck key *) - let javac_version = - let javac_args = - if infer_is_javac then - match Array.to_list Sys.argv with - | [] -> [] - | _::args -> "javac"::args - else - List.rev !rest in - (* stderr contents of build command *) - let chans = Unix.open_process_full (String.concat ~sep:" " javac_args) ~env:[||] in - let err = String.strip (In_channel.input_all chans.stderr) in - Unix.close_process_full chans |> ignore; - err in - let analyzer_name = - List.Assoc.find_exn ~equal:equal_analyzer - (List.map ~f:(fun (n,a) -> (a,n)) string_to_analyzer) - (match !analyzer with Some a -> a | None -> BiAbduction) in - let infer_version = Version.commit in - F.eprintf "%s/%s/%s@." javac_version analyzer_name infer_version - | `Javac -> - prerr_endline version_string - | `Json -> - print_endline Version.versionJson - | `Vcs -> - print_endline Version.commit - | `None -> () - ); - (match !help with - | `Help -> - CLOpt.show_manual !help_format CommandDoc.infer command_opt - | `HelpFull -> - CLOpt.show_manual ~internal_section:manual_internal !help_format CommandDoc.infer command_opt - | `None -> - () - ); - if !version <> `None || !help <> `None then exit 0; - + ( match !version with + | `Full + -> (* TODO(11791235) change back to stdout once buck integration is fixed *) + prerr_endline version_string + | `Javac when !buck + -> (* print buck key *) + let javac_version = + let javac_args = + if infer_is_javac then + match Array.to_list Sys.argv with [] -> [] | _ :: args -> "javac" :: args + else List.rev !rest + in + (* stderr contents of build command *) + let chans = Unix.open_process_full (String.concat ~sep:" " javac_args) ~env:[||] in + let err = String.strip (In_channel.input_all chans.stderr) in + Unix.close_process_full chans |> ignore ; + err + in + let analyzer_name = + List.Assoc.find_exn ~equal:equal_analyzer + (List.map ~f:(fun (n, a) -> (a, n)) string_to_analyzer) + (match !analyzer with Some a -> a | None -> BiAbduction) + in + let infer_version = Version.commit in + F.eprintf "%s/%s/%s@." javac_version analyzer_name infer_version + | `Javac + -> prerr_endline version_string + | `Json + -> print_endline Version.versionJson + | `Vcs + -> print_endline Version.commit + | `None + -> () ) ; + ( match !help with + | `Help + -> CLOpt.show_manual !help_format CommandDoc.infer command_opt + | `HelpFull + -> CLOpt.show_manual ~internal_section:manual_internal !help_format CommandDoc.infer command_opt + | `None + -> () ) ; + if !version <> `None || !help <> `None then exit 0 ; (* Core sets a verbose exception handler by default, with backtrace. This is good for developers but in user-mode we want something lighter weight. *) if not !developer_mode then - Caml.Printexc.set_uncaught_exception_handler - (fun exn _ -> - let exn_msg = match exn with - | Failure msg -> msg - | _ -> Caml.Printexc.to_string exn in - Format.eprintf "ERROR: %s@." exn_msg - ); - + Caml.Printexc.set_uncaught_exception_handler (fun exn _ -> + let exn_msg = match exn with Failure msg -> msg | _ -> Caml.Printexc.to_string exn in + Format.eprintf "ERROR: %s@." exn_msg ) ; F.set_margin !margin ; - - let set_minor_heap_size nMb = (* increase the minor heap size to speed up gc *) + let set_minor_heap_size nMb = + (* increase the minor heap size to speed up gc *) let ctrl = Gc.get () in let words_of_Mb nMb = nMb * 1024 * 1024 * 8 / Sys.word_size in let new_size = max ctrl.minor_heap_size (words_of_Mb nMb) in - Gc.set { ctrl with minor_heap_size = new_size } + Gc.set {ctrl with minor_heap_size= new_size} in set_minor_heap_size 8 ; - let symops_timeout, seconds_timeout = let default_symops_timeout = 1100 in let default_seconds_timeout = 10.0 in - if !models_mode then - (* disable timeouts when analyzing models *) + if !models_mode then (* disable timeouts when analyzing models *) (None, None) - else - (Some default_symops_timeout, Some default_seconds_timeout) + else (Some default_symops_timeout, Some default_seconds_timeout) in if is_none !symops_per_iteration then symops_per_iteration := symops_timeout ; if is_none !seconds_per_iteration then seconds_per_iteration := seconds_timeout ; - - clang_compilation_dbs := - List.rev_map ~f:(fun x -> `Raw x) !compilation_database - |> List.rev_map_append ~f:(fun x -> `Escaped x) !compilation_database_escaped; - + clang_compilation_dbs + := List.rev_map ~f:(fun x -> `Raw x) !compilation_database + |> List.rev_map_append ~f:(fun x -> `Escaped x) !compilation_database_escaped ; (* set analyzer mode to linters in linters developer mode *) - if !linters_developer_mode then ( - analyzer := Some Linters; - ); - if !default_linters then - linters_def_file := linters_def_default_file :: !linters_def_file; - - (match !analyzer with - | Some BiAbduction -> biabduction := true - | Some Crashcontext -> crashcontext := true - | Some Eradicate -> eradicate := true - | Some (CaptureOnly | CompileOnly | Checkers | Linters) -> () - | None -> - let open CLOpt in - match command_opt with - | Some Compile -> analyzer := Some CompileOnly - | Some Capture -> analyzer := Some CaptureOnly - | _ -> biabduction := true (* the default option is to run the biabduction analysis *) - ); + if !linters_developer_mode then analyzer := Some Linters ; + if !default_linters then linters_def_file := linters_def_default_file :: !linters_def_file ; + ( match !analyzer with + | Some BiAbduction + -> biabduction := true + | Some Crashcontext + -> crashcontext := true + | Some Eradicate + -> eradicate := true + | Some (CaptureOnly | CompileOnly | Checkers | Linters) + -> () + | None + -> let open CLOpt in + match command_opt with + | Some Compile + -> analyzer := Some CompileOnly + | Some Capture + -> analyzer := Some CaptureOnly + | _ + -> biabduction := true + (* the default option is to run the biabduction analysis *) ) ; Option.value ~default:CLOpt.Run command_opt let inferconfig_path () = let rec find dir = match Sys.file_exists ~follow_symlinks:false (dir ^/ CommandDoc.inferconfig_file) with - | `Yes -> - Some dir - | `No | `Unknown -> - let parent = Filename.dirname dir in + | `Yes + -> Some dir + | `No | `Unknown + -> let parent = Filename.dirname dir in let is_root = String.equal dir parent in - if is_root then None - else find parent in + if is_root then None else find parent + in match Sys.getenv CommandDoc.inferconfig_env_var with - | Some env_path -> - (* make sure the path makes sense in children infer processes *) - Some ( - if Filename.is_relative env_path then - Utils.filename_to_absolute ~root:CLOpt.init_work_dir env_path - else - env_path - ) - | None -> - find (Sys.getcwd ()) - |> Option.map ~f:(fun dir -> dir ^/ CommandDoc.inferconfig_file) + | Some env_path + -> (* make sure the path makes sense in children infer processes *) + Some + ( if Filename.is_relative env_path then + Utils.filename_to_absolute ~root:CLOpt.init_work_dir env_path + else env_path ) + | None + -> find (Sys.getcwd ()) |> Option.map ~f:(fun dir -> dir ^/ CommandDoc.inferconfig_file) let command, parse_args_and_return_usage_exit = let config_file = inferconfig_path () in let command_opt, usage_exit = - CLOpt.parse ?config_file ~usage:exe_usage startup_action initial_command in + CLOpt.parse ?config_file ~usage:exe_usage startup_action initial_command + in let command = post_parsing_initialization command_opt in - command, usage_exit - -let print_usage_exit () = - parse_args_and_return_usage_exit 1 + (command, usage_exit) +let print_usage_exit () = parse_args_and_return_usage_exit 1 (** Freeze initialized configuration values *) let anon_args = !anon_args + and rest = !rest + and abs_struct = !abs_struct + and abs_val_orig = !abs_val + and allow_specs_cleanup = !allow_specs_cleanup + and analysis_path_regex_whitelist_options = List.map ~f:(fun (a, b) -> (a, !b)) analysis_path_regex_whitelist_options + and analysis_path_regex_blacklist_options = List.map ~f:(fun (a, b) -> (a, !b)) analysis_path_regex_blacklist_options + and analysis_blacklist_files_containing_options = List.map ~f:(fun (a, b) -> (a, !b)) analysis_blacklist_files_containing_options + and analysis_suppress_errors_options = List.map ~f:(fun (a, b) -> (a, !b)) analysis_suppress_errors_options + and analysis_stops = !analysis_stops + and angelic_execution = !angelic_execution + and annotation_reachability = !annotation_reachability + and annotation_reachability_custom_pairs = !annotation_reachability_custom_pairs + and array_level = !array_level + and ast_file = !ast_file + and biabduction = !biabduction + and blacklist = !blacklist + and bootclasspath = !bootclasspath + and bo_debug = !bo_debug + and buck = !buck + and buck_build_args = !buck_build_args + and buck_cache_mode = !buck && not !debug + and buck_compilation_database = !buck_compilation_database + and buck_out = !buck_out + and bufferoverrun = !bufferoverrun + and bugs_csv = !bugs_csv + and bugs_json = !bugs_json + and frontend_tests = !frontend_tests + and generated_classes = !generated_classes + and bugs_tests = !bugs_tests + and bugs_txt = !bugs_txt + and changed_files_index = !changed_files_index + and calls_csv = !calls_csv + and dump_duplicate_symbols = !dump_duplicate_symbols + and clang_biniou_file = !clang_biniou_file + and clang_ignore_regex = !clang_ignore_regex + and clang_include_to_override_regex = !clang_include_to_override_regex + and classpath = !classpath + and cluster_cmdline = !cluster + and compute_analytics = !compute_analytics + and continue_capture = !continue + and linter = !linter + and default_linters = !default_linters + and linters_ignore_clang_failures = !linters_ignore_clang_failures + and copy_propagation = !copy_propagation + and crashcontext = !crashcontext + and create_harness = !android_harness + and cxx = !cxx + and cxx_infer_headers = !cxx_infer_headers + and debug_level_analysis = !debug_level_analysis + and debug_level_capture = !debug_level_capture + and debug_level_linters = !debug_level_linters + and debug_exceptions = !debug_exceptions + and debug_mode = !debug + and dependency_mode = !dependencies + and developer_mode = !developer_mode + and differential_filter_files = !differential_filter_files + and differential_filter_set = !differential_filter_set + and disable_checks = !disable_checks + and dotty_cfg_libs = !dotty_cfg_libs + and enable_checks = !enable_checks + and eradicate = !eradicate + and eradicate_condition_redundant = !eradicate_condition_redundant + and eradicate_field_not_mutable = !eradicate_field_not_mutable + and eradicate_field_over_annotated = !eradicate_field_over_annotated + and eradicate_optional_present = !eradicate_optional_present + and eradicate_propagate_return_nullable = !eradicate_propagate_return_nullable + and eradicate_return_over_annotated = !eradicate_return_over_annotated + and eradicate_debug = !eradicate_debug + and eradicate_verbose = !eradicate_verbose + and fail_on_bug = !fail_on_bug + and failures_allowed = !failures_allowed + and fcp_apple_clang = !fcp_apple_clang + and fcp_syntax_only = !fcp_syntax_only + and file_renamings = !file_renamings + and filter_paths = !filter_paths + and filtering = !filtering + and flavors = !flavors + and fragment_retains_view = !fragment_retains_view + and from_json_report = !from_json_report + and frontend_debug = !frontend_debug + and frontend_stats = !frontend_stats + and headers = !headers + and icfg_dotty_outfile = !icfg_dotty_outfile + and ignore_trivial_traces = !ignore_trivial_traces + and immutable_cast = !immutable_cast + and infer_cache = !infer_cache + and iphoneos_target_sdk_version = !iphoneos_target_sdk_version + and iphoneos_target_sdk_version_skip_path = !iphoneos_target_sdk_version_skip_path + and issues_fields = !issues_fields + and iterations = !iterations + and java_jar_compiler = !java_jar_compiler + and javac_classes_out = !javac_classes_out + and javac_verbose_out = !verbose_out + and jobs = !jobs + and join_cond = !join_cond + and latex = !latex + and linters_def_file = !linters_def_file + and linters_def_folder = !linters_def_folder + and linters_developer_mode = !linters_developer_mode -and load_average = match !load_average with - | None when !buck -> - Some (float_of_int ncpu) - | _ -> - !load_average + +and load_average = + match !load_average with None when !buck -> Some (float_of_int ncpu) | _ -> !load_average + and load_analysis_results = !load_results + and log_file = !log_file + and makefile_cmdline = !makefile + and merge = !merge + and ml_buckets = !ml_buckets + and models_mode = !models_mode + and modified_targets = !modified_targets + and monitor_prop_size = !monitor_prop_size + and nelseg = !nelseg + and suggest_nullable = !suggest_nullable + and no_translate_libs = not !headers + and objc_memory_model_on = !objc_memory_model + and only_cheap_debug = !only_cheap_debug + and only_footprint = !only_footprint + and passthroughs = !passthroughs -and patterns_never_returning_null = match patterns_never_returning_null with (k,r) -> (k,!r) -and patterns_skip_translation = match patterns_skip_translation with (k,r) -> (k,!r) -and patterns_modeled_expensive = match patterns_modeled_expensive with (k,r) -> (k,!r) + +and patterns_never_returning_null = + match patterns_never_returning_null + with k, r -> (k, !r) + +and patterns_skip_translation = + match patterns_skip_translation + with k, r -> (k, !r) + +and patterns_modeled_expensive = + match patterns_modeled_expensive + with k, r -> (k, !r) + and per_procedure_parallelism = !per_procedure_parallelism + and pmd_xml = !pmd_xml + and precondition_stats = !precondition_stats + and printf_args = !printf_args + and print_active_checkers = !print_active_checkers + and print_builtins = !print_builtins + and print_logs = !print_logs + and print_types = !print_types + and print_using_diff = !print_using_diff + and procedures_per_process = !procedures_per_process + and procs_csv = !procs_csv + and procs_xml = !procs_xml + and project_root = !project_root + and quandary = !quandary + and quandary_endpoints = !quandary_endpoints + and quandary_sanitizers = !quandary_sanitizers + and quandary_sources = !quandary_sources + and quandary_sinks = !quandary_sinks + and quiet = !quiet + and reactive_mode = !reactive + and reactive_capture = !reactive_capture + and repeated_calls = !repeated_calls + and report = !report + and report_current = !report_current + and report_custom_error = !report_custom_error + and report_formatter = !report_formatter + and report_hook = !report_hook + and report_previous = !report_previous + and reports_include_ml_loc = !reports_include_ml_loc + and resolve_infer_eradicate_conflict = !resolve_infer_eradicate_conflict + and resource_leak = !resource_leak + and results_dir = !results_dir + and save_analysis_results = !save_results + and seconds_per_iteration = !seconds_per_iteration + and show_buckets = !print_buckets + and show_progress_bar = !progress_bar + and siof = !siof + and siof_safe_methods = !siof_safe_methods + and skip_analysis_in_path = !skip_analysis_in_path + and skip_analysis_in_path_skips_compilation = !skip_analysis_in_path_skips_compilation + and skip_duplicated_types = !skip_duplicated_types + and skip_translation_headers = !skip_translation_headers + and sources = !sources + and sourcepath = !sourcepath + and spec_abs_level = !spec_abs_level + and stacktrace = !stacktrace + and stacktraces_dir = !stacktraces_dir + and stats_mode = !stats + and stats_report = !stats_report + and subtype_multirange = !subtype_multirange + and svg = !svg + and symops_per_iteration = !symops_per_iteration + and test_filtering = !test_filtering + and testing_mode = !testing_mode + and threadsafety = !threadsafety + and threadsafe_aliases = !threadsafe_aliases + and trace_error = !trace_error + and trace_ondemand = !trace_ondemand + and trace_join = !trace_join + and trace_rearrange = !trace_rearrange + and tracing = !tracing + and tv_limit = !tv_limit + and type_size = !type_size + and unsafe_malloc = !unsafe_malloc + and whole_seconds = !whole_seconds + and worklist_mode = !worklist_mode + and write_dotty = !write_dotty + and write_html = !write_html + and write_html_whitelist_regex = !write_html_whitelist_regex + and xcode_developer_dir = !xcode_developer_dir + and xcpretty = !xcpretty -and xml_specs = !xml_specs +and xml_specs = !xml_specs (** Configuration values derived from command-line options *) let analysis_path_regex_whitelist analyzer = List.Assoc.find_exn ~equal:equal_analyzer analysis_path_regex_whitelist_options analyzer + and analysis_path_regex_blacklist analyzer = List.Assoc.find_exn ~equal:equal_analyzer analysis_path_regex_blacklist_options analyzer + and analysis_blacklist_files_containing analyzer = List.Assoc.find_exn ~equal:equal_analyzer analysis_blacklist_files_containing_options analyzer + and analysis_suppress_errors analyzer = List.Assoc.find_exn ~equal:equal_analyzer analysis_suppress_errors_options analyzer @@ -2070,73 +2137,74 @@ let captured_dir = results_dir ^/ captured_dir_name let clang_frontend_do_capture, clang_frontend_do_lint = match !clang_frontend_action with - | Some `Lint -> false, true (* no capture, lint *) - | Some `Capture -> true, false (* capture, no lint *) - | Some `Lint_and_capture -> true, true (* capture, lint *) + | Some `Lint + -> (false, true) (* no capture, lint *) + | Some `Capture + -> (true, false) (* capture, no lint *) + | Some `Lint_and_capture + -> (true, true) (* capture, lint *) | None -> - match !analyzer with - | Some Linters -> false, true (* no capture, lint *) - | Some BiAbduction -> true, false (* capture, no lint *) - | _ -> true, true (* capture, lint *) + match !analyzer with + | Some Linters + -> (false, true) (* no capture, lint *) + | Some BiAbduction + -> (true, false) (* capture, no lint *) + | _ + -> (true, true) + +(* capture, lint *) let analyzer = match !analyzer with Some a -> a | None -> BiAbduction let clang_frontend_action_string = String.concat ~sep:" and " - ((if clang_frontend_do_capture then ["translating"] else []) - @ (if clang_frontend_do_lint then ["linting"] else [])) + ( (if clang_frontend_do_capture then ["translating"] else []) + @ if clang_frontend_do_lint then ["linting"] else [] ) let dynamic_dispatch = let default_mode = - match analyzer with - | BiAbduction -> `Lazy - | Checkers when quandary -> `Sound - | _ -> `None in + match analyzer with BiAbduction -> `Lazy | Checkers when quandary -> `Sound | _ -> `None + in Option.value ~default:default_mode !dynamic_dispatch let specs_library = match infer_cache with - | Some cache_dir when use_jar_cache -> - let add_spec_lib specs_library filename = + | Some cache_dir when use_jar_cache + -> let add_spec_lib specs_library filename = let basename = Filename.basename filename in let key = basename ^ Utils.string_crc_hex32 filename in let key_dir = cache_dir ^/ key in let extract_specs dest_dir filename = if Filename.check_suffix filename ".jar" then - match (Unix.mkdir dest_dir ~perm:0o700) with - | exception Unix.Unix_error _ -> - () - | () -> - let zip_channel = Zip.open_in filename in + match Unix.mkdir dest_dir ~perm:0o700 with + | exception Unix.Unix_error _ + -> () + | () + -> let zip_channel = Zip.open_in filename in let entries = Zip.entries zip_channel in - let extract_entry (entry : Zip.entry) = - let dest_file = dest_dir ^/ (Filename.basename entry.filename) in - if Filename.check_suffix entry.filename specs_files_suffix - then Zip.copy_entry_to_file zip_channel entry dest_file in - List.iter ~f:extract_entry entries; - Zip.close_in zip_channel in - extract_specs key_dir filename; - key_dir :: specs_library in + let extract_entry (entry: Zip.entry) = + let dest_file = dest_dir ^/ Filename.basename entry.filename in + if Filename.check_suffix entry.filename specs_files_suffix then + Zip.copy_entry_to_file zip_channel entry dest_file + in + List.iter ~f:extract_entry entries ; Zip.close_in zip_channel + in + extract_specs key_dir filename ; key_dir :: specs_library + in List.fold ~f:add_spec_lib ~init:[] !specs_library - | _ -> - !specs_library - + | _ + -> !specs_library (** Global variables *) let set_reference_and_call_function reference value f x = let saved = !reference in - let restore () = - reference := saved in + let restore () = reference := saved in try - reference := value; + reference := value ; let res = f x in - restore (); - res - with - | exn -> - restore (); - raise exn + restore () ; res + with exn -> restore () ; raise exn (** Current Objective-C Automatic Reference Counting (ARC) mode *) let arc_mode = ref false @@ -2144,17 +2212,14 @@ let arc_mode = ref false (** Current language *) let curr_language = ref Clang -let curr_language_is lang = - equal_language !curr_language lang +let curr_language_is lang = equal_language !curr_language lang (** Flag for footprint discovery mode *) let footprint = ref true -let run_in_footprint_mode f x = - set_reference_and_call_function footprint true f x +let run_in_footprint_mode f x = set_reference_and_call_function footprint true f x -let run_in_re_execution_mode f x = - set_reference_and_call_function footprint false f x +let run_in_re_execution_mode f x = set_reference_and_call_function footprint false f x (** Set in the middle of forcing delayed prints *) let forcing_delayed_prints = ref false @@ -2162,8 +2227,6 @@ let forcing_delayed_prints = ref false (** if true, user simple pretty printing *) let pp_simple = ref true -let reset_abs_val () = - abs_val := abs_val_orig +let reset_abs_val () = abs_val := abs_val_orig -let run_with_abs_val_equal_zero f x = - set_reference_and_call_function abs_val 0 f x +let run_with_abs_val_equal_zero f x = set_reference_and_call_function abs_val 0 f x diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 4c801daba..c4cfe84d9 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -9,20 +9,25 @@ *) open! IStd - module CLOpt = CommandLineOption (** Configuration values: either constant, determined at compile time, or set at startup time by system calls, environment variables, or command line options *) type analyzer = - | BiAbduction | CaptureOnly | CompileOnly | Eradicate | Checkers | Crashcontext | Linters -[@@deriving compare] + | BiAbduction + | CaptureOnly + | CompileOnly + | Eradicate + | Checkers + | Crashcontext + | Linters + [@@deriving compare] val equal_analyzer : analyzer -> analyzer -> bool -(** Association list of analyzers and their names *) val string_to_analyzer : (string * analyzer) list +(** Association list of analyzers and their names *) val string_of_analyzer : analyzer -> string @@ -32,347 +37,608 @@ val equal_language : language -> language -> bool val string_of_language : language -> string - val ml_bucket_symbols : - (string * [ `MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown ]) - list + (string * [`MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown]) + list val issues_fields_symbols : - (string * [`Issue_field_bug_class - | `Issue_field_kind - | `Issue_field_bug_type - | `Issue_field_qualifier - | `Issue_field_severity - | `Issue_field_visibility - | `Issue_field_line - | `Issue_field_column - | `Issue_field_procedure - | `Issue_field_procedure_id - | `Issue_field_procedure_start_line - | `Issue_field_file - | `Issue_field_bug_trace - | `Issue_field_key - | `Issue_field_hash - | `Issue_field_line_offset - | `Issue_field_procedure_id_without_crc - | `Issue_field_qualifier_contains_potential_exception_note]) list - + ( string + * [ `Issue_field_bug_class + | `Issue_field_kind + | `Issue_field_bug_type + | `Issue_field_qualifier + | `Issue_field_severity + | `Issue_field_visibility + | `Issue_field_line + | `Issue_field_column + | `Issue_field_procedure + | `Issue_field_procedure_id + | `Issue_field_procedure_start_line + | `Issue_field_file + | `Issue_field_bug_trace + | `Issue_field_key + | `Issue_field_hash + | `Issue_field_line_offset + | `Issue_field_procedure_id_without_crc + | `Issue_field_qualifier_contains_potential_exception_note ] ) + list type os_type = Unix | Win32 | Cygwin -type dynamic_dispatch_policy = [ - | `None - | `Interface - | `Sound - | `Lazy -] +type dynamic_dispatch_policy = [`None | `Interface | `Sound | `Lazy] val env_inside_maven : Unix.env (** Constant configuration values *) val allow_missing_index_in_proc_call : bool + val anonymous_block_num_sep : string + val anonymous_block_prefix : string + val assign : string + val attributes_dir_name : string + val backend_stats_dir_name : string + val bin_dir : string + val bound_error_allowed_in_procedure_call : bool + val buck_generated_folder : string + val buck_infer_deps_file_name : string + val captured_dir_name : string + val checks_disabled_by_default : string list + val clang_initializer_prefix : string + val classpath : string option + val cpp_extra_include_dir : string + val duplicates_filename : string + val relative_cpp_models_dir : string + val csl_analysis : bool + val default_failure_name : string + val default_in_zip_results_dir : string + val dotty_output : string + val etc_dir : string + val fail_on_issue_exit_code : int + val frontend_stats_dir_name : string + val global_tenv_filename : string + val idempotent_getters : bool + val incremental_procs : bool + val infer_py_argparse_error_exit_code : int + val initial_analysis_time : float + val ivar_attributes : string + val lib_dir : string + val lint_dotty_dir_name : string + val lint_issues_dir_name : string + val load_average : float option + val log_analysis_crash : string + val log_analysis_file : string + val log_analysis_procedure : string + val log_analysis_recursion_timeout : string + val log_analysis_symops_timeout : string + val log_analysis_wallclock_timeout : string + val log_dir_name : string + val max_recursion : int + val meet_level : int + val models_dir : string + val models_jar : string + val models_src_dir : string + val multicore_dir_name : string + val ncpu : int + val nsnotification_center_checker_backend : bool + val os_type : os_type + val passthroughs : bool + val patterns_modeled_expensive : string * Yojson.Basic.json + val patterns_never_returning_null : string * Yojson.Basic.json + val patterns_skip_translation : string * Yojson.Basic.json + val per_procedure_parallelism : bool + val perf_stats_prefix : string + val pp_version : Format.formatter -> unit -> unit + val proc_stats_filename : string + val property_attributes : string + val report : bool + val report_condition_always_true_in_clang : bool + val report_custom_error : bool + val report_nullable_inconsistency : bool + val reporting_stats_dir_name : string + val save_compact_summaries : bool + val smt_output : bool + val source_file_extentions : string list + val sources : string list + val sourcepath : string option + val specs_dir_name : string + val specs_files_suffix : string + val start_filename : string + val taint_analysis : bool + val trace_absarray : bool + val undo_join : bool + val unsafe_unret : string + val use_jar_cache : bool + val version_string : string + val weak : string + val whitelisted_cpp_methods : string list + val whitelisted_cpp_classes : string list -val wrappers_dir : string +val wrappers_dir : string (** Configuration values specified by command-line options *) val anon_args : string list + val rest : string list + val abs_struct : int + val allow_specs_cleanup : bool + val analysis_path_regex_whitelist : analyzer -> string list + val analysis_path_regex_blacklist : analyzer -> string list + val analysis_blacklist_files_containing : analyzer -> string list + val analysis_stops : bool + val analysis_suppress_errors : analyzer -> string list + val analyzer : analyzer + val angelic_execution : bool + val annotation_reachability : bool + val annotation_reachability_custom_pairs : Yojson.Basic.json + val array_level : int + val ast_file : string option + val biabduction : bool + val blacklist : string option + val bootclasspath : string option + val bo_debug : int + val buck : bool + val buck_build_args : string list + val buck_cache_mode : bool -val buck_compilation_database : [ `Deps | `NoDeps ] option + +val buck_compilation_database : [`Deps | `NoDeps] option + val buck_out : string option + val bufferoverrun : bool + val bugs_csv : string option + val bugs_json : string option + val bugs_tests : string option + val bugs_txt : string option + val changed_files_index : string option + val calls_csv : string option -(** directory where the results of the capture phase are stored *) val captured_dir : string +(** directory where the results of the capture phase are stored *) val clang_biniou_file : string option + val clang_frontend_action_string : string + val clang_frontend_do_capture : bool + val clang_frontend_do_lint : bool + val clang_ignore_regex : string option + val clang_include_to_override_regex : string option + val cluster_cmdline : string option + val command : CLOpt.command + val compute_analytics : bool + val continue_capture : bool + val default_linters : bool + val linters_ignore_clang_failures : bool + val copy_propagation : bool + val crashcontext : bool + val create_harness : bool + val cxx : bool + val cxx_infer_headers : bool + val debug_level_analysis : int + val debug_level_capture : int + val debug_level_linters : int + val debug_exceptions : bool + val debug_mode : bool + val dependency_mode : bool + val developer_mode : bool + val differential_filter_files : string option -val differential_filter_set : [`Introduced | `Fixed | `Preexisting ] list + +val differential_filter_set : [`Introduced | `Fixed | `Preexisting] list + val disable_checks : string list + val dotty_cfg_libs : bool + val dump_duplicate_symbols : bool -val dynamic_dispatch : [ `None | `Interface | `Sound | `Lazy ] + +val dynamic_dispatch : [`None | `Interface | `Sound | `Lazy] + val enable_checks : string list + val eradicate : bool + val eradicate_condition_redundant : bool + val eradicate_field_not_mutable : bool + val eradicate_field_over_annotated : bool + val eradicate_optional_present : bool + val eradicate_propagate_return_nullable : bool + val eradicate_return_over_annotated : bool + val eradicate_debug : bool + val eradicate_verbose : bool + val fail_on_bug : bool + val failures_allowed : bool + val fcp_apple_clang : string option + val fcp_syntax_only : bool + val file_renamings : string option + val filter_paths : bool + val filtering : bool + val flavors : bool + val fragment_retains_view : bool + val from_json_report : string option + val frontend_debug : bool + val frontend_tests : bool + val frontend_stats : bool + val generated_classes : string option + val headers : bool + val icfg_dotty_outfile : string option + val ignore_trivial_traces : bool + val immutable_cast : bool + val infer_cache : string option + val iphoneos_target_sdk_version : string option + val iphoneos_target_sdk_version_skip_path : string list -val issues_fields : [`Issue_field_bug_class - | `Issue_field_kind - | `Issue_field_bug_type - | `Issue_field_qualifier - | `Issue_field_severity - | `Issue_field_visibility - | `Issue_field_line - | `Issue_field_column - | `Issue_field_procedure - | `Issue_field_procedure_id - | `Issue_field_procedure_start_line - | `Issue_field_file - | `Issue_field_bug_trace - | `Issue_field_key - | `Issue_field_hash - | `Issue_field_line_offset - | `Issue_field_procedure_id_without_crc - | `Issue_field_qualifier_contains_potential_exception_note] list + +val issues_fields : + [ `Issue_field_bug_class + | `Issue_field_kind + | `Issue_field_bug_type + | `Issue_field_qualifier + | `Issue_field_severity + | `Issue_field_visibility + | `Issue_field_line + | `Issue_field_column + | `Issue_field_procedure + | `Issue_field_procedure_id + | `Issue_field_procedure_start_line + | `Issue_field_file + | `Issue_field_bug_trace + | `Issue_field_key + | `Issue_field_hash + | `Issue_field_line_offset + | `Issue_field_procedure_id_without_crc + | `Issue_field_qualifier_contains_potential_exception_note ] + list + val iterations : int + val java_jar_compiler : string option + val javac_classes_out : string option + val javac_verbose_out : string + val jobs : int + val join_cond : int + val latex : string option + val linter : string option + val linters_def_file : string list + val linters_def_folder : string list + val linters_developer_mode : bool + val load_analysis_results : string option + val log_file : string + val makefile_cmdline : string + val maven : bool + val merge : bool + val ml_buckets : - [ `MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown ] list + [`MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown] list + val models_mode : bool + val modified_targets : string option + val monitor_prop_size : bool + val nelseg : bool + val no_translate_libs : bool + val objc_memory_model_on : bool + val only_cheap_debug : bool + val only_footprint : bool + val pmd_xml : bool + val precondition_stats : bool + val print_active_checkers : bool + val print_builtins : bool + val print_logs : bool + val print_types : bool + val print_using_diff : bool + val printf_args : bool + val procedures_per_process : int + val procs_csv : string option + val procs_xml : string option + val project_root : string + val quandary : bool + val quandary_endpoints : Yojson.Basic.json + val quandary_sanitizers : Yojson.Basic.json + val quandary_sources : Yojson.Basic.json + val quandary_sinks : Yojson.Basic.json + val quiet : bool + val reactive_mode : bool + val reactive_capture : bool + val repeated_calls : bool + val report_current : string option + val report_formatter : [`No_formatter | `Phabricator_formatter] + val report_hook : string option + val report_previous : string option + val tracing : bool + val reports_include_ml_loc : bool + val resolve_infer_eradicate_conflict : bool + val resource_leak : bool + val results_dir : string + val save_analysis_results : string option + val seconds_per_iteration : float option + val show_buckets : bool + val show_progress_bar : bool + val siof : bool + val siof_safe_methods : string list + val skip_analysis_in_path : string list + val skip_analysis_in_path_skips_compilation : bool + val skip_duplicated_types : bool + val skip_translation_headers : string list + val spec_abs_level : int + val specs_library : string list + val stacktrace : string option + val stacktraces_dir : string option + val stats_mode : bool + val stats_report : string option + val subtype_multirange : bool -val suggest_nullable: bool + +val suggest_nullable : bool + val svg : bool + val symops_per_iteration : int option + val test_filtering : bool + val testing_mode : bool + val threadsafety : bool + val threadsafe_aliases : Yojson.Basic.json + val trace_error : bool + val trace_ondemand : bool + val trace_join : bool + val trace_rearrange : bool + val tv_limit : int + val type_size : bool + val unsafe_malloc : bool + val whole_seconds : bool + val worklist_mode : int + val write_dotty : bool + val write_html : bool + val write_html_whitelist_regex : string list + val xcode_developer_dir : string option + val xcpretty : bool -val xml_specs : bool +val xml_specs : bool (** Global variables *) +val set_reference_and_call_function : 'a ref -> 'a -> ('b -> 'c) -> 'b -> 'c (** [set_reference_and_call_function ref val f x] calls f x with ref set to val. Restore the initial value also in case of exception. *) -val set_reference_and_call_function : 'a ref -> 'a -> ('b -> 'c) -> 'b -> 'c val arc_mode : bool ref @@ -382,17 +648,17 @@ val curr_language_is : language -> bool val footprint : bool ref +val run_in_footprint_mode : ('a -> 'b) -> 'a -> 'b (** Call f x with footprint set to true. Restore the initial value of footprint also in case of exception. *) -val run_in_footprint_mode : ('a -> 'b) -> 'a -> 'b +val run_in_re_execution_mode : ('a -> 'b) -> 'a -> 'b (** Call f x with footprint set to false. Restore the initial value of footprint also in case of exception. *) -val run_in_re_execution_mode : ('a -> 'b) -> 'a -> 'b val forcing_delayed_prints : bool ref -val pp_simple : bool ref +val pp_simple : bool ref (** Global variables with initial values specified by command-line options *) @@ -400,13 +666,13 @@ val abs_val : int ref val reset_abs_val : unit -> unit +val run_with_abs_val_equal_zero : ('a -> 'b) -> 'a -> 'b (** Call f x with abs_val set to zero. Restore the initial value also in case of exception. *) -val run_with_abs_val_equal_zero : ('a -> 'b) -> 'a -> 'b val allow_leak : bool ref -val clang_compilation_dbs : [ `Escaped of string | `Raw of string ] list ref +val clang_compilation_dbs : [`Escaped of string | `Raw of string] list ref (** Command Line Interface Documentation *) diff --git a/infer/src/base/DB.ml b/infer/src/base/DB.ml index 1636b8bbf..eb6c4e9a4 100644 --- a/infer/src/base/DB.ml +++ b/infer/src/base/DB.ml @@ -16,32 +16,32 @@ open! PVariant module F = Format module L = Logging - - let cutoff_length = 100 + let crc_token = '.' -let append_crc_cutoff ?(key="") name = +let append_crc_cutoff ?(key= "") name = let name_up_to_cutoff = - if String.length name <= cutoff_length - then name - else String.sub name ~pos:0 ~len:cutoff_length in + if String.length name <= cutoff_length then name else String.sub name ~pos:0 ~len:cutoff_length + in let crc_str = let name_for_crc = name ^ key in - Utils.string_crc_hex32 name_for_crc in + Utils.string_crc_hex32 name_for_crc + in name_up_to_cutoff ^ Char.to_string crc_token ^ crc_str (* Lengh of .crc part: 32 characters of digest, plus 1 character of crc_token *) let dot_crc_len = 1 + 32 -let strip_crc str = - String.slice str 0 (- dot_crc_len) +let strip_crc str = String.slice str 0 (-dot_crc_len) let string_crc_has_extension ~ext name_crc = let name = strip_crc name_crc in match Filename.split_extension name with - | (_, Some ext') -> String.equal ext ext' - | (_, None) -> false + | _, Some ext' + -> String.equal ext ext' + | _, None + -> false let curr_source_file_encoding = `Enc_crc @@ -49,16 +49,15 @@ let curr_source_file_encoding = `Enc_crc let source_file_encoding source_file = let source_file_s = SourceFile.to_string source_file in match curr_source_file_encoding with - | `Enc_base -> - Filename.basename source_file_s - | `Enc_path_with_underscores -> - Escape.escape_path source_file_s - | `Enc_crc -> - let base = Filename.basename source_file_s in + | `Enc_base + -> Filename.basename source_file_s + | `Enc_path_with_underscores + -> Escape.escape_path source_file_s + | `Enc_crc + -> let base = Filename.basename source_file_s in let dir = Filename.dirname source_file_s in append_crc_cutoff ~key:dir base - (** {2 Source Dirs} *) (** source directory: the directory inside the results dir corresponding to a source file *) @@ -70,7 +69,8 @@ let source_dir_to_string source_dir = source_dir (** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) let source_dir_get_internal_file source_dir extension = let source_dir_name = - append_crc_cutoff (Caml.Filename.remove_extension (Filename.basename source_dir)) in + append_crc_cutoff (Caml.Filename.remove_extension (Filename.basename source_dir)) + in let fname = source_dir_name ^ extension in Filename.concat source_dir fname @@ -84,14 +84,17 @@ let find_source_dirs () = let files_in_results_dir = Array.to_list (Sys.readdir Config.captured_dir) in let add_cg_files_from_dir dir = let files = Array.to_list (Sys.readdir dir) in - List.iter ~f:(fun fname -> + List.iter + ~f:(fun fname -> let path = Filename.concat dir fname in if Filename.check_suffix path ".cg" then source_dirs := dir :: !source_dirs) - files in - List.iter ~f:(fun fname -> + files + in + List.iter + ~f:(fun fname -> let dir = Filename.concat Config.captured_dir fname in if Sys.is_directory dir = `Yes then add_cg_files_from_dir dir) - files_in_results_dir; + files_in_results_dir ; List.rev !source_dirs (** {2 Filename} *) @@ -114,31 +117,26 @@ let file_exists path = Sys.file_exists path = `Yes let file_remove = Sys.remove -module FilenameSet = Caml.Set.Make( - struct - type t = filename [@@deriving compare] - end) +module FilenameSet = Caml.Set.Make (struct + type t = filename [@@deriving compare] +end) -module FilenameMap = Caml.Map.Make( - struct - type t = filename [@@deriving compare] - end) +module FilenameMap = Caml.Map.Make (struct + type t = filename [@@deriving compare] +end) (** Return the time when a file was last modified. The file must exist. *) -let file_modified_time ?(symlink=false) fname = +let file_modified_time ?(symlink= false) fname = try let stat = (if symlink then Unix.lstat else Unix.stat) fname in stat.Unix.st_mtime - with Unix.Unix_error _ -> - failwithf "File %s does not exist." fname + with Unix.Unix_error _ -> failwithf "File %s does not exist." fname let filename_create_dir fname = let dirname = Filename.dirname fname in - if (Sys.file_exists dirname) <> `Yes - then Utils.create_dir dirname + if Sys.file_exists dirname <> `Yes then Utils.create_dir dirname -let read_whole_file fd = - In_channel.input_all (Unix.in_channel_of_descr fd) +let read_whole_file fd = In_channel.input_all (Unix.in_channel_of_descr fd) (** Update the file contents with the update function provided. If the directory does not exist, it is created. @@ -147,40 +145,34 @@ let read_whole_file fd = let update_file_with_lock dir fname update = let reset_file fd = let n = Unix.lseek fd 0L ~mode:Unix.SEEK_SET in - if n <> 0L then - begin - L.internal_error "reset_file: lseek fail@."; - assert false - end in - Utils.create_dir dir; + if n <> 0L then ( + L.internal_error "reset_file: lseek fail@." ; + assert false ) + in + Utils.create_dir dir ; let path = Filename.concat dir fname in - let fd = Unix.openfile path ~mode:Unix.[O_CREAT; O_SYNC; O_RDWR] ~perm:0o640 in - Unix.lockf fd ~mode:Unix.F_LOCK ~len:0L; + let fd = Unix.openfile path ~mode:Unix.([O_CREAT; O_SYNC; O_RDWR]) ~perm:0o640 in + Unix.lockf fd ~mode:Unix.F_LOCK ~len:0L ; let buf = read_whole_file fd in - reset_file fd; + reset_file fd ; let str = update buf in let i = Unix.write fd ~buf:str ~pos:0 ~len:(String.length str) in if Int.equal i (String.length str) then ( - Unix.lockf fd ~mode:Unix.F_ULOCK ~len:0L; - Unix.close fd - ) else ( - L.internal_error "@\nsave_with_lock: fail on path: %s@." path; - assert false - ) + Unix.lockf fd ~mode:Unix.F_ULOCK ~len:0L ; Unix.close fd ) + else ( + L.internal_error "@\nsave_with_lock: fail on path: %s@." path ; + assert false ) (** Read a file using a lock to allow write attempts in parallel. *) let read_file_with_lock dir fname = let path = Filename.concat dir fname in try - let fd = Unix.openfile path ~mode:Unix.[O_RSYNC; O_RDONLY] ~perm:0o646 in + let fd = Unix.openfile path ~mode:Unix.([O_RSYNC; O_RDONLY]) ~perm:0o646 in try - Unix.lockf fd ~mode:Unix.F_RLOCK ~len:0L; + Unix.lockf fd ~mode:Unix.F_RLOCK ~len:0L ; let buf = read_whole_file fd in - Unix.lockf fd ~mode:Unix.F_ULOCK ~len:0L; - Unix.close fd; - Some buf - with Unix.Unix_error _ -> - failwith "read_file_with_lock: Unix error" + Unix.lockf fd ~mode:Unix.F_ULOCK ~len:0L ; Unix.close fd ; Some buf + with Unix.Unix_error _ -> failwith "read_file_with_lock: Unix error" with Unix.Unix_error _ -> None (** {2 Results Directory} *) @@ -191,28 +183,33 @@ module Results_dir = struct (** kind of path: specifies how to interpret a path *) type path_kind = - | Abs_root - (** absolute path implicitly rooted at the root of the results dir *) + | Abs_root (** absolute path implicitly rooted at the root of the results dir *) | Abs_source_dir of SourceFile.t - (** absolute path implicitly rooted at the source directory for the file *) - | Rel - (** relative path *) + (** absolute path implicitly rooted at the source directory for the file *) + | Rel (** relative path *) let filename_from_base base path = let rec f = function - | [] -> base - | name:: names -> - Filename.concat (f names) (if String.equal name ".." then Filename.parent_dir_name else name) in + | [] + -> base + | name :: names + -> Filename.concat (f names) + (if String.equal name ".." then Filename.parent_dir_name else name) + in f (List.rev path) (** convert a path to a filename *) let path_to_filename pk path = - let base = match pk with - | Abs_root -> Config.results_dir - | Abs_source_dir source -> - let dir = source_dir_from_source_file source in + let base = + match pk with + | Abs_root + -> Config.results_dir + | Abs_source_dir source + -> let dir = source_dir_from_source_file source in source_dir_to_string dir - | Rel -> Filename.current_dir_name in + | Rel + -> Filename.current_dir_name + in filename_from_base base path (** directory of spec files *) @@ -220,35 +217,38 @@ module Results_dir = struct (** initialize the results directory *) let init source = - if SourceFile.is_invalid source then - invalid_arg "Invalid source file passed"; - Utils.create_dir Config.results_dir; - Utils.create_dir specs_dir; - Utils.create_dir (path_to_filename Abs_root [Config.attributes_dir_name]); - Utils.create_dir (path_to_filename Abs_root [Config.captured_dir_name]); + if SourceFile.is_invalid source then invalid_arg "Invalid source file passed" ; + Utils.create_dir Config.results_dir ; + Utils.create_dir specs_dir ; + Utils.create_dir (path_to_filename Abs_root [Config.attributes_dir_name]) ; + Utils.create_dir (path_to_filename Abs_root [Config.captured_dir_name]) ; Utils.create_dir (path_to_filename (Abs_source_dir source) []) let clean_specs_dir () = - Utils.create_dir specs_dir; (* create dir just in case it doesn't exist to avoid errors *) + Utils.create_dir specs_dir ; + (* create dir just in case it doesn't exist to avoid errors *) let files_to_remove = Array.map ~f:(Filename.concat specs_dir) (Sys.readdir specs_dir) in Array.iter ~f:Sys.remove files_to_remove (** create a file at the given path, creating any missing directories *) let create_file pk path = let rec create = function - | [] -> - let fname = path_to_filename pk [] in - Utils.create_dir fname; - fname - | name:: names -> - let new_path = Filename.concat (create names) name in - Utils.create_dir new_path; - new_path in - let filename, dir_path = match List.rev path with - | filename:: dir_path -> filename, dir_path - | [] -> raise (Failure "create_path") in + | [] + -> let fname = path_to_filename pk [] in + Utils.create_dir fname ; fname + | name :: names + -> let new_path = Filename.concat (create names) name in + Utils.create_dir new_path ; new_path + in + let filename, dir_path = + match List.rev path with + | filename :: dir_path + -> (filename, dir_path) + | [] + -> raise (Failure "create_path") + in let full_fname = Filename.concat (create dir_path) filename in - Unix.openfile full_fname ~mode:Unix.[O_WRONLY; O_CREAT; O_TRUNC] ~perm:0o777 + Unix.openfile full_fname ~mode:Unix.([O_WRONLY; O_CREAT; O_TRUNC]) ~perm:0o777 end let global_tenv_fname = @@ -256,20 +256,19 @@ let global_tenv_fname = filename_concat Config.captured_dir basename let is_source_file path = - List.exists - ~f:(fun ext -> Filename.check_suffix path ext) - Config.source_file_extentions + List.exists ~f:(fun ext -> Filename.check_suffix path ext) Config.source_file_extentions -let infer_start_time = lazy +let infer_start_time = + ( lazy (file_modified_time (Results_dir.path_to_filename Results_dir.Abs_root [Config.start_filename])) + ) (** Return whether filename was updated after analysis started. File doesn't have to exist *) let file_was_updated_after_start fname = if file_exists fname then let file_mtime = file_modified_time fname in file_mtime > Lazy.force infer_start_time - else - (* since file doesn't exist, it wasn't modified *) + else (* since file doesn't exist, it wasn't modified *) false (** Mark a file as updated by changing its timestamps to be one second in the future. @@ -283,15 +282,12 @@ let fold_paths_matching ~dir ~p ~init ~f = let rec paths path_list dir = Array.fold ~f:(fun acc file -> - let path = dir ^/ file in - if Sys.is_directory path = `Yes then (paths acc path) - else if p path then f path acc - else acc) - ~init:path_list - (Sys.readdir dir) in + let path = dir ^/ file in + if Sys.is_directory path = `Yes then paths acc path else if p path then f path acc else acc) + ~init:path_list (Sys.readdir dir) + in paths init dir (** Return all absolute paths recursively under root_dir, matching the given matcher function p *) -let paths_matching dir p = - fold_paths_matching ~dir ~p ~init:[] ~f:(fun x xs -> x :: xs) +let paths_matching dir p = fold_paths_matching ~dir ~p ~init:[] ~f:(fun x xs -> x :: xs) diff --git a/infer/src/base/DB.mli b/infer/src/base/DB.mli index 01866da5d..8aef13def 100644 --- a/infer/src/base/DB.mli +++ b/infer/src/base/DB.mli @@ -20,25 +20,32 @@ type filename [@@deriving compare] val equal_filename : filename -> filename -> bool module FilenameSet : Caml.Set.S with type elt = filename + module FilenameMap : Caml.Map.S with type key = filename val filename_from_string : string -> filename + val filename_to_string : filename -> string + val chop_extension : filename -> filename + val filename_concat : filename -> string -> filename + val filename_add_suffix : filename -> string -> filename + val file_exists : filename -> bool + val file_remove : filename -> unit -(** Return the time when a file was last modified. The file must exist. *) val file_modified_time : ?symlink:bool -> filename -> float +(** Return the time when a file was last modified. The file must exist. *) +val mark_file_updated : string -> unit (** Mark a file as updated by changing its timestamps to be one second in the future. This guarantees that it appears updated after start. *) -val mark_file_updated : string -> unit -(** Return whether filename was updated after analysis started. File doesn't have to exist *) val file_was_updated_after_start : filename -> bool +(** Return whether filename was updated after analysis started. File doesn't have to exist *) (** {2 Results Directory} *) @@ -48,81 +55,79 @@ module Results_dir : sig (** kind of path: specifies how to interpret a path *) type path_kind = - | Abs_root - (** absolute path implicitly rooted at the root of the results dir *) + | Abs_root (** absolute path implicitly rooted at the root of the results dir *) | Abs_source_dir of SourceFile.t - (** absolute path implicitly rooted at the source directory for the file *) - | Rel - (** relative path *) + (** absolute path implicitly rooted at the source directory for the file *) + | Rel (** relative path *) - (** convert a path to a filename *) val path_to_filename : path_kind -> path -> filename + (** convert a path to a filename *) - (** directory of spec files *) val specs_dir : filename + (** directory of spec files *) - (** Initialize the results directory *) val init : SourceFile.t -> unit + (** Initialize the results directory *) - (** Clean up specs directory *) val clean_specs_dir : unit -> unit + (** Clean up specs directory *) - (** create a file at the given path, creating any missing directories *) val create_file : path_kind -> path -> Unix.File_descr.t + (** create a file at the given path, creating any missing directories *) end +val append_crc_cutoff : ?key:string -> string -> string (** Append a crc to the string, using string_crc_hex32. Cut the string if it exceeds the cutoff limit. Use an optional key to compute the crc. *) -val append_crc_cutoff : ?key:string -> string -> string -(** Remove the crc from the string, and check if it has the given extension *) val string_crc_has_extension : ext:string -> string -> bool +(** Remove the crc from the string, and check if it has the given extension *) -(** Strip any crc attached to any string generated by string_append_crc_cutoff *) val strip_crc : string -> string +(** Strip any crc attached to any string generated by string_append_crc_cutoff *) -(** string encoding of a source file (including path) as a single filename *) val source_file_encoding : SourceFile.t -> string +(** string encoding of a source file (including path) as a single filename *) (** {2 Source Dirs} *) (** source directory: the directory inside the results dir corresponding to a source file *) type source_dir [@@deriving compare] -(** expose the source dir as a string *) val source_dir_to_string : source_dir -> string +(** expose the source dir as a string *) -(** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) val source_dir_get_internal_file : source_dir -> string -> filename +(** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) -(** get the source directory corresponding to a source file *) val source_dir_from_source_file : SourceFile.t -> source_dir +(** get the source directory corresponding to a source file *) -(** create the directory containing the file bane *) val filename_create_dir : filename -> unit +(** create the directory containing the file bane *) -(** Find the source directories in the current results dir *) val find_source_dirs : unit -> source_dir list +(** Find the source directories in the current results dir *) -(** Read a file using a lock to allow write attempts in parallel. *) val read_file_with_lock : string -> string -> string option +(** Read a file using a lock to allow write attempts in parallel. *) +val update_file_with_lock : string -> string -> (string -> string) -> unit (** Update the file contents with the update function provided. If the directory does not exist, it is created. If the file does not exist, it is created, and update is given the empty string. A lock is used to allow write attempts in parallel. *) -val update_file_with_lock : string -> string -> (string -> string) -> unit -(** get the path of the global type environment (only used in Java) *) val global_tenv_fname : filename +(** get the path of the global type environment (only used in Java) *) +val is_source_file : string -> bool (** Check if a path is a Java, C, C++ or Objectve C source file according to the file extention *) -val is_source_file: string -> bool -(** Fold over all file paths recursively under [dir] which match [p]. *) val fold_paths_matching : dir:filename -> p:(filename -> bool) -> init:'a -> f:(filename -> 'a -> 'a) -> 'a +(** Fold over all file paths recursively under [dir] which match [p]. *) -(** Return all file paths recursively under the given directory which match the given predicate *) val paths_matching : string -> (string -> bool) -> string list +(** Return all file paths recursively under the given directory which match the given predicate *) diff --git a/infer/src/base/Epilogues.ml b/infer/src/base/Epilogues.ml index 235847a7d..769b72fff 100644 --- a/infer/src/base/Epilogues.ml +++ b/infer/src/base/Epilogues.ml @@ -7,29 +7,29 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module F = Format (* Run the epilogues when we get SIGINT (Control-C). We do not want to mask SIGINT unless at least one epilogue has been registered, so make this value lazy. *) -let activate_run_epilogues_on_signal = lazy ( - let run_epilogues_on_signal s = - F.eprintf "*** %s: Caught %s, time to die@." (Filename.basename Sys.executable_name) - (Signal.to_string s); - (* Epilogues are registered with [at_exit] so exiting will make them run. *) - exit 0 in - Signal.Expert.handle Signal.int run_epilogues_on_signal -) +let activate_run_epilogues_on_signal = + ( lazy + (let run_epilogues_on_signal s = + F.eprintf "*** %s: Caught %s, time to die@." (Filename.basename Sys.executable_name) + (Signal.to_string s) ; + (* Epilogues are registered with [at_exit] so exiting will make them run. *) + exit 0 + in + Signal.Expert.handle Signal.int run_epilogues_on_signal) ) let register ~f desc = let f_no_exn () = if not !ProcessPool.in_child then - try - f () + try f () with exn -> - F.eprintf "Error while running epilogue \"%s\":@ %a.@ Powering through...@." - desc Exn.pp exn in + F.eprintf "Error while running epilogue \"%s\":@ %a.@ Powering through...@." desc Exn.pp + exn + in (* We call `exit` in a bunch of places, so register the epilogues with [at_exit]. *) - Pervasives.at_exit f_no_exn; + Pervasives.at_exit f_no_exn ; (* Register signal masking. *) Lazy.force activate_run_epilogues_on_signal diff --git a/infer/src/base/Epilogues.mli b/infer/src/base/Epilogues.mli index ad51e2aa4..f964a5547 100644 --- a/infer/src/base/Epilogues.mli +++ b/infer/src/base/Epilogues.mli @@ -6,8 +6,9 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd +val register : f:(unit -> unit) -> string -> unit (** Register a function to run when the program exits or is interrupted. Registered functions are run in the reverse order in which they were registered. *) -val register : f:(unit -> unit) -> string -> unit diff --git a/infer/src/base/Escape.ml b/infer/src/base/Escape.ml index 62f49e36b..2b53fa44d 100644 --- a/infer/src/base/Escape.ml +++ b/infer/src/base/Escape.ml @@ -18,74 +18,103 @@ let escape_map map_fun s = let buf = Buffer.create len in for i = 0 to len - 1 do let c = String.unsafe_get s i in - match map_fun c with - | None -> Buffer.add_char buf c - | Some s' -> Buffer.add_string buf s' - done; + match map_fun c with None -> Buffer.add_char buf c | Some s' -> Buffer.add_string buf s' + done ; Buffer.contents buf let escape_csv s = let map = function - | '"' -> Some "\"\"" - | c when Char.to_int c > 127 -> Some "?" (* non-ascii character: escape *) - | _ -> None in + | '"' + -> Some "\"\"" + | c when Char.to_int c > 127 + -> Some "?" (* non-ascii character: escape *) + | _ + -> None + in escape_map map s let escape_xml s = let map = function - | '"' -> (* on next line to avoid bad indentation *) + | '"' + -> (* on next line to avoid bad indentation *) Some """ - | '>' -> Some ">" - | '<' -> Some "<" - | '&' -> Some "&" - | '%' -> Some "%" - | c when Char.to_int c > 127 -> (* non-ascii character: escape *) + | '>' + -> Some ">" + | '<' + -> Some "<" + | '&' + -> Some "&" + | '%' + -> Some "%" + | c when Char.to_int c > 127 + -> (* non-ascii character: escape *) Some ("&#" ^ string_of_int (Char.to_int c) ^ ";") - | _ -> None in + | _ + -> None + in escape_map map s let escape_url s = let map = function - | '!' -> Some "%21" - | '#' -> Some "%23" - | '$' -> Some "%24" - | '&' -> Some "%26" - | '\'' -> Some "%27" - | '(' -> Some "%28" - | ')' -> Some "%29" - | '*' -> Some "%2A" - | '+' -> Some "%2B" - | ',' -> Some "%2C" - | '/' -> Some "%2F" - | ':' -> Some "%3A" - | ';' -> Some "%3B" - | '=' -> Some "%3D" - | '?' -> Some "%3F" - | '@' -> Some "%40" - | '[' -> Some "%5B" - | ']' -> Some "%5D" - | _ -> None in + | '!' + -> Some "%21" + | '#' + -> Some "%23" + | '$' + -> Some "%24" + | '&' + -> Some "%26" + | '\'' + -> Some "%27" + | '(' + -> Some "%28" + | ')' + -> Some "%29" + | '*' + -> Some "%2A" + | '+' + -> Some "%2B" + | ',' + -> Some "%2C" + | '/' + -> Some "%2F" + | ':' + -> Some "%3A" + | ';' + -> Some "%3B" + | '=' + -> Some "%3D" + | '?' + -> Some "%3F" + | '@' + -> Some "%40" + | '[' + -> Some "%5B" + | ']' + -> Some "%5D" + | _ + -> None + in escape_map map s let escape_dotty s = - let map = function - | '"' -> Some "\\\"" - | '\\' -> Some "\\\\" - | _ -> None in + let map = function '"' -> Some "\\\"" | '\\' -> Some "\\\\" | _ -> None in escape_map map s let escape_path s = let map = function - | c -> - if String.equal (Char.escaped c) Filename.dir_sep - then Some "_" - else None in + | c + -> if String.equal (Char.escaped c) Filename.dir_sep then Some "_" else None + in escape_map map s (* Python 2 sucks at utf8 so do not write unicode file names to disk as Python may need to see them *) let escape_filename s = let map = function - | c when Char.to_int c > 127 -> Some "?" (* non-ascii character: escape *) - | _ -> None in + | c when Char.to_int c > 127 + -> Some "?" (* non-ascii character: escape *) + | _ + -> None + in escape_map map s diff --git a/infer/src/base/Escape.mli b/infer/src/base/Escape.mli index e91dd3741..f640f09f8 100644 --- a/infer/src/base/Escape.mli +++ b/infer/src/base/Escape.mli @@ -12,23 +12,22 @@ open! IStd (** Escape a string for use in a CSV or XML file: replace reserved characters with escape sequences *) -(** escape a string specifying the per character escaping function *) val escape_map : (char -> string option) -> string -> string +(** escape a string specifying the per character escaping function *) -(** escape a string to be used in a dotty file *) val escape_dotty : string -> string +(** escape a string to be used in a dotty file *) -(** escape a string to be used in a csv file *) val escape_csv : string -> string +(** escape a string to be used in a csv file *) -(** escape a path replacing the directory separator with an underscore *) val escape_path : string -> string +(** escape a path replacing the directory separator with an underscore *) -(** escape a string to be used in an xml file *) val escape_xml : string -> string - +(** escape a string to be used in an xml file *) val escape_url : string -> string -(** escape a string to be used as a file name *) val escape_filename : string -> string +(** escape a string to be used as a file name *) diff --git a/infer/src/base/IList.ml b/infer/src/base/IList.ml index f16185902..6fc6c844f 100644 --- a/infer/src/base/IList.ml +++ b/infer/src/base/IList.ml @@ -8,74 +8,70 @@ *) (** like map, but returns the original list if unchanged *) -let map_changed (f : 'a -> 'a) l = +let map_changed (f: 'a -> 'a) l = let l', changed = List.fold_left (fun (l_acc, changed) e -> - let e' = f e in - e' :: l_acc, changed || e' != e) - ([], false) - l in - if changed - then List.rev l' - else l + let e' = f e in + (e' :: l_acc, changed || e' != e)) + ([], false) l + in + if changed then List.rev l' else l (** like filter, but returns the original list if unchanged *) -let filter_changed (f : 'a -> bool) l = +let filter_changed (f: 'a -> bool) l = let l', changed = List.fold_left - (fun (l_acc, changed) e -> - if f e - then e :: l_acc, changed - else l_acc, true) - ([], false) - l in - if changed - then List.rev l' - else l + (fun (l_acc, changed) e -> if f e then (e :: l_acc, changed) else (l_acc, true)) + ([], false) l + in + if changed then List.rev l' else l (** Remove consecutive equal irrelevant elements from a list (according to the given comparison and relevance functions) *) let remove_irrelevant_duplicates compare relevant l = let rec remove compare acc = function - | [] -> List.rev acc - | [x] -> List.rev (x:: acc) - | x:: ((y:: l'') as l') -> - if compare x y = 0 then begin - match relevant x, relevant y with - | false, _ -> remove compare acc l' - | true, false -> remove compare acc (x:: l'') - | true, true -> remove compare (x:: acc) l' - end - else remove compare (x:: acc) l' in + | [] + -> List.rev acc + | [x] + -> List.rev (x :: acc) + | x :: (y :: l'' as l') + -> if compare x y = 0 then + match (relevant x, relevant y) with + | false, _ + -> remove compare acc l' + | true, false + -> remove compare acc (x :: l'') + | true, true + -> remove compare (x :: acc) l' + else remove compare (x :: acc) l' + in remove compare [] l (** The function works on sorted lists without duplicates *) let rec merge_sorted_nodup compare res xs1 xs2 = - match xs1, xs2 with - | [], _ -> - List.rev_append res xs2 - | _, [] -> - List.rev_append res xs1 - | x1 :: xs1', x2 :: xs2' -> - let n = compare x1 x2 in - if n = 0 then - merge_sorted_nodup compare (x1 :: res) xs1' xs2' - else if n < 0 then - merge_sorted_nodup compare (x1 :: res) xs1' xs2 - else - merge_sorted_nodup compare (x2 :: res) xs1 xs2' + match (xs1, xs2) with + | [], _ + -> List.rev_append res xs2 + | _, [] + -> List.rev_append res xs1 + | x1 :: xs1', x2 :: xs2' + -> let n = compare x1 x2 in + if n = 0 then merge_sorted_nodup compare (x1 :: res) xs1' xs2' + else if n < 0 then merge_sorted_nodup compare (x1 :: res) xs1' xs2 + else merge_sorted_nodup compare (x2 :: res) xs1 xs2' let intersect compare l1 l2 = let l1_sorted = List.sort compare l1 in let l2_sorted = List.sort compare l2 in - let rec f l1 l2 = match l1, l2 with - | ([], _) | (_,[]) -> false - | (x1:: l1', x2:: l2') -> - let x_comparison = compare x1 x2 in - if x_comparison = 0 then true - else if x_comparison < 0 then f l1' l2 - else f l1 l2' in + let rec f l1 l2 = + match (l1, l2) with + | [], _ | _, [] + -> false + | x1 :: l1', x2 :: l2' + -> let x_comparison = compare x1 x2 in + if x_comparison = 0 then true else if x_comparison < 0 then f l1' l2 else f l1 l2' + in f l1_sorted l2_sorted let inter compare xs ys = @@ -83,24 +79,17 @@ let inter compare xs ys = let rev_xs = rev_sort xs in let rev_ys = rev_sort ys in let rec inter_ is rev_xxs rev_yys = - match rev_xxs, rev_yys with - | ([], _) | (_, []) -> - is - | (x :: rev_xs, y :: rev_ys) -> - let c = compare x y in - if c = 0 then - inter_ (x :: is) rev_xs rev_ys - else if c < 0 then - inter_ is rev_xs rev_yys - else - inter_ is rev_xxs rev_ys + match (rev_xxs, rev_yys) with + | [], _ | _, [] + -> is + | x :: rev_xs, y :: rev_ys + -> let c = compare x y in + if c = 0 then inter_ (x :: is) rev_xs rev_ys + else if c < 0 then inter_ is rev_xs rev_yys + else inter_ is rev_xxs rev_ys in inter_ [] rev_xs rev_ys let to_string f l = - let rec aux l = - match l with - | [] -> "" - | s:: [] -> (f s) - | s:: rest -> (f s)^", "^(aux rest) in - "["^(aux l)^"]" + let rec aux l = match l with [] -> "" | [s] -> f s | s :: rest -> f s ^ ", " ^ aux rest in + "[" ^ aux l ^ "]" diff --git a/infer/src/base/IList.mli b/infer/src/base/IList.mli index e091ab6cb..d52a0e59e 100644 --- a/infer/src/base/IList.mli +++ b/infer/src/base/IList.mli @@ -7,23 +7,23 @@ * of patent rights can be found in the PATENTS file in the same directory. *) -(** like map, but returns the original list if unchanged *) val map_changed : ('a -> 'a) -> 'a list -> 'a list +(** like map, but returns the original list if unchanged *) -(** like filter, but returns the original list if unchanged *) val filter_changed : ('a -> bool) -> 'a list -> 'a list +(** like filter, but returns the original list if unchanged *) -(** Remove consecutive equal irrelevant elements from a list (according to the given comparison and relevance functions) *) val remove_irrelevant_duplicates : ('a -> 'a -> int) -> ('a -> bool) -> 'a list -> 'a list +(** Remove consecutive equal irrelevant elements from a list (according to the given comparison and relevance functions) *) -(** The function works on sorted lists without duplicates *) val merge_sorted_nodup : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list -> 'a list +(** The function works on sorted lists without duplicates *) +val intersect : ('a -> 'a -> int) -> 'a list -> 'a list -> bool (** Returns whether there is an intersection in the elements of the two lists. The compare function is required to sort the lists. *) -val intersect : ('a -> 'a -> int) -> 'a list -> 'a list -> bool -(** [inter cmp xs ys] are the elements in both [xs] and [ys], sorted according to [cmp]. *) val inter : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list +(** [inter cmp xs ys] are the elements in both [xs] and [ys], sorted according to [cmp]. *) val to_string : ('a -> string) -> 'a list -> string diff --git a/infer/src/base/IStd.ml b/infer/src/base/IStd.ml index 075ae3620..68e9a228a 100644 --- a/infer/src/base/IStd.ml +++ b/infer/src/base/IStd.ml @@ -10,65 +10,61 @@ include Core module Unix_ = struct - let improve f make_arg_sexps = - try f () with - | Unix.Unix_error (e, s, _) -> - let buf = Buffer.create 100 in - let fmt = Format.formatter_of_buffer buf in - Format.pp_set_margin fmt 10000; - Sexp.pp_hum fmt ( - Sexp.List ( - List.map (make_arg_sexps ()) - ~f:(fun (name, value) -> Sexp.List [Sexp.Atom name; value]))); - Format.pp_print_flush fmt (); - let arg_str = Buffer.contents buf in - raise (Unix.Unix_error (e, s, arg_str)) + try f () + with Unix.Unix_error (e, s, _) -> + let buf = Buffer.create 100 in + let fmt = Format.formatter_of_buffer buf in + Format.pp_set_margin fmt 10000 ; + Sexp.pp_hum fmt + (Sexp.List + (List.map (make_arg_sexps ()) ~f:(fun (name, value) -> Sexp.List [Sexp.Atom name; value]))) ; + Format.pp_print_flush fmt () ; + let arg_str = Buffer.contents buf in + raise (Unix.Unix_error (e, s, arg_str)) - let create_process_redirect - ~prog ~args ?(stdin = Unix.stdin) ?(stdout = Unix.stdout) ?(stderr = Unix.stderr) () = + let create_process_redirect ~prog ~args ?(stdin= Unix.stdin) ?(stdout= Unix.stdout) + ?(stderr= Unix.stderr) () = improve (fun () -> - let prog_args = Array.of_list (prog :: args) in - Caml.UnixLabels.create_process ~prog ~args:prog_args ~stdin ~stdout ~stderr - |> Pid.of_int) + let prog_args = Array.of_list (prog :: args) in + Caml.UnixLabels.create_process ~prog ~args:prog_args ~stdin ~stdout ~stderr |> Pid.of_int) (fun () -> - [("prog", Sexp.Atom prog); - ("args", Sexplib.Conv.sexp_of_list (fun a -> Sexp.Atom a) args)]) + [("prog", Sexp.Atom prog); ("args", Sexplib.Conv.sexp_of_list (fun a -> Sexp.Atom a) args)] + ) let fork_redirect_exec_wait ~prog ~args ?stdin ?stdout ?stderr () = Unix.waitpid (create_process_redirect ~prog ~args ?stdin ?stdout ?stderr ()) |> Unix.Exit_or_signal.or_error |> ok_exn - end module List_ = struct let rec fold_until ~init ~f l = - match l, init with - | _, `Stop init' - | [], `Continue init' -> init' - | h :: t, `Continue _ -> fold_until ~init:(f init h) ~f t + match (l, init) with + | _, `Stop init' | [], `Continue init' + -> init' + | h :: t, `Continue _ + -> fold_until ~init:(f init h) ~f t end (* Use Caml.Set since they are serialized using Marshal, and Core.Std.Set includes the comparison function in its representation, which Marshal cannot (de)serialize. *) -module IntSet = Caml.Set.Make(Int) - +module IntSet = Caml.Set.Make (Int) (* Compare police: generic compare mostly disabled. *) let compare = No_polymorphic_compare.compare + let equal = No_polymorphic_compare.equal -let (=) = No_polymorphic_compare.(=) + +let ( = ) = No_polymorphic_compare.( = ) module PVariant = struct (* Equality for polymorphic variants *) - let (=) (v1 : [> ]) (v2 : [> ]) = Polymorphic_compare.(=) v1 v2 + let ( = ) (v1: [> ]) (v2: [> ]) = Polymorphic_compare.( = ) v1 v2 end let failwithf fmt = - Format.kfprintf (fun _ -> failwith (Format.flush_str_formatter ())) - Format.str_formatter fmt + Format.kfprintf (fun _ -> failwith (Format.flush_str_formatter ())) Format.str_formatter fmt let invalid_argf fmt = - Format.kfprintf (fun _ -> invalid_arg (Format.flush_str_formatter ())) - Format.str_formatter fmt + Format.kfprintf (fun _ -> invalid_arg (Format.flush_str_formatter ())) Format.str_formatter fmt diff --git a/infer/src/base/Latex.ml b/infer/src/base/Latex.ml index 1b6fb3bc2..8b1869af9 100644 --- a/infer/src/base/Latex.ml +++ b/infer/src/base/Latex.ml @@ -9,59 +9,60 @@ *) open! IStd - module F = Format (** Produce output in latex *) -type style = - | Boldface - | Roman - | Italics +type style = Boldface | Roman | Italics (** Convert a string to a latex-friendly format *) let convert_string s = - if String.contains s '_' then begin + if String.contains s '_' then let cnt = ref 0 in let s' = ref "" in let f c = - if Char.equal c '_' then s' := !s' ^ "\\_" - else s' := !s' ^ Char.escaped (String.get s !cnt); - incr cnt in - String.iter ~f s; - !s' - end + if Char.equal c '_' then s' := !s' ^ "\\_" else s' := !s' ^ Char.escaped s.[!cnt] ; + incr cnt + in + String.iter ~f s ; !s' else s (** Print a string in the given style, after converting it into latex-friendly format *) let pp_string style f s = let converted = convert_string s in match style with - | Boldface -> F.fprintf f "\\textbf{%s}" converted - | Roman -> F.fprintf f "\\textrm{%s}" converted - | Italics -> F.fprintf f "\\textit{%s}" converted + | Boldface + -> F.fprintf f "\\textbf{%s}" converted + | Roman + -> F.fprintf f "\\textrm{%s}" converted + | Italics + -> F.fprintf f "\\textit{%s}" converted -let color_to_string (c : Pp.color) = +let color_to_string (c: Pp.color) = match c with - | Black -> "black" - | Blue -> "blue" - | Green -> "green" - | Orange -> "orange" - | Red -> "red" + | Black + -> "black" + | Blue + -> "blue" + | Green + -> "green" + | Orange + -> "orange" + | Red + -> "red" (** Print color command *) -let pp_color f color = - F.fprintf f "\\color{%s}" (color_to_string color) +let pp_color f color = F.fprintf f "\\color{%s}" (color_to_string color) (** Prelude for a latex file with the given author and title *) let pp_begin f (author, title, table_of_contents) = let pp_toc f () = if table_of_contents then F.fprintf f "\\tableofcontents@\n" else () in - F.fprintf f "\\documentclass{article}@\n\\usepackage{hyperref}@\n\\usepackage{color}@\n\\author{%s}@\n\\title{%s}@\n\\begin{document}@\n\\maketitle@\n%a" author title pp_toc () + F.fprintf f + "\\documentclass{article}@\n\\usepackage{hyperref}@\n\\usepackage{color}@\n\\author{%s}@\n\\title{%s}@\n\\begin{document}@\n\\maketitle@\n%a" + author title pp_toc () (** Epilogue for a latex file *) -let pp_end f () = - F.fprintf f "\\end{document}@\n" +let pp_end f () = F.fprintf f "\\end{document}@\n" (** Section with the given title *) -let pp_section f title = - F.fprintf f "\\section{%s}@\n" title +let pp_section f title = F.fprintf f "\\section{%s}@\n" title diff --git a/infer/src/base/Latex.mli b/infer/src/base/Latex.mli index b4ff0e7af..f262c255c 100644 --- a/infer/src/base/Latex.mli +++ b/infer/src/base/Latex.mli @@ -10,25 +10,22 @@ open! IStd -type style = - | Boldface - | Roman - | Italics +type style = Boldface | Roman | Italics -(** Convert a string to a latex-friendly format *) val convert_string : string -> string +(** Convert a string to a latex-friendly format *) -(** Print a string in the given style, after converting it into latex-friendly format *) val pp_string : style -> Format.formatter -> string -> unit +(** Print a string in the given style, after converting it into latex-friendly format *) -(** Print color command *) val pp_color : Format.formatter -> Pp.color -> unit +(** Print color command *) +val pp_begin : Format.formatter -> string * string * bool -> unit (** Prelude for a latex file with the given author and title and table of contents flag *) -val pp_begin : Format.formatter -> (string * string * bool) -> unit -(** Epilogue for a latex file *) val pp_end : Format.formatter -> unit -> unit +(** Epilogue for a latex file *) -(** Section with the given title *) val pp_section : Format.formatter -> string -> unit +(** Section with the given title *) diff --git a/infer/src/base/Logging.ml b/infer/src/base/Logging.ml index 9d0557469..dbdda791a 100644 --- a/infer/src/base/Logging.ml +++ b/infer/src/base/Logging.ml @@ -13,30 +13,26 @@ open! IStd (** log messages at different levels of verbosity *) module F = Format - module CLOpt = CommandLineOption (* log files *) - (* make a copy of [f] *) let copy_formatter f = let out_string, flush = F.pp_get_formatter_output_functions f () in let out_funs = F.pp_get_formatter_out_functions f () in let new_f = F.make_formatter out_string flush in - F.pp_set_formatter_out_functions new_f out_funs; - new_f + F.pp_set_formatter_out_functions new_f out_funs ; new_f (* Return a formatter that multiplexes to [fmt1] and [fmt2]. *) let dup_formatter fmt1 fmt2 = let out_funs1 = F.pp_get_formatter_out_functions fmt1 () in let out_funs2 = F.pp_get_formatter_out_functions fmt2 () in let f = copy_formatter fmt1 in - F.pp_set_formatter_out_functions f { - F.out_string = (fun s p n -> out_funs1.out_string s p n; out_funs2.out_string s p n); - out_flush = (fun () -> out_funs1.out_flush (); out_funs2.out_flush ()); - out_newline = (fun () -> out_funs1.out_newline (); out_funs2.out_newline ()); - out_spaces = (fun n -> out_funs1.out_spaces n; out_funs2.out_spaces n); - }; + F.pp_set_formatter_out_functions f + { F.out_string= (fun s p n -> out_funs1.out_string s p n ; out_funs2.out_string s p n) + ; out_flush= (fun () -> out_funs1.out_flush () ; out_funs2.out_flush ()) + ; out_newline= (fun () -> out_funs1.out_newline () ; out_funs2.out_newline ()) + ; out_spaces= (fun n -> out_funs1.out_spaces n ; out_funs2.out_spaces n) } ; f (* should be set up to emit to a file later on; initially a string buffer so that logging is not @@ -45,18 +41,19 @@ let log_file = let b = Buffer.create 256 in let fmt = let f = F.formatter_of_buffer b in - if Config.print_logs then dup_formatter f F.err_formatter else f in + if Config.print_logs then dup_formatter f F.err_formatter else f + in ref (fmt, `Buffer b) -type formatters = { - file : F.formatter; (** send to log file *) - console_file : F.formatter; (** send both to console and log file *) -} +type formatters = + { file: F.formatter (** send to log file *) + ; console_file: F.formatter (** send both to console and log file *) } let logging_formatters = ref [] (* shared ref is less punishing to sloppy accounting of newlines *) let is_newline = ref true + let prev_category = ref "" let mk_file_formatter category0 = @@ -67,228 +64,226 @@ let mk_file_formatter category0 = let print_prefix_if_newline () = let category_has_changed = (* take category + PID into account *) - not (phys_equal !prev_category prefix) in + not (phys_equal !prev_category prefix) + in if !is_newline || category_has_changed then ( if not !is_newline && category_has_changed then (* category change but previous line has not ended: print newline *) - out_functions_orig.out_newline (); - is_newline := false; - prev_category := prefix; - out_functions_orig.out_string prefix 0 (String.length prefix) - ) in - let out_string s p n = - print_prefix_if_newline (); - out_functions_orig.out_string s p n in + out_functions_orig.out_newline () ; + is_newline := false ; + prev_category := prefix ; + out_functions_orig.out_string prefix 0 (String.length prefix) ) + in + let out_string s p n = print_prefix_if_newline () ; out_functions_orig.out_string s p n in let out_newline () = - print_prefix_if_newline (); - out_functions_orig.out_newline (); - is_newline := true in - let out_spaces n = - print_prefix_if_newline (); - out_functions_orig.out_spaces n in + print_prefix_if_newline () ; + out_functions_orig.out_newline () ; + is_newline := true + in + let out_spaces n = print_prefix_if_newline () ; out_functions_orig.out_spaces n in F.pp_set_formatter_out_functions f - { F.out_string; out_flush=out_functions_orig.out_flush; out_newline; out_spaces }; + {F.out_string= out_string; out_flush= out_functions_orig.out_flush; out_newline; out_spaces} ; f let register_formatter = let all_prefixes = ref [] in - fun ?(use_stdout=false) prefix -> - all_prefixes := prefix::!all_prefixes; + fun ?(use_stdout= false) prefix -> + all_prefixes := prefix :: !all_prefixes ; (* lazy so that we get a chance to register all prefixes before computing their max length for alignment purposes *) - lazy ( - let max_prefix = List.map ~f:String.length !all_prefixes |> List.fold_left ~f:max ~init:0 in - let fill = - let n = max_prefix - (String.length prefix) in - String.make n ' ' in - let justified_prefix = fill ^ prefix in - let mk_formatters () = - let file = mk_file_formatter justified_prefix in - let console_file = - let console = if use_stdout then F.std_formatter else F.err_formatter in - dup_formatter console file in - { file; console_file } in - let formatters = mk_formatters () in - let formatters_ref = ref formatters in - logging_formatters := ((formatters_ref, mk_formatters), formatters) - ::!logging_formatters; - formatters_ref - ) + ( lazy + (let max_prefix = List.map ~f:String.length !all_prefixes |> List.fold_left ~f:max ~init:0 in + let fill = + let n = max_prefix - String.length prefix in + String.make n ' ' + in + let justified_prefix = fill ^ prefix in + let mk_formatters () = + let file = mk_file_formatter justified_prefix in + let console_file = + let console = if use_stdout then F.std_formatter else F.err_formatter in + dup_formatter console file + in + {file; console_file} + in + let formatters = mk_formatters () in + let formatters_ref = ref formatters in + logging_formatters := ((formatters_ref, mk_formatters), formatters) :: !logging_formatters ; + formatters_ref) ) let flush_formatters {file; console_file} = - F.pp_print_flush file (); - F.pp_print_flush console_file () + F.pp_print_flush file () ; F.pp_print_flush console_file () let reset_formatters () = let refresh_formatter ((formatters_ref, mk_formatters), formatters) = (* flush to be nice *) - flush_formatters formatters; + flush_formatters formatters ; (* recreate formatters, in particular update PID info *) - formatters_ref := mk_formatters () in + formatters_ref := mk_formatters () + in let previous_formatters = !logging_formatters in (* delete previous formatters *) - logging_formatters := []; + logging_formatters := [] ; (* create new formatters *) - List.iter ~f:refresh_formatter previous_formatters; - if not !is_newline then F.pp_print_newline (fst !log_file) (); + List.iter ~f:refresh_formatter previous_formatters ; + if not !is_newline then F.pp_print_newline (fst !log_file) () ; is_newline := true let close_logs () = let close_fmt (_, formatters) = flush_formatters formatters in - List.iter ~f:close_fmt !logging_formatters; + List.iter ~f:close_fmt !logging_formatters ; let fmt, chan = !log_file in - F.pp_print_flush fmt (); + F.pp_print_flush fmt () ; match chan with - | `Buffer b -> - prerr_endline (Buffer.contents b) - | `Channel c -> - Out_channel.close c + | `Buffer b + -> prerr_endline (Buffer.contents b) + | `Channel c + -> Out_channel.close c let () = Epilogues.register ~f:close_logs "flushing logs and closing log file" -let log ~to_console ?(to_file=true) (lazy formatters) = - match to_console, to_file with - | false, false -> - F.ifprintf F.std_formatter - | true, _ when not Config.print_logs -> - F.fprintf !formatters.console_file - | _ -> - (* to_console might be true, but in that case so is Config.print_logs so do not print to +let log ~to_console ?(to_file= true) (lazy formatters) = + match (to_console, to_file) with + | false, false + -> F.ifprintf F.std_formatter + | true, _ when not Config.print_logs + -> F.fprintf !formatters.console_file + | _ + -> (* to_console might be true, but in that case so is Config.print_logs so do not print to stderr because it will get logs from the log file already *) F.fprintf !formatters.file let debug_file_fmts = register_formatter "debug" + let environment_info_file_fmts = register_formatter "environment" + let external_warning_file_fmts = register_formatter "extern warn" + let external_error_file_fmts = register_formatter "extern err" + let internal_error_file_fmts = register_formatter "intern err" + let progress_file_fmts = register_formatter "progress" + let result_file_fmts = register_formatter ~use_stdout:true "result" + let user_warning_file_fmts = register_formatter "user warn" + let user_error_file_fmts = register_formatter "user err" -let progress fmt = - log ~to_console:(not Config.quiet) progress_file_fmts fmt +let progress fmt = log ~to_console:(not Config.quiet) progress_file_fmts fmt let progress_bar text = - log ~to_console:(Config.show_progress_bar && not Config.quiet) + log + ~to_console:(Config.show_progress_bar && not Config.quiet) ~to_file:true progress_file_fmts "%s@?" text -let progressbar_file () = - progress_bar Config.log_analysis_file +let progressbar_file () = progress_bar Config.log_analysis_file -let progressbar_procedure () = - progress_bar Config.log_analysis_procedure +let progressbar_procedure () = progress_bar Config.log_analysis_procedure let progressbar_timeout_event failure_kind = if Config.stats_mode || Config.debug_mode then - begin - match failure_kind with - | SymOp.FKtimeout -> - progress_bar Config.log_analysis_wallclock_timeout - | SymOp.FKsymops_timeout _ -> - progress_bar Config.log_analysis_symops_timeout - | SymOp.FKrecursion_timeout _ -> - progress_bar Config.log_analysis_recursion_timeout - | SymOp.FKcrash msg -> - progress_bar (Printf.sprintf "%s(%s)" Config.log_analysis_crash msg) - end - -let user_warning fmt = - log ~to_console:(not Config.quiet) user_warning_file_fmts fmt - -let user_error fmt = - log ~to_console:(not Config.quiet) user_error_file_fmts fmt + match failure_kind with + | SymOp.FKtimeout + -> progress_bar Config.log_analysis_wallclock_timeout + | SymOp.FKsymops_timeout _ + -> progress_bar Config.log_analysis_symops_timeout + | SymOp.FKrecursion_timeout _ + -> progress_bar Config.log_analysis_recursion_timeout + | SymOp.FKcrash msg + -> progress_bar (Printf.sprintf "%s(%s)" Config.log_analysis_crash msg) + +let user_warning fmt = log ~to_console:(not Config.quiet) user_warning_file_fmts fmt + +let user_error fmt = log ~to_console:(not Config.quiet) user_error_file_fmts fmt type debug_level = Quiet | Medium | Verbose [@@deriving compare] let debug_level_of_int n = - if n <= 0 then Quiet - else if Int.equal n 1 then Medium - else (* >= 2 *) Verbose + if n <= 0 then Quiet else if Int.equal n 1 then Medium else (* >= 2 *) Verbose let analysis_debug_level = debug_level_of_int Config.debug_level_analysis + let bufferoverrun_debug_level = debug_level_of_int Config.bo_debug + let capture_debug_level = debug_level_of_int Config.debug_level_capture + let linters_debug_level = debug_level_of_int Config.debug_level_linters + let mergecapture_debug_level = Quiet -type debug_kind = - | Analysis - | BufferOverrun - | Capture - | Linters - | MergeCapture +type debug_kind = Analysis | BufferOverrun | Capture | Linters | MergeCapture let debug kind level fmt = - let base_level = match kind with - | Analysis -> analysis_debug_level - | BufferOverrun -> bufferoverrun_debug_level - | Capture -> capture_debug_level - | Linters -> linters_debug_level - | MergeCapture -> mergecapture_debug_level in + let base_level = + match kind with + | Analysis + -> analysis_debug_level + | BufferOverrun + -> bufferoverrun_debug_level + | Capture + -> capture_debug_level + | Linters + -> linters_debug_level + | MergeCapture + -> mergecapture_debug_level + in let to_file = compare_debug_level level base_level <= 0 in log ~to_console:false ~to_file debug_file_fmts fmt -let result fmt = - log ~to_console:true result_file_fmts fmt +let result fmt = log ~to_console:true result_file_fmts fmt -let environment_info fmt = - log ~to_console:false environment_info_file_fmts fmt +let environment_info fmt = log ~to_console:false environment_info_file_fmts fmt -let external_warning fmt = - log ~to_console:(not Config.quiet) external_warning_file_fmts fmt +let external_warning fmt = log ~to_console:(not Config.quiet) external_warning_file_fmts fmt -let external_error fmt = - log ~to_console:(not Config.quiet) external_error_file_fmts fmt +let external_error fmt = log ~to_console:(not Config.quiet) external_error_file_fmts fmt -let internal_error fmt = - log ~to_console:Config.developer_mode internal_error_file_fmts fmt +let internal_error fmt = log ~to_console:Config.developer_mode internal_error_file_fmts fmt (** Type of location in ml source: __POS__ *) type ml_loc = string * int * int * int (** Convert a ml location to a string *) -let ml_loc_to_string (file, lnum, cnum, enum) = - Printf.sprintf "%s:%d:%d-%d:" file lnum cnum enum +let ml_loc_to_string (file, lnum, cnum, enum) = Printf.sprintf "%s:%d:%d-%d:" file lnum cnum enum (** Pretty print a location of ml source *) -let pp_ml_loc fmt ml_loc = - F.fprintf fmt "%s" (ml_loc_to_string ml_loc) +let pp_ml_loc fmt ml_loc = F.fprintf fmt "%s" (ml_loc_to_string ml_loc) let pp_ml_loc_opt fmt ml_loc_opt = - if Config.developer_mode then match ml_loc_opt with - | None -> () - | Some ml_loc -> F.fprintf fmt "(%a)" pp_ml_loc ml_loc + if Config.developer_mode then + match ml_loc_opt with None -> () | Some ml_loc -> F.fprintf fmt "(%a)" pp_ml_loc ml_loc (* create new channel from the log file, and dumps the contents of the temporary log buffer there *) let setup_log_file () = match !log_file with - | _, `Channel _ -> - (* already set up *) + | _, `Channel _ + -> (* already set up *) () - | _, `Buffer b -> - let fmt, chan, preexisting_logfile = + | _, `Buffer b + -> let fmt, chan, preexisting_logfile = if Config.buck_cache_mode then (* suppress log file in order not to cause flakiness in the Buck cache *) let devnull_chan = Out_channel.create "/dev/null" in let devnull_fmt = F.formatter_of_out_channel devnull_chan in - devnull_fmt, devnull_chan, true + (devnull_fmt, devnull_chan, true) else (* assumes Config.results_dir exists already *) let logfile_path = Config.results_dir ^/ Config.log_file in - let preexisting_logfile = PVariant.(=) (Sys.file_exists logfile_path) `Yes in + let preexisting_logfile = PVariant.( = ) (Sys.file_exists logfile_path) `Yes in let chan = Pervasives.open_out_gen [Open_append; Open_creat] 0o666 logfile_path in let file_fmt = let f = F.formatter_of_out_channel chan in - if Config.print_logs then dup_formatter f F.err_formatter else f in - file_fmt, chan, preexisting_logfile in - log_file := fmt, `Channel chan; - if preexisting_logfile then is_newline := false; - reset_formatters (); + if Config.print_logs then dup_formatter f F.err_formatter else f + in + (file_fmt, chan, preexisting_logfile) + in + log_file := (fmt, `Channel chan) ; + if preexisting_logfile then is_newline := false ; + reset_formatters () ; Buffer.output_buffer chan b - (** type of printable elements *) type print_type = | PTatom @@ -331,8 +326,7 @@ type print_type = | PTinfo (** delayable print action *) -type print_action = - print_type * Obj.t (** data to be printed *) +type print_action = print_type * Obj.t (** data to be printed *) let delayed_actions = ref [] @@ -345,16 +339,13 @@ let add_print_action pact = else if not Config.only_cheap_debug then !printer_hook (fst !log_file) pact (** reset the delayed print actions *) -let reset_delayed_prints () = - delayed_actions := [] +let reset_delayed_prints () = delayed_actions := [] (** return the delayed print actions *) -let get_delayed_prints () = - !delayed_actions +let get_delayed_prints () = !delayed_actions (** set the delayed print actions *) -let set_delayed_prints new_delayed_actions = - delayed_actions := new_delayed_actions +let set_delayed_prints new_delayed_actions = delayed_actions := new_delayed_actions (** dump a string *) let d_str (s: string) = add_print_action (PTstr, Obj.repr s) @@ -383,13 +374,11 @@ let d_ln () = add_print_action (PTstrln, Obj.repr "") (** dump an indentation *) let d_indent indent = let s = ref "" in - for _ = 1 to indent do s := " " ^ !s done; + for _ = 1 to indent do s := " " ^ !s done ; if indent <> 0 then add_print_action (PTstr, Obj.repr !s) (** dump command to increase the indentation level *) -let d_increase_indent (indent: int) = - add_print_action (PTincrease_indent, Obj.repr indent) +let d_increase_indent (indent: int) = add_print_action (PTincrease_indent, Obj.repr indent) (** dump command to decrease the indentation level *) -let d_decrease_indent (indent: int) = - add_print_action (PTdecrease_indent, Obj.repr indent) +let d_decrease_indent (indent: int) = add_print_action (PTdecrease_indent, Obj.repr indent) diff --git a/infer/src/base/Logging.mli b/infer/src/base/Logging.mli index 2d764bc8d..f812f5d3b 100644 --- a/infer/src/base/Logging.mli +++ b/infer/src/base/Logging.mli @@ -12,66 +12,66 @@ open! IStd (** log messages at different levels of verbosity *) -(** log information about the environment *) val environment_info : ('a, Format.formatter, unit) format -> 'a +(** log information about the environment *) -(** print immediately to standard error unless --quiet is specified *) val progress : ('a, Format.formatter, unit) format -> 'a +(** print immediately to standard error unless --quiet is specified *) -(** Progress bar: start of the analysis of a file. *) val progressbar_file : unit -> unit +(** Progress bar: start of the analysis of a file. *) -(** Progress bar: start of the analysis of a procedure. *) val progressbar_procedure : unit -> unit +(** Progress bar: start of the analysis of a procedure. *) -(** Progress bar: log a timeout event if in developer mode. *) val progressbar_timeout_event : SymOp.failure_kind -> unit +(** Progress bar: log a timeout event if in developer mode. *) +val result : ('a, Format.formatter, unit) format -> 'a (** Emit a result to stdout. Use only if the output format is stable and useful enough that it may conceivably get piped to another program, ie, almost never (use [progress] instead otherwise). *) -val result : ('a, Format.formatter, unit) format -> 'a -(** bad input, etc. detected *) val user_error : ('a, Format.formatter, unit) format -> 'a +(** bad input, etc. detected *) + val user_warning : ('a, Format.formatter, unit) format -> 'a -(** huho, infer has a bug *) val internal_error : ('a, Format.formatter, unit) format -> 'a +(** huho, infer has a bug *) -(** some other tool has a bug or is called wrongly *) val external_error : ('a, Format.formatter, unit) format -> 'a +(** some other tool has a bug or is called wrongly *) + val external_warning : ('a, Format.formatter, unit) format -> 'a type debug_kind = Analysis | BufferOverrun | Capture | Linters | MergeCapture (** Level of verbosity for debug output. Each level enables all the levels before it. *) type debug_level = - | Quiet (** innocuous, eg emitted once per toplevel execution *) - | Medium (** still fairly lightweight, eg emitted O() *) - | Verbose (** go crazy *) + | Quiet (** innocuous, eg emitted once per toplevel execution *) + | Medium (** still fairly lightweight, eg emitted O() *) + | Verbose (** go crazy *) -(** log debug info *) val debug : debug_kind -> debug_level -> ('a, Format.formatter, unit) format -> 'a +(** log debug info *) (** Type of location in ml source: __POS__ *) type ml_loc = string * int * int * int -(** Convert a ml location to a string *) val ml_loc_to_string : ml_loc -> string +(** Convert a ml location to a string *) -(** Pretty print a location of ml source *) val pp_ml_loc_opt : Format.formatter -> ml_loc option -> unit - +(** Pretty print a location of ml source *) (** log management *) -(** Set up logging to go to the log file. Call this once the results directory has been set up. *) val setup_log_file : unit -> unit +(** Set up logging to go to the log file. Call this once the results directory has been set up. *) -(** Reset the formatters used for logging. Call this when you fork(2). *) val reset_formatters : unit -> unit - +(** Reset the formatters used for logging. Call this when you fork(2). *) (** Delayed printing (HTML debug, ...) *) @@ -117,53 +117,52 @@ type print_type = | PTinfo (** delayable print action *) -type print_action = - print_type * Obj.t (** data to be printed *) +type print_action = print_type * Obj.t (** data to be printed *) -(** hook for the current printer of delayed print actions *) val printer_hook : (Format.formatter -> print_action -> unit) ref +(** hook for the current printer of delayed print actions *) -(** extend he current print log *) val add_print_action : print_action -> unit +(** extend he current print log *) -(** return the delayed print actions *) val get_delayed_prints : unit -> print_action list +(** return the delayed print actions *) -(** set the delayed print actions *) val set_delayed_prints : print_action list -> unit +(** set the delayed print actions *) -(** reset the delayed print actions *) val reset_delayed_prints : unit -> unit +(** reset the delayed print actions *) -(** dump a string *) val d_str : string -> unit +(** dump a string *) -(** dump a string with the given color *) val d_str_color : Pp.color -> string -> unit +(** dump a string with the given color *) -(** dump a string plus newline *) val d_strln : string -> unit +(** dump a string plus newline *) -(** dump a string plus newline with the given color *) val d_strln_color : Pp.color -> string -> unit +(** dump a string plus newline with the given color *) -(** dump a newline *) val d_ln : unit -> unit +(** dump a newline *) -(** dump an error string *) val d_error : string -> unit +(** dump an error string *) -(** dump a warning string *) val d_warning : string -> unit +(** dump a warning string *) -(** dump an info string *) val d_info : string -> unit +(** dump an info string *) -(** dump an indentation *) val d_indent : int -> unit +(** dump an indentation *) -(** dump command to increase the indentation level *) val d_increase_indent : int -> unit +(** dump command to increase the indentation level *) -(** dump command to decrease the indentation level *) val d_decrease_indent : int -> unit +(** dump command to decrease the indentation level *) diff --git a/infer/src/base/MarkupFormatter.ml b/infer/src/base/MarkupFormatter.ml index 398006c2b..95f2e13de 100644 --- a/infer/src/base/MarkupFormatter.ml +++ b/infer/src/base/MarkupFormatter.ml @@ -9,36 +9,34 @@ open! IStd -type 'a formatter = { - wrap_monospaced : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit; - pp_monospaced : Format.formatter -> string -> unit; - monospaced_to_string : string -> string; - - wrap_code : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit; - pp_code : Format.formatter -> string -> unit; - code_to_string : string -> string; - - wrap_bold : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit; - pp_bold : Format.formatter -> string -> unit; - bold_to_string : string -> string; -} +type 'a formatter = + { wrap_monospaced: (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit + ; pp_monospaced: Format.formatter -> string -> unit + ; monospaced_to_string: string -> string + ; wrap_code: (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit + ; pp_code: Format.formatter -> string -> unit + ; code_to_string: string -> string + ; wrap_bold: (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit + ; pp_bold: Format.formatter -> string -> unit + ; bold_to_string: string -> string } module NoFormatter : sig val formatter : 'a formatter end = struct let wrap_simple pp fmt x = pp fmt x + let pp_simple = wrap_simple Format.pp_print_string - let formatter = { - wrap_monospaced = wrap_simple; - pp_monospaced = pp_simple; - monospaced_to_string = Fn.id; - wrap_code = wrap_simple; - pp_code = pp_simple; - code_to_string = Fn.id; - wrap_bold = wrap_simple; - pp_bold = pp_simple; - bold_to_string = Fn.id - } + + let formatter = + { wrap_monospaced= wrap_simple + ; pp_monospaced= pp_simple + ; monospaced_to_string= Fn.id + ; wrap_code= wrap_simple + ; pp_code= pp_simple + ; code_to_string= Fn.id + ; wrap_bold= wrap_simple + ; pp_bold= pp_simple + ; bold_to_string= Fn.id } end module PhabricatorFormatter : sig @@ -46,40 +44,56 @@ module PhabricatorFormatter : sig end = struct (* https://secure.phabricator.com/book/phabricator/article/remarkup/ *) let wrap_monospaced pp fmt x = Format.fprintf fmt "`%a`" pp x + let pp_monospaced fmt s = wrap_monospaced Format.pp_print_string fmt s + let monospaced_to_string s = Format.asprintf "%a" pp_monospaced s let wrap_code pp fmt x = Format.fprintf fmt "```%a```" pp x + let pp_code fmt s = wrap_code Format.pp_print_string fmt s + let code_to_string s = Format.asprintf "%a" pp_code s let wrap_bold pp fmt x = Format.fprintf fmt "**%a**" pp x + let pp_bold fmt s = wrap_bold Format.pp_print_string fmt s + let bold_to_string s = Format.asprintf "%a" pp_bold s - let formatter = { - wrap_monospaced; - pp_monospaced; - monospaced_to_string; - wrap_code; - pp_code; - code_to_string; - wrap_bold; - pp_bold; - bold_to_string; - } + let formatter = + { wrap_monospaced + ; pp_monospaced + ; monospaced_to_string + ; wrap_code + ; pp_code + ; code_to_string + ; wrap_bold + ; pp_bold + ; bold_to_string } end -let formatter = match Config.report_formatter with - | `No_formatter -> NoFormatter.formatter - | `Phabricator_formatter -> PhabricatorFormatter.formatter +let formatter = + match Config.report_formatter with + | `No_formatter + -> NoFormatter.formatter + | `Phabricator_formatter + -> PhabricatorFormatter.formatter let wrap_monospaced = formatter.wrap_monospaced + let pp_monospaced = formatter.pp_monospaced + let monospaced_to_string = formatter.monospaced_to_string + let wrap_code = formatter.wrap_code + let pp_code = formatter.pp_code + let code_to_string = formatter.code_to_string + let wrap_bold = formatter.wrap_bold + let pp_bold = formatter.pp_bold + let bold_to_string = formatter.bold_to_string diff --git a/infer/src/base/MarkupFormatter.mli b/infer/src/base/MarkupFormatter.mli index ecdf5c3d7..bd3a1057f 100644 --- a/infer/src/base/MarkupFormatter.mli +++ b/infer/src/base/MarkupFormatter.mli @@ -9,29 +9,32 @@ open! IStd -(** used to combine pp together, wrap content into a monospaced block *) val wrap_monospaced : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit +(** used to combine pp together, wrap content into a monospaced block *) -(** pp to wrap into a monospaced block *) val pp_monospaced : Format.formatter -> string -> unit +(** pp to wrap into a monospaced block *) (* wrap into a monospaced block *) + val monospaced_to_string : string -> string -(** used to combine pp together, wrap content into a code block *) val wrap_code : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit +(** used to combine pp together, wrap content into a code block *) -(** pp to wrap into a code block *) val pp_code : Format.formatter -> string -> unit +(** pp to wrap into a code block *) (* wrap into a code block *) + val code_to_string : string -> string -(** used to combine pp together, wrap content into a bold block *) val wrap_bold : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a -> unit +(** used to combine pp together, wrap content into a bold block *) -(** pp to wrap into a bold block *) val pp_bold : Format.formatter -> string -> unit +(** pp to wrap into a bold block *) (* wrap into a bold block *) + val bold_to_string : string -> string diff --git a/infer/src/base/Multilinks.ml b/infer/src/base/Multilinks.ml new file mode 100644 index 000000000..df532544c --- /dev/null +++ b/infer/src/base/Multilinks.ml @@ -0,0 +1,64 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +open! PVariant +module F = Format +module L = Logging + +let multilink_file_name = "multilink.txt" + +type t = string String.Table.t + +let add multilinks fname = String.Table.set multilinks ~key:(Filename.basename fname) ~data:fname + +let create () : t = String.Table.create ~size:1 () + +(* Cache of multilinks files read from disk *) +let multilink_files_cache = String.Table.create ~size:1 () + +let reset_cache () = String.Table.clear multilink_files_cache + +let read ~dir : t option = + let multilink_fname = Filename.concat dir multilink_file_name in + match Utils.read_file multilink_fname with + | Error _ + -> None + | Ok lines + -> let links = create () in + List.iter + ~f:(fun line -> String.Table.set links ~key:(Filename.basename line) ~data:line) + lines ; + String.Table.set multilink_files_cache ~key:dir ~data:links ; + Some links + +(* Write a multilink file in the given directory *) +let write multilinks ~dir = + let fname = Filename.concat dir multilink_file_name in + let outc = Out_channel.create fname in + String.Table.iteri + ~f:(fun ~key:_ ~data:src -> Out_channel.output_string outc (src ^ "\n")) + multilinks ; + Out_channel.close outc + +let lookup ~dir = + try Some (String.Table.find_exn multilink_files_cache dir) + with Not_found -> read ~dir + +let resolve fname = + let fname_s = DB.filename_to_string fname in + if Sys.file_exists fname_s = `Yes then fname + else + let base = Filename.basename fname_s in + let dir = Filename.dirname fname_s in + match lookup ~dir with + | None + -> fname + | Some links -> + try DB.filename_from_string (String.Table.find_exn links base) + with Not_found -> fname diff --git a/infer/src/base/Multilinks.mli b/infer/src/base/Multilinks.mli new file mode 100644 index 000000000..b91fa1c6e --- /dev/null +++ b/infer/src/base/Multilinks.mli @@ -0,0 +1,46 @@ +(* + * Copyright (c) 2015 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd +module F = Format +module L = Logging + +(** In-memory representation of multilink files. *) + +type t + +(** Add a link. *) + +val add : t -> string -> unit + +(** Create a new multilink. *) + +val create : unit -> t + +(** Name of the multilink file. + A multilink file is recognized by its file name. *) + +val multilink_file_name : string + +(** Read a multilink file from disk. *) + +val read : dir:string -> t option + +(** Resolve a filename following multilinks. + The cache is updated if a new multilinks file is read. *) + +val resolve : DB.filename -> DB.filename + +(** Reset the cache of multilink files *) + +val reset_cache : unit -> unit + +(** Write a multilink file in the given directory *) + +val write : t -> dir:string -> unit diff --git a/infer/src/base/Multilinks.re b/infer/src/base/Multilinks.re deleted file mode 100644 index 45f427c44..000000000 --- a/infer/src/base/Multilinks.re +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -open! PVariant; - -module F = Format; - -module L = Logging; - -let multilink_file_name = "multilink.txt"; - -type t = String.Table.t string; - -let add multilinks fname => String.Table.set multilinks key::(Filename.basename fname) data::fname; - -let create () :t => String.Table.create size::1 (); - -/* Cache of multilinks files read from disk */ -let multilink_files_cache = String.Table.create size::1 (); - -let reset_cache () => String.Table.clear multilink_files_cache; - -let read ::dir :option t => { - let multilink_fname = Filename.concat dir multilink_file_name; - switch (Utils.read_file multilink_fname) { - | Error _ => None - | Ok lines => - let links = create (); - List.iter - f::(fun line => String.Table.set links key::(Filename.basename line) data::line) lines; - String.Table.set multilink_files_cache key::dir data::links; - Some links - } -}; - -/* Write a multilink file in the given directory */ -let write multilinks ::dir => { - let fname = Filename.concat dir multilink_file_name; - let outc = Out_channel.create fname; - String.Table.iteri - f::(fun key::_ data::src => Out_channel.output_string outc (src ^ "\n")) multilinks; - Out_channel.close outc -}; - -let lookup ::dir => - try (Some (String.Table.find_exn multilink_files_cache dir)) { - | Not_found => read ::dir - }; - -let resolve fname => { - let fname_s = DB.filename_to_string fname; - if (Sys.file_exists fname_s == `Yes) { - fname - } else { - let base = Filename.basename fname_s; - let dir = Filename.dirname fname_s; - switch (lookup ::dir) { - | None => fname - | Some links => - try (DB.filename_from_string (String.Table.find_exn links base)) { - | Not_found => fname - } - } - } -}; diff --git a/infer/src/base/Multilinks.rei b/infer/src/base/Multilinks.rei deleted file mode 100644 index da47e0b90..000000000 --- a/infer/src/base/Multilinks.rei +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2015 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module F = Format; - -module L = Logging; - - -/** In-memory representation of multilink files. */ -type t; - - -/** Add a link. */ -let add: t => string => unit; - - -/** Create a new multilink. */ -let create: unit => t; - - -/** Name of the multilink file. - A multilink file is recognized by its file name. */ -let multilink_file_name: string; - - -/** Read a multilink file from disk. */ -let read: dir::string => option t; - - -/** Resolve a filename following multilinks. - The cache is updated if a new multilinks file is read. */ -let resolve: DB.filename => DB.filename; - - -/** Reset the cache of multilink files */ -let reset_cache: unit => unit; - - -/** Write a multilink file in the given directory */ -let write: t => dir::string => unit; diff --git a/infer/src/base/Pp.ml b/infer/src/base/Pp.ml index 97b7e7df1..f5d7293a7 100644 --- a/infer/src/base/Pp.ml +++ b/infer/src/base/Pp.ml @@ -8,7 +8,6 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module F = Format (** Pretty Printing} *) @@ -19,9 +18,9 @@ type simple_kind = SIM_DEFAULT | SIM_WITH_TYP (** Kind of printing *) type print_kind = TEXT | LATEX | HTML [@@deriving compare] -let equal_print_kind = [%compare.equal : print_kind]; +let equal_print_kind = [%compare.equal : print_kind] - (** Colors supported in printing *) +(** Colors supported in printing *) type color = Black | Blue | Green | Orange | Red [@@deriving compare] let equal_color = [%compare.equal : color] @@ -30,14 +29,13 @@ let equal_color = [%compare.equal : color] type colormap = Obj.t -> color (** Print environment threaded through all the printing functions *) -type env = { - opt : simple_kind; (** Current option for simple printing *) - kind : print_kind; (** Current kind of printing *) - cmap_norm : colormap; (** Current colormap for the normal part *) - cmap_foot : colormap; (** Current colormap for the footprint part *) - color : color; (** Current color *) - obj_sub : (Obj.t -> Obj.t) option (** generic object substitution *) -} +type env = + { opt: simple_kind (** Current option for simple printing *) + ; kind: print_kind (** Current kind of printing *) + ; cmap_norm: colormap (** Current colormap for the normal part *) + ; cmap_foot: colormap (** Current colormap for the footprint part *) + ; color: color (** Current color *) + ; obj_sub: (Obj.t -> Obj.t) option (** generic object substitution *) } (** Create a colormap of a given color *) let colormap_from_color color (_: Obj.t) = color @@ -50,84 +48,89 @@ let colormap_red (_: Obj.t) = Red (** Default text print environment *) let text = - { opt = SIM_DEFAULT; - kind = TEXT; - cmap_norm = colormap_black; - cmap_foot = colormap_black; - color = Black; - obj_sub = None } + { opt= SIM_DEFAULT + ; kind= TEXT + ; cmap_norm= colormap_black + ; cmap_foot= colormap_black + ; color= Black + ; obj_sub= None } (** Default html print environment *) let html color = { text with - kind = HTML; - cmap_norm = colormap_from_color color; - cmap_foot = colormap_from_color color; - color = color } + kind= HTML; cmap_norm= colormap_from_color color; cmap_foot= colormap_from_color color; color } (** Default latex print environment *) let latex color = - { opt = SIM_DEFAULT; - kind = LATEX; - cmap_norm = colormap_from_color color; - cmap_foot = colormap_from_color color; - color = color; - obj_sub = None } + { opt= SIM_DEFAULT + ; kind= LATEX + ; cmap_norm= colormap_from_color color + ; cmap_foot= colormap_from_color color + ; color + ; obj_sub= None } (** Extend the normal colormap for the given object with the given color *) let extend_colormap pe (x: Obj.t) (c: color) = - let colormap (y: Obj.t) = - if phys_equal x y then c - else pe.cmap_norm y in - { pe with cmap_norm = colormap } + let colormap (y: Obj.t) = if phys_equal x y then c else pe.cmap_norm y in + {pe with cmap_norm= colormap} (** Set the object substitution, which is supposed to preserve the type. Currently only used for a map from (identifier) expressions to the program var containing them *) let set_obj_sub pe (sub: 'a -> 'a) = let new_obj_sub x = let x' = Obj.repr (sub (Obj.obj x)) in - match pe.obj_sub with - | None -> x' - | Some sub' -> sub' x' in - { pe with obj_sub = Some (new_obj_sub) } + match pe.obj_sub with None -> x' | Some sub' -> sub' x' + in + {pe with obj_sub= Some new_obj_sub} (** Reset the object substitution, so that no substitution takes place *) -let reset_obj_sub pe = - { pe with obj_sub = None } +let reset_obj_sub pe = {pe with obj_sub= None} (** string representation of colors *) let color_string = function - | Black -> "color_black" - | Blue -> "color_blue" - | Green -> "color_green" - | Orange -> "color_orange" - | Red -> "color_red" + | Black + -> "color_black" + | Blue + -> "color_blue" + | Green + -> "color_green" + | Orange + -> "color_orange" + | Red + -> "color_red" (** Pretty print a space-separated sequence *) let rec seq pp f = function - | [] -> () - | [x] -> F.fprintf f "%a" pp x - | x:: l -> F.fprintf f "%a %a" pp x (seq pp) l + | [] + -> () + | [x] + -> F.fprintf f "%a" pp x + | x :: l + -> F.fprintf f "%a %a" pp x (seq pp) l (** Print a comma-separated sequence *) let rec comma_seq pp f = function - | [] -> () - | [x] -> F.fprintf f "%a" pp x - | x:: l -> F.fprintf f "%a,%a" pp x (comma_seq pp) l + | [] + -> () + | [x] + -> F.fprintf f "%a" pp x + | x :: l + -> F.fprintf f "%a,%a" pp x (comma_seq pp) l (** Print a ;-separated sequence. *) let rec _semicolon_seq oneline pe pp f = - let sep fmt () = - if oneline then F.fprintf fmt " " else F.fprintf fmt "@\n" in + let sep fmt () = if oneline then F.fprintf fmt " " else F.fprintf fmt "@\n" in function - | [] -> () - | [x] -> F.fprintf f "%a" pp x - | x:: l -> - (match pe.kind with - | TEXT | HTML -> - F.fprintf f "%a ; %a%a" pp x sep () (_semicolon_seq oneline pe pp) l - | LATEX -> - F.fprintf f "%a ;\\\\%a %a" pp x sep () (_semicolon_seq oneline pe pp) l) + | [] + -> () + | [x] + -> F.fprintf f "%a" pp x + | x :: l -> + match pe.kind with + | TEXT | HTML + -> F.fprintf f "%a ; %a%a" pp x sep () (_semicolon_seq oneline pe pp) l + | LATEX + -> F.fprintf f "%a ;\\\\%a %a" pp x sep () (_semicolon_seq oneline pe pp) l (** Print a ;-separated sequence with newlines. *) let semicolon_seq pe = _semicolon_seq false pe @@ -137,21 +140,24 @@ let semicolon_seq_oneline pe = _semicolon_seq true pe (** Print an or-separated sequence. *) let or_seq pe pp f = function - | [] -> () - | [x] -> F.fprintf f "%a" pp x - | x:: l -> - (match pe.kind with - | TEXT -> - F.fprintf f "%a || %a" pp x (semicolon_seq pe pp) l - | HTML -> - F.fprintf f "%a ∨ %a" pp x (semicolon_seq pe pp) l - | LATEX -> - F.fprintf f "%a \\vee %a" pp x (semicolon_seq pe pp) l) + | [] + -> () + | [x] + -> F.fprintf f "%a" pp x + | x :: l -> + match pe.kind with + | TEXT + -> F.fprintf f "%a || %a" pp x (semicolon_seq pe pp) l + | HTML + -> F.fprintf f "%a ∨ %a" pp x (semicolon_seq pe pp) l + | LATEX + -> F.fprintf f "%a \\vee %a" pp x (semicolon_seq pe pp) l (** Print the current time and date in a format similar to the "date" command *) let current_time f () = let tm = Unix.localtime (Unix.time ()) in - F.fprintf f "%02d/%02d/%4d %02d:%02d" tm.Unix.tm_mday tm.Unix.tm_mon (tm.Unix.tm_year + 1900) tm.Unix.tm_hour tm.Unix.tm_min + F.fprintf f "%02d/%02d/%4d %02d:%02d" tm.Unix.tm_mday tm.Unix.tm_mon (tm.Unix.tm_year + 1900) + tm.Unix.tm_hour tm.Unix.tm_min (** Print the time in seconds elapsed since the beginning of the execution of the current command. *) let elapsed_time fmt () = diff --git a/infer/src/base/Pp.mli b/infer/src/base/Pp.mli index a8f2151a6..095a441d1 100644 --- a/infer/src/base/Pp.mli +++ b/infer/src/base/Pp.mli @@ -7,6 +7,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd (** Pretty Printing} *) @@ -28,60 +29,60 @@ type print_kind = TEXT | LATEX | HTML [@@deriving compare] val equal_print_kind : print_kind -> print_kind -> bool (** Print environment threaded through all the printing functions *) -type env = { - opt : simple_kind; (** Current option for simple printing *) - kind : print_kind; (** Current kind of printing *) - cmap_norm : colormap; (** Current colormap for the normal part *) - cmap_foot : colormap; (** Current colormap for the footprint part *) - color : color; (** Current color *) - obj_sub : (Obj.t -> Obj.t) option (** generic object substitution *) -} +type env = + { opt: simple_kind (** Current option for simple printing *) + ; kind: print_kind (** Current kind of printing *) + ; cmap_norm: colormap (** Current colormap for the normal part *) + ; cmap_foot: colormap (** Current colormap for the footprint part *) + ; color: color (** Current color *) + ; obj_sub: (Obj.t -> Obj.t) option (** generic object substitution *) } -(** Reset the object substitution, so that no substitution takes place *) val reset_obj_sub : env -> env +(** Reset the object substitution, so that no substitution takes place *) +val set_obj_sub : env -> ('a -> 'a) -> env (** Set the object substitution, which is supposed to preserve the type. Currently only used for a map from (identifier) expressions to the program var containing them *) -val set_obj_sub : env -> ('a -> 'a) -> env -(** standard colormap: black *) val colormap_black : colormap +(** standard colormap: black *) -(** red colormap *) val colormap_red : colormap +(** red colormap *) -(** Extend the normal colormap for the given object with the given color *) val extend_colormap : env -> Obj.t -> color -> env +(** Extend the normal colormap for the given object with the given color *) -(** Default text print environment *) val text : env +(** Default text print environment *) -(** Default html print environment *) val html : color -> env +(** Default html print environment *) -(** Default latex print environment *) val latex : color -> env +(** Default latex print environment *) -(** string representation of colors *) val color_string : color -> string +(** string representation of colors *) -(** Pretty print a space-separated sequence *) val seq : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit +(** Pretty print a space-separated sequence *) -(** Pretty print a comma-separated sequence *) val comma_seq : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit +(** Pretty print a comma-separated sequence *) -(** Pretty print a ;-separated sequence *) val semicolon_seq : env -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit +(** Pretty print a ;-separated sequence *) +val semicolon_seq_oneline : + env -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit (** Pretty print a ;-separated sequence on one line *) -val semicolon_seq_oneline : env -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit -(** Pretty print a or-separated sequence *) val or_seq : env -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit +(** Pretty print a or-separated sequence *) -(** Print the current time and date in a format similar to the "date" command *) val current_time : Format.formatter -> unit -> unit +(** Print the current time and date in a format similar to the "date" command *) -(** Print the time in seconds elapsed since the beginning of the execution of the current command. *) val elapsed_time : Format.formatter -> unit -> unit +(** Print the time in seconds elapsed since the beginning of the execution of the current command. *) diff --git a/infer/src/base/PrettyPrintable.ml b/infer/src/base/PrettyPrintable.ml index 92eafb42c..a27151fa6 100644 --- a/infer/src/base/PrettyPrintable.ml +++ b/infer/src/base/PrettyPrintable.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Wrappers for making pretty-printable modules *) @@ -23,6 +22,7 @@ module type PPSet = sig include Caml.Set.S val pp_element : F.formatter -> elt -> unit + val pp : F.formatter -> t -> unit end @@ -30,26 +30,27 @@ module type PPMap = sig include Caml.Map.S val pp_key : F.formatter -> key -> unit + val pp : pp_value:(F.formatter -> 'a -> unit) -> F.formatter -> 'a t -> unit end let pp_collection ~pp_item fmt c = let pp_collection fmt c = let pp_sep fmt () = F.fprintf fmt ", " in - F.pp_print_list ~pp_sep pp_item fmt c in + F.pp_print_list ~pp_sep pp_item fmt c + in F.fprintf fmt "{ %a }" pp_collection c module MakePPSet (Ord : PrintableOrderedType) = struct - include Caml.Set.Make(Ord) + include Caml.Set.Make (Ord) let pp_element = Ord.pp - let pp fmt s = - pp_collection ~pp_item:pp_element fmt (elements s) + let pp fmt s = pp_collection ~pp_item:pp_element fmt (elements s) end module MakePPMap (Ord : PrintableOrderedType) = struct - include Caml.Map.Make(Ord) + include Caml.Map.Make (Ord) let pp_key = Ord.pp diff --git a/infer/src/base/PrettyPrintable.mli b/infer/src/base/PrettyPrintable.mli index 7c6e84b5d..c1c742046 100644 --- a/infer/src/base/PrettyPrintable.mli +++ b/infer/src/base/PrettyPrintable.mli @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Wrappers for making pretty-printable modules *) @@ -23,16 +22,20 @@ end module type PPSet = sig include Caml.Set.S + val pp_element : F.formatter -> elt -> unit + val pp : F.formatter -> t -> unit end module type PPMap = sig include Caml.Map.S + val pp_key : F.formatter -> key -> unit + val pp : pp_value:(F.formatter -> 'a -> unit) -> F.formatter -> 'a t -> unit end -module MakePPSet (Ord : PrintableOrderedType) : (PPSet with type elt = Ord.t) +module MakePPSet (Ord : PrintableOrderedType) : PPSet with type elt = Ord.t -module MakePPMap (Ord : PrintableOrderedType) : (PPMap with type key = Ord.t) +module MakePPMap (Ord : PrintableOrderedType) : PPMap with type key = Ord.t diff --git a/infer/src/base/Process.ml b/infer/src/base/Process.ml index 7bec6bb33..71bdfd0b0 100644 --- a/infer/src/base/Process.ml +++ b/infer/src/base/Process.ml @@ -13,38 +13,41 @@ module F = Format (** Prints an error message to a log file, prints a message saying that the error can be found in that file, and exits, with default code 1 or a given code. *) -let print_error_and_exit ?(exit_code=1) fmt = - F.kfprintf (fun _ -> - L.external_error "%s" (F.flush_str_formatter ()); - exit exit_code - ) +let print_error_and_exit ?(exit_code= 1) fmt = + F.kfprintf + (fun _ -> + L.external_error "%s" (F.flush_str_formatter ()) ; + exit exit_code) F.str_formatter fmt (** Given a command to be executed, create a process to execute this command, and wait for it to terminate. The standard out and error are not redirected. If the command fails to execute, print an error message and exit. *) let create_process_and_wait ~prog ~args = - Unix.fork_exec ~prog ~argv:(prog :: args) () - |> Unix.waitpid + Unix.fork_exec ~prog ~argv:(prog :: args) () |> Unix.waitpid |> function - | Ok () -> () - | Error err as status -> - L.external_error "Error executing: %s@\n%s@\n" - (String.concat ~sep:" " (prog :: args)) (Unix.Exit_or_signal.to_string_hum status) ; - exit (match err with `Exit_non_zero i -> i | `Signal _ -> 1) + | Ok () + -> () + | Error err as status + -> L.external_error "Error executing: %s@\n%s@\n" + (String.concat ~sep:" " (prog :: args)) + (Unix.Exit_or_signal.to_string_hum status) ; + exit (match err with `Exit_non_zero i -> i | `Signal _ -> 1) (** Given a process id and a function that describes the command that the process id represents, prints a message explaining the command and its status, if in debug or stats mode. It also prints a dot to show progress of jobs being finished. *) let print_status ~fail_on_failed_job f pid status = - L.(debug Analysis Medium) "%a%s@." - (fun fmt pid -> F.pp_print_string fmt (f pid)) pid - (Unix.Exit_or_signal.to_string_hum status) ; - L.progress ".%!"; + L.(debug Analysis Medium) + "%a%s@." + (fun fmt pid -> F.pp_print_string fmt (f pid)) + pid (Unix.Exit_or_signal.to_string_hum status) ; + L.progress ".%!" ; match status with - | Error err when fail_on_failed_job -> - exit (match err with `Exit_non_zero i -> i | `Signal _ -> 1) - | _ -> () + | Error err when fail_on_failed_job + -> exit (match err with `Exit_non_zero i -> i | `Signal _ -> 1) + | _ + -> () let start_current_jobs_count () = ref 0 @@ -57,16 +60,15 @@ module PidMap = Caml.Map.Make (Pid) Use f to print the job status *) let rec wait_for_child ~fail_on_failed_job f current_jobs_count jobs_map = let pid, status = Unix.wait `Any in - Pervasives.decr current_jobs_count; - Pervasives.incr waited_for_jobs; - print_status ~fail_on_failed_job f pid status; - jobs_map := PidMap.remove pid !jobs_map; + Pervasives.decr current_jobs_count ; + Pervasives.incr waited_for_jobs ; + print_status ~fail_on_failed_job f pid status ; + jobs_map := PidMap.remove pid !jobs_map ; if not (PidMap.is_empty !jobs_map) then wait_for_child ~fail_on_failed_job f current_jobs_count jobs_map let pid_to_program jobsMap pid = - try - PidMap.find pid jobsMap + try PidMap.find pid jobsMap with Not_found -> "" (** [run_jobs_in_parallel jobs_stack gen_prog prog_to_string] runs the jobs in the given stack, by @@ -74,35 +76,36 @@ let pid_to_program jobsMap pid = and starts a new batch and so on. [gen_prog] should return a tuple [(dir_opt, command, args, env)] where [dir_opt] is an optional directory to chdir to before executing [command] with [args] in [env]. [prog_to_string] is used for printing information about the job's status. *) -let run_jobs_in_parallel ?(fail_on_failed_job=false) jobs_stack gen_prog prog_to_string = +let run_jobs_in_parallel ?(fail_on_failed_job= false) jobs_stack gen_prog prog_to_string = let run_job () = let jobs_map = ref PidMap.empty in let current_jobs_count = start_current_jobs_count () in while not (Stack.is_empty jobs_stack) do let job_prog = Stack.pop_exn jobs_stack in - let (dir_opt, prog, args, env) = gen_prog job_prog in - Pervasives.incr current_jobs_count; + let dir_opt, prog, args, env = gen_prog job_prog in + Pervasives.incr current_jobs_count ; match Unix.fork () with - | `In_the_child -> - Option.iter dir_opt ~f:Unix.chdir ; - Unix.exec ~prog ~argv:(prog :: args) ~env ~use_path:false - |> Unix.handle_unix_error + | `In_the_child + -> Option.iter dir_opt ~f:Unix.chdir ; + Unix.exec ~prog ~argv:(prog :: args) ~env ~use_path:false |> Unix.handle_unix_error |> never_returns - | `In_the_parent pid_child -> - jobs_map := PidMap.add pid_child (prog_to_string job_prog) !jobs_map; + | `In_the_parent pid_child + -> jobs_map := PidMap.add pid_child (prog_to_string job_prog) !jobs_map ; if Int.equal (Stack.length jobs_stack) 0 || !current_jobs_count >= Config.jobs then - wait_for_child ~fail_on_failed_job (pid_to_program !jobs_map) current_jobs_count - jobs_map - done in - run_job (); - L.progress ".@."; + wait_for_child ~fail_on_failed_job + (pid_to_program !jobs_map) + current_jobs_count jobs_map + done + in + run_job () ; + L.progress ".@." ; L.(debug Analysis Medium) "Waited for %d jobs" !waited_for_jobs let pipeline ~producer_prog ~producer_args ~consumer_prog ~consumer_args = let pipe_in, pipe_out = Unix.pipe () in match Unix.fork () with - | `In_the_child -> - (* redirect producer's stdout to pipe_out *) + | `In_the_child + -> (* redirect producer's stdout to pipe_out *) Unix.dup2 ~src:pipe_out ~dst:Unix.stdout ; (* close producer's copy of pipe ends *) Unix.close pipe_out ; @@ -110,20 +113,20 @@ let pipeline ~producer_prog ~producer_args ~consumer_prog ~consumer_args = (* exec producer *) never_returns (Unix.exec ~prog:producer_prog ~argv:producer_args ()) | `In_the_parent producer_pid -> - match Unix.fork () with - | `In_the_child -> - (* redirect consumer's stdin to pipe_in *) - Unix.dup2 ~src:pipe_in ~dst:Unix.stdin ; - (* close consumer's copy of pipe ends *) - Unix.close pipe_out ; - Unix.close pipe_in ; - (* exec consumer *) - never_returns (Unix.exec ~prog:consumer_prog ~argv:consumer_args ()) - | `In_the_parent consumer_pid -> - (* close parent's copy of pipe ends *) - Unix.close pipe_out ; - Unix.close pipe_in ; - (* wait for children *) - let producer_status = Unix.waitpid producer_pid in - let consumer_status = Unix.waitpid consumer_pid in - (producer_status, consumer_status) + match Unix.fork () with + | `In_the_child + -> (* redirect consumer's stdin to pipe_in *) + Unix.dup2 ~src:pipe_in ~dst:Unix.stdin ; + (* close consumer's copy of pipe ends *) + Unix.close pipe_out ; + Unix.close pipe_in ; + (* exec consumer *) + never_returns (Unix.exec ~prog:consumer_prog ~argv:consumer_args ()) + | `In_the_parent consumer_pid + -> (* close parent's copy of pipe ends *) + Unix.close pipe_out ; + Unix.close pipe_in ; + (* wait for children *) + let producer_status = Unix.waitpid producer_pid in + let consumer_status = Unix.waitpid consumer_pid in + (producer_status, consumer_status) diff --git a/infer/src/base/Process.mli b/infer/src/base/Process.mli index 586be827e..a86814e55 100644 --- a/infer/src/base/Process.mli +++ b/infer/src/base/Process.mli @@ -9,27 +9,25 @@ open! IStd +val create_process_and_wait : prog:string -> args:string list -> unit (** Given an command to be executed, creates a process to execute this command, and waits for its execution. The standard out and error are not redirected. If the commands fails to execute, prints an error message and exits. *) -val create_process_and_wait : prog:string -> args:string list -> unit +val print_error_and_exit : ?exit_code:int -> ('a, Format.formatter, unit, 'b) format4 -> 'a (** Prints an error message to a log file, prints a message saying that the error can be found in that file, and exist, with default code 1 or a given code. *) -val print_error_and_exit : - ?exit_code:int -> ('a, Format.formatter, unit, 'b) format4 -> 'a +val run_jobs_in_parallel : + ?fail_on_failed_job:bool -> 'a Stack.t -> ('a -> string option * string * string list * Unix.env) + -> ('a -> string) -> unit (** [run_jobs_in_parallel jobs_stack gen_prog prog_to_string] runs the jobs in the given stack, by spawning the jobs in batches of n, where n is [Config.jobs]. It then waits for all those jobs and starts a new batch and so on. [gen_prog] should return a tuple [(dir_opt, command, args, env)] where [dir_opt] is an optional directory to chdir to before executing [command] with [args] in [env]. [prog_to_string] is used for printing information about the job's status. *) -val run_jobs_in_parallel : - ?fail_on_failed_job:bool -> 'a Stack.t -> - ('a -> (string option * string * string list * Unix.env)) -> ('a -> string) -> unit -(** Pipeline producer program into consumer program *) val pipeline : - producer_prog:string -> producer_args:string list -> - consumer_prog:string -> consumer_args:string list -> - Unix.Exit_or_signal.t * Unix.Exit_or_signal.t + producer_prog:string -> producer_args:string list -> consumer_prog:string + -> consumer_args:string list -> Unix.Exit_or_signal.t * Unix.Exit_or_signal.t +(** Pipeline producer program into consumer program *) diff --git a/infer/src/base/ProcessPool.ml b/infer/src/base/ProcessPool.ml index ed76a9a89..653af4b42 100644 --- a/infer/src/base/ProcessPool.ml +++ b/infer/src/base/ProcessPool.ml @@ -11,42 +11,28 @@ open! IStd (** Keep track of whether the current execution is in a child process *) let in_child = ref false -type t = - { - mutable num_processes : int; - jobs : int; - } -let create ~jobs = - { - num_processes = 0; - jobs; - } - -let incr counter = - counter.num_processes <- counter.num_processes + 1 - -let decr counter = - counter.num_processes <- counter.num_processes - 1 +type t = {mutable num_processes: int; jobs: int} + +let create ~jobs = {num_processes= 0; jobs} + +let incr counter = counter.num_processes <- counter.num_processes + 1 + +let decr counter = counter.num_processes <- counter.num_processes - 1 let wait counter = let _ = Unix.wait `Any in decr counter -let wait_all counter = - for _ = 1 to counter.num_processes do - wait counter - done +let wait_all counter = for _ = 1 to counter.num_processes do wait counter done -let should_wait counter = - counter.num_processes >= counter.jobs +let should_wait counter = counter.num_processes >= counter.jobs let start_child ~f ~pool x = match Unix.fork () with - | `In_the_child -> - in_child := true; - f x; + | `In_the_child + -> in_child := true ; + f x ; exit 0 - | `In_the_parent _pid -> - incr pool; - if should_wait pool - then wait pool + | `In_the_parent _pid + -> incr pool ; + if should_wait pool then wait pool diff --git a/infer/src/base/ProcessPool.mli b/infer/src/base/ProcessPool.mli index 594c97d7c..0b0923894 100644 --- a/infer/src/base/ProcessPool.mli +++ b/infer/src/base/ProcessPool.mli @@ -6,20 +6,21 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd (** Pool of processes to execute in parallel up to a number of jobs. *) type t -(** Create a new pool of processes *) val create : jobs:int -> t +(** Create a new pool of processes *) +val start_child : f:('a -> unit) -> pool:t -> 'a -> unit (** Start a new child process in the pool. If all the jobs are taken, wait until one is free. *) -val start_child : f:('a -> unit) -> pool:t -> 'a -> unit -(** Wait until all the currently executing processes terminate *) val wait_all : t -> unit +(** Wait until all the currently executing processes terminate *) -(** Keep track of whether the current execution is in a child process *) val in_child : bool ref +(** Keep track of whether the current execution is in a child process *) diff --git a/infer/src/base/Serialization.ml b/infer/src/base/Serialization.ml index 401b9de54..5ef79a35e 100644 --- a/infer/src/base/Serialization.ml +++ b/infer/src/base/Serialization.ml @@ -9,149 +9,141 @@ *) open! IStd - module L = Logging module F = Format (** Generic serializer *) type 'a serializer = - { - read_from_string: string -> 'a option; - read_from_file: DB.filename -> 'a option; - update_file : f:('a option -> 'a) -> DB.filename -> unit; - write_to_file: data:'a -> DB.filename -> unit; - } + { read_from_string: string -> 'a option + ; read_from_file: DB.filename -> 'a option + ; update_file: f:('a option -> 'a) -> DB.filename -> unit + ; write_to_file: data:'a -> DB.filename -> unit } module Key = struct - (** Serialization key, used to distinguish versions of serializers and avoid assert faults *) type t = int (** current key for tenv, procedure summary, cfg, error trace, call graph *) - let tenv, summary, cfg, trace, cg, - analysis_results, cluster, attributes, lint_issues = - 425184201, 160179325, 1062389858, 221487792, 477305409, - 799050016, 579094948, 972393003, 852343110 + let tenv, summary, cfg, trace, cg, analysis_results, cluster, attributes, lint_issues = + ( 425184201 + , 160179325 + , 1062389858 + , 221487792 + , 477305409 + , 799050016 + , 579094948 + , 972393003 + , 852343110 ) end (** version of the binary files, to be incremented for each change *) let version = 27 - (** Retry the function while an exception filtered is thrown, or until the timeout in seconds expires. *) let retry_exception ~timeout ~catch_exn ~f x = let init_time = Unix.gettimeofday () in - let expired () = - Unix.gettimeofday () -. init_time >= timeout in + let expired () = Unix.gettimeofday () -. init_time >= timeout in let rec retry () = - try f x with - | e when catch_exn e && not (expired ()) -> - retry () in + try f x + with e when catch_exn e && not (expired ()) -> retry () + in retry () -type 'a write_command = - | Replace of 'a - | Update of ('a option -> 'a) +type 'a write_command = Replace of 'a | Update of ('a option -> 'a) -let create_serializer (key : Key.t) : 'a serializer = +let create_serializer (key: Key.t) : 'a serializer = let read_data ((key': Key.t), (version': int), (value: 'a)) source_msg = - if key <> key' then - begin - L.user_error "Wrong key in when loading data from %s -- are you running infer with results \ - coming from a previous version of infer?@\n" source_msg; - None - end - else if version <> version' then - begin - L.user_error "Wrong version in when loading data from %s -- are you running infer with \ - results coming from a previous version of infer?@\n" source_msg; - None - end - else Some value in - let read_from_string (str : string) : 'a option = - try - read_data (Marshal.from_string str 0) "string" - with Sys_error _ -> None in + if key <> key' then ( + L.user_error + "Wrong key in when loading data from %s -- are you running infer with results coming from a previous version of infer?@\n" + source_msg ; + None ) + else if version <> version' then ( + L.user_error + "Wrong version in when loading data from %s -- are you running infer with results coming from a previous version of infer?@\n" + source_msg ; + None ) + else Some value + in + let read_from_string (str: string) : 'a option = + try read_data (Marshal.from_string str 0) "string" + with Sys_error _ -> None + in (* The reads happen without synchronization. The writes are synchronized with a .lock file. *) - let read_from_file (fname : DB.filename) : 'a option = + let read_from_file (fname: DB.filename) : 'a option = let fname_str = DB.filename_to_string fname in match In_channel.create ~binary:true fname_str with - | exception Sys_error _ -> - None - | inc -> - let read () = + | exception Sys_error _ + -> None + | inc + -> let read () = try In_channel.seek inc 0L ; read_data (Marshal.from_channel inc) fname_str - with - | Sys_error _ -> None in + with Sys_error _ -> None + in let catch_exn = function - | End_of_file -> true - | Failure _ -> true (* handle input_value: truncated object *) - | _ -> false in + | End_of_file + -> true + | Failure _ + -> true (* handle input_value: truncated object *) + | _ + -> false + in (* Retry to read for 1 second in case of end of file, *) (* which indicates that another process is writing the same file. *) - SymOp.try_finally - (fun () -> retry_exception ~timeout:1.0 ~catch_exn ~f:read ()) - (fun () -> In_channel.close inc) in - + SymOp.try_finally (fun () -> retry_exception ~timeout:1.0 ~catch_exn ~f:read ()) (fun () -> + In_channel.close inc ) + in let write_to_tmp_file fname data = - let fname_tmp = Filename.temp_file - ~in_dir:(Filename.dirname fname) (Filename.basename fname) ".tmp" in - Utils.write_file_with_locking - fname_tmp - ~f:(fun outc -> Marshal.to_channel outc (key, version, data) []); - fname_tmp in - + let fname_tmp = + Filename.temp_file ~in_dir:(Filename.dirname fname) (Filename.basename fname) ".tmp" + in + Utils.write_file_with_locking fname_tmp ~f:(fun outc -> + Marshal.to_channel outc (key, version, data) [] ) ; + fname_tmp + in (* The .lock file is used to synchronize the writers. Once a lock on `file.lock` is obtained, the new data is written into a temporary file and rename is used to move it atomically to `file` *) - let execute_write_command_with_lock (fname : DB.filename) (cmd : 'a write_command) = + let execute_write_command_with_lock (fname: DB.filename) (cmd: 'a write_command) = let fname_str = DB.filename_to_string fname in let fname_str_lock = fname_str ^ ".lock" in - - Utils.write_file_with_locking - fname_str_lock - ~delete:true - ~f:(fun _outc -> - let (data_to_write : 'a) = match cmd with - | Replace data -> - data - | Update upd -> - let old_data_opt = - if DB.file_exists fname - then - (* Because of locking, this should be the latest data written + Utils.write_file_with_locking fname_str_lock ~delete:true ~f:(fun _outc -> + let data_to_write : 'a = + match cmd with + | Replace data + -> data + | Update upd + -> let old_data_opt = + if DB.file_exists fname then + (* Because of locking, this should be the latest data written by any writer, and can be used for updating *) - read_from_file fname - else - None in - upd old_data_opt in - - let fname_str_tmp = write_to_tmp_file fname_str data_to_write in - (* Rename is atomic: the readers can only see one version of this file, + read_from_file fname + else None + in + upd old_data_opt + in + let fname_str_tmp = write_to_tmp_file fname_str data_to_write in + (* Rename is atomic: the readers can only see one version of this file, possibly stale but not corrupted. *) - Unix.rename ~src:fname_str_tmp ~dst:fname_str) in - let write_to_file ~(data : 'a) (fname : DB.filename) = - execute_write_command_with_lock fname (Replace data) in - let update_file ~f (fname : DB.filename) = - execute_write_command_with_lock fname (Update f) in - {read_from_string; read_from_file; update_file; write_to_file; } - + Unix.rename ~src:fname_str_tmp ~dst:fname_str ) + in + let write_to_file ~(data: 'a) (fname: DB.filename) = + execute_write_command_with_lock fname (Replace data) + in + let update_file ~f (fname: DB.filename) = execute_write_command_with_lock fname (Update f) in + {read_from_string; read_from_file; update_file; write_to_file} -let read_from_string s = - s.read_from_string +let read_from_string s = s.read_from_string -let read_from_file s = - s.read_from_file +let read_from_file s = s.read_from_file -let update_file s = - s.update_file +let update_file s = s.update_file -let write_to_file s = - s.write_to_file +let write_to_file s = s.write_to_file (* (** Generate random keys, to be used in an ocaml toplevel *) diff --git a/infer/src/base/Serialization.mli b/infer/src/base/Serialization.mli index 6e00d8c1c..316094440 100644 --- a/infer/src/base/Serialization.mli +++ b/infer/src/base/Serialization.mli @@ -13,55 +13,53 @@ open! IStd (** Serialization of data stuctures *) module Key : sig - (** Serialization key, used to distinguish versions of serializers and avoid assert faults *) type t - (** current key for an analysis results value *) val analysis_results : t + (** current key for an analysis results value *) - (** current key for proc attributes *) val attributes : t + (** current key for proc attributes *) - (** current key for a cfg *) val cfg : t + (** current key for a cfg *) - (** current key for a call graph *) val cg : t + (** current key for a call graph *) - (** current key for a cluster *) val cluster : t + (** current key for a cluster *) - (** current key for lint issues *) val lint_issues : t + (** current key for lint issues *) - (** current key for a procedure summary *) val summary : t + (** current key for a procedure summary *) - (** current key for tenv *) val tenv : t + (** current key for tenv *) - (** current key for an error trace *) val trace : t - + (** current key for an error trace *) end (** Generic serializer *) type 'a serializer +val create_serializer : Key.t -> 'a serializer (** create a serializer from a file name given an integer key used as double-check of the file type *) -val create_serializer : Key.t -> 'a serializer -(** Deserialize a file and check the keys *) val read_from_file : 'a serializer -> DB.filename -> 'a option +(** Deserialize a file and check the keys *) -(** Deserialize a string and check the keys *) val read_from_string : 'a serializer -> string -> 'a option +(** Deserialize a string and check the keys *) +val update_file : 'a serializer -> f:('a option -> 'a) -> DB.filename -> unit (** Serialize into a file. The upd function takes the old value, if any, and returns the value to write *) -val update_file : 'a serializer -> f:('a option -> 'a) -> DB.filename -> unit -(** Serialize into a file writing value *) val write_to_file : 'a serializer -> data:'a -> DB.filename -> unit +(** Serialize into a file writing value *) diff --git a/infer/src/base/SourceFile.ml b/infer/src/base/SourceFile.ml index 898a12279..f64157845 100644 --- a/infer/src/base/SourceFile.ml +++ b/infer/src/base/SourceFile.ml @@ -9,102 +9,114 @@ open! IStd open! PVariant - module L = Logging -let count_newlines (path: string): int = +let count_newlines (path: string) : int = let f file = In_channel.fold_lines file ~init:0 ~f:(fun i _ -> i + 1) in In_channel.with_file path ~f type t = - | Invalid of string (* ML function of origin *) + | Invalid of string + (* ML function of origin *) | Absolute of string - | RelativeProjectRoot of string (* relative to project root *) - | RelativeInferModel of string (* relative to infer models *) -[@@deriving compare] + | RelativeProjectRoot of string + (* relative to project root *) + | RelativeInferModel of string + (* relative to infer models *) + [@@deriving compare] let equal = [%compare.equal : t] -module OrderedSourceFile = -struct +module OrderedSourceFile = struct (* Don't use nonrec due to https://github.com/janestreet/ppx_compare/issues/2 *) type _t = t [@@deriving compare] + type t = _t [@@deriving compare] end module Map = Caml.Map.Make (OrderedSourceFile) - module Set = Caml.Set.Make (OrderedSourceFile) -let from_abs_path ?(warn_on_error=true) fname = +let from_abs_path ?(warn_on_error= true) fname = if Filename.is_relative fname then - (failwithf - "ERROR: Path %s is relative, when absolute path was expected .@." - fname); + failwithf "ERROR: Path %s is relative, when absolute path was expected .@." fname ; (* try to get realpath of source file. Use original if it fails *) - let fname_real = try Utils.realpath ~warn_on_error fname with Unix.Unix_error _ -> fname in + let fname_real = + try Utils.realpath ~warn_on_error fname + with Unix.Unix_error _ -> fname + in let project_root_real = Utils.realpath ~warn_on_error Config.project_root in let models_dir_real = Config.models_src_dir in match Utils.filename_to_relative ~root:project_root_real fname_real with - | Some path -> RelativeProjectRoot path - | None -> ( - match Utils.filename_to_relative ~root:models_dir_real fname_real with - | Some path -> RelativeInferModel path - | None -> Absolute fname_real (* fname_real is absolute already *) - ) + | Some path + -> RelativeProjectRoot path + | None -> + match Utils.filename_to_relative ~root:models_dir_real fname_real with + | Some path + -> RelativeInferModel path + | None + -> Absolute fname_real +(* fname_real is absolute already *) let to_string fname = match fname with - | Invalid origin -> "DUMMY from " ^ origin - | RelativeInferModel path -> "INFER_MODEL/" ^ path - | RelativeProjectRoot path - | Absolute path -> path + | Invalid origin + -> "DUMMY from " ^ origin + | RelativeInferModel path + -> "INFER_MODEL/" ^ path + | RelativeProjectRoot path | Absolute path + -> path -let pp fmt fname = - Format.fprintf fmt "%s" (to_string fname) +let pp fmt fname = Format.fprintf fmt "%s" (to_string fname) (* Checking if the path exists may be needed only in some cases, hence the flag check_exists *) let to_abs_path fname = match fname with - | Invalid origin -> - invalid_arg ("cannot be called with Invalid source file originating in " ^ origin) - | RelativeProjectRoot path -> Filename.concat Config.project_root path - | RelativeInferModel path -> Filename.concat Config.models_src_dir path - | Absolute path -> path + | Invalid origin + -> invalid_arg ("cannot be called with Invalid source file originating in " ^ origin) + | RelativeProjectRoot path + -> Filename.concat Config.project_root path + | RelativeInferModel path + -> Filename.concat Config.models_src_dir path + | Absolute path + -> path let line_count source_file = let abs_path = to_abs_path source_file in count_newlines abs_path let to_rel_path fname = - match fname with - | RelativeProjectRoot path -> path - | _ -> to_abs_path fname + match fname with RelativeProjectRoot path -> path | _ -> to_abs_path fname let invalid origin = Invalid origin -let is_invalid = function - | Invalid _ -> true - | _ -> false - +let is_invalid = function Invalid _ -> true | _ -> false -let is_infer_model source_file = match source_file with - | Invalid origin -> invalid_arg ("cannot be called with Invalid source file from " ^ origin) - | RelativeProjectRoot _ | Absolute _ -> false - | RelativeInferModel _ -> true +let is_infer_model source_file = + match source_file with + | Invalid origin + -> invalid_arg ("cannot be called with Invalid source file from " ^ origin) + | RelativeProjectRoot _ | Absolute _ + -> false + | RelativeInferModel _ + -> true (** Returns true if the file is a C++ model *) let is_cpp_model file = match file with - | RelativeInferModel path -> - String.is_prefix ~prefix:Config.relative_cpp_models_dir path - | _ -> false + | RelativeInferModel path + -> String.is_prefix ~prefix:Config.relative_cpp_models_dir path + | _ + -> false let is_under_project_root = function - | Invalid origin -> invalid_arg ("cannot be called with Invalid source file from " ^ origin) - | RelativeProjectRoot _ -> true - | Absolute _ | RelativeInferModel _ -> false + | Invalid origin + -> invalid_arg ("cannot be called with Invalid source file from " ^ origin) + | RelativeProjectRoot _ + -> true + | Absolute _ | RelativeInferModel _ + -> false let exists_cache = String.Table.create ~size:256 () @@ -112,28 +124,28 @@ let path_exists abs_path = try String.Table.find_exn exists_cache abs_path with Not_found -> let result = Sys.file_exists abs_path = `Yes in - String.Table.set exists_cache ~key:abs_path ~data:result; - result + String.Table.set exists_cache ~key:abs_path ~data:result ; result -let of_header ?(warn_on_error=true) header_file = +let of_header ?(warn_on_error= true) header_file = let abs_path = to_abs_path header_file in let source_exts = ["c"; "cc"; "cpp"; "cxx"; "m"; "mm"] in let header_exts = ["h"; "hh"; "hpp"; "hxx"] in let file_no_ext, ext_opt = Filename.split_extension abs_path in - let file_opt = match ext_opt with - | Some ext when List.mem ~equal:String.equal header_exts ext -> ( - let possible_files = List.map ~f:(fun ext -> file_no_ext ^ "." ^ ext) source_exts in + let file_opt = + match ext_opt with + | Some ext when List.mem ~equal:String.equal header_exts ext + -> let possible_files = List.map ~f:(fun ext -> file_no_ext ^ "." ^ ext) source_exts in List.find ~f:path_exists possible_files - ) - | _ -> None in + | _ + -> None + in Option.map ~f:(from_abs_path ~warn_on_error) file_opt -let create ?(warn_on_error=true) path = +let create ?(warn_on_error= true) path = if Filename.is_relative path then (* sources in changed-files-index may be specified relative to project root *) RelativeProjectRoot path - else - from_abs_path ~warn_on_error path + else from_abs_path ~warn_on_error path let changed_sources_from_changed_files changed_files = List.fold changed_files ~init:Set.empty ~f:(fun changed_files_set line -> @@ -141,14 +153,11 @@ let changed_sources_from_changed_files changed_files = let changed_files' = Set.add source_file changed_files_set in (* Add source corresponding to changed header if it exists *) match of_header source_file with - | Some src -> Set.add src changed_files' - | None -> changed_files' - ) + | Some src + -> Set.add src changed_files' + | None + -> changed_files' ) module UNSAFE = struct - let from_string str = - if Filename.is_relative str then - RelativeProjectRoot str - else - Absolute str + let from_string str = if Filename.is_relative str then RelativeProjectRoot str else Absolute str end diff --git a/infer/src/base/SourceFile.mli b/infer/src/base/SourceFile.mli index a74ed935c..402d85fd6 100644 --- a/infer/src/base/SourceFile.mli +++ b/infer/src/base/SourceFile.mli @@ -18,63 +18,64 @@ module Map : Caml.Map.S with type key = t module Set : Caml.Set.S with type elt = t module UNSAFE : sig + val from_string : string -> t (** Create a SourceFile from any path. This is unchecked and should not be used when the existence of source files is a requirement. Furthermore, absolute paths won't be made relative to project root.*) - val from_string : string -> t end -(** Is the source file the invalid source file? *) val is_invalid : t -> bool +(** Is the source file the invalid source file? *) +val changed_sources_from_changed_files : string list -> Set.t (** Set of files read from --changed-files-index file, None if option not specified NOTE: it may include extra source_files if --changed-files-index contains paths to header files *) -val changed_sources_from_changed_files : string list -> Set.t -(** Invalid source file *) val invalid : string -> t +(** Invalid source file *) -(** equality of source files *) val equal : t -> t -> bool +(** equality of source files *) +val from_abs_path : ?warn_on_error:bool -> string -> t (** create source file from absolute path. WARNING: If warn_on_error is false, no warning will be shown whenever an error occurs for the given path (e.g. if it does not exist). *) -val from_abs_path : ?warn_on_error:bool -> string -> t (* Create a SourceFile from a given path. If relative, it assumes it is w.r.t. project root. WARNING: If warn_on_error is false, no warning will be shown whenever an error occurs for the given path (e.g. if it does not exist). *) + val create : ?warn_on_error:bool -> string -> t -(** Returns true if the file is a C++ model *) val is_cpp_model : t -> bool +(** Returns true if the file is a C++ model *) val is_infer_model : t -> bool -(** Returns true if the file is in project root *) val is_under_project_root : t -> bool +(** Returns true if the file is in project root *) -(** compute line count of a source file *) val line_count : t -> int +(** compute line count of a source file *) +val of_header : ?warn_on_error:bool -> t -> t option (** Return approximate source file corresponding to the parameter if it's header file and file exists. returns None otherwise. WARNING: If warn_on_error is false, no warning will be shown whenever an error occurs for the given SourceFile (e.g. if it does not exist).*) -val of_header : ?warn_on_error:bool -> t -> t option -(** pretty print t *) val pp : Format.formatter -> t -> unit +(** pretty print t *) -(** get the full path of a source file *) val to_abs_path : t -> string +(** get the full path of a source file *) -(** get the relative path of a source file *) val to_rel_path : t -> string +(** get the relative path of a source file *) +val to_string : t -> string (** convert a source file to a string WARNING: result may not be valid file path, do not use this function to perform operations on filenames *) -val to_string : t -> string diff --git a/infer/src/base/StatisticsToolbox.ml b/infer/src/base/StatisticsToolbox.ml new file mode 100644 index 000000000..1d7d207b4 --- /dev/null +++ b/infer/src/base/StatisticsToolbox.ml @@ -0,0 +1,69 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd + +type t = + { sum: float + ; avg: float + ; min: float + ; p10: float + ; median: float + ; p75: float + ; max: float + ; count: int } + +let to_json s = + `Assoc + [ ("sum", `Float s.sum) + ; ("avg", `Float s.avg) + ; ("min", `Float s.min) + ; ("p10", `Float s.p10) + ; ("median", `Float s.median) + ; ("p75", `Float s.p75) + ; ("max", `Float s.max) + ; ("count", `Int s.count) ] + +let from_json json = + let open! Yojson.Basic.Util in + { sum= json |> member "sum" |> to_float + ; avg= json |> member "avg" |> to_float + ; min= json |> member "min" |> to_float + ; p10= json |> member "p10" |> to_float + ; median= json |> member "median" |> to_float + ; p75= json |> member "p75" |> to_float + ; max= json |> member "max" |> to_float + ; count= json |> member "count" |> to_int } + +let compute_statistics values = + let num_elements = List.length values in + let sum = List.fold ~f:(fun acc v -> acc +. v) ~init:0.0 values in + let average = sum /. float_of_int num_elements in + let values_arr = Array.of_list values in + Array.sort + ~cmp:(fun a b -> if Float.equal a b then 0 else if a -. b < 0.0 then -1 else 1) + values_arr ; + let percentile pct = + assert (pct >= 0.0 && pct <= 1.0) ; + assert (num_elements > 0) ; + let max_index = num_elements - 1 in + let pct_index = float_of_int max_index *. pct in + let low_index = int_of_float (Pervasives.floor pct_index) in + let high_index = int_of_float (Pervasives.ceil pct_index) in + let low = values_arr.(low_index) in + let high = values_arr.(high_index) in + (low +. high) /. 2.0 + in + { sum + ; avg= average + ; min= percentile 0.0 + ; p10= percentile 0.10 + ; median= percentile 0.50 + ; p75= percentile 0.75 + ; max= percentile 1.0 + ; count= num_elements } diff --git a/infer/src/base/StatisticsToolbox.rei b/infer/src/base/StatisticsToolbox.mli similarity index 68% rename from infer/src/base/StatisticsToolbox.rei rename to infer/src/base/StatisticsToolbox.mli index f265b92fd..c7a195157 100644 --- a/infer/src/base/StatisticsToolbox.rei +++ b/infer/src/base/StatisticsToolbox.mli @@ -1,17 +1,18 @@ -/* +(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) -type t; +open! IStd -let to_json: t => Yojson.Basic.json; +type t -let from_json: Yojson.Basic.json => t; +val to_json : t -> Yojson.Basic.json -let compute_statistics: list float => t; +val from_json : Yojson.Basic.json -> t + +val compute_statistics : float list -> t diff --git a/infer/src/base/StatisticsToolbox.re b/infer/src/base/StatisticsToolbox.re deleted file mode 100644 index c73d49f6e..000000000 --- a/infer/src/base/StatisticsToolbox.re +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -type t = { - sum: float, - avg: float, - min: float, - p10: float, - median: float, - p75: float, - max: float, - count: int -}; - -let to_json s => - `Assoc [ - ("sum", `Float s.sum), - ("avg", `Float s.avg), - ("min", `Float s.min), - ("p10", `Float s.p10), - ("median", `Float s.median), - ("p75", `Float s.p75), - ("max", `Float s.max), - ("count", `Int s.count) - ]; - -let from_json json => { - open! Yojson.Basic.Util; - { - sum: json |> member "sum" |> to_float, - avg: json |> member "avg" |> to_float, - min: json |> member "min" |> to_float, - p10: json |> member "p10" |> to_float, - median: json |> member "median" |> to_float, - p75: json |> member "p75" |> to_float, - max: json |> member "max" |> to_float, - count: json |> member "count" |> to_int - } -}; - -let compute_statistics values => { - let num_elements = List.length values; - let sum = List.fold f::(fun acc v => acc +. v) init::0.0 values; - let average = sum /. float_of_int num_elements; - let values_arr = Array.of_list values; - Array.sort - cmp::( - fun a b => - if (Float.equal a b) { - 0 - } else if (a -. b < 0.0) { - (-1) - } else { - 1 - } - ) - values_arr; - let percentile pct => { - assert (pct >= 0.0 && pct <= 1.0); - assert (num_elements > 0); - let max_index = num_elements - 1; - let pct_index = float_of_int max_index *. pct; - let low_index = int_of_float (Pervasives.floor pct_index); - let high_index = int_of_float (Pervasives.ceil pct_index); - let low = values_arr.(low_index); - let high = values_arr.(high_index); - (low +. high) /. 2.0 - }; - { - sum, - avg: average, - min: percentile 0.0, - p10: percentile 0.10, - median: percentile 0.50, - p75: percentile 0.75, - max: percentile 1.0, - count: num_elements - } -}; diff --git a/infer/src/base/SymOp.ml b/infer/src/base/SymOp.ml index 02ba49ccd..a69dc55d2 100644 --- a/infer/src/base/SymOp.ml +++ b/infer/src/base/SymOp.ml @@ -11,57 +11,54 @@ (** Symbolic Operations and Failures: the units in which analysis work is measured *) open! IStd - module F = Format - type failure_kind = - | FKtimeout (** max time exceeded *) - | FKsymops_timeout of int (** max symop's exceeded *) - | FKrecursion_timeout of int (** max recursion level exceeded *) - | FKcrash of string (** uncaught exception or failed assertion *) + | FKtimeout (** max time exceeded *) + | FKsymops_timeout of int (** max symop's exceeded *) + | FKrecursion_timeout of int (** max recursion level exceeded *) + | FKcrash of string (** uncaught exception or failed assertion *) -(** failure that prevented analysis from finishing *) -exception Analysis_failure_exe of failure_kind +exception Analysis_failure_exe of failure_kind(** failure that prevented analysis from finishing *) -let exn_not_failure = function - | Analysis_failure_exe _ -> false - | _ -> true +let exn_not_failure = function Analysis_failure_exe _ -> false | _ -> true -let try_finally ?(fail_early=false) f g = +let try_finally ?(fail_early= false) f g = match f () with - | r -> - g () ; - r - | exception (Analysis_failure_exe _ as f_exn) -> - if not fail_early then - (try g () with _ -> ()); + | r + -> g () ; r + | exception (Analysis_failure_exe _ as f_exn) + -> ( if not fail_early then + try g () + with _ -> () ) ; raise f_exn | exception f_exn -> - match g () with - | () -> - raise f_exn - | exception (Analysis_failure_exe _ as g_exn) -> - raise g_exn - | exception _ -> - raise f_exn + match g () with + | () + -> raise f_exn + | exception (Analysis_failure_exe _ as g_exn) + -> raise g_exn + | exception _ + -> raise f_exn let finally_try g f = try_finally f g let pp_failure_kind fmt = function - | FKtimeout -> F.fprintf fmt "TIMEOUT" - | FKsymops_timeout symops -> F.fprintf fmt "SYMOPS TIMEOUT (%d)" symops - | FKrecursion_timeout level -> F.fprintf fmt "RECURSION TIMEOUT(%d)" level - | FKcrash msg -> F.fprintf fmt "CRASH (%s)" msg - + | FKtimeout + -> F.fprintf fmt "TIMEOUT" + | FKsymops_timeout symops + -> F.fprintf fmt "SYMOPS TIMEOUT (%d)" symops + | FKrecursion_timeout level + -> F.fprintf fmt "RECURSION TIMEOUT(%d)" level + | FKcrash msg + -> F.fprintf fmt "CRASH (%s)" msg (** Count the number of symbolic operations *) (** Timeout in seconds for each function *) let timeout_seconds = ref - (Option.map Config.seconds_per_iteration - ~f:(fun sec -> sec *. (float_of_int Config.iterations))) + (Option.map Config.seconds_per_iteration ~f:(fun sec -> sec *. float_of_int Config.iterations)) (** Timeout in SymOps *) let timeout_symops = @@ -71,36 +68,22 @@ let get_timeout_seconds () = !timeout_seconds (** Internal state of the module *) type t = - { - mutable alarm_active : bool; - (** Only throw timeout exception when alarm is active *) - - mutable last_wallclock : float option; - (** last wallclock set by an alarm, if any *) - - mutable symop_count : int; - (** Number of symop's *) - - symop_total : int ref; - (** Counter for the total number of symop's. + { mutable alarm_active: bool (** Only throw timeout exception when alarm is active *) + ; mutable last_wallclock: float option (** last wallclock set by an alarm, if any *) + ; mutable symop_count: int (** Number of symop's *) + ; symop_total: int ref + (** Counter for the total number of symop's. The new state created when save_state is called shares this counter if keep_symop_total is true. Otherwise, a new counter is created. *) } -let initial () : t = - { - alarm_active = false; - last_wallclock = None; - symop_count = 0; - symop_total = ref 0; - } +let initial () : t = {alarm_active= false; last_wallclock= None; symop_count= 0; symop_total= ref 0} (** Global State *) let gs : t ref = ref (initial ()) (** Restore the old state. *) -let restore_state state = - gs := state +let restore_state state = gs := state (** Return the old state, and revert the current state to the initial one. If keep_symop_total is true, share the total counter. *) @@ -108,73 +91,63 @@ let save_state ~keep_symop_total = let old_state = !gs in let new_state = let st = initial () in - if keep_symop_total - then - { st with symop_total = old_state.symop_total } - else - st in - gs := new_state; + if keep_symop_total then {st with symop_total= old_state.symop_total} else st + in + gs := new_state ; old_state (** handler for the wallclock timeout *) let wallclock_timeout_handler = ref None (** set the handler for the wallclock timeout *) -let set_wallclock_timeout_handler handler = - wallclock_timeout_handler := Some handler +let set_wallclock_timeout_handler handler = wallclock_timeout_handler := Some handler (** Set the wallclock alarm checked at every pay() *) -let set_wallclock_alarm nsecs = - !gs.last_wallclock <- Some (Unix.gettimeofday () +. nsecs) +let set_wallclock_alarm nsecs = !gs.last_wallclock <- Some (Unix.gettimeofday () +. nsecs) (** Unset the wallclock alarm checked at every pay() *) -let unset_wallclock_alarm () = - !gs.last_wallclock <- None +let unset_wallclock_alarm () = !gs.last_wallclock <- None (** if the wallclock alarm has expired, raise a timeout exception *) let check_wallclock_alarm () = - match !gs.last_wallclock, !wallclock_timeout_handler with - | Some alarm_time, Some handler when Unix.gettimeofday () >= alarm_time -> - unset_wallclock_alarm (); - handler () - | _ -> () + match (!gs.last_wallclock, !wallclock_timeout_handler) with + | Some alarm_time, Some handler when Unix.gettimeofday () >= alarm_time + -> unset_wallclock_alarm () ; handler () + | _ + -> () (** Return the time remaining before the wallclock alarm expires *) let get_remaining_wallclock_time () = match !gs.last_wallclock with - | Some alarm_time -> - max 0.0 (alarm_time -. Unix.gettimeofday ()) - | None -> - 0.0 + | Some alarm_time + -> max 0.0 (alarm_time -. Unix.gettimeofday ()) + | None + -> 0.0 (** Return the total number of symop's since the beginning *) -let get_total () = - !(!gs.symop_total) +let get_total () = !(!gs.symop_total) (** Reset the total number of symop's *) -let reset_total () = - !gs.symop_total := 0 +let reset_total () = !gs.symop_total := 0 (** Count one symop *) let pay () = - !gs.symop_count <- !gs.symop_count + 1; - !gs.symop_total := !(!gs.symop_total) + 1; - (match !timeout_symops with - | Some symops when !gs.symop_count > symops && !gs.alarm_active -> - raise (Analysis_failure_exe (FKsymops_timeout !gs.symop_count)) - | _ -> () - ); + !gs.symop_count <- !gs.symop_count + 1 ; + !gs.symop_total := !(!gs.symop_total) + 1 ; + ( match !timeout_symops with + | Some symops when !gs.symop_count > symops && !gs.alarm_active + -> raise (Analysis_failure_exe (FKsymops_timeout !gs.symop_count)) + | _ + -> () ) ; check_wallclock_alarm () (** Reset the counter *) -let reset_count () = - !gs.symop_count <- 0 +let reset_count () = !gs.symop_count <- 0 (** Reset the counter and activate the alarm *) let set_alarm () = - reset_count (); + reset_count () ; !gs.alarm_active <- true (** De-activate the alarm *) -let unset_alarm () = - !gs.alarm_active <- false +let unset_alarm () = !gs.alarm_active <- false diff --git a/infer/src/base/SymOp.mli b/infer/src/base/SymOp.mli index 5caa41502..e1abb6db8 100644 --- a/infer/src/base/SymOp.mli +++ b/infer/src/base/SymOp.mli @@ -15,67 +15,65 @@ open! IStd (** Internal state of the module *) type t -(** if the wallclock alarm has expired, raise a timeout exception *) val check_wallclock_alarm : unit -> unit +(** if the wallclock alarm has expired, raise a timeout exception *) -(** Return the time remaining before the wallclock alarm expires *) val get_remaining_wallclock_time : unit -> float +(** Return the time remaining before the wallclock alarm expires *) -(** Timeout in seconds for each function *) val get_timeout_seconds : unit -> float option +(** Timeout in seconds for each function *) -(** Return the total number of symop's since the beginning *) val get_total : unit -> int +(** Return the total number of symop's since the beginning *) -(** Count one symop *) val pay : unit -> unit +(** Count one symop *) -(** Reset the total number of symop's *) val reset_total : unit -> unit +(** Reset the total number of symop's *) -(** Restore the old state. *) val restore_state : t -> unit +(** Restore the old state. *) +val save_state : keep_symop_total:bool -> t (** Return the old state, and revert the current state to the initial one. If keep_symop_total is true, share the total counter. *) -val save_state : keep_symop_total:bool -> t -(** Reset the counter and activate the alarm *) val set_alarm : unit -> unit +(** Reset the counter and activate the alarm *) -(** Set the wallclock alarm checked at every pay() *) val set_wallclock_alarm : float -> unit +(** Set the wallclock alarm checked at every pay() *) -(** set the handler for the wallclock timeout *) val set_wallclock_timeout_handler : (unit -> unit) -> unit +(** set the handler for the wallclock timeout *) -(** De-activate the alarm *) val unset_alarm : unit -> unit +(** De-activate the alarm *) -(** Unset the wallclock alarm checked at every pay() *) val unset_wallclock_alarm : unit -> unit - +(** Unset the wallclock alarm checked at every pay() *) type failure_kind = - | FKtimeout (** max time exceeded *) - | FKsymops_timeout of int (** max symop's exceeded *) - | FKrecursion_timeout of int (** max recursion level exceeded *) - | FKcrash of string (** uncaught exception or failed assertion *) + | FKtimeout (** max time exceeded *) + | FKsymops_timeout of int (** max symop's exceeded *) + | FKrecursion_timeout of int (** max recursion level exceeded *) + | FKcrash of string (** uncaught exception or failed assertion *) -(** Timeout exception *) -exception Analysis_failure_exe of failure_kind +exception Analysis_failure_exe of failure_kind(** Timeout exception *) -(** check that the exception is not a timeout exception *) val exn_not_failure : exn -> bool +(** check that the exception is not a timeout exception *) +val try_finally : ?fail_early:bool -> (unit -> 'a) -> (unit -> unit) -> 'a (** [try_finally ~fail_early f g] executes [f] and then [g] even if [f] raises an exception. Assuming that [g ()] terminates quickly [Analysis_failure_exe] exceptions are handled correctly. In particular, an exception raised by [f ()] is delayed until [g ()] finishes, so [g ()] should return reasonably quickly. [~fail_early=true] can be passed to skip executing [g ()] when [f ()] raises a [Analysis_failure_exe] exception. *) -val try_finally : ?fail_early:bool -> (unit -> 'a) -> (unit -> unit) -> 'a -(** [finally_try g f] is equivalent to [try_finally f g]. *) val finally_try : (unit -> unit) -> (unit -> 'a) -> 'a +(** [finally_try g f] is equivalent to [try_finally f g]. *) val pp_failure_kind : Format.formatter -> failure_kind -> unit diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index 90adc5ddd..3828834d1 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -9,7 +9,6 @@ *) open! IStd open! PVariant - module F = Format module Hashtbl = Caml.Hashtbl @@ -23,25 +22,21 @@ let initial_timeofday = Unix.gettimeofday () let read_file fname = let res = ref [] in let cin_ref = ref None in - let cleanup () = - match !cin_ref with - | None -> () - | Some cin -> In_channel.close cin in + let cleanup () = match !cin_ref with None -> () | Some cin -> In_channel.close cin in try let cin = In_channel.create fname in - cin_ref := Some cin; + cin_ref := Some cin ; while true do let line = In_channel.input_line_exn cin in res := line :: !res - done; + done ; assert false with - | End_of_file -> - cleanup (); + | End_of_file + -> cleanup () ; Ok (List.rev !res) - | Sys_error error -> - cleanup (); - Error error + | Sys_error error + -> cleanup () ; Error error (** copy a source file, return the number of lines, or None in case of error *) let copy_file fname_from fname_to = @@ -49,194 +44,178 @@ let copy_file fname_from fname_to = let cin_ref = ref None in let cout_ref = ref None in let cleanup () = - begin match !cin_ref with - | None -> () - | Some cin -> In_channel.close cin - end; - begin match !cout_ref with - | None -> () - | Some cout -> Out_channel.close cout - end in + (match !cin_ref with None -> () | Some cin -> In_channel.close cin) ; + match !cout_ref with None -> () | Some cout -> Out_channel.close cout + in try let cin = In_channel.create fname_from in - cin_ref := Some cin; + cin_ref := Some cin ; let cout = Out_channel.create fname_to in - cout_ref := Some cout; + cout_ref := Some cout ; while true do let line = In_channel.input_line_exn cin in - Out_channel.output_string cout line; - Out_channel.output_char cout '\n'; - incr res - done; + Out_channel.output_string cout line ; Out_channel.output_char cout '\n' ; incr res + done ; assert false with - | End_of_file -> - cleanup (); - Some !res - | Sys_error _ -> - cleanup(); - None + | End_of_file + -> cleanup () ; Some !res + | Sys_error _ + -> cleanup () ; None (** type for files used for printing *) type outfile = - { fname : string; (** name of the file *) - out_c : Out_channel.t; (** output channel *) - fmt : F.formatter (** formatter for printing *) } + { fname: string (** name of the file *) + ; out_c: Out_channel.t (** output channel *) + ; fmt: F.formatter (** formatter for printing *) } (** create an outfile for the command line *) let create_outfile fname = try let out_c = Out_channel.create fname in let fmt = F.formatter_of_out_channel out_c in - Some { fname = fname; out_c = out_c; fmt = fmt } - with Sys_error _ -> - F.fprintf F.err_formatter "error: cannot create file %s@." fname; - None + Some {fname; out_c; fmt} + with Sys_error _ -> F.fprintf F.err_formatter "error: cannot create file %s@." fname ; None (** operate on an outfile reference if it is not None *) -let do_outf outf_opt f = - match outf_opt with - | None -> () - | Some outf -> - f outf +let do_outf outf_opt f = match outf_opt with None -> () | Some outf -> f outf (** close an outfile *) -let close_outf outf = - Out_channel.close outf.out_c - +let close_outf outf = Out_channel.close outf.out_c (** Convert a filename to an absolute one if it is relative, and normalize "." and ".." *) let filename_to_absolute ~root fname = let add_entry rev_done entry = - match entry, rev_done with - | ".", [] -> entry :: rev_done (* id on . *) - | ".", _ -> rev_done (* path/. --> path *) - | "..", ("." | "..") :: _ -> entry :: rev_done (* id on {.,..}/.. *) - | "..", ["/"] -> rev_done (* /.. -> / *) - | "..", _ :: rev_done_parent -> rev_done_parent (* path/dir/.. --> path *) - | _ -> entry :: rev_done + match (entry, rev_done) with + | ".", [] + -> entry :: rev_done (* id on . *) + | ".", _ + -> rev_done (* path/. --> path *) + | "..", ("." | "..") :: _ + -> entry :: rev_done (* id on {.,..}/.. *) + | "..", ["/"] + -> rev_done (* /.. -> / *) + | "..", _ :: rev_done_parent + -> rev_done_parent (* path/dir/.. --> path *) + | _ + -> entry :: rev_done in let abs_fname = if Filename.is_absolute fname then fname else root ^/ fname in Filename.of_parts (List.rev (List.fold ~f:add_entry ~init:[] (Filename.parts abs_fname))) - (** Convert an absolute filename to one relative to the given directory. *) let filename_to_relative ~root fname = let rec relativize_if_under origin target = - match origin, target with - | x :: xs, y :: ys when String.equal x y -> relativize_if_under xs ys - | [], [] -> Some "." - | [], ys -> Some (Filename.of_parts ys) - | _ -> None + match (origin, target) with + | x :: xs, y :: ys when String.equal x y + -> relativize_if_under xs ys + | [], [] + -> Some "." + | [], ys + -> Some (Filename.of_parts ys) + | _ + -> None in relativize_if_under (Filename.parts root) (Filename.parts fname) - let directory_fold f init path = let collect current_dir (accu, dirs) path = let full_path = current_dir ^/ path in try - if Sys.is_directory full_path = `Yes then - (accu, full_path:: dirs) - else - (f accu full_path, dirs) - with Sys_error _ -> - (accu, dirs) in + if Sys.is_directory full_path = `Yes then (accu, full_path :: dirs) + else (f accu full_path, dirs) + with Sys_error _ -> (accu, dirs) + in let rec loop accu dirs = match dirs with - | [] -> accu - | d:: tl -> - let (new_accu, new_dirs) = Array.fold ~f:(collect d) ~init:(accu, tl) (Sys.readdir d) in - loop new_accu new_dirs in - if Sys.is_directory path = `Yes then - loop init [path] - else - f init path - + | [] + -> accu + | d :: tl + -> let new_accu, new_dirs = Array.fold ~f:(collect d) ~init:(accu, tl) (Sys.readdir d) in + loop new_accu new_dirs + in + if Sys.is_directory path = `Yes then loop init [path] else f init path let directory_iter f path = let apply current_dir dirs path = let full_path = current_dir ^/ path in try - if Sys.is_directory full_path = `Yes then - full_path:: dirs + if Sys.is_directory full_path = `Yes then full_path :: dirs else let () = f full_path in dirs - with Sys_error _ -> - dirs in + with Sys_error _ -> dirs + in let rec loop dirs = match dirs with - | [] -> () - | d:: tl -> - let new_dirs = Array.fold ~f:(apply d) ~init:tl (Sys.readdir d) in - loop new_dirs in - if Sys.is_directory path = `Yes then - loop [path] - else - f path + | [] + -> () + | d :: tl + -> let new_dirs = Array.fold ~f:(apply d) ~init:tl (Sys.readdir d) in + loop new_dirs + in + if Sys.is_directory path = `Yes then loop [path] else f path let dir_is_empty path = let dir_handle = Unix.opendir path in let is_empty = ref true in - (try - while !is_empty; - do if not (Option.value_map (Unix.readdir_opt dir_handle) ~default:false ~f:(List.mem ~equal:String.equal ["."; ".."])) then - is_empty := false; - done; - with End_of_file -> () - ); - Unix.closedir dir_handle; - !is_empty - + ( try + while !is_empty do + if not + (Option.value_map (Unix.readdir_opt dir_handle) ~default:false + ~f:(List.mem ~equal:String.equal ["."; ".."])) + then is_empty := false + done + with End_of_file -> () ) ; + Unix.closedir dir_handle ; !is_empty let string_crc_hex32 s = Digest.to_hex (Digest.string s) let read_json_file path = - try - Ok (Yojson.Basic.from_file path) - with Sys_error msg | Yojson.Json_error msg -> - Error msg + try Ok (Yojson.Basic.from_file path) + with Sys_error msg | Yojson.Json_error msg -> Error msg let do_finally f g = - let res = try f () with exc -> g () |> ignore; raise exc in + let res = + try f () + with exc -> + g () |> ignore ; + raise exc + in let res' = g () in (res, res') let with_file_in file ~f = let ic = In_channel.create file in let f () = f ic in - let g () = In_channel.close ic in + let g () = In_channel.close ic in do_finally f g |> fst let with_file_out file ~f = let oc = Out_channel.create file in let f () = f oc in - let g () = Out_channel.close oc in + let g () = Out_channel.close oc in do_finally f g |> fst let write_json_to_file destfile json = with_file_out destfile ~f:(fun oc -> Yojson.Basic.pretty_to_channel oc json) let consume_in chan_in = - try - while true do In_channel.input_line_exn chan_in |> ignore done + try while true do In_channel.input_line_exn chan_in |> ignore done with End_of_file -> () let with_process_in command read = let chan = Unix.open_process_in command in let f () = read chan in - let g () = - consume_in chan; - Unix.close_process_in chan in + let g () = consume_in chan ; Unix.close_process_in chan in do_finally f g let shell_escape_command cmd = let escape arg = (* ends on-going single quote, output single quote inside double quotes, then open a new single quote *) - Escape.escape_map (function | '\'' -> Some "'\"'\"'" | _ -> None) arg - |> Printf.sprintf "'%s'" in + Escape.escape_map (function '\'' -> Some "'\"'\"'" | _ -> None) arg |> Printf.sprintf "'%s'" + in List.map ~f:escape cmd |> String.concat ~sep:" " (** Create a directory if it does not exist already. *) @@ -245,44 +224,42 @@ let create_dir dir = if (Unix.stat dir).Unix.st_kind <> Unix.S_DIR then failwithf "file %s exists and is not a directory@." dir with Unix.Unix_error _ -> - try Unix.mkdir dir ~perm:0o700 with - Unix.Unix_error _ -> - let created_concurrently = (* check if another process created it meanwhile *) - try Polymorphic_compare.(=) ((Unix.stat dir).Unix.st_kind) Unix.S_DIR - with Unix.Unix_error _ -> false in - if not created_concurrently then - failwithf "cannot create directory %s@." dir + try Unix.mkdir dir ~perm:0o700 + with Unix.Unix_error _ -> + let created_concurrently = + (* check if another process created it meanwhile *) + try Polymorphic_compare.( = ) (Unix.stat dir).Unix.st_kind Unix.S_DIR + with Unix.Unix_error _ -> false + in + if not created_concurrently then failwithf "cannot create directory %s@." dir let realpath_cache = Hashtbl.create 1023 -let realpath ?(warn_on_error=true) path = +let realpath ?(warn_on_error= true) path = match Hashtbl.find realpath_cache path with | exception Not_found -> ( - match Filename.realpath path with - | realpath -> - Hashtbl.add realpath_cache path (Ok realpath); - realpath - | exception Unix.Unix_error (code, f, arg) -> - if warn_on_error then - F.eprintf - "WARNING: Failed to resolve file %s with \"%s\" @\n@." arg (Unix.Error.message code); - (* cache failures as well *) - Hashtbl.add realpath_cache path (Error (code, f, arg)); - raise (Unix.Unix_error (code, f, arg)) - ) - | Ok path -> path - | Error (code, f, arg) -> raise (Unix.Unix_error (code, f, arg)) - + match Filename.realpath path with + | realpath + -> Hashtbl.add realpath_cache path (Ok realpath) ; realpath + | exception Unix.Unix_error (code, f, arg) + -> if warn_on_error then + F.eprintf "WARNING: Failed to resolve file %s with \"%s\" @\n@." arg + (Unix.Error.message code) ; + (* cache failures as well *) + Hashtbl.add realpath_cache path (Error (code, f, arg)) ; + raise (Unix.Unix_error (code, f, arg)) ) + | Ok path + -> path + | Error (code, f, arg) + -> raise (Unix.Unix_error (code, f, arg)) (* never closed *) -let devnull = lazy (Unix.openfile "/dev/null" ~mode:[Unix.O_WRONLY]) +let devnull = (lazy (Unix.openfile "/dev/null" ~mode:[Unix.O_WRONLY])) let suppress_stderr2 f2 x1 x2 = - let restore_stderr src = - Unix.dup2 ~src ~dst:Unix.stderr; - Unix.close src in + let restore_stderr src = Unix.dup2 ~src ~dst:Unix.stderr ; Unix.close src in let orig_stderr = Unix.dup Unix.stderr in - Unix.dup2 ~src:(Lazy.force devnull) ~dst:Unix.stderr; + Unix.dup2 ~src:(Lazy.force devnull) ~dst:Unix.stderr ; let f () = f2 x1 x2 in let finally () = restore_stderr orig_stderr in protect ~f ~finally @@ -292,48 +269,47 @@ let compare_versions v1 v2 = let lv = String.split ~on:'.' v in let int_of_string_or_zero v = try int_of_string v - with Failure _ -> 0 in - List.map ~f:int_of_string_or_zero lv in - let lv1 = int_list_of_version v1 in - let lv2 = int_list_of_version v2 in + with Failure _ -> 0 + in + List.map ~f:int_of_string_or_zero lv + in + let lv1 = int_list_of_version v1 in + let lv2 = int_list_of_version v2 in [%compare : int list] lv1 lv2 -let write_file_with_locking ?(delete=false) ~f:do_write fname = - Unix.with_file ~mode:Unix.[O_WRONLY; O_CREAT] fname ~f:(fun file_descr -> +let write_file_with_locking ?(delete= false) ~f:do_write fname = + Unix.with_file ~mode:Unix.([O_WRONLY; O_CREAT]) fname ~f:(fun file_descr -> if Unix.flock file_descr Unix.Flock_command.lock_exclusive then ( (* make sure we're not writing over some existing, possibly longer content: some other process may have snagged the file from under us between open(2) and flock(2) so passing O_TRUNC to open(2) above would not be a good substitute for calling ftruncate(2) below. *) - Unix.ftruncate file_descr ~len:Int64.zero; + Unix.ftruncate file_descr ~len:Int64.zero ; let outc = Unix.out_channel_of_descr file_descr in - do_write outc; - Out_channel.flush outc; - ignore (Unix.flock file_descr Unix.Flock_command.unlock) - ); - ); + do_write outc ; + Out_channel.flush outc ; + ignore (Unix.flock file_descr Unix.Flock_command.unlock) ) ) ; if delete then - try Unix.unlink fname with - | Unix.Unix_error _ -> () + try Unix.unlink fname + with Unix.Unix_error _ -> () let rec rmtree name = match Unix.((lstat name).st_kind) with - | S_DIR -> - let dir = Unix.opendir name in + | S_DIR + -> let dir = Unix.opendir name in let rec rmdir dir = match Unix.readdir_opt dir with - | Some entry -> - if not (String.equal entry Filename.current_dir_name || - String.equal entry Filename.parent_dir_name) - then ( - rmtree (name ^/ entry) - ); + | Some entry + -> if not + ( String.equal entry Filename.current_dir_name + || String.equal entry Filename.parent_dir_name ) + then rmtree (name ^/ entry) ; rmdir dir - | None -> - Unix.closedir dir ; - Unix.rmdir name in + | None + -> Unix.closedir dir ; Unix.rmdir name + in rmdir dir - | _ -> - Unix.unlink name - | exception Unix.Unix_error (Unix.ENOENT, _, _) -> - () + | _ + -> Unix.unlink name + | exception Unix.Unix_error (Unix.ENOENT, _, _) + -> () diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 474abe0ae..e620ae746 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -7,88 +7,90 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd -(** initial process times *) val initial_times : Unix.process_times +(** initial process times *) -(** precise time of day at the start of the analysis *) val initial_timeofday : float +(** precise time of day at the start of the analysis *) -(** Compute a 32-character hexadecimal crc using the Digest module *) val string_crc_hex32 : string -> string +(** Compute a 32-character hexadecimal crc using the Digest module *) -(** copy a source file, return the number of lines, or None in case of error *) val copy_file : string -> string -> int option +(** copy a source file, return the number of lines, or None in case of error *) -(** read a source file and return a list of lines *) val read_file : string -> (string list, string) Result.t +(** read a source file and return a list of lines *) -(** Convert a filename to an absolute one if it is relative, and normalize "." and ".." *) val filename_to_absolute : root:string -> string -> string +(** Convert a filename to an absolute one if it is relative, and normalize "." and ".." *) +val filename_to_relative : root:string -> string -> string option (** Convert an absolute filename to one relative to a root directory. Returns [None] if filename is not under root. *) -val filename_to_relative : root:string -> string -> string option (** type for files used for printing *) type outfile = - { fname : string; (** name of the file *) - out_c : Out_channel.t; (** output channel *) - fmt : Format.formatter (** formatter for printing *) } + { fname: string (** name of the file *) + ; out_c: Out_channel.t (** output channel *) + ; fmt: Format.formatter (** formatter for printing *) } -(** create an outfile for the command line, the boolean indicates whether to do demangling when closing the file *) val create_outfile : string -> outfile option +(** create an outfile for the command line, the boolean indicates whether to do demangling when closing the file *) -(** operate on an outfile reference if it is not None *) val do_outf : outfile option -> (outfile -> unit) -> unit +(** operate on an outfile reference if it is not None *) -(** close an outfile *) val close_outf : outfile -> unit +(** close an outfile *) -(** Functional fold function over all the file of a directory *) val directory_fold : ('a -> string -> 'a) -> 'a -> string -> 'a +(** Functional fold function over all the file of a directory *) -(** Functional iter function over all the file of a directory *) val directory_iter : (string -> unit) -> string -> unit +(** Functional iter function over all the file of a directory *) -(** Returns true if a given directory is empty. The directory is assumed to exist. *) val dir_is_empty : string -> bool +(** Returns true if a given directory is empty. The directory is assumed to exist. *) val read_json_file : string -> (Yojson.Basic.json, string) Result.t val with_file_in : string -> f:(In_channel.t -> 'a) -> 'a + val with_file_out : string -> f:(Out_channel.t -> 'a) -> 'a val write_json_to_file : string -> Yojson.Basic.json -> unit val consume_in : In_channel.t -> unit -val with_process_in : string -> (In_channel.t -> 'a) -> ('a * Unix.Exit_or_signal.t) +val with_process_in : string -> (In_channel.t -> 'a) -> 'a * Unix.Exit_or_signal.t val shell_escape_command : string list -> string -(** create a directory if it does not exist already *) val create_dir : string -> unit +(** create a directory if it does not exist already *) +val realpath : ?warn_on_error:bool -> string -> string (** [realpath warn_on_error path] returns path with all symbolic links resolved. It caches results of previous calls to avoid expensive system calls. WARNING: If warn_on_error is false, no warning will be shown whenever an error occurs for the given path (e.g. if it does not exist). *) -val realpath : ?warn_on_error:bool -> string -> string +val suppress_stderr2 : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c (** wraps a function expecting 2 arguments in another that temporarily redirects stderr to /dev/null for the duration of the function call *) -val suppress_stderr2 : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c +val compare_versions : string -> string -> int (** [compare_versions v1 v2] returns 1 if v1 is newer than v2, -1 if v1 is older than v2 and 0 if they are the same version. The versions are strings of the shape "n.m.t", the order is lexicographic. *) -val compare_versions : string -> string -> int +val write_file_with_locking : ?delete:bool -> f:(Out_channel.t -> unit) -> string -> unit (** Lock file passed as argument and write into it using [f]. If [delete] then the file is unlinked once this is done. *) -val write_file_with_locking : ?delete:bool -> f:(Out_channel.t -> unit) -> string -> unit -(** [rmtree path] removes [path] and, if [path] is a directory, recursively removes its contents *) val rmtree : string -> unit +(** [rmtree path] removes [path] and, if [path] is a directory, recursively removes its contents *) diff --git a/infer/src/base/Version.ml.in b/infer/src/base/Version.ml.in index 5692f14eb..37f09d9f9 100644 --- a/infer/src/base/Version.ml.in +++ b/infer/src/base/Version.ml.in @@ -11,31 +11,40 @@ open! IStd let is_yes = String.equal "yes" + let is_not_no = Fn.non (String.equal "no") let major = @INFER_MAJOR@ + let minor = @INFER_MINOR@ + let patch = @INFER_PATCH@ let commit = "@INFER_GIT_COMMIT@" + let branch = "@INFER_GIT_BRANCH@" + let is_release = is_yes "@IS_RELEASE_TREE@" + let tag = Printf.sprintf "v%d.%d.%d" major minor patch -let versionString = - if is_release then tag - else Printf.sprintf "%s-%s" tag commit +let versionString = if is_release then tag else Printf.sprintf "%s-%s" tag commit -let versionJson = String.concat ~sep:"\n" [ - "{"; "\"major\": " ^ (string_of_int major) ^ ", "; - "\"minor\": " ^ (string_of_int minor) ^ ", "; - "\"patch\": " ^ (string_of_int patch) ^ ", "; - "\"commit\": \"" ^ commit ^ "\", "; - "\"branch\": \"" ^ branch ^ "\", "; - "\"tag\": \"" ^ tag ^ "\""; "}" ] +let versionJson = + String.concat ~sep:"\n" + [ "{" + ; ("\"major\": " ^ string_of_int major ^ ", ") + ; ("\"minor\": " ^ string_of_int minor ^ ", ") + ; ("\"patch\": " ^ string_of_int patch ^ ", ") + ; ("\"commit\": \"" ^ commit ^ "\", ") + ; ("\"branch\": \"" ^ branch ^ "\", ") + ; ("\"tag\": \"" ^ tag ^ "\"") + ; "}" ] let clang_enabled = is_yes "@BUILD_C_ANALYZERS@" + let java_enabled = is_yes "@BUILD_JAVA_ANALYZERS@" + let xcode_enabled = is_not_no "@XCODE_SELECT@" let man_pages_last_modify_date = "@INFER_MAN_LAST_MODIFIED@" diff --git a/infer/src/base/Version.mli b/infer/src/base/Version.mli index e3f2ee16e..0cd024615 100644 --- a/infer/src/base/Version.mli +++ b/infer/src/base/Version.mli @@ -10,11 +10,15 @@ open! IStd val commit : string + val versionString : string + val versionJson : string val clang_enabled : bool + val java_enabled : bool + val xcode_enabled : bool val man_pages_last_modify_date : string diff --git a/infer/src/base/ZipLib.ml b/infer/src/base/ZipLib.ml index 6639e5141..d111bda05 100644 --- a/infer/src/base/ZipLib.ml +++ b/infer/src/base/ZipLib.ml @@ -9,16 +9,9 @@ open! IStd open! PVariant - module L = Logging - -type zip_library = { - zip_filename: string; - zip_channel: Zip.in_file Lazy.t; - models: bool; -} - +type zip_library = {zip_filename: string; zip_channel: Zip.in_file Lazy.t; models: bool} let get_cache_dir infer_cache zip_filename = let basename = Filename.basename zip_filename in @@ -29,69 +22,74 @@ let load_from_cache serializer zip_path cache_dir zip_library = let absolute_path = Filename.concat cache_dir zip_path in let deserialize = Serialization.read_from_file serializer in let extract to_path = - if (Sys.file_exists to_path) <> `Yes then - begin - Unix.mkdir_p (Filename.dirname to_path); - let lazy zip_channel = zip_library.zip_channel in - let entry = Zip.find_entry zip_channel zip_path in - Zip.copy_entry_to_file zip_channel entry to_path - end; - DB.filename_from_string to_path in + if Sys.file_exists to_path <> `Yes then ( + Unix.mkdir_p (Filename.dirname to_path) ; + let lazy zip_channel = zip_library.zip_channel in + let entry = Zip.find_entry zip_channel zip_path in + Zip.copy_entry_to_file zip_channel entry to_path ) ; + DB.filename_from_string to_path + in match deserialize (extract absolute_path) with - | Some data -> Some data - | None -> None - | exception Not_found -> None + | Some data + -> Some data + | None + -> None + | exception Not_found + -> None let load_from_zip serializer zip_path zip_library = let lazy zip_channel = zip_library.zip_channel in let deserialize = Serialization.read_from_string serializer in match deserialize (Zip.read_entry zip_channel (Zip.find_entry zip_channel zip_path)) with - | Some data -> Some data - | None -> None - | exception Not_found -> None + | Some data + -> Some data + | None + -> None + | exception Not_found + -> None let load_data serializer path zip_library = let zip_path = Filename.concat Config.default_in_zip_results_dir path in match Config.infer_cache with - | None -> - load_from_zip serializer zip_path zip_library - | Some infer_cache -> - let cache_dir = get_cache_dir infer_cache zip_library.zip_filename in + | None + -> load_from_zip serializer zip_path zip_library + | Some infer_cache + -> let cache_dir = get_cache_dir infer_cache zip_library.zip_filename in load_from_cache serializer zip_path cache_dir zip_library (** list of the zip files to search for specs files *) let zip_libraries = (* delay until load is called, to avoid stating/opening files at init time *) - lazy ( - let mk_zip_lib models zip_filename = - { models; zip_filename; zip_channel = lazy (Zip.open_in zip_filename) } in - let zip_libs = - if Config.use_jar_cache && Config.infer_cache <> None then - [] - else - (* Order matters, jar files should appear in the order in which they should be searched for + ( lazy + (let mk_zip_lib models zip_filename = + {models; zip_filename; zip_channel= (lazy (Zip.open_in zip_filename))} + in + let zip_libs = + if Config.use_jar_cache && Config.infer_cache <> None then [] + else + (* Order matters, jar files should appear in the order in which they should be searched for specs files. Config.specs_library is in reverse order of appearance on command line. *) - let add_zip zip_libs fname = - if Filename.check_suffix fname ".jar" then - (* fname is a zip of specs *) - (mk_zip_lib false fname) :: zip_libs - else - (* fname is a dir of specs *) - zip_libs in - List.fold ~f:add_zip ~init:[] Config.specs_library in - if Config.biabduction && (Sys.file_exists Config.models_jar) = `Yes then - (mk_zip_lib true Config.models_jar) :: zip_libs - else - zip_libs - ) + let add_zip zip_libs fname = + if Filename.check_suffix fname ".jar" then + (* fname is a zip of specs *) + mk_zip_lib false fname :: zip_libs + else (* fname is a dir of specs *) + zip_libs + in + List.fold ~f:add_zip ~init:[] Config.specs_library + in + if Config.biabduction && Sys.file_exists Config.models_jar = `Yes then + mk_zip_lib true Config.models_jar :: zip_libs + else zip_libs) ) (* Search path in the list of zip libraries and use a cache directory to save already deserialized data *) let load serializer path = let rec loop = function - | [] -> None - | zip_library :: other_libraries -> - let opt = load_data serializer path zip_library in - if Option.is_some opt then opt - else loop other_libraries in + | [] + -> None + | zip_library :: other_libraries + -> let opt = load_data serializer path zip_library in + if Option.is_some opt then opt else loop other_libraries + in loop (Lazy.force zip_libraries) diff --git a/infer/src/base/ZipLib.mli b/infer/src/base/ZipLib.mli index 942342af6..80283cd5e 100644 --- a/infer/src/base/ZipLib.mli +++ b/infer/src/base/ZipLib.mli @@ -9,7 +9,7 @@ open! IStd +val load : 'a Serialization.serializer -> string -> 'a option (** [load serializer path] searches for the file at the given path in the zip libraries. If Config.infer_cache is set, already deserialized data will be saved there and [path] will be searched from the cache first. *) -val load : 'a Serialization.serializer -> string -> 'a option diff --git a/infer/src/bufferoverrun/absLoc.ml b/infer/src/bufferoverrun/absLoc.ml index cd2d592e0..3005469be 100644 --- a/infer/src/bufferoverrun/absLoc.ml +++ b/infer/src/bufferoverrun/absLoc.ml @@ -11,61 +11,73 @@ *) open! IStd - module F = Format -module Allocsite = -struct +module Allocsite = struct include String + let pp fmt s = Format.fprintf fmt "%s" s + let make x = x + let unknown = "Unknown" end -module Loc = -struct +module Loc = struct type t = | Var of Var.t | Allocsite of Allocsite.t | Field of t * Typ.Fieldname.t - [@@deriving compare] + [@@deriving compare] let unknown = Allocsite Allocsite.unknown + let rec pp fmt = function - | Var v -> - Var.pp F.str_formatter v; + | Var v + -> Var.pp F.str_formatter v ; let s = F.flush_str_formatter () in if Char.equal s.[0] '&' then F.fprintf fmt "%s" (String.sub s ~pos:1 ~len:(String.length s - 1)) else F.fprintf fmt "%s" s - | Allocsite a -> Allocsite.pp fmt a - | Field (l, f) -> F.fprintf fmt "%a.%a" pp l Typ.Fieldname.pp f + | Allocsite a + -> Allocsite.pp fmt a + | Field (l, f) + -> F.fprintf fmt "%a.%a" pp l Typ.Fieldname.pp f + let is_var = function Var _ -> true | _ -> false - let is_logical_var = function - | Var (Var.LogicalVar _) -> true - | _ -> false + + let is_logical_var = function Var Var.LogicalVar _ -> true | _ -> false + let of_var v = Var v + let of_allocsite a = Allocsite a + let of_pvar pvar = Var (Var.of_pvar pvar) + let of_id id = Var (Var.of_id id) + let append_field l f = Field (l, f) let is_return = function - | Var (Var.ProgramVar x) -> - Mangled.equal (Pvar.get_name x) Ident.name_return - | _ -> false + | Var Var.ProgramVar x + -> Mangled.equal (Pvar.get_name x) Ident.name_return + | _ + -> false end -module PowLoc = -struct - include AbstractDomain.FiniteSet(Loc) +module PowLoc = struct + include AbstractDomain.FiniteSet (Loc) let bot = empty + let is_bot = is_empty let unknown = singleton Loc.unknown + let of_pvar pvar = singleton (Loc.of_pvar pvar) + let of_id id = singleton (Loc.of_id id) + let append_field ploc fn = if is_bot ploc then singleton Loc.unknown else fold (fun l -> add (Loc.append_field l fn)) ploc empty diff --git a/infer/src/bufferoverrun/arrayBlk.ml b/infer/src/bufferoverrun/arrayBlk.ml index 6768cd416..e5302ed70 100644 --- a/infer/src/bufferoverrun/arrayBlk.ml +++ b/infer/src/bufferoverrun/arrayBlk.ml @@ -9,183 +9,153 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Abstract Array Block *) open! IStd open AbsLoc -module ArrInfo = -struct - type t = - { offset : Itv.t; - size : Itv.t; - stride : Itv.t; } - [@@deriving compare] +module ArrInfo = struct + type t = {offset: Itv.t; size: Itv.t; stride: Itv.t} [@@deriving compare] type astate = t - let bot : t - = { offset = Itv.bot; size = Itv.bot; stride = Itv.bot; } - - let initial : t - = bot - - let top : t - = { offset = Itv.top; size = Itv.top; stride = Itv.top; } - - let input : t - = { offset = Itv.zero; size = Itv.pos; stride = Itv.one; } - - let make : Itv.t * Itv.t * Itv.t -> t - = fun (o, s, stride) -> { offset = o; size = s; stride = stride; } - - let join : t -> t -> t - = fun a1 a2 -> - if phys_equal a1 a2 then a2 else - { offset = Itv.join a1.offset a2.offset; - size = Itv.join a1.size a2.size; - stride = Itv.join a1.stride a2.stride; } - - let widen : prev:t -> next:t -> num_iters:int -> t - = fun ~prev ~next ~num_iters -> - if phys_equal prev next then next else - { offset = Itv.widen ~prev:prev.offset ~next:next.offset ~num_iters; - size = Itv.widen ~prev:prev.size ~next:next.size ~num_iters; - stride = Itv.widen ~prev:prev.stride ~next:next.stride ~num_iters; } - - let eq : t -> t -> bool - = fun a1 a2 -> - if phys_equal a1 a2 then true else - Itv.eq a1.offset a2.offset - && Itv.eq a1.size a2.size - && Itv.eq a1.stride a2.stride - - let (<=) : lhs:t -> rhs:t -> bool - = fun ~lhs ~rhs -> - if phys_equal lhs rhs then true else - Itv.le ~lhs:lhs.offset ~rhs:rhs.offset - && Itv.le ~lhs:lhs.size ~rhs:rhs.size + let bot : t = {offset= Itv.bot; size= Itv.bot; stride= Itv.bot} + + let initial : t = bot + + let top : t = {offset= Itv.top; size= Itv.top; stride= Itv.top} + + let input : t = {offset= Itv.zero; size= Itv.pos; stride= Itv.one} + + let make : Itv.t * Itv.t * Itv.t -> t = fun (o, s, stride) -> {offset= o; size= s; stride} + + let join : t -> t -> t = + fun a1 a2 -> + if phys_equal a1 a2 then a2 + else + { offset= Itv.join a1.offset a2.offset + ; size= Itv.join a1.size a2.size + ; stride= Itv.join a1.stride a2.stride } + + let widen : prev:t -> next:t -> num_iters:int -> t = + fun ~prev ~next ~num_iters -> + if phys_equal prev next then next + else + { offset= Itv.widen ~prev:prev.offset ~next:next.offset ~num_iters + ; size= Itv.widen ~prev:prev.size ~next:next.size ~num_iters + ; stride= Itv.widen ~prev:prev.stride ~next:next.stride ~num_iters } + + let eq : t -> t -> bool = + fun a1 a2 -> + if phys_equal a1 a2 then true + else Itv.eq a1.offset a2.offset && Itv.eq a1.size a2.size && Itv.eq a1.stride a2.stride + + let ( <= ) : lhs:t -> rhs:t -> bool = + fun ~lhs ~rhs -> + if phys_equal lhs rhs then true + else Itv.le ~lhs:lhs.offset ~rhs:rhs.offset && Itv.le ~lhs:lhs.size ~rhs:rhs.size && Itv.le ~lhs:lhs.stride ~rhs:rhs.stride - let weak_plus_size : t -> Itv.t -> t - = fun arr i -> { arr with size = Itv.join arr.size (Itv.plus i arr.size) } + let weak_plus_size : t -> Itv.t -> t = + fun arr i -> {arr with size= Itv.join arr.size (Itv.plus i arr.size)} - let plus_offset : t -> Itv.t -> t - = fun arr i -> { arr with offset = Itv.plus arr.offset i } + let plus_offset : t -> Itv.t -> t = fun arr i -> {arr with offset= Itv.plus arr.offset i} - let minus_offset : t -> Itv.astate -> t - = fun arr i -> { arr with offset = Itv.minus arr.offset i } + let minus_offset : t -> Itv.astate -> t = fun arr i -> {arr with offset= Itv.minus arr.offset i} - let diff : t -> t -> Itv.astate - = fun arr1 arr2 -> - Itv.minus arr1.offset arr2.offset + let diff : t -> t -> Itv.astate = fun arr1 arr2 -> Itv.minus arr1.offset arr2.offset - let subst : t -> Itv.Bound.t Itv.SubstMap.t -> t - = fun arr subst_map -> - { arr with offset = Itv.subst arr.offset subst_map; - size = Itv.subst arr.size subst_map; } + let subst : t -> Itv.Bound.t Itv.SubstMap.t -> t = + fun arr subst_map -> + {arr with offset= Itv.subst arr.offset subst_map; size= Itv.subst arr.size subst_map} - let pp : Format.formatter -> t -> unit - = fun fmt arr -> - Format.fprintf fmt "offset : %a, size : %a" - Itv.pp arr.offset Itv.pp arr.size + let pp : Format.formatter -> t -> unit = + fun fmt arr -> Format.fprintf fmt "offset : %a, size : %a" Itv.pp arr.offset Itv.pp arr.size - let get_symbols : t -> Itv.Symbol.t list - = fun arr -> + let get_symbols : t -> Itv.Symbol.t list = + fun arr -> let s1 = Itv.get_symbols arr.offset in let s2 = Itv.get_symbols arr.size in let s3 = Itv.get_symbols arr.stride in List.concat [s1; s2; s3] - let normalize : t -> t - = fun arr -> - { offset = Itv.normalize arr.offset; - size = Itv.normalize arr.size; - stride = Itv.normalize arr.stride } + let normalize : t -> t = + fun arr -> + { offset= Itv.normalize arr.offset + ; size= Itv.normalize arr.size + ; stride= Itv.normalize arr.stride } - let prune_comp : Binop.t -> t -> t -> t - = fun c arr1 arr2 -> - { arr1 with offset = Itv.prune_comp c arr1.offset arr2.offset } + let prune_comp : Binop.t -> t -> t -> t = + fun c arr1 arr2 -> {arr1 with offset= Itv.prune_comp c arr1.offset arr2.offset} - let prune_eq : t -> t -> t - = fun arr1 arr2 -> { arr1 with offset = Itv.prune_eq arr1.offset arr2.offset } + let prune_eq : t -> t -> t = + fun arr1 arr2 -> {arr1 with offset= Itv.prune_eq arr1.offset arr2.offset} - let prune_ne : t -> t -> t - = fun arr1 arr2 -> { arr1 with offset = Itv.prune_ne arr1.offset arr2.offset } + let prune_ne : t -> t -> t = + fun arr1 arr2 -> {arr1 with offset= Itv.prune_ne arr1.offset arr2.offset} end include AbstractDomain.Map (Allocsite) (ArrInfo) -let bot : astate - = empty +let bot : astate = empty -let unknown : astate - = add Allocsite.unknown (ArrInfo.top) bot +let unknown : astate = add Allocsite.unknown ArrInfo.top bot -let is_bot : astate -> bool - = is_empty +let is_bot : astate -> bool = is_empty -let make : Allocsite.t -> Itv.t -> Itv.t -> Itv.t -> astate - = fun a o sz st -> add a (ArrInfo.make (o, sz, st)) bot +let make : Allocsite.t -> Itv.t -> Itv.t -> Itv.t -> astate = + fun a o sz st -> add a (ArrInfo.make (o, sz, st)) bot -let offsetof : astate -> Itv.t - = fun a -> fold (fun _ arr -> Itv.join arr.ArrInfo.offset) a Itv.bot +let offsetof : astate -> Itv.t = fun a -> fold (fun _ arr -> Itv.join arr.ArrInfo.offset) a Itv.bot -let sizeof : astate -> Itv.t - = fun a -> fold (fun _ arr -> Itv.join arr.ArrInfo.size) a Itv.bot +let sizeof : astate -> Itv.t = fun a -> fold (fun _ arr -> Itv.join arr.ArrInfo.size) a Itv.bot -let extern : string -> astate - = fun allocsite -> add allocsite ArrInfo.top empty +let extern : string -> astate = fun allocsite -> add allocsite ArrInfo.top empty -let input : string -> astate - = fun allocsite -> add allocsite ArrInfo.input empty +let input : string -> astate = fun allocsite -> add allocsite ArrInfo.input empty -let weak_plus_size : astate -> Itv.t -> astate - = fun arr i -> map (fun a -> ArrInfo.weak_plus_size a i) arr +let weak_plus_size : astate -> Itv.t -> astate = + fun arr i -> map (fun a -> ArrInfo.weak_plus_size a i) arr -let plus_offset : astate -> Itv.t -> astate - = fun arr i -> map (fun a -> ArrInfo.plus_offset a i) arr +let plus_offset : astate -> Itv.t -> astate = + fun arr i -> map (fun a -> ArrInfo.plus_offset a i) arr -let minus_offset : astate -> Itv.t -> astate - = fun arr i -> map (fun a -> ArrInfo.minus_offset a i) arr +let minus_offset : astate -> Itv.t -> astate = + fun arr i -> map (fun a -> ArrInfo.minus_offset a i) arr -let diff : astate -> astate -> Itv.t - = fun arr1 arr2 -> +let diff : astate -> astate -> Itv.t = + fun arr1 arr2 -> let diff_join k a2 acc = match find k arr1 with - | a1 -> Itv.join acc (ArrInfo.diff a1 a2) - | exception Not_found -> Itv.top + | a1 + -> Itv.join acc (ArrInfo.diff a1 a2) + | exception Not_found + -> Itv.top in fold diff_join arr2 Itv.bot -let get_pow_loc : astate -> PowLoc.t - = fun array -> +let get_pow_loc : astate -> PowLoc.t = + fun array -> let pow_loc_of_allocsite k _ acc = PowLoc.add (Loc.of_allocsite k) acc in fold pow_loc_of_allocsite array PowLoc.bot -let subst : astate -> Itv.Bound.t Itv.SubstMap.t -> astate - = fun a subst_map -> map (fun info -> ArrInfo.subst info subst_map) a +let subst : astate -> Itv.Bound.t Itv.SubstMap.t -> astate = + fun a subst_map -> map (fun info -> ArrInfo.subst info subst_map) a -let get_symbols : astate -> Itv.Symbol.t list - = fun a -> - List.concat_map ~f:(fun (_, ai) -> ArrInfo.get_symbols ai) (bindings a) +let get_symbols : astate -> Itv.Symbol.t list = + fun a -> List.concat_map ~f:(fun (_, ai) -> ArrInfo.get_symbols ai) (bindings a) -let normalize : astate -> astate - = fun a -> map ArrInfo.normalize a +let normalize : astate -> astate = fun a -> map ArrInfo.normalize a -let do_prune - : (ArrInfo.t -> ArrInfo.t -> ArrInfo.t) -> astate -> astate -> astate - = fun arr_info_prune a1 a2 -> +let do_prune : (ArrInfo.t -> ArrInfo.t -> ArrInfo.t) -> astate -> astate -> astate = + fun arr_info_prune a1 a2 -> if Int.equal (cardinal a2) 1 then - let (k, v2) = choose a2 in + let k, v2 = choose a2 in if mem k a1 then add k (arr_info_prune (find k a1) v2) a1 else a1 else a1 -let prune_comp : Binop.t -> astate -> astate -> astate - = fun c a1 a2 -> do_prune (ArrInfo.prune_comp c) a1 a2 +let prune_comp : Binop.t -> astate -> astate -> astate = + fun c a1 a2 -> do_prune (ArrInfo.prune_comp c) a1 a2 -let prune_eq : astate -> astate -> astate - = fun a1 a2 -> do_prune ArrInfo.prune_eq a1 a2 +let prune_eq : astate -> astate -> astate = fun a1 a2 -> do_prune ArrInfo.prune_eq a1 a2 -let prune_ne : astate -> astate -> astate - = fun a1 a2 -> do_prune ArrInfo.prune_ne a1 a2 +let prune_ne : astate -> astate -> astate = fun a1 a2 -> do_prune ArrInfo.prune_ne a1 a2 diff --git a/infer/src/bufferoverrun/bufferOverrunChecker.ml b/infer/src/bufferoverrun/bufferOverrunChecker.ml index 8887fced7..5ff7d1694 100644 --- a/infer/src/bufferoverrun/bufferOverrunChecker.ml +++ b/infer/src/bufferoverrun/bufferOverrunChecker.ml @@ -12,7 +12,6 @@ open! IStd open AbsLoc - module F = Format module L = Logging module Dom = BufferOverrunDomain @@ -20,203 +19,200 @@ module Trace = BufferOverrunTrace module TraceSet = Trace.Set module Summary = Summary.Make (struct - type payload = Dom.Summary.t + type payload = Dom.Summary.t - let update_payload astate (summary : Specs.summary) = - { summary with payload = { summary.payload with buffer_overrun = Some astate }} + let update_payload astate (summary: Specs.summary) = + {summary with payload= {summary.payload with buffer_overrun= Some astate}} - let read_payload (summary : Specs.summary) = - summary.payload.buffer_overrun - end) + let read_payload (summary: Specs.summary) = summary.payload.buffer_overrun +end) -module TransferFunctions (CFG : ProcCfg.S) = -struct +module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Dom.Mem module Sem = BufferOverrunSemantics.Make (CFG) type extras = Typ.Procname.t -> Procdesc.t option - let set_uninitialized node (typ : Typ.t) loc mem = match typ.desc with - | Tint _ | Tfloat _ -> Dom.Mem.weak_update_heap loc Dom.Val.Itv.top mem - | _ -> - L.(debug BufferOverrun Verbose) "/!\\ Do not know how to uninitialize type %a at %a@\n" - (Typ.pp Pp.text) typ - Location.pp (CFG.loc node); + let set_uninitialized node (typ: Typ.t) loc mem = + match typ.desc with + | Tint _ | Tfloat _ + -> Dom.Mem.weak_update_heap loc Dom.Val.Itv.top mem + | _ + -> L.(debug BufferOverrun Verbose) + "/!\\ Do not know how to uninitialize type %a at %a@\n" (Typ.pp Pp.text) typ Location.pp + (CFG.loc node) ; mem (* NOTE: heuristic *) - let get_malloc_info : Exp.t -> Typ.t * Int.t option * Exp.t - = function - | Exp.BinOp (Binop.Mult, Exp.Sizeof {typ; nbytes}, length) - | Exp.BinOp (Binop.Mult, length, Exp.Sizeof {typ; nbytes}) -> (typ, nbytes, length) - | Exp.Sizeof {typ; nbytes} -> (typ, nbytes, Exp.one) - | x -> (Typ.mk (Typ.Tint Typ.IChar), Some 1, x) + let get_malloc_info : Exp.t -> Typ.t * Int.t option * Exp.t = function + | Exp.BinOp (Binop.Mult, Exp.Sizeof {typ; nbytes}, length) + | Exp.BinOp (Binop.Mult, length, Exp.Sizeof {typ; nbytes}) + -> (typ, nbytes, length) + | Exp.Sizeof {typ; nbytes} + -> (typ, nbytes, Exp.one) + | x + -> (Typ.mk (Typ.Tint Typ.IChar), Some 1, x) let model_malloc - : Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node - -> Location.t -> Dom.Mem.astate -> Dom.Mem.astate - = fun pname ret params node location mem -> + : Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node + -> Location.t -> Dom.Mem.astate -> Dom.Mem.astate = + fun pname ret params node location mem -> match ret with - | Some (id, _) -> - let (typ, stride, length0) = get_malloc_info (List.hd_exn params |> fst) in + | Some (id, _) + -> let typ, stride, length0 = get_malloc_info (List.hd_exn params |> fst) in let length = Sem.eval length0 mem (CFG.loc node) in let traces = TraceSet.add_elem (Trace.ArrDecl location) (Dom.Val.get_traces length) in - let v = Sem.eval_array_alloc pname node typ ?stride Itv.zero (Dom.Val.get_itv length) 0 1 - |> Dom.Val.set_traces traces in - mem - |> Dom.Mem.add_stack (Loc.of_id id) v + let v = + Sem.eval_array_alloc pname node typ ?stride Itv.zero (Dom.Val.get_itv length) 0 1 + |> Dom.Val.set_traces traces + in + mem |> Dom.Mem.add_stack (Loc.of_id id) v |> set_uninitialized node typ (Dom.Val.get_array_locs v) - | _ -> - L.(debug BufferOverrun Verbose) "/!\\ Do not know where to model malloc at %a@\n" - Location.pp (CFG.loc node); + | _ + -> L.(debug BufferOverrun Verbose) + "/!\\ Do not know where to model malloc at %a@\n" Location.pp (CFG.loc node) ; mem let model_realloc - : Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node - -> Location.t -> Dom.Mem.astate -> Dom.Mem.astate - = fun pname ret params node location mem -> + : Typ.Procname.t -> (Ident.t * Typ.t) option -> (Exp.t * Typ.t) list -> CFG.node + -> Location.t -> Dom.Mem.astate -> Dom.Mem.astate = + fun pname ret params node location mem -> model_malloc pname ret (List.tl_exn params) node location mem let model_by_value value ret mem = match ret with - | Some (id, _) -> - Dom.Mem.add_stack (Loc.of_id id) value mem - | None -> - L.(debug BufferOverrun Verbose) "/!\\ Do not know where to model value %a@\n" - Dom.Val.pp value; + | Some (id, _) + -> Dom.Mem.add_stack (Loc.of_id id) value mem + | None + -> L.(debug BufferOverrun Verbose) + "/!\\ Do not know where to model value %a@\n" Dom.Val.pp value ; mem - let model_infer_print - : (Exp.t * Typ.t) list -> Dom.Mem.astate -> Location.t -> Dom.Mem.astate - = fun params mem loc -> + let model_infer_print : (Exp.t * Typ.t) list -> Dom.Mem.astate -> Location.t -> Dom.Mem.astate = + fun params mem loc -> match params with - | (e, _) :: _ -> - L.(debug BufferOverrun Medium) "@[=== Infer Print === at %a@,%a@]%!" - Location.pp loc - Dom.Val.pp (Sem.eval e mem loc); + | (e, _) :: _ + -> L.(debug BufferOverrun Medium) + "@[=== Infer Print === at %a@,%a@]%!" Location.pp loc Dom.Val.pp + (Sem.eval e mem loc) ; mem - | _ -> mem + | _ + -> mem let model_infer_set_array_length pname node params mem loc = match params with - | (Exp.Lvar array_pvar, {Typ.desc=Typ.Tarray (typ, _, stride0)}) - :: (length_exp, _) :: [] -> - let length = Sem.eval length_exp mem loc |> Dom.Val.get_itv in + | [(Exp.Lvar array_pvar, {Typ.desc= Typ.Tarray (typ, _, stride0)}); (length_exp, _)] + -> let length = Sem.eval length_exp mem loc |> Dom.Val.get_itv in let stride = Option.map ~f:IntLit.to_int stride0 in let v = Sem.eval_array_alloc pname node typ ?stride Itv.zero length 0 1 in - mem - |> Dom.Mem.add_stack (Loc.of_pvar array_pvar) v + mem |> Dom.Mem.add_stack (Loc.of_pvar array_pvar) v |> set_uninitialized node typ (Dom.Val.get_array_locs v) - | _ :: _ :: [] -> - failwithf "Unexpected type of arguments for __set_array_length()" - | _ -> - failwithf "Unexpected number of arguments for __set_array_length()" + | [_; _] + -> failwithf "Unexpected type of arguments for __set_array_length()" + | _ + -> failwithf "Unexpected number of arguments for __set_array_length()" let handle_unknown_call - : Typ.Procname.t -> (Ident.t * Typ.t) option -> Typ.Procname.t - -> (Exp.t * Typ.t) list -> CFG.node -> Dom.Mem.astate -> Location.t - -> Dom.Mem.astate - = fun pname ret callee_pname params node mem loc -> + : Typ.Procname.t -> (Ident.t * Typ.t) option -> Typ.Procname.t -> (Exp.t * Typ.t) list + -> CFG.node -> Dom.Mem.astate -> Location.t -> Dom.Mem.astate = + fun pname ret callee_pname params node mem loc -> match Typ.Procname.get_method callee_pname with - | "__exit" - | "exit" -> Dom.Mem.Bottom - | "fgetc" -> model_by_value Dom.Val.Itv.m1_255 ret mem - | "infer_print" -> model_infer_print params mem loc - | "malloc" - | "__new_array" -> model_malloc pname ret params node loc mem - | "realloc" -> model_realloc pname ret params node loc mem - | "__set_array_length" -> model_infer_set_array_length pname node params mem loc - | "strlen" -> model_by_value Dom.Val.Itv.nat ret mem - | proc_name -> - L.(debug BufferOverrun Verbose) "/!\\ Unknown call to %s at %a@\n" - proc_name - Location.pp loc; - model_by_value Dom.Val.unknown ret mem - |> Dom.Mem.add_heap Loc.unknown Dom.Val.unknown + | "__exit" | "exit" + -> Dom.Mem.Bottom + | "fgetc" + -> model_by_value Dom.Val.Itv.m1_255 ret mem + | "infer_print" + -> model_infer_print params mem loc + | "malloc" | "__new_array" + -> model_malloc pname ret params node loc mem + | "realloc" + -> model_realloc pname ret params node loc mem + | "__set_array_length" + -> model_infer_set_array_length pname node params mem loc + | "strlen" + -> model_by_value Dom.Val.Itv.nat ret mem + | proc_name + -> L.(debug BufferOverrun Verbose) + "/!\\ Unknown call to %s at %a@\n" proc_name Location.pp loc ; + model_by_value Dom.Val.unknown ret mem |> Dom.Mem.add_heap Loc.unknown Dom.Val.unknown let rec declare_array - : Typ.Procname.t -> CFG.node -> Location.t -> Loc.t -> Typ.t -> length:IntLit.t option - -> ?stride:int -> inst_num:int -> dimension:int -> Dom.Mem.astate -> Dom.Mem.astate - = fun pname node location loc typ ~length ?stride ~inst_num ~dimension mem -> + : Typ.Procname.t -> CFG.node -> Location.t -> Loc.t -> Typ.t -> length:IntLit.t option + -> ?stride:int -> inst_num:int -> dimension:int -> Dom.Mem.astate -> Dom.Mem.astate = + fun pname node location loc typ ~length ?stride ~inst_num ~dimension mem -> let size = Option.value_map ~default:Itv.top ~f:Itv.of_int_lit length in let arr = Sem.eval_array_alloc pname node typ Itv.zero size ?stride inst_num dimension |> Dom.Val.add_trace_elem (Trace.ArrDecl location) in let mem = - if Int.equal dimension 1 - then Dom.Mem.add_stack loc arr mem + if Int.equal dimension 1 then Dom.Mem.add_stack loc arr mem else Dom.Mem.add_heap loc arr mem in - let loc = - Loc.of_allocsite (Sem.get_allocsite pname node inst_num dimension) - in + let loc = Loc.of_allocsite (Sem.get_allocsite pname node inst_num dimension) in match typ.Typ.desc with - | Typ.Tarray (typ, length, stride) -> - declare_array pname node location loc typ ~length + | Typ.Tarray (typ, length, stride) + -> declare_array pname node location loc typ ~length ?stride:(Option.map ~f:IntLit.to_int stride) ~inst_num ~dimension:(dimension + 1) mem - | _ -> mem + | _ + -> mem let counter_gen init = let num_ref = ref init in let get_num () = let v = !num_ref in - num_ref := v + 1; + num_ref := v + 1 ; v in get_num let declare_symbolic_val - : Typ.Procname.t -> Tenv.t -> CFG.node -> Location.t -> Loc.t -> Typ.typ - -> inst_num:int -> new_sym_num: (unit -> int) -> Domain.t -> Domain.t - = fun pname tenv node location loc typ ~inst_num ~new_sym_num mem -> + : Typ.Procname.t -> Tenv.t -> CFG.node -> Location.t -> Loc.t -> Typ.typ -> inst_num:int + -> new_sym_num:(unit -> int) -> Domain.t -> Domain.t = + fun pname tenv node location loc typ ~inst_num ~new_sym_num mem -> let max_depth = 2 in let new_alloc_num = counter_gen 1 in let rec decl_sym_val ~depth loc typ mem = - if depth > max_depth then mem else + if depth > max_depth then mem + else let depth = depth + 1 in match typ.Typ.desc with - | Typ.Tint ikind -> - let unsigned = Typ.ikind_is_unsigned ikind in - let v = Dom.Val.make_sym ~unsigned pname new_sym_num - |> Dom.Val.add_trace_elem (Trace.SymAssign location) + | Typ.Tint ikind + -> let unsigned = Typ.ikind_is_unsigned ikind in + let v = + Dom.Val.make_sym ~unsigned pname new_sym_num + |> Dom.Val.add_trace_elem (Trace.SymAssign location) in Dom.Mem.add_heap loc v mem - | Typ.Tfloat _ -> - let v = Dom.Val.make_sym pname new_sym_num - |> Dom.Val.add_trace_elem (Trace.SymAssign location) + | Typ.Tfloat _ + -> let v = + Dom.Val.make_sym pname new_sym_num + |> Dom.Val.add_trace_elem (Trace.SymAssign location) in Dom.Mem.add_heap loc v mem - | Typ.Tptr (typ, _) -> - decl_sym_arr ~depth loc location typ mem - | Typ.Tarray (typ, opt_int_lit, _) -> - let opt_size = Option.map ~f:Itv.of_int_lit opt_int_lit in + | Typ.Tptr (typ, _) + -> decl_sym_arr ~depth loc location typ mem + | Typ.Tarray (typ, opt_int_lit, _) + -> let opt_size = Option.map ~f:Itv.of_int_lit opt_int_lit in let opt_offset = Some Itv.zero in decl_sym_arr ~depth loc location typ ~opt_offset ~opt_size mem - | Typ.Tstruct typename -> - let decl_fld mem (fn, typ, _) = + | Typ.Tstruct typename + -> let decl_fld mem (fn, typ, _) = let loc_fld = Loc.append_field loc fn in decl_sym_val ~depth loc_fld typ mem in - let decl_flds str = - List.fold ~f:decl_fld ~init:mem str.Typ.Struct.fields - in + let decl_flds str = List.fold ~f:decl_fld ~init:mem str.Typ.Struct.fields in let opt_struct = Tenv.lookup tenv typename in Option.value_map opt_struct ~default:mem ~f:decl_flds - | _ -> - if Config.bo_debug >= 3 then - L.(debug BufferOverrun Verbose) "/!\\ decl_fld of unhandled type: %a at %a@." - (Typ.pp Pp.text) typ - Location.pp (CFG.loc node); + | _ + -> if Config.bo_debug >= 3 then + L.(debug BufferOverrun Verbose) + "/!\\ decl_fld of unhandled type: %a at %a@." (Typ.pp Pp.text) typ Location.pp + (CFG.loc node) ; mem - - and decl_sym_arr ~depth loc location typ ?(opt_offset=None) ?(opt_size=None) mem = - let option_value opt_x default_f = - match opt_x with - | Some x -> x - | None -> default_f () - in + and decl_sym_arr ~depth loc location typ ?(opt_offset= None) ?(opt_size= None) mem = + let option_value opt_x default_f = match opt_x with Some x -> x | None -> default_f () in let itv_make_sym () = Itv.make_sym pname new_sym_num in let offset = option_value opt_offset itv_make_sym in let size = option_value opt_size itv_make_sym in @@ -227,16 +223,14 @@ struct |> Dom.Val.add_trace_elem elem in let mem = Dom.Mem.add_heap loc arr mem in - let deref_loc = - Loc.of_allocsite (Sem.get_allocsite pname node inst_num alloc_num) - in + let deref_loc = Loc.of_allocsite (Sem.get_allocsite pname node inst_num alloc_num) in decl_sym_val ~depth deref_loc typ mem in decl_sym_val ~depth:0 loc typ mem let declare_symbolic_parameter - : Procdesc.t -> Tenv.t -> CFG.node -> Location.t -> int -> Dom.Mem.astate -> Dom.Mem.astate - = fun pdesc tenv node location inst_num mem -> + : Procdesc.t -> Tenv.t -> CFG.node -> Location.t -> int -> Dom.Mem.astate -> Dom.Mem.astate = + fun pdesc tenv node location inst_num mem -> let pname = Procdesc.get_proc_name pdesc in let new_sym_num = counter_gen 0 in let add_formal (mem, inst_num) (pvar, typ) = @@ -246,157 +240,154 @@ struct in (mem, inst_num + 1) in - List.fold ~f:add_formal ~init:(mem, inst_num) (Sem.get_formals pdesc) - |> fst + List.fold ~f:add_formal ~init:(mem, inst_num) (Sem.get_formals pdesc) |> fst let instantiate_ret ret callee_pname callee_exit_mem subst_map mem loc = match ret with - | Some (id, _) -> - let ret_loc = Loc.of_pvar (Pvar.get_ret_pvar callee_pname) in + | Some (id, _) + -> let ret_loc = Loc.of_pvar (Pvar.get_ret_pvar callee_pname) in let ret_val = Dom.Mem.find_heap ret_loc callee_exit_mem in let ret_var = Loc.of_var (Var.of_id id) in - Dom.Val.subst ret_val subst_map loc - |> Dom.Val.add_trace_elem (Trace.Return loc) + Dom.Val.subst ret_val subst_map loc |> Dom.Val.add_trace_elem (Trace.Return loc) |> Fn.flip (Dom.Mem.add_stack ret_var) mem - | None -> mem + | None + -> mem let instantiate_param tenv pdesc params callee_entry_mem callee_exit_mem subst_map location mem = let formals = Sem.get_formals pdesc in let actuals = List.map ~f:(fun (a, _) -> Sem.eval a mem location) params in let f mem formal actual = match (snd formal).Typ.desc with - | Typ.Tptr (typ, _) -> - (match typ.Typ.desc with - | Typ.Tstruct typename -> - (match Tenv.lookup tenv typename with - | Some str -> - let formal_locs = Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem - |> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc in - let instantiate_fld mem (fn, _, _) = - let formal_fields = PowLoc.append_field formal_locs fn in - let v = Dom.Mem.find_heap_set formal_fields callee_exit_mem in - let actual_fields = PowLoc.append_field (Dom.Val.get_all_locs actual) fn in - Dom.Val.subst v subst_map location - |> Fn.flip (Dom.Mem.strong_update_heap actual_fields) mem - in - List.fold ~f:instantiate_fld ~init:mem str.Typ.Struct.fields - | _ -> mem) - | _ -> - let formal_locs = Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem - |> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc in - let v = Dom.Mem.find_heap_set formal_locs callee_exit_mem in - let actual_locs = Dom.Val.get_all_locs actual in - Dom.Val.subst v subst_map location - |> Fn.flip (Dom.Mem.strong_update_heap actual_locs) mem) - | _ -> mem + | Typ.Tptr (typ, _) -> ( + match typ.Typ.desc with + | Typ.Tstruct typename -> ( + match Tenv.lookup tenv typename with + | Some str + -> let formal_locs = + Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem + |> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc + in + let instantiate_fld mem (fn, _, _) = + let formal_fields = PowLoc.append_field formal_locs fn in + let v = Dom.Mem.find_heap_set formal_fields callee_exit_mem in + let actual_fields = PowLoc.append_field (Dom.Val.get_all_locs actual) fn in + Dom.Val.subst v subst_map location + |> Fn.flip (Dom.Mem.strong_update_heap actual_fields) mem + in + List.fold ~f:instantiate_fld ~init:mem str.Typ.Struct.fields + | _ + -> mem ) + | _ + -> let formal_locs = + Dom.Mem.find_heap (Loc.of_pvar (fst formal)) callee_entry_mem + |> Dom.Val.get_array_blk |> ArrayBlk.get_pow_loc + in + let v = Dom.Mem.find_heap_set formal_locs callee_exit_mem in + let actual_locs = Dom.Val.get_all_locs actual in + Dom.Val.subst v subst_map location + |> Fn.flip (Dom.Mem.strong_update_heap actual_locs) mem ) + | _ + -> mem in - try List.fold2_exn formals actuals ~init:mem ~f with Invalid_argument _ -> mem + try List.fold2_exn formals actuals ~init:mem ~f + with Invalid_argument _ -> mem let instantiate_mem - : Tenv.t -> (Ident.t * Typ.t) option -> Procdesc.t option -> Typ.Procname.t - -> (Exp.t * Typ.t) list -> Dom.Mem.astate -> Dom.Summary.t -> Location.t -> Dom.Mem.astate - = fun tenv ret callee_pdesc callee_pname params caller_mem summary loc -> + : Tenv.t -> (Ident.t * Typ.t) option -> Procdesc.t option -> Typ.Procname.t + -> (Exp.t * Typ.t) list -> Dom.Mem.astate -> Dom.Summary.t -> Location.t -> Dom.Mem.astate = + fun tenv ret callee_pdesc callee_pname params caller_mem summary loc -> let callee_entry_mem = Dom.Summary.get_input summary in let callee_exit_mem = Dom.Summary.get_output summary in match callee_pdesc with - | Some pdesc -> - let subst_map = - Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem loc - in + | Some pdesc + -> let subst_map = Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem loc in instantiate_ret ret callee_pname callee_exit_mem subst_map caller_mem loc |> instantiate_param tenv pdesc params callee_entry_mem callee_exit_mem subst_map loc - | None -> caller_mem - - let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.Mem.astate -> unit - = fun instr pre post -> - L.(debug BufferOverrun Verbose) "@\n@\n================================@\n"; - L.(debug BufferOverrun Verbose) "@[Pre-state : @,%a" Dom.Mem.pp pre; - L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr; - L.(debug BufferOverrun Verbose) "@\n@\n"; - L.(debug BufferOverrun Verbose) "@[Post-state : @,%a" Dom.Mem.pp post; - L.(debug BufferOverrun Verbose) "@]@\n"; + | None + -> caller_mem + + let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.Mem.astate -> unit = + fun instr pre post -> + L.(debug BufferOverrun Verbose) "@\n@\n================================@\n" ; + L.(debug BufferOverrun Verbose) "@[Pre-state : @,%a" Dom.Mem.pp pre ; + L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr ; + L.(debug BufferOverrun Verbose) "@\n@\n" ; + L.(debug BufferOverrun Verbose) "@[Post-state : @,%a" Dom.Mem.pp post ; + L.(debug BufferOverrun Verbose) "@]@\n" ; L.(debug BufferOverrun Verbose) "================================@\n@." - let exec_instr - : Dom.Mem.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Dom.Mem.astate - = fun mem { pdesc; tenv; extras } node instr -> + let exec_instr : Dom.Mem.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Dom.Mem.astate = + fun mem {pdesc; tenv; extras} node instr -> let pname = Procdesc.get_proc_name pdesc in let output_mem = match instr with - | Load (id, exp, _, loc) -> - let locs = Sem.eval exp mem loc |> Dom.Val.get_all_locs in + | Load (id, exp, _, loc) + -> let locs = Sem.eval exp mem loc |> Dom.Val.get_all_locs in let v = Dom.Mem.find_heap_set locs mem in - if Ident.is_none id then - mem - else - Dom.Mem.add_stack (Loc.of_var (Var.of_id id)) v mem - |> Dom.Mem.load_alias id exp - | Store (exp1, _, exp2, loc) -> - let locs = Sem.eval exp1 mem loc |> Dom.Val.get_all_locs in + if Ident.is_none id then mem + else Dom.Mem.add_stack (Loc.of_var (Var.of_id id)) v mem |> Dom.Mem.load_alias id exp + | Store (exp1, _, exp2, loc) + -> let locs = Sem.eval exp1 mem loc |> Dom.Val.get_all_locs in let v = Sem.eval exp2 mem loc |> Dom.Val.add_trace_elem (Trace.Assign loc) in - Dom.Mem.update_mem locs v mem - |> Dom.Mem.store_alias exp1 exp2 - | Prune (exp, loc, _, _) -> Sem.prune exp loc mem - | Call (ret, Const (Cfun callee_pname), params, loc, _) -> - (match Summary.read_summary pdesc callee_pname with - | Some summary -> - let callee = extras callee_pname in - instantiate_mem tenv ret callee callee_pname params mem summary loc - | None -> - handle_unknown_call pname ret callee_pname params node mem loc) - | Declare_locals (locals, location) -> - (* array allocation in stack e.g., int arr[10] *) + Dom.Mem.update_mem locs v mem |> Dom.Mem.store_alias exp1 exp2 + | Prune (exp, loc, _, _) + -> Sem.prune exp loc mem + | Call (ret, Const Cfun callee_pname, params, loc, _) -> ( + match Summary.read_summary pdesc callee_pname with + | Some summary + -> let callee = extras callee_pname in + instantiate_mem tenv ret callee callee_pname params mem summary loc + | None + -> handle_unknown_call pname ret callee_pname params node mem loc ) + | Declare_locals (locals, location) + -> (* array allocation in stack e.g., int arr[10] *) let try_decl_arr location (mem, inst_num) (pvar, typ) = match typ.Typ.desc with - | Typ.Tarray (typ, length, stride0) -> - let loc = Loc.of_pvar pvar in + | Typ.Tarray (typ, length, stride0) + -> let loc = Loc.of_pvar pvar in let stride = Option.map ~f:IntLit.to_int stride0 in let mem = declare_array pname node location loc typ ~length ?stride ~inst_num ~dimension:1 mem in (mem, inst_num + 1) - | _ -> (mem, inst_num) + | _ + -> (mem, inst_num) in - let (mem, inst_num) = List.fold ~f:(try_decl_arr location) ~init:(mem, 1) locals in + let mem, inst_num = List.fold ~f:(try_decl_arr location) ~init:(mem, 1) locals in declare_symbolic_parameter pdesc tenv node location inst_num mem - | Call (_, fun_exp, _, loc, _) -> - let () = - L.(debug BufferOverrun Verbose) "/!\\ Call to non-const function %a at %a" - Exp.pp fun_exp - Location.pp loc + | Call (_, fun_exp, _, loc, _) + -> let () = + L.(debug BufferOverrun Verbose) + "/!\\ Call to non-const function %a at %a" Exp.pp fun_exp Location.pp loc in mem - | Remove_temps _ - | Abstract _ - | Nullify _ -> mem + | Remove_temps _ | Abstract _ | Nullify _ + -> mem in - print_debug_info instr mem output_mem; - output_mem + print_debug_info instr mem output_mem ; output_mem end module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions) - module CFG = Analyzer.TransferFunctions.CFG module Sem = BufferOverrunSemantics.Make (CFG) -module Report = -struct +module Report = struct type extras = Typ.Procname.t -> Procdesc.t option let add_condition - : Typ.Procname.t -> CFG.node -> Exp.t -> Location.t -> Dom.Mem.astate - -> Dom.ConditionSet.t -> Dom.ConditionSet.t - = fun pname node exp loc mem cond_set -> + : Typ.Procname.t -> CFG.node -> Exp.t -> Location.t -> Dom.Mem.astate -> Dom.ConditionSet.t + -> Dom.ConditionSet.t = + fun pname node exp loc mem cond_set -> let array_access = match exp with - | Exp.Var _ -> - let v = Sem.eval exp mem loc in + | Exp.Var _ + -> let v = Sem.eval exp mem loc in let arr = Dom.Val.get_array_blk v in let arr_traces = Dom.Val.get_traces v in Some (arr, arr_traces, Itv.zero, TraceSet.empty, true) - | Exp.Lindex (e1, e2) -> - let locs = Sem.eval_locs e1 mem loc |> Dom.Val.get_all_locs in + | Exp.Lindex (e1, e2) + -> let locs = Sem.eval_locs e1 mem loc |> Dom.Val.get_all_locs in let v_arr = Dom.Mem.find_set locs mem in let arr = Dom.Val.get_array_blk v_arr in let arr_traces = Dom.Val.get_traces v_arr in @@ -404,9 +395,8 @@ struct let idx = Dom.Val.get_itv v_idx in let idx_traces = Dom.Val.get_traces v_idx in Some (arr, arr_traces, idx, idx_traces, true) - | Exp.BinOp (Binop.PlusA as bop, e1, e2) - | Exp.BinOp (Binop.MinusA as bop, e1, e2) -> - let v_arr = Sem.eval e1 mem loc in + | Exp.BinOp ((Binop.PlusA as bop), e1, e2) | Exp.BinOp ((Binop.MinusA as bop), e1, e2) + -> let v_arr = Sem.eval e1 mem loc in let arr = Dom.Val.get_array_blk v_arr in let arr_traces = Dom.Val.get_traces v_arr in let v_idx = Sem.eval e2 mem loc in @@ -414,170 +404,182 @@ struct let idx_traces = Dom.Val.get_traces v_idx in let is_plus = Binop.equal bop Binop.PlusA in Some (arr, arr_traces, idx, idx_traces, is_plus) - | _ -> None + | _ + -> None in match array_access with - | Some (arr, traces_arr, idx, traces_idx, is_plus) -> - let site = Sem.get_allocsite pname node 0 0 in + | Some (arr, traces_arr, idx, traces_idx, is_plus) + -> let site = Sem.get_allocsite pname node 0 0 in let size = ArrayBlk.sizeof arr in let offset = ArrayBlk.offsetof arr in let idx = (if is_plus then Itv.plus else Itv.minus) offset idx in - L.(debug BufferOverrun Verbose) "@[Add condition :@,"; - L.(debug BufferOverrun Verbose) "array: %a@," ArrayBlk.pp arr; - L.(debug BufferOverrun Verbose) " idx: %a@," Itv.pp idx; - L.(debug BufferOverrun Verbose) "@]@."; + L.(debug BufferOverrun Verbose) "@[Add condition :@," ; + L.(debug BufferOverrun Verbose) "array: %a@," ArrayBlk.pp arr ; + L.(debug BufferOverrun Verbose) " idx: %a@," Itv.pp idx ; + L.(debug BufferOverrun Verbose) "@]@." ; if size <> Itv.bot && idx <> Itv.bot then let traces = TraceSet.merge ~traces_arr ~traces_idx loc in Dom.ConditionSet.add_bo_safety pname loc site ~size ~idx traces cond_set else cond_set - | None -> cond_set + | None + -> cond_set let instantiate_cond - : Tenv.t -> Typ.Procname.t -> Procdesc.t option -> (Exp.t * Typ.t) list - -> Dom.Mem.astate -> Summary.payload -> Location.t -> Dom.ConditionSet.t - = fun tenv caller_pname callee_pdesc params caller_mem summary loc -> + : Tenv.t -> Typ.Procname.t -> Procdesc.t option -> (Exp.t * Typ.t) list -> Dom.Mem.astate + -> Summary.payload -> Location.t -> Dom.ConditionSet.t = + fun tenv caller_pname callee_pdesc params caller_mem summary loc -> let callee_entry_mem = Dom.Summary.get_input summary in let callee_cond = Dom.Summary.get_cond_set summary in match callee_pdesc with - | Some pdesc -> - let subst_map = - Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem loc - in + | Some pdesc + -> let subst_map = Sem.get_subst_map tenv pdesc params caller_mem callee_entry_mem loc in let pname = Procdesc.get_proc_name pdesc in Dom.ConditionSet.subst callee_cond subst_map caller_pname pname loc - | _ -> callee_cond - - let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.ConditionSet.t -> unit - = fun instr pre cond_set -> - L.(debug BufferOverrun Verbose) "@\n@\n================================@\n"; - L.(debug BufferOverrun Verbose) "@[Pre-state : @,%a" Dom.Mem.pp pre; - L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr; - L.(debug BufferOverrun Verbose) "@[@\n@\n%a" Dom.ConditionSet.pp cond_set; - L.(debug BufferOverrun Verbose) "@]@\n"; + | _ + -> callee_cond + + let print_debug_info : Sil.instr -> Dom.Mem.astate -> Dom.ConditionSet.t -> unit = + fun instr pre cond_set -> + L.(debug BufferOverrun Verbose) "@\n@\n================================@\n" ; + L.(debug BufferOverrun Verbose) "@[Pre-state : @,%a" Dom.Mem.pp pre ; + L.(debug BufferOverrun Verbose) "@]@\n@\n%a" (Sil.pp_instr Pp.text) instr ; + L.(debug BufferOverrun Verbose) "@[@\n@\n%a" Dom.ConditionSet.pp cond_set ; + L.(debug BufferOverrun Verbose) "@]@\n" ; L.(debug BufferOverrun Verbose) "================================@\n@." let is_last_statement_of_if_branch rem_instrs node = if rem_instrs <> [] then false else match Procdesc.Node.get_succs node with - | [succ] -> - (match Procdesc.Node.get_preds succ with - | _ :: _ :: _ -> true - | _ -> false) - | _ -> false + | [succ] -> ( + match Procdesc.Node.get_preds succ with _ :: _ :: _ -> true | _ -> false ) + | _ + -> false let rec collect_instrs - : extras ProcData.t -> CFG.node -> Sil.instr list -> Dom.Mem.astate - -> Dom.ConditionSet.t -> Dom.ConditionSet.t - = fun ({ pdesc; tenv; extras } as pdata) node instrs mem cond_set -> + : extras ProcData.t -> CFG.node -> Sil.instr list -> Dom.Mem.astate -> Dom.ConditionSet.t + -> Dom.ConditionSet.t = + fun ({pdesc; tenv; extras} as pdata) node instrs mem cond_set -> match instrs with - | [] -> cond_set - | instr :: rem_instrs -> - let pname = Procdesc.get_proc_name pdesc in + | [] + -> cond_set + | instr :: rem_instrs + -> let pname = Procdesc.get_proc_name pdesc in let cond_set = match instr with - | Sil.Load (_, exp, _, loc) - | Sil.Store (exp, _, _, loc) -> - add_condition pname node exp loc mem cond_set - | Sil.Call (_, Const (Cfun callee_pname), params, loc, _) -> - (match Summary.read_summary pdesc callee_pname with - | Some summary -> - let callee = extras callee_pname in - instantiate_cond tenv pname callee params mem summary loc - |> Dom.ConditionSet.rm_invalid - |> Dom.ConditionSet.join cond_set - | _ -> cond_set) - | _ -> cond_set + | Sil.Load (_, exp, _, loc) | Sil.Store (exp, _, _, loc) + -> add_condition pname node exp loc mem cond_set + | Sil.Call (_, Const Cfun callee_pname, params, loc, _) -> ( + match Summary.read_summary pdesc callee_pname with + | Some summary + -> let callee = extras callee_pname in + instantiate_cond tenv pname callee params mem summary loc + |> Dom.ConditionSet.rm_invalid |> Dom.ConditionSet.join cond_set + | _ + -> cond_set ) + | _ + -> cond_set in let mem' = Analyzer.TransferFunctions.exec_instr mem pdata node instr in let () = - match mem, mem' with - | NonBottom _, Bottom -> - (match instr with - | Sil.Prune (_, _, _, (Ik_land_lor | Ik_bexp)) -> () - | Sil.Prune (cond, loc, true_branch, _) -> - let i = match cond with - | Exp.Const (Const.Cint i) -> i - | _ -> IntLit.zero in - let desc = Errdesc.explain_condition_always_true_false tenv i cond node loc in - let exn = - Exceptions.Condition_always_true_false (desc, not true_branch, __POS__) in - Reporting.log_warning_deprecated pname ~loc exn - | Sil.Call (_, Const (Cfun pname), _, _, _) when - String.equal (Typ.Procname.get_method pname) "exit" && - is_last_statement_of_if_branch rem_instrs node -> () - | _ -> - let loc = Sil.instr_get_loc instr in - let desc = Errdesc.explain_unreachable_code_after loc in - let exn = Exceptions.Unreachable_code_after (desc, __POS__) in - Reporting.log_error_deprecated pname ~loc exn) - | _ -> () + match (mem, mem') with + | NonBottom _, Bottom -> ( + match instr with + | Sil.Prune (_, _, _, (Ik_land_lor | Ik_bexp)) + -> () + | Sil.Prune (cond, loc, true_branch, _) + -> let i = match cond with Exp.Const Const.Cint i -> i | _ -> IntLit.zero in + let desc = Errdesc.explain_condition_always_true_false tenv i cond node loc in + let exn = + Exceptions.Condition_always_true_false (desc, not true_branch, __POS__) + in + Reporting.log_warning_deprecated pname ~loc exn + | Sil.Call (_, Const Cfun pname, _, _, _) + when String.equal (Typ.Procname.get_method pname) "exit" + && is_last_statement_of_if_branch rem_instrs node + -> () + | _ + -> let loc = Sil.instr_get_loc instr in + let desc = Errdesc.explain_unreachable_code_after loc in + let exn = Exceptions.Unreachable_code_after (desc, __POS__) in + Reporting.log_error_deprecated pname ~loc exn ) + | _ + -> () in - print_debug_info instr mem' cond_set; - collect_instrs pdata node rem_instrs mem' cond_set + print_debug_info instr mem' cond_set ; collect_instrs pdata node rem_instrs mem' cond_set let collect_node - : extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t -> - CFG.node -> Dom.ConditionSet.t - = fun pdata inv_map cond_set node -> + : extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t -> CFG.node + -> Dom.ConditionSet.t = + fun pdata inv_map cond_set node -> match Analyzer.extract_pre (CFG.id node) inv_map with - | Some mem -> - let instrs = CFG.instrs node in + | Some mem + -> let instrs = CFG.instrs node in collect_instrs pdata node instrs mem cond_set - | _ -> cond_set + | _ + -> cond_set - let collect : extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t - = fun ({ pdesc } as pdata) inv_map -> + let collect : extras ProcData.t -> Analyzer.invariant_map -> Dom.ConditionSet.t = + fun ({pdesc} as pdata) inv_map -> let add_node1 acc node = collect_node pdata inv_map acc node in Procdesc.fold_nodes add_node1 Dom.ConditionSet.empty pdesc - let make_err_trace : Trace.t -> string -> Errlog.loc_trace - = fun trace desc -> - let f elem (trace,depth) = + let make_err_trace : Trace.t -> string -> Errlog.loc_trace = + fun trace desc -> + let f elem (trace, depth) = match elem with - | Trace.Assign loc -> - ((Errlog.make_trace_element depth loc "Assignment" []) :: trace, depth) - | Trace.ArrDecl loc -> - ((Errlog.make_trace_element depth loc "ArrayDeclaration" []) :: trace, depth) - | Trace.Call loc -> - ((Errlog.make_trace_element depth loc "Call" []) :: trace, depth + 1) - | Trace.Return loc -> - ((Errlog.make_trace_element (depth - 1) loc "Return" []) :: trace, depth - 1) - | Trace.SymAssign _ -> (trace,depth) - | Trace.ArrAccess loc -> - ((Errlog.make_trace_element depth loc ("ArrayAccess: " ^ desc) []) :: trace, depth) + | Trace.Assign loc + -> (Errlog.make_trace_element depth loc "Assignment" [] :: trace, depth) + | Trace.ArrDecl loc + -> (Errlog.make_trace_element depth loc "ArrayDeclaration" [] :: trace, depth) + | Trace.Call loc + -> (Errlog.make_trace_element depth loc "Call" [] :: trace, depth + 1) + | Trace.Return loc + -> (Errlog.make_trace_element (depth - 1) loc "Return" [] :: trace, depth - 1) + | Trace.SymAssign _ + -> (trace, depth) + | Trace.ArrAccess loc + -> (Errlog.make_trace_element depth loc ("ArrayAccess: " ^ desc) [] :: trace, depth) in - List.fold_right ~f ~init:([],0) trace.trace |> fst |> List.rev + List.fold_right ~f ~init:([], 0) trace.trace |> fst |> List.rev - let report_error : Procdesc.t -> Dom.ConditionSet.t -> unit - = fun pdesc conds -> + let report_error : Procdesc.t -> Dom.ConditionSet.t -> unit = + fun pdesc conds -> let pname = Procdesc.get_proc_name pdesc in let report1 cond = let alarm = Dom.Condition.check cond in - let (caller_pname, loc) = + let caller_pname, loc = match Dom.Condition.get_cond_trace cond with - | Dom.Condition.Inter (caller_pname, _, loc) -> (caller_pname, loc) - | Dom.Condition.Intra pname -> (pname, Dom.Condition.get_location cond) + | Dom.Condition.Inter (caller_pname, _, loc) + -> (caller_pname, loc) + | Dom.Condition.Intra pname + -> (pname, Dom.Condition.get_location cond) in match alarm with - | None -> () - | Some bucket when Typ.Procname.equal pname caller_pname -> - let description = Dom.Condition.to_string cond in + | None + -> () + | Some bucket when Typ.Procname.equal pname caller_pname + -> let description = Dom.Condition.to_string cond in let error_desc = Localise.desc_buffer_overrun bucket description in let exn = - Exceptions.Checkers (Localise.to_issue_id Localise.buffer_overrun, error_desc) in - let trace = match TraceSet.choose_shortest cond.Dom.Condition.traces with - | trace -> make_err_trace trace description - | exception _ -> [Errlog.make_trace_element 0 loc description []] in + Exceptions.Checkers (Localise.to_issue_id Localise.buffer_overrun, error_desc) + in + let trace = + match TraceSet.choose_shortest cond.Dom.Condition.traces with + | trace + -> make_err_trace trace description + | exception _ + -> [Errlog.make_trace_element 0 loc description []] + in Reporting.log_error_deprecated pname ~loc ~ltr:trace exn - | _ -> () + | _ + -> () in Dom.ConditionSet.iter report1 conds end -let compute_post - : Analyzer.TransferFunctions.extras ProcData.t -> Summary.payload option - = fun { pdesc; tenv; extras = get_pdesc } -> +let compute_post : Analyzer.TransferFunctions.extras ProcData.t -> Summary.payload option = + fun {pdesc; tenv; extras= get_pdesc} -> let cfg = CFG.from_pdesc pdesc in let pdata = ProcData.make pdesc tenv get_pdesc in let inv_map = Analyzer.exec_pdesc ~initial:Dom.Mem.init pdata in @@ -590,25 +592,25 @@ let compute_post Analyzer.extract_post exit_id inv_map in let cond_set = Report.collect pdata inv_map in - Report.report_error pdesc cond_set; - match entry_mem, exit_mem with - | Some entry_mem, Some exit_mem -> - Some (entry_mem, exit_mem, cond_set) - | _ -> None - -let print_summary : Typ.Procname.t -> Dom.Summary.t -> unit - = fun proc_name s -> - L.(debug BufferOverrun Medium) "@\n@[Summary of %a :@,%a@]@." - Typ.Procname.pp proc_name - Dom.Summary.pp_summary s - -let checker : Callbacks.proc_callback_args -> Specs.summary - = fun { proc_desc; tenv; summary; get_proc_desc; } -> + Report.report_error pdesc cond_set ; + match (entry_mem, exit_mem) with + | Some entry_mem, Some exit_mem + -> Some (entry_mem, exit_mem, cond_set) + | _ + -> None + +let print_summary : Typ.Procname.t -> Dom.Summary.t -> unit = + fun proc_name s -> + L.(debug BufferOverrun Medium) + "@\n@[Summary of %a :@,%a@]@." Typ.Procname.pp proc_name Dom.Summary.pp_summary s + +let checker : Callbacks.proc_callback_args -> Specs.summary = + fun {proc_desc; tenv; summary; get_proc_desc} -> let proc_name = Specs.get_proc_name summary in let proc_data = ProcData.make proc_desc tenv get_proc_desc in match compute_post proc_data with - | Some post -> - if Config.bo_debug >= 1 then print_summary proc_name post; + | Some post + -> if Config.bo_debug >= 1 then print_summary proc_name post ; Summary.update_summary post summary - | None -> - summary + | None + -> summary diff --git a/infer/src/bufferoverrun/bufferOverrunDomain.ml b/infer/src/bufferoverrun/bufferOverrunDomain.ml index 38a371f1f..5fa173989 100644 --- a/infer/src/bufferoverrun/bufferOverrunDomain.ml +++ b/infer/src/bufferoverrun/bufferOverrunDomain.ml @@ -12,223 +12,212 @@ open! IStd open AbsLoc - module F = Format module L = Logging module MF = MarkupFormatter module Trace = BufferOverrunTrace module TraceSet = Trace.Set -let always_strong_update = true (* unsound but ok for bug catching *) - -module Condition = -struct - type cond_trace = Intra of Typ.Procname.t - | Inter of Typ.Procname.t * Typ.Procname.t * Location.t - [@@deriving compare] - - type t = { - proc_name : Typ.Procname.t; - loc : Location.t; - id : string; - cond_trace : cond_trace; - idx : Itv.astate; - size : Itv.astate; - traces : TraceSet.t; - } - [@@deriving compare] +let always_strong_update = true + +(* unsound but ok for bug catching *) + +module Condition = struct + type cond_trace = + | Intra of Typ.Procname.t + | Inter of Typ.Procname.t * Typ.Procname.t * Location.t + [@@deriving compare] + + type t = + { proc_name: Typ.Procname.t + ; loc: Location.t + ; id: string + ; cond_trace: cond_trace + ; idx: Itv.astate + ; size: Itv.astate + ; traces: TraceSet.t } + [@@deriving compare] type astate = t - let set_size_pos : t -> t - = fun c -> - if Itv.Bound.lt (Itv.lb c.size) Itv.Bound.zero - then { c with size = Itv.make Itv.Bound.zero (Itv.ub c.size) } + let set_size_pos : t -> t = + fun c -> + if Itv.Bound.lt (Itv.lb c.size) Itv.Bound.zero then + {c with size= Itv.make Itv.Bound.zero (Itv.ub c.size)} else c - let pp_location : F.formatter -> t -> unit - = fun fmt c -> - Location.pp_file_pos fmt c.loc + let pp_location : F.formatter -> t -> unit = fun fmt c -> Location.pp_file_pos fmt c.loc - let pp : F.formatter -> t -> unit - = fun fmt c -> + let pp : F.formatter -> t -> unit = + fun fmt c -> let c = set_size_pos c in if Config.bo_debug <= 1 then F.fprintf fmt "%a < %a at %a" Itv.pp c.idx Itv.pp c.size pp_location c else match c.cond_trace with - Inter (_, pname, loc) -> - let pname = Typ.Procname.to_string pname in - F.fprintf fmt "%a < %a at %a by call %s() at %a (%a)" - Itv.pp c.idx Itv.pp c.size pp_location c pname Location.pp_file_pos loc - TraceSet.pp c.traces - | Intra _ -> F.fprintf fmt "%a < %a at %a (%a)" Itv.pp c.idx Itv.pp c.size pp_location c - TraceSet.pp c.traces - - let get_location : t -> Location.t - = fun c -> c.loc - - let get_cond_trace : t -> cond_trace - = fun c -> c.cond_trace - - let get_proc_name : t -> Typ.Procname.t - = fun c -> c.proc_name - - let make : Typ.Procname.t -> Location.t -> string -> idx:Itv.t -> size:Itv.t -> TraceSet.t -> t - = fun proc_name loc id ~idx ~size traces -> - { proc_name; idx; size; loc; id ; cond_trace = Intra proc_name; traces } - - let filter1 : t -> bool - = fun c -> - Itv.eq c.idx Itv.top || Itv.eq c.size Itv.top - || Itv.Bound.eq (Itv.lb c.idx) Itv.Bound.MInf + | Inter (_, pname, loc) + -> let pname = Typ.Procname.to_string pname in + F.fprintf fmt "%a < %a at %a by call %s() at %a (%a)" Itv.pp c.idx Itv.pp c.size + pp_location c pname Location.pp_file_pos loc TraceSet.pp c.traces + | Intra _ + -> F.fprintf fmt "%a < %a at %a (%a)" Itv.pp c.idx Itv.pp c.size pp_location c TraceSet.pp + c.traces + + let get_location : t -> Location.t = fun c -> c.loc + + let get_cond_trace : t -> cond_trace = fun c -> c.cond_trace + + let get_proc_name : t -> Typ.Procname.t = fun c -> c.proc_name + + let make : Typ.Procname.t -> Location.t -> string -> idx:Itv.t -> size:Itv.t -> TraceSet.t -> t = + fun proc_name loc id ~idx ~size traces -> + {proc_name; idx; size; loc; id; cond_trace= Intra proc_name; traces} + + let filter1 : t -> bool = + fun c -> + Itv.eq c.idx Itv.top || Itv.eq c.size Itv.top || Itv.Bound.eq (Itv.lb c.idx) Itv.Bound.MInf || Itv.Bound.eq (Itv.lb c.size) Itv.Bound.MInf - || (Itv.eq c.idx Itv.nat && Itv.eq c.size Itv.nat) + || Itv.eq c.idx Itv.nat && Itv.eq c.size Itv.nat - let filter2 : t -> bool - = fun c -> + let filter2 : t -> bool = + fun c -> (* basically, alarms involving infinity are filtered *) (not (Itv.is_finite c.idx) || not (Itv.is_finite c.size)) - && (* except the following cases *) - not ((Itv.Bound.is_not_infty (Itv.lb c.idx) && (* idx non-infty lb < 0 *) - Itv.Bound.lt (Itv.lb c.idx) Itv.Bound.zero) - || - (Itv.Bound.is_not_infty (Itv.lb c.idx) && (* idx non-infty lb > size lb *) - (Itv.Bound.gt (Itv.lb c.idx) (Itv.lb c.size))) - || - (Itv.Bound.is_not_infty (Itv.lb c.idx) && (* idx non-infty lb > size ub *) - (Itv.Bound.gt (Itv.lb c.idx) (Itv.ub c.size))) - || - (Itv.Bound.is_not_infty (Itv.ub c.idx) && (* idx non-infty ub > size lb *) - (Itv.Bound.gt (Itv.ub c.idx) (Itv.lb c.size))) - || - (Itv.Bound.is_not_infty (Itv.ub c.idx) && (* idx non-infty ub > size ub *) - (Itv.Bound.gt (Itv.ub c.idx) (Itv.ub c.size)))) + && (* except the following cases *) + not + ( Itv.Bound.is_not_infty (Itv.lb c.idx) + && (* idx non-infty lb < 0 *) + Itv.Bound.lt (Itv.lb c.idx) Itv.Bound.zero + || Itv.Bound.is_not_infty (Itv.lb c.idx) + && (* idx non-infty lb > size lb *) + Itv.Bound.gt (Itv.lb c.idx) (Itv.lb c.size) + || Itv.Bound.is_not_infty (Itv.lb c.idx) + && (* idx non-infty lb > size ub *) + Itv.Bound.gt (Itv.lb c.idx) (Itv.ub c.size) + || Itv.Bound.is_not_infty (Itv.ub c.idx) + && (* idx non-infty ub > size lb *) + Itv.Bound.gt (Itv.ub c.idx) (Itv.lb c.size) + || Itv.Bound.is_not_infty (Itv.ub c.idx) + && (* idx non-infty ub > size ub *) + Itv.Bound.gt (Itv.ub c.idx) (Itv.ub c.size) ) (* check buffer overrun and return its confidence *) - let check : t -> string option - = fun c -> + let check : t -> string option = + fun c -> (* idx = [il, iu], size = [sl, su], we want to check that 0 <= idx < size *) - let c' = set_size_pos c in (* if sl < 0, use sl' = 0 *) + let c' = set_size_pos c in + (* if sl < 0, use sl' = 0 *) let not_overrun = Itv.lt_sem c'.idx c'.size in let not_underrun = Itv.le_sem Itv.zero c'.idx in (* il >= 0 and iu < sl, definitely not an error *) - if Itv.eq not_overrun Itv.one && Itv.eq not_underrun Itv.one then - None + if Itv.eq not_overrun Itv.one && Itv.eq not_underrun Itv.one then None (* iu < 0 or il >= su, definitely an error *) else if Itv.eq not_overrun Itv.zero || Itv.eq not_underrun Itv.zero then - Some Localise.BucketLevel.b1 - (* su <= iu < +oo, most probably an error *) - else if Itv.Bound.is_not_infty (Itv.ub c.idx) - && Itv.Bound.le (Itv.ub c.size) (Itv.ub c.idx) then - Some Localise.BucketLevel.b2 - (* symbolic il >= sl, probably an error *) - else if Itv.Bound.is_symbolic (Itv.lb c.idx) - && Itv.Bound.le (Itv.lb c'.size) (Itv.lb c.idx) then - Some Localise.BucketLevel.b3 - (* other symbolic bounds are probably too noisy *) - else if Config.bo_debug <= 3 && (Itv.is_symbolic c.idx || Itv.is_symbolic c.size) then - None - else if filter1 c then - Some Localise.BucketLevel.b5 - else if filter2 c then - Some Localise.BucketLevel.b3 - else - Some Localise.BucketLevel.b2 - - let invalid : t -> bool - = fun x -> Itv.invalid x.idx || Itv.invalid x.size - - let to_string : t -> string - = fun c -> + Some Localise.BucketLevel.b1 (* su <= iu < +oo, most probably an error *) + else if Itv.Bound.is_not_infty (Itv.ub c.idx) && Itv.Bound.le (Itv.ub c.size) (Itv.ub c.idx) + then Some Localise.BucketLevel.b2 (* symbolic il >= sl, probably an error *) + else if Itv.Bound.is_symbolic (Itv.lb c.idx) && Itv.Bound.le (Itv.lb c'.size) (Itv.lb c.idx) + then Some Localise.BucketLevel.b3 (* other symbolic bounds are probably too noisy *) + else if Config.bo_debug <= 3 && (Itv.is_symbolic c.idx || Itv.is_symbolic c.size) then None + else if filter1 c then Some Localise.BucketLevel.b5 + else if filter2 c then Some Localise.BucketLevel.b3 + else Some Localise.BucketLevel.b2 + + let invalid : t -> bool = fun x -> Itv.invalid x.idx || Itv.invalid x.size + + let to_string : t -> string = + fun c -> let c = set_size_pos c in "Offset: " ^ Itv.to_string c.idx ^ " Size: " ^ Itv.to_string c.size - ^ (match c.cond_trace with - | Inter (_, pname, _) - when Config.bo_debug >= 1 - || not (SourceFile.is_cpp_model c.loc.Location.file) -> - let loc = pp_location F.str_formatter c; F.flush_str_formatter () in - " @ " ^ loc ^ " by call " - ^ MF.monospaced_to_string (Typ.Procname.to_string pname ^ "()") ^ " " - | _ -> "") - - let subst : t -> (Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t) -> Typ.Procname.t -> - Typ.Procname.t -> Location.t -> t - = fun c (bound_map, trace_map) caller_pname callee_pname loc -> + ^ + match c.cond_trace with + | Inter (_, pname, _) + when Config.bo_debug >= 1 || not (SourceFile.is_cpp_model c.loc.Location.file) + -> let loc = pp_location F.str_formatter c ; F.flush_str_formatter () in + " @ " ^ loc ^ " by call " ^ MF.monospaced_to_string (Typ.Procname.to_string pname ^ "()") + ^ " " + | _ + -> "" + + let subst + : t -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t -> Typ.Procname.t + -> Typ.Procname.t -> Location.t -> t = + fun c (bound_map, trace_map) caller_pname callee_pname loc -> if Itv.is_symbolic c.idx || Itv.is_symbolic c.size then let symbols = Itv.get_symbols c.idx @ Itv.get_symbols c.size in - let traces_caller = List.fold symbols ~init:TraceSet.empty - ~f:(fun traces symbol -> - match Itv.SubstMap.find symbol trace_map with - | symbol_trace -> TraceSet.join symbol_trace traces - | exception Not_found -> traces) in + let traces_caller = + List.fold symbols ~init:TraceSet.empty ~f:(fun traces symbol -> + match Itv.SubstMap.find symbol trace_map with + | symbol_trace + -> TraceSet.join symbol_trace traces + | exception Not_found + -> traces ) + in let traces = TraceSet.instantiate ~traces_caller ~traces_callee:c.traces loc in - { c with idx = Itv.subst c.idx bound_map; - size = Itv.subst c.size bound_map; - cond_trace = Inter (caller_pname, callee_pname, loc); - traces } + { c with + idx= Itv.subst c.idx bound_map + ; size= Itv.subst c.size bound_map + ; cond_trace= Inter (caller_pname, callee_pname, loc) + ; traces } else c end -module ConditionSet = -struct +module ConditionSet = struct include AbstractDomain.FiniteSet (Condition) - module Map = Caml.Map.Make (Location) let add_bo_safety - : Typ.Procname.t -> Location.t -> string -> idx:Itv.t -> size:Itv.t -> TraceSet.t -> t -> t - = fun pname loc id ~idx ~size traces cond -> + : Typ.Procname.t -> Location.t -> string -> idx:Itv.t -> size:Itv.t -> TraceSet.t -> t -> t = + fun pname loc id ~idx ~size traces cond -> add (Condition.make pname loc id ~idx ~size traces) cond - let subst : t -> (Itv.Bound.t Itv.SubstMap.t) * (TraceSet.t Itv.SubstMap.t) -> Typ.Procname.t -> - Typ.Procname.t -> Location.t -> t - = fun x subst_map caller_pname callee_pname loc -> + let subst + : t -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t -> Typ.Procname.t + -> Typ.Procname.t -> Location.t -> t = + fun x subst_map caller_pname callee_pname loc -> fold (fun e -> add (Condition.subst e subst_map caller_pname callee_pname loc)) x empty - let group : t -> t Map.t - = fun x -> - fold (fun cond map -> - let old_set = try Map.find cond.loc map with _ -> empty in - Map.add cond.loc (add cond old_set) map) x Map.empty - - let pp_summary : F.formatter -> t -> unit - = fun fmt x -> + let group : t -> t Map.t = + fun x -> + fold + (fun cond map -> + let old_set = + try Map.find cond.loc map + with _ -> empty + in + Map.add cond.loc (add cond old_set) map) + x Map.empty + + let pp_summary : F.formatter -> t -> unit = + fun fmt x -> let pp_sep fmt () = F.fprintf fmt ", @," in let pp_element fmt v = Condition.pp fmt v in - F.fprintf fmt "@[Safety conditions:@,"; - F.fprintf fmt "@[{ "; - F.pp_print_list ~pp_sep pp_element fmt (elements x); - F.fprintf fmt " }@]"; + F.fprintf fmt "@[Safety conditions:@," ; + F.fprintf fmt "@[{ " ; + F.pp_print_list ~pp_sep pp_element fmt (elements x) ; + F.fprintf fmt " }@]" ; F.fprintf fmt "@]" - let pp : Format.formatter -> t -> unit - = fun fmt x -> + let pp : Format.formatter -> t -> unit = + fun fmt x -> let pp_sep fmt () = F.fprintf fmt ", @," in let pp_element fmt v = Condition.pp fmt v in - F.fprintf fmt "@[Safety conditions :@,"; - F.fprintf fmt "@[{"; - F.pp_print_list ~pp_sep pp_element fmt (elements x); - F.fprintf fmt " }@]"; + F.fprintf fmt "@[Safety conditions :@," ; + F.fprintf fmt "@[{" ; + F.pp_print_list ~pp_sep pp_element fmt (elements x) ; + F.fprintf fmt " }@]" ; F.fprintf fmt "@]" - let rm_invalid : t -> t - = fun x -> filter (fun c -> not (Condition.invalid c)) x + let rm_invalid : t -> t = fun x -> filter (fun c -> not (Condition.invalid c)) x end -module Val = -struct - type astate = { - itv : Itv.astate; - powloc : PowLoc.astate; - arrayblk : ArrayBlk.astate; - traces : TraceSet.t; - } +module Val = struct + type astate = + {itv: Itv.astate; powloc: PowLoc.astate; arrayblk: ArrayBlk.astate; traces: TraceSet.t} type t = astate - let bot : t - = { itv = Itv.bot; powloc = PowLoc.bot; arrayblk = ArrayBlk.bot; traces = TraceSet.empty } + let bot : t = {itv= Itv.bot; powloc= PowLoc.bot; arrayblk= ArrayBlk.bot; traces= TraceSet.empty} let pp fmt x = if Config.bo_debug <= 1 then @@ -237,287 +226,243 @@ struct F.fprintf fmt "(%a, %a, %a, %a)" Itv.pp x.itv PowLoc.pp x.powloc ArrayBlk.pp x.arrayblk TraceSet.pp x.traces - let unknown : t - = { bot with itv = Itv.top; powloc = PowLoc.unknown; arrayblk = ArrayBlk.unknown } + let unknown : t = {bot with itv= Itv.top; powloc= PowLoc.unknown; arrayblk= ArrayBlk.unknown} - let (<=) ~lhs ~rhs = + let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true - else - Itv.(<=) ~lhs:(lhs.itv) ~rhs:(rhs.itv) - && PowLoc.(<=) ~lhs:(lhs.powloc) ~rhs:(rhs.powloc) - && ArrayBlk.(<=) ~lhs:(lhs.arrayblk) ~rhs:(rhs.arrayblk) + else Itv.( <= ) ~lhs:lhs.itv ~rhs:rhs.itv && PowLoc.( <= ) ~lhs:lhs.powloc ~rhs:rhs.powloc + && ArrayBlk.( <= ) ~lhs:lhs.arrayblk ~rhs:rhs.arrayblk let widen ~prev ~next ~num_iters = if phys_equal prev next then prev else - { itv = Itv.widen ~prev:(prev.itv) ~next:(next.itv) ~num_iters; - powloc = PowLoc.widen ~prev:(prev.powloc) ~next:(next.powloc) ~num_iters; - arrayblk = ArrayBlk.widen ~prev:(prev.arrayblk) ~next:(next.arrayblk) ~num_iters; - traces = TraceSet.join prev.traces next.traces; } + { itv= Itv.widen ~prev:prev.itv ~next:next.itv ~num_iters + ; powloc= PowLoc.widen ~prev:prev.powloc ~next:next.powloc ~num_iters + ; arrayblk= ArrayBlk.widen ~prev:prev.arrayblk ~next:next.arrayblk ~num_iters + ; traces= TraceSet.join prev.traces next.traces } - let join : t -> t -> t - = fun x y -> + let join : t -> t -> t = + fun x y -> if phys_equal x y then x else - { itv = Itv.join x.itv y.itv; - powloc = PowLoc.join x.powloc y.powloc; - arrayblk = ArrayBlk.join x.arrayblk y.arrayblk; - traces = TraceSet.join x.traces y.traces; } + { itv= Itv.join x.itv y.itv + ; powloc= PowLoc.join x.powloc y.powloc + ; arrayblk= ArrayBlk.join x.arrayblk y.arrayblk + ; traces= TraceSet.join x.traces y.traces } - let rec joins : t list -> t - = function - | [] -> bot - | [a] -> a - | a :: b -> join a (joins b) + let rec joins : t list -> t = function [] -> bot | [a] -> a | a :: b -> join a (joins b) - let get_itv : t -> Itv.t - = fun x -> x.itv + let get_itv : t -> Itv.t = fun x -> x.itv - let get_pow_loc : t -> PowLoc.t - = fun x -> x.powloc + let get_pow_loc : t -> PowLoc.t = fun x -> x.powloc - let get_array_blk : t -> ArrayBlk.astate - = fun x -> x.arrayblk + let get_array_blk : t -> ArrayBlk.astate = fun x -> x.arrayblk - let get_array_locs : t -> PowLoc.t - = fun x -> ArrayBlk.get_pow_loc x.arrayblk + let get_array_locs : t -> PowLoc.t = fun x -> ArrayBlk.get_pow_loc x.arrayblk - let get_all_locs : t -> PowLoc.t - = fun x -> PowLoc.join x.powloc (get_array_locs x) + let get_all_locs : t -> PowLoc.t = fun x -> PowLoc.join x.powloc (get_array_locs x) - let get_traces : t -> TraceSet.t - = fun x -> x.traces + let get_traces : t -> TraceSet.t = fun x -> x.traces - let set_traces : TraceSet.t -> t -> t - = fun traces x -> { x with traces } + let set_traces : TraceSet.t -> t -> t = fun traces x -> {x with traces} - let of_itv itv = { bot with itv } + let of_itv itv = {bot with itv} let of_int n = of_itv (Itv.of_int n) - let of_itv : Itv.t -> t - = fun itv -> { bot with itv } + let of_itv : Itv.t -> t = fun itv -> {bot with itv} - let of_pow_loc : PowLoc.t -> t - = fun x -> { bot with powloc = x } + let of_pow_loc : PowLoc.t -> t = fun x -> {bot with powloc= x} - let of_array_blk : ArrayBlk.astate -> t - = fun a -> { bot with arrayblk = a } + let of_array_blk : ArrayBlk.astate -> t = fun a -> {bot with arrayblk= a} - let modify_itv : Itv.t -> t -> t - = fun i x -> { x with itv = i } + let modify_itv : Itv.t -> t -> t = fun i x -> {x with itv= i} - let make_sym : ?unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t - = fun ?(unsigned=false) pname new_sym_num -> - { bot with itv = Itv.make_sym ~unsigned pname new_sym_num } + let make_sym : ?unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t = + fun ?(unsigned= false) pname new_sym_num -> + {bot with itv= Itv.make_sym ~unsigned pname new_sym_num} - let unknown_bit : t -> t - = fun x -> { x with itv = Itv.top } + let unknown_bit : t -> t = fun x -> {x with itv= Itv.top} - let neg : t -> t - = fun x -> { x with itv = Itv.neg x.itv } + let neg : t -> t = fun x -> {x with itv= Itv.neg x.itv} - let lnot : t -> t - = fun x -> { x with itv = Itv.lnot x.itv } + let lnot : t -> t = fun x -> {x with itv= Itv.lnot x.itv} - let lift_itv : (Itv.t -> Itv.t -> Itv.t) -> t -> t -> t - = fun f x y -> { bot with itv = f x.itv y.itv } + let lift_itv : (Itv.t -> Itv.t -> Itv.t) -> t -> t -> t = + fun f x y -> {bot with itv= f x.itv y.itv} - let has_pointer : t -> bool - = fun x -> - not (PowLoc.is_bot x.powloc && ArrayBlk.is_bot x.arrayblk) + let has_pointer : t -> bool = fun x -> not (PowLoc.is_bot x.powloc && ArrayBlk.is_bot x.arrayblk) - let lift_cmp_itv : (Itv.t -> Itv.t -> Itv.t) -> t -> t -> t - = fun f x y -> - if has_pointer x || has_pointer y then - {bot with itv = Itv.unknown_bool} - else lift_itv f x y + let lift_cmp_itv : (Itv.t -> Itv.t -> Itv.t) -> t -> t -> t = + fun f x y -> + if has_pointer x || has_pointer y then {bot with itv= Itv.unknown_bool} else lift_itv f x y - let plus : t -> t -> t - = fun x y -> - { x with itv = Itv.plus x.itv y.itv; arrayblk = ArrayBlk.plus_offset x.arrayblk y.itv; - traces = TraceSet.join x.traces y.traces } + let plus : t -> t -> t = + fun x y -> + { x with + itv= Itv.plus x.itv y.itv + ; arrayblk= ArrayBlk.plus_offset x.arrayblk y.itv + ; traces= TraceSet.join x.traces y.traces } - let minus : t -> t -> t - = fun x y -> + let minus : t -> t -> t = + fun x y -> let n = Itv.join (Itv.minus x.itv y.itv) (ArrayBlk.diff x.arrayblk y.arrayblk) in let a = ArrayBlk.minus_offset x.arrayblk y.itv in - { bot with itv = n; arrayblk = a; traces = TraceSet.join x.traces y.traces } + {bot with itv= n; arrayblk= a; traces= TraceSet.join x.traces y.traces} - let mult : t -> t -> t - = fun x y -> { (lift_itv Itv.mult x y) with traces = TraceSet.join x.traces y.traces } + let mult : t -> t -> t = + fun x y -> {(lift_itv Itv.mult x y) with traces= TraceSet.join x.traces y.traces} - let div : t -> t -> t - = fun x y -> { (lift_itv Itv.div x y) with traces = TraceSet.join x.traces y.traces } + let div : t -> t -> t = + fun x y -> {(lift_itv Itv.div x y) with traces= TraceSet.join x.traces y.traces} - let mod_sem : t -> t -> t - = lift_itv Itv.mod_sem + let mod_sem : t -> t -> t = lift_itv Itv.mod_sem - let shiftlt : t -> t -> t - = lift_itv Itv.shiftlt + let shiftlt : t -> t -> t = lift_itv Itv.shiftlt - let shiftrt : t -> t -> t - = lift_itv Itv.shiftrt + let shiftrt : t -> t -> t = lift_itv Itv.shiftrt - let lt_sem : t -> t -> t - = lift_cmp_itv Itv.lt_sem + let lt_sem : t -> t -> t = lift_cmp_itv Itv.lt_sem - let gt_sem : t -> t -> t - = lift_cmp_itv Itv.gt_sem + let gt_sem : t -> t -> t = lift_cmp_itv Itv.gt_sem - let le_sem : t -> t -> t - = lift_cmp_itv Itv.le_sem + let le_sem : t -> t -> t = lift_cmp_itv Itv.le_sem - let ge_sem : t -> t -> t - = lift_cmp_itv Itv.ge_sem + let ge_sem : t -> t -> t = lift_cmp_itv Itv.ge_sem - let eq_sem : t -> t -> t - = lift_cmp_itv Itv.eq_sem + let eq_sem : t -> t -> t = lift_cmp_itv Itv.eq_sem - let ne_sem : t -> t -> t - = lift_cmp_itv Itv.ne_sem + let ne_sem : t -> t -> t = lift_cmp_itv Itv.ne_sem - let land_sem : t -> t -> t - = lift_itv Itv.land_sem + let land_sem : t -> t -> t = lift_itv Itv.land_sem - let lor_sem : t -> t -> t - = lift_itv Itv.lor_sem + let lor_sem : t -> t -> t = lift_itv Itv.lor_sem - let lift_prune1 : (Itv.t -> Itv.t) -> t -> t - = fun f x -> { x with itv = f x.itv } + let lift_prune1 : (Itv.t -> Itv.t) -> t -> t = fun f x -> {x with itv= f x.itv} let lift_prune2 - : (Itv.t -> Itv.t -> Itv.t) - -> (ArrayBlk.astate -> ArrayBlk.astate -> ArrayBlk.astate) -> t -> t -> t - = fun f g x y -> - { x with itv = f x.itv y.itv; arrayblk = g x.arrayblk y.arrayblk; - traces = TraceSet.join x.traces y.traces } + : (Itv.t -> Itv.t -> Itv.t) -> (ArrayBlk.astate -> ArrayBlk.astate -> ArrayBlk.astate) -> t + -> t -> t = + fun f g x y -> + { x with + itv= f x.itv y.itv + ; arrayblk= g x.arrayblk y.arrayblk + ; traces= TraceSet.join x.traces y.traces } - let prune_zero : t -> t - = lift_prune1 Itv.prune_zero + let prune_zero : t -> t = lift_prune1 Itv.prune_zero - let prune_comp : Binop.t -> t -> t -> t - = fun c -> lift_prune2 (Itv.prune_comp c) (ArrayBlk.prune_comp c) + let prune_comp : Binop.t -> t -> t -> t = + fun c -> lift_prune2 (Itv.prune_comp c) (ArrayBlk.prune_comp c) - let prune_eq : t -> t -> t - = lift_prune2 Itv.prune_eq ArrayBlk.prune_eq + let prune_eq : t -> t -> t = lift_prune2 Itv.prune_eq ArrayBlk.prune_eq - let prune_ne : t -> t -> t - = lift_prune2 Itv.prune_ne ArrayBlk.prune_eq + let prune_ne : t -> t -> t = lift_prune2 Itv.prune_ne ArrayBlk.prune_eq - let lift_pi : (ArrayBlk.astate -> Itv.t -> ArrayBlk.astate) -> t -> t -> t - = fun f x y -> - { bot with arrayblk = f x.arrayblk y.itv; traces = TraceSet.join x.traces y.traces } + let lift_pi : (ArrayBlk.astate -> Itv.t -> ArrayBlk.astate) -> t -> t -> t = + fun f x y -> {bot with arrayblk= f x.arrayblk y.itv; traces= TraceSet.join x.traces y.traces} - let plus_pi : t -> t -> t - = fun x y -> lift_pi ArrayBlk.plus_offset x y + let plus_pi : t -> t -> t = fun x y -> lift_pi ArrayBlk.plus_offset x y - let minus_pi : t -> t -> t - = fun x y -> lift_pi ArrayBlk.minus_offset x y + let minus_pi : t -> t -> t = fun x y -> lift_pi ArrayBlk.minus_offset x y - let minus_pp : t -> t -> t - = fun x y -> + let minus_pp : t -> t -> t = + fun x y -> (* when we cannot precisely follow the physical memory model, return top *) - if (not (PowLoc.is_bot x.powloc) && ArrayBlk.is_bot x.arrayblk) || - (not (PowLoc.is_bot y.powloc) && ArrayBlk.is_bot y.arrayblk) - then { bot with itv = Itv.top } - else { bot with itv = ArrayBlk.diff x.arrayblk y.arrayblk; - traces = TraceSet.join x.traces y.traces } - - let get_symbols : t -> Itv.Symbol.t list - = fun x -> - List.append (Itv.get_symbols x.itv) (ArrayBlk.get_symbols x.arrayblk) - - let normalize : t -> t - = fun x -> - { x with itv = Itv.normalize x.itv; arrayblk = ArrayBlk.normalize x.arrayblk } - - let subst : t -> (Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t) -> Location.t -> t - = fun x (bound_map, trace_map) loc -> + if not (PowLoc.is_bot x.powloc) && ArrayBlk.is_bot x.arrayblk + || not (PowLoc.is_bot y.powloc) && ArrayBlk.is_bot y.arrayblk + then {bot with itv= Itv.top} + else + {bot with itv= ArrayBlk.diff x.arrayblk y.arrayblk; traces= TraceSet.join x.traces y.traces} + + let get_symbols : t -> Itv.Symbol.t list = + fun x -> List.append (Itv.get_symbols x.itv) (ArrayBlk.get_symbols x.arrayblk) + + let normalize : t -> t = + fun x -> {x with itv= Itv.normalize x.itv; arrayblk= ArrayBlk.normalize x.arrayblk} + + let subst : t -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t -> Location.t -> t = + fun x (bound_map, trace_map) loc -> let symbols = get_symbols x in - let traces_caller = List.fold ~f:(fun traces symbol -> - try - TraceSet.join (Itv.SubstMap.find symbol trace_map) traces - with _ -> traces) ~init:TraceSet.empty symbols in + let traces_caller = + List.fold + ~f:(fun traces symbol -> + try TraceSet.join (Itv.SubstMap.find symbol trace_map) traces + with _ -> traces) + ~init:TraceSet.empty symbols + in let traces = TraceSet.instantiate ~traces_caller ~traces_callee:x.traces loc in - { x with itv = Itv.subst x.itv bound_map; arrayblk = ArrayBlk.subst x.arrayblk bound_map; - traces } - |> normalize (* normalize bottom *) + {x with itv= Itv.subst x.itv bound_map; arrayblk= ArrayBlk.subst x.arrayblk bound_map; traces} + |> normalize - let add_trace_elem : Trace.elem -> t -> t - = fun elem x -> + (* normalize bottom *) + + let add_trace_elem : Trace.elem -> t -> t = + fun elem x -> let traces = TraceSet.add_elem elem x.traces in - { x with traces } + {x with traces} - let add_trace_elem_last : Trace.elem -> t -> t - = fun elem x -> + let add_trace_elem_last : Trace.elem -> t -> t = + fun elem x -> let traces = TraceSet.add_elem_last elem x.traces in - { x with traces } + {x with traces} - let pp_summary : F.formatter -> t -> unit - = fun fmt x -> F.fprintf fmt "(%a, %a)" Itv.pp x.itv ArrayBlk.pp x.arrayblk + let pp_summary : F.formatter -> t -> unit = + fun fmt x -> F.fprintf fmt "(%a, %a)" Itv.pp x.itv ArrayBlk.pp x.arrayblk - module Itv = - struct + module Itv = struct let nat = of_itv Itv.nat + let m1_255 = of_itv Itv.m1_255 + let pos = of_itv Itv.pos + let top = of_itv Itv.top end end -module Stack = -struct +module Stack = struct include AbstractDomain.Map (Loc) (Val) let bot = empty - let find : Loc.t -> astate -> Val.t - = fun l m -> - try find l m with - | Not_found -> Val.bot + let find : Loc.t -> astate -> Val.t = + fun l m -> + try find l m + with Not_found -> Val.bot - let find_set : PowLoc.t -> astate -> Val.t - = fun locs mem -> + let find_set : PowLoc.t -> astate -> Val.t = + fun locs mem -> let find_join loc acc = Val.join acc (find loc mem) in PowLoc.fold find_join locs Val.bot - let strong_update : PowLoc.t -> Val.astate -> astate -> astate - = fun locs v mem -> - PowLoc.fold (fun x -> add x v) locs mem + let strong_update : PowLoc.t -> Val.astate -> astate -> astate = + fun locs v mem -> PowLoc.fold (fun x -> add x v) locs mem - let weak_update : PowLoc.t -> Val.astate -> astate -> astate - = fun locs v mem -> - PowLoc.fold (fun x -> add x (Val.join v (find x mem))) locs mem + let weak_update : PowLoc.t -> Val.astate -> astate -> astate = + fun locs v mem -> PowLoc.fold (fun x -> add x (Val.join v (find x mem))) locs mem - let pp_summary : F.formatter -> astate -> unit - = fun fmt mem -> + let pp_summary : F.formatter -> astate -> unit = + fun fmt mem -> let pp_not_logical_var k v = - if Loc.is_logical_var k then () else - F.fprintf fmt "%a -> %a@," Loc.pp k Val.pp_summary v + if Loc.is_logical_var k then () else F.fprintf fmt "%a -> %a@," Loc.pp k Val.pp_summary v in iter pp_not_logical_var mem end -module Heap = -struct - module PPMap = - struct +module Heap = struct + module PPMap = struct include PrettyPrintable.MakePPMap (Loc) - let pp_collection - : pp_item:(F.formatter -> 'a -> unit) -> F.formatter -> 'a list -> unit - = fun ~pp_item fmt c -> + let pp_collection : pp_item:(F.formatter -> 'a -> unit) -> F.formatter -> 'a list -> unit = + fun ~pp_item fmt c -> let pp_sep fmt () = F.fprintf fmt ",@," in F.pp_print_list ~pp_sep pp_item fmt c - let pp : pp_value:(F.formatter -> 'a -> unit) -> F.formatter -> 'a t -> unit - = fun ~pp_value fmt m -> - let pp_item fmt (k, v) = - F.fprintf fmt "%a -> %a" Loc.pp k pp_value v - in - F.fprintf fmt "@[{ "; - pp_collection ~pp_item fmt (bindings m); + let pp : pp_value:(F.formatter -> 'a -> unit) -> F.formatter -> 'a t -> unit = + fun ~pp_value fmt m -> + let pp_item fmt (k, v) = F.fprintf fmt "%a -> %a" Loc.pp k pp_value v in + F.fprintf fmt "@[{ " ; + pp_collection ~pp_item fmt (bindings m) ; F.fprintf fmt " }@]" end @@ -525,202 +470,178 @@ struct let bot = empty - let find : Loc.t -> astate -> Val.t - = fun l m -> - try find l m with - | Not_found -> Val.Itv.top + let find : Loc.t -> astate -> Val.t = + fun l m -> + try find l m + with Not_found -> Val.Itv.top - let find_set : PowLoc.t -> astate -> Val.t - = fun locs mem -> + let find_set : PowLoc.t -> astate -> Val.t = + fun locs mem -> let find_join loc acc = Val.join acc (find loc mem) in PowLoc.fold find_join locs Val.bot - let strong_update : PowLoc.t -> Val.t -> astate -> astate - = fun locs v mem -> - PowLoc.fold (fun x -> add x v) locs mem + let strong_update : PowLoc.t -> Val.t -> astate -> astate = + fun locs v mem -> PowLoc.fold (fun x -> add x v) locs mem - let weak_update : PowLoc.t -> Val.t -> astate -> astate - = fun locs v mem -> - PowLoc.fold (fun x -> add x (Val.join v (find x mem))) locs mem + let weak_update : PowLoc.t -> Val.t -> astate -> astate = + fun locs v mem -> PowLoc.fold (fun x -> add x (Val.join v (find x mem))) locs mem - let pp_summary : F.formatter -> astate -> unit - = fun fmt mem -> + let pp_summary : F.formatter -> astate -> unit = + fun fmt mem -> let pp_map fmt (k, v) = F.fprintf fmt "%a -> %a" Loc.pp k Val.pp_summary v in - F.fprintf fmt "@[{ "; - F.pp_print_list pp_map fmt (bindings mem); + F.fprintf fmt "@[{ " ; + F.pp_print_list pp_map fmt (bindings mem) ; F.fprintf fmt " }@]" - let get_symbols : astate -> Itv.Symbol.t list - = fun mem -> - List.concat_map ~f:(fun (_, v) -> Val.get_symbols v) (bindings mem) + let get_symbols : astate -> Itv.Symbol.t list = + fun mem -> List.concat_map ~f:(fun (_, v) -> Val.get_symbols v) (bindings mem) - let get_return : astate -> Val.t - = fun mem -> + let get_return : astate -> Val.t = + fun mem -> let mem = filter (fun l _ -> Loc.is_return l) mem in if is_empty mem then Val.bot else snd (choose mem) end -module Alias = -struct +module Alias = struct module M = Caml.Map.Make (Ident) type t = Pvar.t M.t type astate = t - let bot : t - = M.empty + let bot : t = M.empty - let (<=) : lhs:t -> rhs:t -> bool - = fun ~lhs ~rhs -> + let ( <= ) : lhs:t -> rhs:t -> bool = + fun ~lhs ~rhs -> let is_in_rhs k v = - match M.find k rhs with - | v' -> Pvar.equal v v' - | exception Not_found -> false + match M.find k rhs with v' -> Pvar.equal v v' | exception Not_found -> false in M.for_all is_in_rhs lhs - let join : t -> t -> t - = fun x y -> + let join : t -> t -> t = + fun x y -> let join_v _ v1_opt v2_opt = - match v1_opt, v2_opt with - | None, None -> None - | Some v, None - | None, Some v -> Some v - | Some v1, Some v2 -> if Pvar.equal v1 v2 then Some v1 else assert false + match (v1_opt, v2_opt) with + | None, None + -> None + | Some v, None | None, Some v + -> Some v + | Some v1, Some v2 + -> if Pvar.equal v1 v2 then Some v1 else assert false in M.merge join_v x y - let widen : prev:t -> next:t -> num_iters:int -> t - = fun ~prev ~next ~num_iters:_ -> join prev next + let widen : prev:t -> next:t -> num_iters:int -> t = + fun ~prev ~next ~num_iters:_ -> join prev next - let pp : F.formatter -> t -> unit - = fun fmt x -> + let pp : F.formatter -> t -> unit = + fun fmt x -> let pp_sep fmt () = F.fprintf fmt ", @," in - let pp1 fmt (k, v) = - F.fprintf fmt "%a=%a" (Ident.pp Pp.text) k (Pvar.pp Pp.text) v - in + let pp1 fmt (k, v) = F.fprintf fmt "%a=%a" (Ident.pp Pp.text) k (Pvar.pp Pp.text) v in (* F.fprintf fmt "@[Logical Variables :@,"; *) - F.fprintf fmt "@[{ @,"; - F.pp_print_list ~pp_sep pp1 fmt (M.bindings x); - F.fprintf fmt " }@]"; + F.fprintf fmt "@[{ @," ; + F.pp_print_list ~pp_sep pp1 fmt (M.bindings x) ; + F.fprintf fmt " }@]" ; F.fprintf fmt "@]" - let load : Ident.t -> Exp.t -> t -> t - = fun id exp m -> - match exp with - | Exp.Lvar x -> M.add id x m - | _ -> m + let load : Ident.t -> Exp.t -> t -> t = + fun id exp m -> match exp with Exp.Lvar x -> M.add id x m | _ -> m - let store : Exp.t -> Exp.t -> t -> t - = fun e _ m -> - match e with - | Exp.Lvar x -> M.filter (fun _ y -> not (Pvar.equal x y)) m - | _ -> m + let store : Exp.t -> Exp.t -> t -> t = + fun e _ m -> match e with Exp.Lvar x -> M.filter (fun _ y -> not (Pvar.equal x y)) m | _ -> m - let find : Ident.t -> t -> Pvar.t option - = fun k m -> try Some (M.find k m) with Not_found -> None + let find : Ident.t -> t -> Pvar.t option = + fun k m -> + try Some (M.find k m) + with Not_found -> None end -module MemReach = -struct - type astate = { stack : Stack.astate; heap : Heap.astate; alias : Alias.astate } +module MemReach = struct + type astate = {stack: Stack.astate; heap: Heap.astate; alias: Alias.astate} + type t = astate - let bot : t - = { stack = Stack.bot; heap = Heap.bot; alias = Alias.bot } + let bot : t = {stack= Stack.bot; heap= Heap.bot; alias= Alias.bot} - let (<=) ~lhs ~rhs = + let ( <= ) ~lhs ~rhs = if phys_equal lhs rhs then true - else - Stack.(<=) ~lhs:(lhs.stack) ~rhs:(rhs.stack) - && Heap.(<=) ~lhs:(lhs.heap) ~rhs:(rhs.heap) - && Alias.(<=) ~lhs:(lhs.alias) ~rhs:(rhs.alias) + else Stack.( <= ) ~lhs:lhs.stack ~rhs:rhs.stack && Heap.( <= ) ~lhs:lhs.heap ~rhs:rhs.heap + && Alias.( <= ) ~lhs:lhs.alias ~rhs:rhs.alias let widen ~prev ~next ~num_iters = if phys_equal prev next then prev else - { stack = Stack.widen ~prev:(prev.stack) ~next:(next.stack) ~num_iters; - heap = Heap.widen ~prev:(prev.heap) ~next:(next.heap) ~num_iters; - alias = Alias.widen ~prev:(prev.alias) ~next:(next.alias) ~num_iters; } - - let join : t -> t -> t - = fun x y -> - { stack = Stack.join x.stack y.stack; - heap = Heap.join x.heap y.heap; - alias = Alias.join x.alias y.alias } - - let pp : F.formatter -> t -> unit - = fun fmt x -> - F.fprintf fmt "Stack:@;"; - F.fprintf fmt "%a@;" Stack.pp x.stack; - F.fprintf fmt "Heap:@;"; + { stack= Stack.widen ~prev:prev.stack ~next:next.stack ~num_iters + ; heap= Heap.widen ~prev:prev.heap ~next:next.heap ~num_iters + ; alias= Alias.widen ~prev:prev.alias ~next:next.alias ~num_iters } + + let join : t -> t -> t = + fun x y -> + { stack= Stack.join x.stack y.stack + ; heap= Heap.join x.heap y.heap + ; alias= Alias.join x.alias y.alias } + + let pp : F.formatter -> t -> unit = + fun fmt x -> + F.fprintf fmt "Stack:@;" ; + F.fprintf fmt "%a@;" Stack.pp x.stack ; + F.fprintf fmt "Heap:@;" ; F.fprintf fmt "%a" Heap.pp x.heap - let pp_summary : F.formatter -> t -> unit - = fun fmt x -> - F.fprintf fmt "@[Parameters:@,"; + let pp_summary : F.formatter -> t -> unit = + fun fmt x -> + F.fprintf fmt "@[Parameters:@," ; F.fprintf fmt "%a" Heap.pp_summary x.heap ; F.fprintf fmt "@]" - let find_stack : Loc.t -> t -> Val.t - = fun k m -> Stack.find k m.stack + let find_stack : Loc.t -> t -> Val.t = fun k m -> Stack.find k m.stack - let find_stack_set : PowLoc.t -> t -> Val.t - = fun k m -> Stack.find_set k m.stack + let find_stack_set : PowLoc.t -> t -> Val.t = fun k m -> Stack.find_set k m.stack - let find_heap : Loc.t -> t -> Val.t - = fun k m -> Heap.find k m.heap + let find_heap : Loc.t -> t -> Val.t = fun k m -> Heap.find k m.heap - let find_heap_set : PowLoc.t -> t -> Val.t - = fun k m -> Heap.find_set k m.heap + let find_heap_set : PowLoc.t -> t -> Val.t = fun k m -> Heap.find_set k m.heap - let find_set : PowLoc.t -> t -> Val.t - = fun k m -> - Val.join (find_stack_set k m) (find_heap_set k m) + let find_set : PowLoc.t -> t -> Val.t = + fun k m -> Val.join (find_stack_set k m) (find_heap_set k m) - let find_alias : Ident.t -> t -> Pvar.t option - = fun k m -> Alias.find k m.alias + let find_alias : Ident.t -> t -> Pvar.t option = fun k m -> Alias.find k m.alias - let load_alias : Ident.t -> Exp.t -> t -> t - = fun id e m -> { m with alias = Alias.load id e m.alias } + let load_alias : Ident.t -> Exp.t -> t -> t = + fun id e m -> {m with alias= Alias.load id e m.alias} - let store_alias : Exp.t -> Exp.t -> t -> t - = fun e1 e2 m -> { m with alias = Alias.store e1 e2 m.alias } + let store_alias : Exp.t -> Exp.t -> t -> t = + fun e1 e2 m -> {m with alias= Alias.store e1 e2 m.alias} - let add_stack : Loc.t -> Val.t -> t -> t - = fun k v m -> { m with stack = Stack.add k v m.stack } + let add_stack : Loc.t -> Val.t -> t -> t = fun k v m -> {m with stack= Stack.add k v m.stack} - let add_heap : Loc.t -> Val.t -> t -> t - = fun k v m -> { m with heap = Heap.add k v m.heap } + let add_heap : Loc.t -> Val.t -> t -> t = fun k v m -> {m with heap= Heap.add k v m.heap} - let strong_update_stack : PowLoc.t -> Val.t -> t -> t - = fun p v m -> { m with stack = Stack.strong_update p v m.stack } + let strong_update_stack : PowLoc.t -> Val.t -> t -> t = + fun p v m -> {m with stack= Stack.strong_update p v m.stack} - let strong_update_heap : PowLoc.t -> Val.t -> t -> t - = fun p v m -> { m with heap = Heap.strong_update p v m.heap } + let strong_update_heap : PowLoc.t -> Val.t -> t -> t = + fun p v m -> {m with heap= Heap.strong_update p v m.heap} - let weak_update_stack : PowLoc.t -> Val.t -> t -> t - = fun p v m -> { m with stack = Stack.weak_update p v m.stack } + let weak_update_stack : PowLoc.t -> Val.t -> t -> t = + fun p v m -> {m with stack= Stack.weak_update p v m.stack} - let weak_update_heap : PowLoc.t -> Val.t -> t -> t - = fun p v m -> { m with heap = Heap.weak_update p v m.heap } + let weak_update_heap : PowLoc.t -> Val.t -> t -> t = + fun p v m -> {m with heap= Heap.weak_update p v m.heap} - let get_heap_symbols : t -> Itv.Symbol.t list - = fun m -> Heap.get_symbols m.heap + let get_heap_symbols : t -> Itv.Symbol.t list = fun m -> Heap.get_symbols m.heap - let get_return : t -> Val.t - = fun m -> Heap.get_return m.heap + let get_return : t -> Val.t = fun m -> Heap.get_return m.heap - let can_strong_update : PowLoc.t -> bool - = fun ploc -> - if always_strong_update then true else - if Int.equal (PowLoc.cardinal ploc) 1 then Loc.is_var (PowLoc.choose ploc) else false + let can_strong_update : PowLoc.t -> bool = + fun ploc -> + if always_strong_update then true + else if Int.equal (PowLoc.cardinal ploc) 1 then Loc.is_var (PowLoc.choose ploc) + else false - let update_mem : PowLoc.t -> Val.t -> t -> t - = fun ploc v s -> - if can_strong_update ploc - then strong_update_heap ploc v s + let update_mem : PowLoc.t -> Val.t -> t -> t = + fun ploc v s -> + if can_strong_update ploc then strong_update_heap ploc v s else let () = L.(debug BufferOverrun Verbose) "Weak update for %a <- %a@." PowLoc.pp ploc Val.pp v @@ -735,133 +656,97 @@ module Mem = struct let bot : t = Bottom - let init : t = NonBottom (MemReach.bot) + let init : t = NonBottom MemReach.bot let f_lift_default : 'a -> (MemReach.t -> 'a) -> t -> 'a = - fun default f m -> - match m with - | Bottom -> default - | NonBottom m' -> f m' + fun default f m -> match m with Bottom -> default | NonBottom m' -> f m' let f_lift : (MemReach.t -> MemReach.t) -> t -> t = - fun f -> - f_lift_default Bottom (fun m' -> NonBottom (f m')) + fun f -> f_lift_default Bottom (fun m' -> NonBottom (f m')) - let pp_summary : F.formatter -> t -> unit - = fun fmt m -> + let pp_summary : F.formatter -> t -> unit = + fun fmt m -> match m with - | Bottom -> F.fprintf fmt "unreachable" - | NonBottom m' -> MemReach.pp_summary fmt m' + | Bottom + -> F.fprintf fmt "unreachable" + | NonBottom m' + -> MemReach.pp_summary fmt m' - let find_stack : Loc.t -> t -> Val.t - = fun k -> - f_lift_default Val.bot (MemReach.find_stack k) + let find_stack : Loc.t -> t -> Val.t = fun k -> f_lift_default Val.bot (MemReach.find_stack k) - let find_stack_set : PowLoc.t -> t -> Val.t - = fun k -> - f_lift_default Val.bot (MemReach.find_stack_set k) + let find_stack_set : PowLoc.t -> t -> Val.t = + fun k -> f_lift_default Val.bot (MemReach.find_stack_set k) - let find_heap : Loc.t -> t -> Val.t - = fun k -> - f_lift_default Val.bot (MemReach.find_heap k) + let find_heap : Loc.t -> t -> Val.t = fun k -> f_lift_default Val.bot (MemReach.find_heap k) - let find_heap_set : PowLoc.t -> t -> Val.t - = fun k -> - f_lift_default Val.bot (MemReach.find_heap_set k) + let find_heap_set : PowLoc.t -> t -> Val.t = + fun k -> f_lift_default Val.bot (MemReach.find_heap_set k) - let find_set : PowLoc.t -> t -> Val.t - = fun k -> - f_lift_default Val.bot (MemReach.find_set k) + let find_set : PowLoc.t -> t -> Val.t = fun k -> f_lift_default Val.bot (MemReach.find_set k) - let find_alias : Ident.t -> t -> Pvar.t option - = fun k -> - f_lift_default None (MemReach.find_alias k) + let find_alias : Ident.t -> t -> Pvar.t option = + fun k -> f_lift_default None (MemReach.find_alias k) - let load_alias : Ident.t -> Exp.t -> t -> t - = fun id e -> - f_lift (MemReach.load_alias id e) + let load_alias : Ident.t -> Exp.t -> t -> t = fun id e -> f_lift (MemReach.load_alias id e) - let store_alias : Exp.t -> Exp.t -> t -> t - = fun e1 e2 -> - f_lift (MemReach.store_alias e1 e2) + let store_alias : Exp.t -> Exp.t -> t -> t = fun e1 e2 -> f_lift (MemReach.store_alias e1 e2) - let add_stack : Loc.t -> Val.t -> t -> t - = fun k v -> - f_lift (MemReach.add_stack k v) + let add_stack : Loc.t -> Val.t -> t -> t = fun k v -> f_lift (MemReach.add_stack k v) - let add_heap : Loc.t -> Val.t -> t -> t - = fun k v -> - f_lift (MemReach.add_heap k v) + let add_heap : Loc.t -> Val.t -> t -> t = fun k v -> f_lift (MemReach.add_heap k v) - let strong_update_stack : PowLoc.t -> Val.t -> t -> t - = fun p v -> - f_lift (MemReach.strong_update_stack p v) + let strong_update_stack : PowLoc.t -> Val.t -> t -> t = + fun p v -> f_lift (MemReach.strong_update_stack p v) - let strong_update_heap : PowLoc.t -> Val.t -> t -> t - = fun p v -> - f_lift (MemReach.strong_update_heap p v) + let strong_update_heap : PowLoc.t -> Val.t -> t -> t = + fun p v -> f_lift (MemReach.strong_update_heap p v) - let weak_update_stack : PowLoc.t -> Val.t -> t -> t - = fun p v -> - f_lift (MemReach.weak_update_stack p v) + let weak_update_stack : PowLoc.t -> Val.t -> t -> t = + fun p v -> f_lift (MemReach.weak_update_stack p v) - let weak_update_heap : PowLoc.t -> Val.t -> t -> t - = fun p v -> - f_lift (MemReach.weak_update_heap p v) + let weak_update_heap : PowLoc.t -> Val.t -> t -> t = + fun p v -> f_lift (MemReach.weak_update_heap p v) - let get_heap_symbols : t -> Itv.Symbol.t list - = f_lift_default [] MemReach.get_heap_symbols + let get_heap_symbols : t -> Itv.Symbol.t list = f_lift_default [] MemReach.get_heap_symbols - let get_return : t -> Val.t - = f_lift_default Val.bot MemReach.get_return + let get_return : t -> Val.t = f_lift_default Val.bot MemReach.get_return - let can_strong_update : PowLoc.t -> bool - = MemReach.can_strong_update + let can_strong_update : PowLoc.t -> bool = MemReach.can_strong_update - let update_mem : PowLoc.t -> Val.t -> t -> t - = fun ploc v -> - f_lift (MemReach.update_mem ploc v) + let update_mem : PowLoc.t -> Val.t -> t -> t = fun ploc v -> f_lift (MemReach.update_mem ploc v) end -module Summary = -struct +module Summary = struct type t = Mem.t * Mem.t * ConditionSet.t - let get_input : t -> Mem.t - = fst3 + let get_input : t -> Mem.t = fst3 - let get_output : t -> Mem.t - = snd3 + let get_output : t -> Mem.t = snd3 - let get_cond_set : t -> ConditionSet.t - = trd3 + let get_cond_set : t -> ConditionSet.t = trd3 - let get_symbols : t -> Itv.Symbol.t list - = fun s -> Mem.get_heap_symbols (get_input s) + let get_symbols : t -> Itv.Symbol.t list = fun s -> Mem.get_heap_symbols (get_input s) - let get_return : t -> Val.t - = fun s -> Mem.get_return (get_output s) + let get_return : t -> Val.t = fun s -> Mem.get_return (get_output s) - let pp_symbols : F.formatter -> t -> unit - = fun fmt s -> + let pp_symbols : F.formatter -> t -> unit = + fun fmt s -> let pp_sep fmt () = F.fprintf fmt ", @," in - F.fprintf fmt "@[Symbols: {"; - F.pp_print_list ~pp_sep Itv.Symbol.pp fmt (get_symbols s); + F.fprintf fmt "@[Symbols: {" ; + F.pp_print_list ~pp_sep Itv.Symbol.pp fmt (get_symbols s) ; F.fprintf fmt "}@]" - let pp_symbol_map : F.formatter -> t -> unit - = fun fmt s -> Mem.pp_summary fmt (get_input s) + let pp_symbol_map : F.formatter -> t -> unit = fun fmt s -> Mem.pp_summary fmt (get_input s) - let pp_return : F.formatter -> t -> unit - = fun fmt s -> F.fprintf fmt "Return value: %a" Val.pp_summary (get_return s) + let pp_return : F.formatter -> t -> unit = + fun fmt s -> F.fprintf fmt "Return value: %a" Val.pp_summary (get_return s) - let pp_summary : F.formatter -> t -> unit - = fun fmt s -> - F.fprintf fmt "%a@,%a@,%a" pp_symbol_map s pp_return s - ConditionSet.pp_summary (get_cond_set s) + let pp_summary : F.formatter -> t -> unit = + fun fmt s -> + F.fprintf fmt "%a@,%a@,%a" pp_symbol_map s pp_return s ConditionSet.pp_summary + (get_cond_set s) - let pp : F.formatter -> t -> unit - = fun fmt (entry_mem, exit_mem, condition_set) -> - F.fprintf fmt "%a@,%a@,%a@," - Mem.pp entry_mem Mem.pp exit_mem ConditionSet.pp condition_set + let pp : F.formatter -> t -> unit = + fun fmt (entry_mem, exit_mem, condition_set) -> + F.fprintf fmt "%a@,%a@,%a@," Mem.pp entry_mem Mem.pp exit_mem ConditionSet.pp condition_set end diff --git a/infer/src/bufferoverrun/bufferOverrunSemantics.ml b/infer/src/bufferoverrun/bufferOverrunSemantics.ml index 6a265f557..52804c1f6 100644 --- a/infer/src/bufferoverrun/bufferOverrunSemantics.ml +++ b/infer/src/bufferoverrun/bufferOverrunSemantics.ml @@ -12,7 +12,6 @@ open! IStd open AbsLoc - module F = Format module L = Logging module Domain = BufferOverrunDomain @@ -20,365 +19,413 @@ module Trace = BufferOverrunTrace module TraceSet = Trace.Set open Domain -module Make (CFG : ProcCfg.S) = -struct +module Make (CFG : ProcCfg.S) = struct exception Not_implemented - let eval_const : Const.t -> Val.t - = function - | Const.Cint intlit -> (try Val.of_int (IntLit.to_int intlit) with _ -> Val.Itv.top) - | Const.Cfloat f -> f |> int_of_float |> Val.of_int - | _ -> Val.Itv.top (* TODO *) + let eval_const : Const.t -> Val.t = function + | Const.Cint intlit -> ( + try Val.of_int (IntLit.to_int intlit) + with _ -> Val.Itv.top ) + | Const.Cfloat f + -> f |> int_of_float |> Val.of_int + | _ + -> Val.Itv.top + + (* TODO *) - let sizeof_ikind : Typ.ikind -> int - = function - | Typ.IChar | Typ.ISChar | Typ.IUChar | Typ.IBool -> 1 - | Typ.IInt | Typ.IUInt -> 4 - | Typ.IShort | Typ.IUShort -> 2 - | Typ.ILong | Typ.IULong -> 4 - | Typ.ILongLong | Typ.IULongLong -> 8 - | Typ.I128 | Typ.IU128 -> 16 + let sizeof_ikind : Typ.ikind -> int = function + | Typ.IChar | Typ.ISChar | Typ.IUChar | Typ.IBool + -> 1 + | Typ.IInt | Typ.IUInt + -> 4 + | Typ.IShort | Typ.IUShort + -> 2 + | Typ.ILong | Typ.IULong + -> 4 + | Typ.ILongLong | Typ.IULongLong + -> 8 + | Typ.I128 | Typ.IU128 + -> 16 - let sizeof_fkind : Typ.fkind -> int - = function - | Typ.FFloat -> 4 - | Typ.FDouble | Typ.FLongDouble -> 8 + let sizeof_fkind : Typ.fkind -> int = function + | Typ.FFloat + -> 4 + | Typ.FDouble | Typ.FLongDouble + -> 8 (* NOTE: assume 32bit machine *) - let rec sizeof (typ : Typ.t) : int = + let rec sizeof (typ: Typ.t) : int = match typ.desc with - | Typ.Tint ikind -> sizeof_ikind ikind - | Typ.Tfloat fkind -> sizeof_fkind fkind - | Typ.Tvoid -> 1 - | Typ.Tptr (_, _) -> 4 - | Typ.Tstruct _ | Typ.TVar _ -> 4 (* TODO *) - | Typ.Tarray (_, Some length, Some stride) -> IntLit.to_int stride * IntLit.to_int length - | Typ.Tarray (typ, Some length, None) -> sizeof typ * IntLit.to_int length - | _ -> 4 + | Typ.Tint ikind + -> sizeof_ikind ikind + | Typ.Tfloat fkind + -> sizeof_fkind fkind + | Typ.Tvoid + -> 1 + | Typ.Tptr (_, _) + -> 4 + | Typ.Tstruct _ | Typ.TVar _ + -> 4 (* TODO *) + | Typ.Tarray (_, Some length, Some stride) + -> IntLit.to_int stride * IntLit.to_int length + | Typ.Tarray (typ, Some length, None) + -> sizeof typ * IntLit.to_int length + | _ + -> 4 - let rec must_alias : Exp.t -> Exp.t -> Mem.astate -> bool - = fun e1 e2 m -> - match e1, e2 with - | Exp.Var x1, Exp.Var x2 -> - (match Mem.find_alias x1 m, Mem.find_alias x2 m with - | Some x1', Some x2' -> Pvar.equal x1' x2' - | _, _ -> false) - | Exp.UnOp (uop1, e1', _), Exp.UnOp (uop2, e2', _) -> - Unop.equal uop1 uop2 && must_alias e1' e2' m - | Exp.BinOp (bop1, e11, e12), Exp.BinOp (bop2, e21, e22) -> - Binop.equal bop1 bop2 - && must_alias e11 e21 m - && must_alias e12 e22 m - | Exp.Exn t1, Exp.Exn t2 -> must_alias t1 t2 m - | Exp.Const c1, Exp.Const c2 -> Const.equal c1 c2 - | Exp.Cast (t1, e1'), Exp.Cast (t2, e2') -> - Typ.equal t1 t2 && must_alias e1' e2' m - | Exp.Lvar x1, Exp.Lvar x2 -> - Pvar.equal x1 x2 - | Exp.Lfield (e1, fld1, _), Exp.Lfield (e2, fld2, _) -> - must_alias e1 e2 m && Typ.Fieldname.equal fld1 fld2 - | Exp.Lindex (e11, e12), Exp.Lindex (e21, e22) -> - must_alias e11 e21 m && must_alias e12 e22 m - | Exp.Sizeof {nbytes=Some nbytes1}, Exp.Sizeof {nbytes=Some nbytes2} -> - Int.equal nbytes1 nbytes2 - | Exp.Sizeof {typ=t1; dynamic_length=dynlen1; subtype=subt1}, - Exp.Sizeof {typ=t2; dynamic_length=dynlen2; subtype=subt2} -> - Typ.equal t1 t2 - && must_alias_opt dynlen1 dynlen2 m + let rec must_alias : Exp.t -> Exp.t -> Mem.astate -> bool = + fun e1 e2 m -> + match (e1, e2) with + | Exp.Var x1, Exp.Var x2 -> ( + match (Mem.find_alias x1 m, Mem.find_alias x2 m) with + | Some x1', Some x2' + -> Pvar.equal x1' x2' + | _, _ + -> false ) + | Exp.UnOp (uop1, e1', _), Exp.UnOp (uop2, e2', _) + -> Unop.equal uop1 uop2 && must_alias e1' e2' m + | Exp.BinOp (bop1, e11, e12), Exp.BinOp (bop2, e21, e22) + -> Binop.equal bop1 bop2 && must_alias e11 e21 m && must_alias e12 e22 m + | Exp.Exn t1, Exp.Exn t2 + -> must_alias t1 t2 m + | Exp.Const c1, Exp.Const c2 + -> Const.equal c1 c2 + | Exp.Cast (t1, e1'), Exp.Cast (t2, e2') + -> Typ.equal t1 t2 && must_alias e1' e2' m + | Exp.Lvar x1, Exp.Lvar x2 + -> Pvar.equal x1 x2 + | Exp.Lfield (e1, fld1, _), Exp.Lfield (e2, fld2, _) + -> must_alias e1 e2 m && Typ.Fieldname.equal fld1 fld2 + | Exp.Lindex (e11, e12), Exp.Lindex (e21, e22) + -> must_alias e11 e21 m && must_alias e12 e22 m + | Exp.Sizeof {nbytes= Some nbytes1}, Exp.Sizeof {nbytes= Some nbytes2} + -> Int.equal nbytes1 nbytes2 + | ( Exp.Sizeof {typ= t1; dynamic_length= dynlen1; subtype= subt1} + , Exp.Sizeof {typ= t2; dynamic_length= dynlen2; subtype= subt2} ) + -> Typ.equal t1 t2 && must_alias_opt dynlen1 dynlen2 m && Int.equal (Subtype.compare subt1 subt2) 0 - | _, _ -> false + | _, _ + -> false - and must_alias_opt : Exp.t option -> Exp.t option -> Mem.astate -> bool - = fun e1_opt e2_opt m -> - match e1_opt, e2_opt with - | Some e1, Some e2 -> must_alias e1 e2 m - | None, None -> true - | _, _ -> false + and must_alias_opt : Exp.t option -> Exp.t option -> Mem.astate -> bool = + fun e1_opt e2_opt m -> + match (e1_opt, e2_opt) with + | Some e1, Some e2 + -> must_alias e1 e2 m + | None, None + -> true + | _, _ + -> false - let comp_rev : Binop.t -> Binop.t - = function - | Binop.Lt -> Binop.Gt - | Binop.Gt -> Binop.Lt - | Binop.Le -> Binop.Ge - | Binop.Ge -> Binop.Le - | Binop.Eq -> Binop.Eq - | Binop.Ne -> Binop.Ne - | _ -> assert (false) + let comp_rev : Binop.t -> Binop.t = function + | Binop.Lt + -> Binop.Gt + | Binop.Gt + -> Binop.Lt + | Binop.Le + -> Binop.Ge + | Binop.Ge + -> Binop.Le + | Binop.Eq + -> Binop.Eq + | Binop.Ne + -> Binop.Ne + | _ + -> assert false - let comp_not : Binop.t -> Binop.t - = function - | Binop.Lt -> Binop.Ge - | Binop.Gt -> Binop.Le - | Binop.Le -> Binop.Gt - | Binop.Ge -> Binop.Lt - | Binop.Eq -> Binop.Ne - | Binop.Ne -> Binop.Eq - | _ -> assert (false) + let comp_not : Binop.t -> Binop.t = function + | Binop.Lt + -> Binop.Ge + | Binop.Gt + -> Binop.Le + | Binop.Le + -> Binop.Gt + | Binop.Ge + -> Binop.Lt + | Binop.Eq + -> Binop.Ne + | Binop.Ne + -> Binop.Eq + | _ + -> assert false - let rec must_alias_cmp : Exp.t -> Mem.astate -> bool - = fun e m -> + let rec must_alias_cmp : Exp.t -> Mem.astate -> bool = + fun e m -> match e with - | Exp.BinOp (Binop.Lt, e1, e2) - | Exp.BinOp (Binop.Gt, e1, e2) - | Exp.BinOp (Binop.Ne, e1, e2) -> must_alias e1 e2 m - | Exp.BinOp (Binop.LAnd, e1, e2) -> - must_alias_cmp e1 m || must_alias_cmp e2 m - | Exp.BinOp (Binop.LOr, e1, e2) -> - must_alias_cmp e1 m && must_alias_cmp e2 m - | Exp.UnOp (Unop.LNot, Exp.UnOp (Unop.LNot, e1, _), _) -> - must_alias_cmp e1 m - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Lt as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Gt as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Le as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Ge as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Ne as c, e1, e2), _) -> - must_alias_cmp (Exp.BinOp (comp_not c, e1, e2)) m - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t) -> - let e1' = Exp.UnOp (Unop.LNot, e1, t) in + | Exp.BinOp (Binop.Lt, e1, e2) | Exp.BinOp (Binop.Gt, e1, e2) | Exp.BinOp (Binop.Ne, e1, e2) + -> must_alias e1 e2 m + | Exp.BinOp (Binop.LAnd, e1, e2) + -> must_alias_cmp e1 m || must_alias_cmp e2 m + | Exp.BinOp (Binop.LOr, e1, e2) + -> must_alias_cmp e1 m && must_alias_cmp e2 m + | Exp.UnOp (Unop.LNot, Exp.UnOp (Unop.LNot, e1, _), _) + -> must_alias_cmp e1 m + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Lt as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Gt as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Le as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ge as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Eq as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ne as c), e1, e2), _) + -> must_alias_cmp (Exp.BinOp (comp_not c, e1, e2)) m + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t) + -> let e1' = Exp.UnOp (Unop.LNot, e1, t) in let e2' = Exp.UnOp (Unop.LNot, e2, t) in must_alias_cmp (Exp.BinOp (Binop.LAnd, e1', e2')) m - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LAnd, e1, e2), t) -> - let e1' = Exp.UnOp (Unop.LNot, e1, t) in + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LAnd, e1, e2), t) + -> let e1' = Exp.UnOp (Unop.LNot, e1, t) in let e2' = Exp.UnOp (Unop.LNot, e2, t) in must_alias_cmp (Exp.BinOp (Binop.LOr, e1', e2')) m - | _ -> false + | _ + -> false - let rec eval : Exp.t -> Mem.astate -> Location.t -> Val.t - = fun exp mem loc -> - if must_alias_cmp exp mem then Val.of_int 0 else + let rec eval : Exp.t -> Mem.astate -> Location.t -> Val.t = + fun exp mem loc -> + if must_alias_cmp exp mem then Val.of_int 0 + else match exp with - | Exp.Var id -> Mem.find_stack (Var.of_id id |> Loc.of_var) mem - | Exp.Lvar pvar -> - let ploc = pvar |> Loc.of_pvar |> PowLoc.singleton in + | Exp.Var id + -> Mem.find_stack (Var.of_id id |> Loc.of_var) mem + | Exp.Lvar pvar + -> let ploc = pvar |> Loc.of_pvar |> PowLoc.singleton in let arr = Mem.find_stack_set ploc mem in ploc |> Val.of_pow_loc |> Val.join arr - | Exp.UnOp (uop, e, _) -> eval_unop uop e mem loc - | Exp.BinOp (bop, e1, e2) -> eval_binop bop e1 e2 mem loc - | Exp.Const c -> eval_const c - | Exp.Cast (_, e) -> eval e mem loc - | Exp.Lfield (e, fn, _) -> - eval e mem loc - |> Val.get_array_locs - |> Fn.flip PowLoc.append_field fn + | Exp.UnOp (uop, e, _) + -> eval_unop uop e mem loc + | Exp.BinOp (bop, e1, e2) + -> eval_binop bop e1 e2 mem loc + | Exp.Const c + -> eval_const c + | Exp.Cast (_, e) + -> eval e mem loc + | Exp.Lfield (e, fn, _) + -> eval e mem loc |> Val.get_array_locs |> Fn.flip PowLoc.append_field fn |> Val.of_pow_loc - | Exp.Lindex (e1, _) -> - let arr = eval e1 mem loc |> Val.get_array_blk in (* must have array blk *) + | Exp.Lindex (e1, _) + -> let arr = eval e1 mem loc |> Val.get_array_blk in + (* must have array blk *) (* let idx = eval e2 mem loc in *) let ploc = if ArrayBlk.is_bot arr then PowLoc.unknown else ArrayBlk.get_pow_loc arr in (* if nested array, add the array blk *) let arr = Mem.find_heap_set ploc mem in Val.join (Val.of_pow_loc ploc) arr - | Exp.Sizeof {nbytes=Some size} -> Val.of_int size - | Exp.Sizeof {typ; nbytes=None} -> Val.of_int (sizeof typ) - | Exp.Exn _ - | Exp.Closure _ -> Val.Itv.top + | Exp.Sizeof {nbytes= Some size} + -> Val.of_int size + | Exp.Sizeof {typ; nbytes= None} + -> Val.of_int (sizeof typ) + | Exp.Exn _ | Exp.Closure _ + -> Val.Itv.top - and eval_unop : Unop.t -> Exp.t -> Mem.astate -> Location.t -> Val.t - = fun unop e mem loc -> + and eval_unop : Unop.t -> Exp.t -> Mem.astate -> Location.t -> Val.t = + fun unop e mem loc -> let v = eval e mem loc in match unop with - | Unop.Neg -> Val.neg v - | Unop.BNot -> Val.unknown_bit v - | Unop.LNot -> Val.lnot v + | Unop.Neg + -> Val.neg v + | Unop.BNot + -> Val.unknown_bit v + | Unop.LNot + -> Val.lnot v - and eval_binop - : Binop.t -> Exp.t -> Exp.t -> Mem.astate -> Location.t -> Val.t - = fun binop e1 e2 mem loc -> + and eval_binop : Binop.t -> Exp.t -> Exp.t -> Mem.astate -> Location.t -> Val.t = + fun binop e1 e2 mem loc -> let v1 = eval e1 mem loc in let v2 = eval e2 mem loc in match binop with - | Binop.PlusA -> - Val.join (Val.plus v1 v2) (Val.plus_pi v1 v2) - | Binop.PlusPI -> Val.plus_pi v1 v2 - | Binop.MinusA -> - Val.joins - [ Val.minus v1 v2 - ; Val.minus_pi v1 v2 - ; Val.minus_pp v1 v2 ] - | Binop.MinusPI -> Val.minus_pi v1 v2 - | Binop.MinusPP -> Val.minus_pp v1 v2 - | Binop.Mult -> Val.mult v1 v2 - | Binop.Div -> Val.div v1 v2 - | Binop.Mod -> Val.mod_sem v1 v2 - | Binop.Shiftlt -> Val.shiftlt v1 v2 - | Binop.Shiftrt -> Val.shiftrt v1 v2 - | Binop.Lt -> Val.lt_sem v1 v2 - | Binop.Gt -> Val.gt_sem v1 v2 - | Binop.Le -> Val.le_sem v1 v2 - | Binop.Ge -> Val.ge_sem v1 v2 - | Binop.Eq -> Val.eq_sem v1 v2 - | Binop.Ne -> Val.ne_sem v1 v2 - | Binop.BAnd - | Binop.BXor - | Binop.BOr -> Val.unknown_bit v1 - | Binop.LAnd -> Val.land_sem v1 v2 - | Binop.LOr -> Val.lor_sem v1 v2 + | Binop.PlusA + -> Val.join (Val.plus v1 v2) (Val.plus_pi v1 v2) + | Binop.PlusPI + -> Val.plus_pi v1 v2 + | Binop.MinusA + -> Val.joins [Val.minus v1 v2; Val.minus_pi v1 v2; Val.minus_pp v1 v2] + | Binop.MinusPI + -> Val.minus_pi v1 v2 + | Binop.MinusPP + -> Val.minus_pp v1 v2 + | Binop.Mult + -> Val.mult v1 v2 + | Binop.Div + -> Val.div v1 v2 + | Binop.Mod + -> Val.mod_sem v1 v2 + | Binop.Shiftlt + -> Val.shiftlt v1 v2 + | Binop.Shiftrt + -> Val.shiftrt v1 v2 + | Binop.Lt + -> Val.lt_sem v1 v2 + | Binop.Gt + -> Val.gt_sem v1 v2 + | Binop.Le + -> Val.le_sem v1 v2 + | Binop.Ge + -> Val.ge_sem v1 v2 + | Binop.Eq + -> Val.eq_sem v1 v2 + | Binop.Ne + -> Val.ne_sem v1 v2 + | Binop.BAnd | Binop.BXor | Binop.BOr + -> Val.unknown_bit v1 + | Binop.LAnd + -> Val.land_sem v1 v2 + | Binop.LOr + -> Val.lor_sem v1 v2 - let rec eval_locs : Exp.t -> Mem.astate -> Location.t -> Val.t - = fun exp mem loc -> + let rec eval_locs : Exp.t -> Mem.astate -> Location.t -> Val.t = + fun exp mem loc -> match exp with - | Exp.Var id -> - (match Mem.find_alias id mem with - | Some pvar -> - Var.of_pvar pvar |> Loc.of_var |> PowLoc.singleton |> Val.of_pow_loc - | None -> Val.bot) - | Exp.Lvar pvar -> - pvar |> Loc.of_pvar |> PowLoc.singleton |> Val.of_pow_loc - | Exp.BinOp (bop, e1, e2) -> eval_binop bop e1 e2 mem loc - | Exp.Cast (_, e) -> eval_locs e mem loc - | Exp.Lfield (e, fn, _) -> - eval e mem loc - |> Val.get_all_locs - |> Fn.flip PowLoc.append_field fn - |> Val.of_pow_loc - | Exp.Lindex (e1, e2) -> - let arr = eval e1 mem loc in + | Exp.Var id -> ( + match Mem.find_alias id mem with + | Some pvar + -> Var.of_pvar pvar |> Loc.of_var |> PowLoc.singleton |> Val.of_pow_loc + | None + -> Val.bot ) + | Exp.Lvar pvar + -> pvar |> Loc.of_pvar |> PowLoc.singleton |> Val.of_pow_loc + | Exp.BinOp (bop, e1, e2) + -> eval_binop bop e1 e2 mem loc + | Exp.Cast (_, e) + -> eval_locs e mem loc + | Exp.Lfield (e, fn, _) + -> eval e mem loc |> Val.get_all_locs |> Fn.flip PowLoc.append_field fn |> Val.of_pow_loc + | Exp.Lindex (e1, e2) + -> let arr = eval e1 mem loc in let idx = eval e2 mem loc in Val.plus_pi arr idx - | Exp.Const _ - | Exp.UnOp _ - | Exp.Sizeof _ - | Exp.Exn _ - | Exp.Closure _ -> Val.bot + | Exp.Const _ | Exp.UnOp _ | Exp.Sizeof _ | Exp.Exn _ | Exp.Closure _ + -> Val.bot - let get_allocsite : Typ.Procname.t -> CFG.node -> int -> int -> string - = fun proc_name node inst_num dimension -> + let get_allocsite : Typ.Procname.t -> CFG.node -> int -> int -> string = + fun proc_name node inst_num dimension -> let proc_name = Typ.Procname.to_string proc_name in let node_num = CFG.hash node |> string_of_int in let inst_num = string_of_int inst_num in let dimension = string_of_int dimension in - (proc_name ^ "-" ^ node_num ^ "-" ^ inst_num ^ "-" ^ dimension) - |> Allocsite.make + proc_name ^ "-" ^ node_num ^ "-" ^ inst_num ^ "-" ^ dimension |> Allocsite.make let eval_array_alloc - : Typ.Procname.t -> CFG.node -> Typ.t -> ?stride:int -> Itv.t -> Itv.t -> int -> int -> Val.t - = fun pdesc node typ ?stride:stride0 offset size inst_num dimension -> + : Typ.Procname.t -> CFG.node -> Typ.t -> ?stride:int -> Itv.t -> Itv.t -> int -> int -> Val.t = + fun pdesc node typ ?stride:stride0 offset size inst_num dimension -> let allocsite = get_allocsite pdesc node inst_num dimension in - let int_stride = match stride0 with - | None -> sizeof typ - | Some stride -> stride in + let int_stride = match stride0 with None -> sizeof typ | Some stride -> stride in let stride = Itv.of_int int_stride in - ArrayBlk.make allocsite offset size stride - |> Val.of_array_blk + ArrayBlk.make allocsite offset size stride |> Val.of_array_blk - let prune_unop : Exp.t -> Mem.astate -> Mem.astate - = fun e mem -> + let prune_unop : Exp.t -> Mem.astate -> Mem.astate = + fun e mem -> match e with - | Exp.Var x -> - (match Mem.find_alias x mem with - | Some x' -> - let lv = Loc.of_pvar x' in - let v = Mem.find_heap lv mem in - let v' = Val.prune_zero v in - Mem.update_mem (PowLoc.singleton lv) v' mem - | None -> mem) - | Exp.UnOp (Unop.LNot, Exp.Var x, _) -> - (match Mem.find_alias x mem with - | Some x' -> - let lv = Loc.of_pvar x' in - let v = Mem.find_heap lv mem in - let itv_v = - if Itv.is_bot (Val.get_itv v) then Itv.bot else - Itv.false_sem - in - let v' = Val.modify_itv itv_v v in - Mem.update_mem (PowLoc.singleton lv) v' mem - | None -> mem) - | _ -> mem + | Exp.Var x -> ( + match Mem.find_alias x mem with + | Some x' + -> let lv = Loc.of_pvar x' in + let v = Mem.find_heap lv mem in + let v' = Val.prune_zero v in + Mem.update_mem (PowLoc.singleton lv) v' mem + | None + -> mem ) + | Exp.UnOp (Unop.LNot, Exp.Var x, _) -> ( + match Mem.find_alias x mem with + | Some x' + -> let lv = Loc.of_pvar x' in + let v = Mem.find_heap lv mem in + let itv_v = if Itv.is_bot (Val.get_itv v) then Itv.bot else Itv.false_sem in + let v' = Val.modify_itv itv_v v in + Mem.update_mem (PowLoc.singleton lv) v' mem + | None + -> mem ) + | _ + -> mem - let prune_binop_left : Exp.t -> Location.t -> Mem.astate -> Mem.astate - = fun e loc mem -> + let prune_binop_left : Exp.t -> Location.t -> Mem.astate -> Mem.astate = + fun e loc mem -> match e with - | Exp.BinOp (Binop.Lt as comp, Exp.Var x, e') - | Exp.BinOp (Binop.Gt as comp, Exp.Var x, e') - | Exp.BinOp (Binop.Le as comp, Exp.Var x, e') - | Exp.BinOp (Binop.Ge as comp, Exp.Var x, e') -> - (match Mem.find_alias x mem with - | Some x' -> - let lv = Loc.of_pvar x' in - let v = Mem.find_heap lv mem in - let v' = Val.prune_comp comp v (eval e' mem loc) in - Mem.update_mem (PowLoc.singleton lv) v' mem - | None -> mem) - | Exp.BinOp (Binop.Eq, Exp.Var x, e') -> - (match Mem.find_alias x mem with - | Some x' -> - let lv = Loc.of_pvar x' in - let v = Mem.find_heap lv mem in - let v' = Val.prune_eq v (eval e' mem loc) in - Mem.update_mem (PowLoc.singleton lv) v' mem - | None -> mem) - | Exp.BinOp (Binop.Ne, Exp.Var x, e') -> - (match Mem.find_alias x mem with - | Some x' -> - let lv = Loc.of_pvar x' in - let v = Mem.find_heap lv mem in - let v' = Val.prune_ne v (eval e' mem loc) in - Mem.update_mem (PowLoc.singleton lv) v' mem - | None -> mem) - | _ -> mem + | Exp.BinOp ((Binop.Lt as comp), Exp.Var x, e') + | Exp.BinOp ((Binop.Gt as comp), Exp.Var x, e') + | Exp.BinOp ((Binop.Le as comp), Exp.Var x, e') + | Exp.BinOp ((Binop.Ge as comp), Exp.Var x, e') -> ( + match Mem.find_alias x mem with + | Some x' + -> let lv = Loc.of_pvar x' in + let v = Mem.find_heap lv mem in + let v' = Val.prune_comp comp v (eval e' mem loc) in + Mem.update_mem (PowLoc.singleton lv) v' mem + | None + -> mem ) + | Exp.BinOp (Binop.Eq, Exp.Var x, e') -> ( + match Mem.find_alias x mem with + | Some x' + -> let lv = Loc.of_pvar x' in + let v = Mem.find_heap lv mem in + let v' = Val.prune_eq v (eval e' mem loc) in + Mem.update_mem (PowLoc.singleton lv) v' mem + | None + -> mem ) + | Exp.BinOp (Binop.Ne, Exp.Var x, e') -> ( + match Mem.find_alias x mem with + | Some x' + -> let lv = Loc.of_pvar x' in + let v = Mem.find_heap lv mem in + let v' = Val.prune_ne v (eval e' mem loc) in + Mem.update_mem (PowLoc.singleton lv) v' mem + | None + -> mem ) + | _ + -> mem - let prune_binop_right : Exp.t -> Location.t -> Mem.astate -> Mem.astate - = fun e loc mem -> + let prune_binop_right : Exp.t -> Location.t -> Mem.astate -> Mem.astate = + fun e loc mem -> match e with - | Exp.BinOp (Binop.Lt as c, e', Exp.Var x) - | Exp.BinOp (Binop.Gt as c, e', Exp.Var x) - | Exp.BinOp (Binop.Le as c, e', Exp.Var x) - | Exp.BinOp (Binop.Ge as c, e', Exp.Var x) - | Exp.BinOp (Binop.Eq as c, e', Exp.Var x) - | Exp.BinOp (Binop.Ne as c, e', Exp.Var x) -> - prune_binop_left (Exp.BinOp (comp_rev c, Exp.Var x, e')) loc mem - | _ -> mem + | Exp.BinOp ((Binop.Lt as c), e', Exp.Var x) + | Exp.BinOp ((Binop.Gt as c), e', Exp.Var x) + | Exp.BinOp ((Binop.Le as c), e', Exp.Var x) + | Exp.BinOp ((Binop.Ge as c), e', Exp.Var x) + | Exp.BinOp ((Binop.Eq as c), e', Exp.Var x) + | Exp.BinOp ((Binop.Ne as c), e', Exp.Var x) + -> prune_binop_left (Exp.BinOp (comp_rev c, Exp.Var x, e')) loc mem + | _ + -> mem - let is_unreachable_constant : Exp.t -> Location.t -> Mem.astate -> bool - = fun e loc m -> - Val.(<=) ~lhs:(eval e m loc) ~rhs:(Val.of_int 0) + let is_unreachable_constant : Exp.t -> Location.t -> Mem.astate -> bool = + fun e loc m -> Val.( <= ) ~lhs:(eval e m loc) ~rhs:(Val.of_int 0) - let prune_unreachable : Exp.t -> Location.t -> Mem.astate -> Mem.astate - = fun e loc mem -> - if is_unreachable_constant e loc mem then Mem.bot else mem + let prune_unreachable : Exp.t -> Location.t -> Mem.astate -> Mem.astate = + fun e loc mem -> if is_unreachable_constant e loc mem then Mem.bot else mem - let rec prune : Exp.t -> Location.t -> Mem.astate -> Mem.astate - = fun e loc mem -> + let rec prune : Exp.t -> Location.t -> Mem.astate -> Mem.astate = + fun e loc mem -> let mem = - mem - |> prune_unreachable e loc - |> prune_unop e - |> prune_binop_left e loc + mem |> prune_unreachable e loc |> prune_unop e |> prune_binop_left e loc |> prune_binop_right e loc in match e with - | Exp.BinOp (Binop.Ne, e, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - prune e loc mem - | Exp.BinOp (Binop.Eq, e, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - prune (Exp.UnOp (Unop.LNot, e, None)) loc mem - | Exp.UnOp (Unop.Neg, Exp.Var x, _) -> prune (Exp.Var x) loc mem - | Exp.BinOp (Binop.LAnd, e1, e2) -> - mem |> prune e1 loc |> prune e2 loc - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t) -> - mem - |> prune (Exp.UnOp (Unop.LNot, e1, t)) loc - |> prune (Exp.UnOp (Unop.LNot, e2, t)) loc - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Lt as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Gt as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Le as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Ge as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq as c, e1, e2), _) - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Ne as c, e1, e2), _) -> - prune (Exp.BinOp (comp_not c, e1, e2)) loc mem - | _ -> mem + | Exp.BinOp (Binop.Ne, e, Exp.Const Const.Cint i) when IntLit.iszero i + -> prune e loc mem + | Exp.BinOp (Binop.Eq, e, Exp.Const Const.Cint i) when IntLit.iszero i + -> prune (Exp.UnOp (Unop.LNot, e, None)) loc mem + | Exp.UnOp (Unop.Neg, Exp.Var x, _) + -> prune (Exp.Var x) loc mem + | Exp.BinOp (Binop.LAnd, e1, e2) + -> mem |> prune e1 loc |> prune e2 loc + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.LOr, e1, e2), t) + -> mem |> prune (Exp.UnOp (Unop.LNot, e1, t)) loc |> prune (Exp.UnOp (Unop.LNot, e2, t)) loc + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Lt as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Gt as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Le as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ge as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Eq as c), e1, e2), _) + | Exp.UnOp (Unop.LNot, Exp.BinOp ((Binop.Ne as c), e1, e2), _) + -> prune (Exp.BinOp (comp_not c, e1, e2)) loc mem + | _ + -> mem - let get_formals : Procdesc.t -> (Pvar.t * Typ.t) list - = fun pdesc -> + let get_formals : Procdesc.t -> (Pvar.t * Typ.t) list = + fun pdesc -> let proc_name = Procdesc.get_proc_name pdesc in - Procdesc.get_formals pdesc - |> List.map ~f:(fun (name, typ) -> (Pvar.mk name proc_name, typ)) + Procdesc.get_formals pdesc |> List.map ~f:(fun (name, typ) -> (Pvar.mk name proc_name, typ)) let get_matching_pairs - : Tenv.t -> Val.t -> Val.t -> Typ.t -> Mem.astate -> Mem.astate - -> (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list - = fun tenv formal actual typ caller_mem callee_mem -> + : Tenv.t -> Val.t -> Val.t -> Typ.t -> Mem.astate -> Mem.astate + -> (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list = + fun tenv formal actual typ caller_mem callee_mem -> let get_itv v = Val.get_itv v in let get_offset v = v |> Val.get_array_blk |> ArrayBlk.offsetof in let get_size v = v |> Val.get_array_blk |> ArrayBlk.sizeof in @@ -389,16 +436,14 @@ struct let deref_ptr v mem = Mem.find_heap_set (Val.get_array_locs v) mem in let add_pair_itv itv1 itv2 traces l = let open Itv in - if itv1 <> bot && itv1 <> top && itv2 <> bot then - (lb itv1, lb itv2, traces) :: (ub itv1, ub itv2, traces) :: l + if itv1 <> bot && itv1 <> top && itv2 <> bot then (lb itv1, lb itv2, traces) + :: (ub itv1, ub itv2, traces) :: l else if itv1 <> bot && itv1 <> top && Itv.eq itv2 bot then (lb itv1, Bound.Bot, TraceSet.empty) :: (ub itv1, Bound.Bot, TraceSet.empty) :: l - else - l + else l in let add_pair_val v1 v2 pairs = - pairs - |> add_pair_itv (get_itv v1) (get_itv v2) (Val.get_traces v2) + pairs |> add_pair_itv (get_itv v1) (get_itv v2) (Val.get_traces v2) |> add_pair_itv (get_offset v1) (get_offset v2) (Val.get_traces v2) |> add_pair_itv (get_size v1) (get_size v2) (Val.get_traces v2) in @@ -409,56 +454,62 @@ struct in let add_pair_ptr typ v1 v2 pairs = match typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct typename}, _) -> - (match Tenv.lookup tenv typename with - | Some str -> - let fns = List.map ~f:get_field_name str.Typ.Struct.fields in - List.fold ~f:(add_pair_field v1 v2) ~init:pairs fns - | _ -> pairs) - | Typ.Tptr (_ ,_) -> - let v1' = deref_ptr v1 callee_mem in + | Typ.Tptr ({desc= Tstruct typename}, _) -> ( + match Tenv.lookup tenv typename with + | Some str + -> let fns = List.map ~f:get_field_name str.Typ.Struct.fields in + List.fold ~f:(add_pair_field v1 v2) ~init:pairs fns + | _ + -> pairs ) + | Typ.Tptr (_, _) + -> let v1' = deref_ptr v1 callee_mem in let v2' = deref_ptr v2 caller_mem in add_pair_val v1' v2' pairs - | _ -> pairs + | _ + -> pairs in [] |> add_pair_val formal actual |> add_pair_ptr typ formal actual - let subst_map_of_pairs : (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list - -> (Itv.Bound.t Itv.SubstMap.t) * (TraceSet.t Itv.SubstMap.t) - = fun pairs -> + let subst_map_of_pairs + : (Itv.Bound.t * Itv.Bound.t * TraceSet.t) list + -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t = + fun pairs -> let add_pair (bound_map, trace_map) (formal, actual, traces) = match formal with - | Itv.Bound.Linear (_, se1) when Itv.SymLinear.is_zero se1 -> (bound_map, trace_map) - | Itv.Bound.Linear (0, se1) when Itv.SymLinear.cardinal se1 > 0 -> - let (symbol, coeff) = Itv.SymLinear.choose se1 in - if Int.equal coeff 1 - then + | Itv.Bound.Linear (_, se1) when Itv.SymLinear.is_zero se1 + -> (bound_map, trace_map) + | Itv.Bound.Linear (0, se1) when Itv.SymLinear.cardinal se1 > 0 + -> let symbol, coeff = Itv.SymLinear.choose se1 in + if Int.equal coeff 1 then (Itv.SubstMap.add symbol actual bound_map, Itv.SubstMap.add symbol traces trace_map) else assert false - | Itv.Bound.MinMax (Itv.Bound.Max, 0, symbol) -> - (Itv.SubstMap.add symbol actual bound_map, Itv.SubstMap.add symbol traces trace_map) - | _ -> assert false + | Itv.Bound.MinMax (Itv.Bound.Max, 0, symbol) + -> (Itv.SubstMap.add symbol actual bound_map, Itv.SubstMap.add symbol traces trace_map) + | _ + -> assert false in List.fold ~f:add_pair ~init:(Itv.SubstMap.empty, Itv.SubstMap.empty) pairs let rec list_fold2_def - : default:Val.t -> f:('a -> Val.t -> 'b -> 'b) -> 'a list -> Val.t list -> init:'b -> 'b - = fun ~default ~f xs ys ~init:acc -> - match xs, ys with - | [], _ -> acc - | x :: xs', [] -> list_fold2_def ~default ~f xs' ys ~init:(f x default acc) - | [x], _ :: _ -> f x (List.fold ~f:Val.join ~init:Val.bot ys) acc - | x :: xs', y :: ys' -> list_fold2_def ~default ~f xs' ys' ~init:(f x y acc) + : default:Val.t -> f:('a -> Val.t -> 'b -> 'b) -> 'a list -> Val.t list -> init:'b -> 'b = + fun ~default ~f xs ys ~init:acc -> + match (xs, ys) with + | [], _ + -> acc + | x :: xs', [] + -> list_fold2_def ~default ~f xs' ys ~init:(f x default acc) + | [x], _ :: _ + -> f x (List.fold ~f:Val.join ~init:Val.bot ys) acc + | x :: xs', y :: ys' + -> list_fold2_def ~default ~f xs' ys' ~init:(f x y acc) let get_subst_map - : Tenv.t -> Procdesc.t -> (Exp.t * 'a) list -> Mem.astate -> Mem.astate - -> Location.t -> (Itv.Bound.t Itv.SubstMap.t) * (TraceSet.t Itv.SubstMap.t) - = fun tenv callee_pdesc params caller_mem callee_entry_mem loc -> + : Tenv.t -> Procdesc.t -> (Exp.t * 'a) list -> Mem.astate -> Mem.astate -> Location.t + -> Itv.Bound.t Itv.SubstMap.t * TraceSet.t Itv.SubstMap.t = + fun tenv callee_pdesc params caller_mem callee_entry_mem loc -> let add_pair (formal, typ) actual l = let formal = Mem.find_heap (Loc.of_pvar formal) callee_entry_mem in - let new_matching = - get_matching_pairs tenv formal actual typ caller_mem callee_entry_mem - in + let new_matching = get_matching_pairs tenv formal actual typ caller_mem callee_entry_mem in List.rev_append new_matching l in let formals = get_formals callee_pdesc in diff --git a/infer/src/bufferoverrun/bufferOverrunTrace.ml b/infer/src/bufferoverrun/bufferOverrunTrace.ml index 3f6f62dcb..304246602 100644 --- a/infer/src/bufferoverrun/bufferOverrunTrace.ml +++ b/infer/src/bufferoverrun/bufferOverrunTrace.ml @@ -8,78 +8,98 @@ *) open! IStd - module F = Format -module BoTrace = -struct - type elem = ArrAccess of Location.t | ArrDecl of Location.t | Assign of Location.t - | Call of Location.t | Return of Location.t | SymAssign of Location.t - [@@deriving compare] - type t = { length : int; trace : elem list } [@@deriving compare] - let empty = { length = 0; trace = [] } - let singleton elem = { length = 1; trace = [elem] } - let add_elem elem t = { length = t.length + 1; trace = elem :: t.trace } - let add_elem_last elem t = { length = t.length + 1; trace = t.trace @ [elem] } - let append x y = { length = x.length + y.length; trace = x.trace @ y.trace } - - let pp_elem : F.formatter -> elem -> unit - = fun fmt elem -> +module BoTrace = struct + type elem = + | ArrAccess of Location.t + | ArrDecl of Location.t + | Assign of Location.t + | Call of Location.t + | Return of Location.t + | SymAssign of Location.t + [@@deriving compare] + + type t = {length: int; trace: elem list} [@@deriving compare] + + let empty = {length= 0; trace= []} + + let singleton elem = {length= 1; trace= [elem]} + + let add_elem elem t = {length= t.length + 1; trace= elem :: t.trace} + + let add_elem_last elem t = {length= t.length + 1; trace= t.trace @ [elem]} + + let append x y = {length= x.length + y.length; trace= x.trace @ y.trace} + + let pp_elem : F.formatter -> elem -> unit = + fun fmt elem -> match elem with - | Assign loc -> F.fprintf fmt "Assign (%a)" Location.pp_file_pos loc - | ArrDecl loc -> F.fprintf fmt "ArrDecl (%a)" Location.pp_file_pos loc - | Call loc -> F.fprintf fmt "Call (%a)" Location.pp_file_pos loc - | Return loc -> F.fprintf fmt "Return (%a)" Location.pp_file_pos loc - | SymAssign loc -> F.fprintf fmt "SymAssign (%a)" Location.pp_file_pos loc - | ArrAccess loc -> F.fprintf fmt "ArrAccess (%a)" Location.pp_file_pos loc - - let pp : F.formatter -> t -> unit - = fun fmt t -> + | Assign loc + -> F.fprintf fmt "Assign (%a)" Location.pp_file_pos loc + | ArrDecl loc + -> F.fprintf fmt "ArrDecl (%a)" Location.pp_file_pos loc + | Call loc + -> F.fprintf fmt "Call (%a)" Location.pp_file_pos loc + | Return loc + -> F.fprintf fmt "Return (%a)" Location.pp_file_pos loc + | SymAssign loc + -> F.fprintf fmt "SymAssign (%a)" Location.pp_file_pos loc + | ArrAccess loc + -> F.fprintf fmt "ArrAccess (%a)" Location.pp_file_pos loc + + let pp : F.formatter -> t -> unit = + fun fmt t -> let pp_sep fmt () = F.fprintf fmt " :: " in F.pp_print_list ~pp_sep pp_elem fmt t.trace end -module Set = -struct - include AbstractDomain.FiniteSet(BoTrace) +module Set = struct + include AbstractDomain.FiniteSet (BoTrace) + (* currently, we keep only one trace for efficiency *) let join x y = if is_empty x then y else if is_empty y then x else - let (tx, ty) = (min_elt x, min_elt y) in - if Pervasives.(<=) tx.length ty.length then x - else y + let tx, ty = (min_elt x, min_elt y) in + if Pervasives.( <= ) tx.length ty.length then x else y let choose_shortest set = min_elt set let add_elem elem t = - if is_empty t then singleton (BoTrace.singleton elem) - else map (BoTrace.add_elem elem) t + if is_empty t then singleton (BoTrace.singleton elem) else map (BoTrace.add_elem elem) t let add_elem_last elem t = - if is_empty t then singleton (BoTrace.singleton elem) - else map (BoTrace.add_elem_last elem) t + if is_empty t then singleton (BoTrace.singleton elem) else map (BoTrace.add_elem_last elem) t let instantiate ~traces_caller ~traces_callee loc = if is_empty traces_caller then map (fun trace_callee -> BoTrace.add_elem_last (BoTrace.Call loc) trace_callee) traces_callee else - fold (fun trace_callee traces -> - fold (fun trace_caller traces -> + fold + (fun trace_callee traces -> + fold + (fun trace_caller traces -> let new_trace_caller = BoTrace.add_elem (BoTrace.Call loc) trace_caller in let new_trace = BoTrace.append trace_callee new_trace_caller in - add new_trace traces) traces_caller traces) traces_callee empty + add new_trace traces) + traces_caller traces) + traces_callee empty let merge ~traces_arr ~traces_idx loc = if is_empty traces_idx then map (fun trace_arr -> BoTrace.add_elem (BoTrace.ArrAccess loc) trace_arr) traces_arr else - fold (fun trace_idx traces -> - fold (fun trace_arr traces -> + fold + (fun trace_idx traces -> + fold + (fun trace_arr traces -> let new_trace_idx = BoTrace.add_elem (BoTrace.ArrAccess loc) trace_idx in let new_trace = BoTrace.append new_trace_idx trace_arr in - add new_trace traces) traces_arr traces) traces_idx empty + add new_trace traces) + traces_arr traces) + traces_idx empty end include BoTrace diff --git a/infer/src/bufferoverrun/itv.ml b/infer/src/bufferoverrun/itv.ml index de52718c5..798f717da 100644 --- a/infer/src/bufferoverrun/itv.ml +++ b/infer/src/bufferoverrun/itv.ml @@ -16,556 +16,545 @@ module L = Logging let sym_size = ref 0 -module Symbol = -struct +module Symbol = struct type t = Typ.Procname.t * int [@@deriving compare] let eq = [%compare.equal : t] - let get_new : Typ.Procname.t -> t - = fun pname -> + let get_new : Typ.Procname.t -> t = + fun pname -> let i = !sym_size in - sym_size := !sym_size + 1; + sym_size := !sym_size + 1 ; (pname, i) - let make : Typ.Procname.t -> int -> t - = fun pname i -> (pname, i) + let make : Typ.Procname.t -> int -> t = fun pname i -> (pname, i) - let pp : F.formatter -> t -> unit - = fun fmt x -> - if Config.bo_debug <= 1 then - F.fprintf fmt "s$%d" (snd x) - else - F.fprintf fmt "%s-s$%d" (fst x |> Typ.Procname.to_string) (snd x) + let pp : F.formatter -> t -> unit = + fun fmt x -> + if Config.bo_debug <= 1 then F.fprintf fmt "s$%d" (snd x) + else F.fprintf fmt "%s-s$%d" (fst x |> Typ.Procname.to_string) (snd x) end module SubstMap = Caml.Map.Make (Symbol) -module SymLinear = -struct +module SymLinear = struct module M = Caml.Map.Make (Symbol) type t = int M.t [@@deriving compare] - let empty : t - = M.empty + let empty : t = M.empty - let is_empty : t -> bool - = M.is_empty + let is_empty : t -> bool = M.is_empty - let add : Symbol.t -> int -> t -> t - = M.add + let add : Symbol.t -> int -> t -> t = M.add - let cardinal : t -> int - = M.cardinal + let cardinal : t -> int = M.cardinal - let choose : t -> (Symbol.t * int) - = M.choose + let choose : t -> Symbol.t * int = M.choose - let fold : (Symbol.t -> int -> 'b -> 'b) -> t -> 'b -> 'b - = M.fold + let fold : (Symbol.t -> int -> 'b -> 'b) -> t -> 'b -> 'b = M.fold - let mem : Symbol.t -> t -> bool - = M.mem + let mem : Symbol.t -> t -> bool = M.mem - let initial : t - = empty + let initial : t = empty - let find : Symbol.t -> t -> int - = fun s x -> - try M.find s x with - | Not_found -> 0 + let find : Symbol.t -> t -> int = + fun s x -> + try M.find s x + with Not_found -> 0 - let le : t -> t -> bool - = fun x y -> M.for_all (fun s v -> v <= find s y) x + let le : t -> t -> bool = fun x y -> M.for_all (fun s v -> v <= find s y) x - let get_new : Typ.Procname.t -> t - = fun pname -> M.add (Symbol.get_new pname) 1 empty + let get_new : Typ.Procname.t -> t = fun pname -> M.add (Symbol.get_new pname) 1 empty - let make : Typ.Procname.t -> int -> t - = fun pname i -> M.add (Symbol.make pname i) 1 empty + let make : Typ.Procname.t -> int -> t = fun pname i -> M.add (Symbol.make pname i) 1 empty - let eq : t -> t -> bool - = fun x y -> le x y && le y x + let eq : t -> t -> bool = fun x y -> le x y && le y x - let pp1 : F.formatter -> (Symbol.t * int) -> unit - = fun fmt (s, c) -> + let pp1 : F.formatter -> Symbol.t * int -> unit = + fun fmt (s, c) -> if Int.equal c 0 then () - else if Int.equal c 1 then - F.fprintf fmt "%a" Symbol.pp s - else if c < 0 then - F.fprintf fmt "(%d)x%a" c Symbol.pp s - else - F.fprintf fmt "%dx%a" c Symbol.pp s + else if Int.equal c 1 then F.fprintf fmt "%a" Symbol.pp s + else if c < 0 then F.fprintf fmt "(%d)x%a" c Symbol.pp s + else F.fprintf fmt "%dx%a" c Symbol.pp s - let pp : F.formatter -> t -> unit - = fun fmt x -> - if M.is_empty x then F.fprintf fmt "empty" else - let (s1, c1) = M.min_binding x in - pp1 fmt (s1, c1); + let pp : F.formatter -> t -> unit = + fun fmt x -> + if M.is_empty x then F.fprintf fmt "empty" + else + let s1, c1 = M.min_binding x in + pp1 fmt (s1, c1) ; M.iter (fun s c -> F.fprintf fmt " + %a" pp1 (s, c)) (M.remove s1 x) - let zero : t - = M.empty + let zero : t = M.empty - let is_zero : t -> bool - = fun x -> M.for_all (fun _ v -> Int.equal v 0) x + let is_zero : t -> bool = fun x -> M.for_all (fun _ v -> Int.equal v 0) x - let is_mod_zero : t -> int -> bool - = fun x n -> - assert (n <> 0); + let is_mod_zero : t -> int -> bool = + fun x n -> + assert (n <> 0) ; M.for_all (fun _ v -> Int.equal (v mod n) 0) x - let neg : t -> t - = fun x -> M.map (~-) x + let neg : t -> t = fun x -> M.map ( ~- )x (* Returns (Some n) only when n is not 0. *) - let is_non_zero : int -> int option - = fun n -> if Int.equal n 0 then None else Some n + let is_non_zero : int -> int option = fun n -> if Int.equal n 0 then None else Some n - let plus : t -> t -> t - = fun x y -> + let plus : t -> t -> t = + fun x y -> let plus' _ n_opt m_opt = - match n_opt, m_opt with - | None, None -> None - | Some v, None - | None, Some v -> is_non_zero v - | Some n, Some m -> is_non_zero (n + m) + match (n_opt, m_opt) with + | None, None + -> None + | Some v, None | None, Some v + -> is_non_zero v + | Some n, Some m + -> is_non_zero (n + m) in M.merge plus' x y - let minus : t -> t -> t - = fun x y -> + let minus : t -> t -> t = + fun x y -> let minus' _ n_opt m_opt = - match n_opt, m_opt with - | None, None -> None - | Some v, None -> is_non_zero v - | None, Some v -> is_non_zero (-v) - | Some n, Some m -> is_non_zero (n - m) + match (n_opt, m_opt) with + | None, None + -> None + | Some v, None + -> is_non_zero v + | None, Some v + -> is_non_zero (-v) + | Some n, Some m + -> is_non_zero (n - m) in M.merge minus' x y - let mult_const : t -> int -> t - = fun x n -> M.map (( * ) n) x + let mult_const : t -> int -> t = fun x n -> M.map (( * ) n) x - let div_const : t -> int -> t - = fun x n -> M.map ((/) n) x + let div_const : t -> int -> t = fun x n -> M.map (( / ) n) x (* Returns a symbol when the map contains only one symbol s with the coefficient 1. *) - let one_symbol : t -> Symbol.t option - = fun x -> + let one_symbol : t -> Symbol.t option = + fun x -> let x = M.filter (fun _ v -> v <> 0) x in if Int.equal (M.cardinal x) 1 then - let (k, v) = M.choose x in + let k, v = M.choose x in if Int.equal v 1 then Some k else None else None - let is_one_symbol : t -> bool - = fun x -> - match one_symbol x with - | Some _ -> true - | None -> false + let is_one_symbol : t -> bool = fun x -> match one_symbol x with Some _ -> true | None -> false - let get_symbols : t -> Symbol.t list - = fun x -> List.map ~f:fst (M.bindings x) + let get_symbols : t -> Symbol.t list = fun x -> List.map ~f:fst (M.bindings x) end -module Bound = -struct +module Bound = struct type t = | MInf | Linear of int * SymLinear.t | MinMax of min_max_t * int * Symbol.t | PInf | Bot - [@@deriving compare] -and min_max_t = Min | Max - -let equal = [%compare.equal : t] - -let pp_min_max : F.formatter -> min_max_t -> unit - = fun fmt -> function - | Min -> F.fprintf fmt "min" - | Max -> F.fprintf fmt "max" - -let pp : F.formatter -> t -> unit - = fun fmt -> function - | MInf -> F.fprintf fmt "-oo" - | PInf -> F.fprintf fmt "+oo" - | Bot -> F.fprintf fmt "_|_" - | Linear (c, x) -> - if SymLinear.le x SymLinear.empty then - F.fprintf fmt "%d" c - else if Int.equal c 0 then - F.fprintf fmt "%a" SymLinear.pp x - else - F.fprintf fmt "%a + %d" SymLinear.pp x c - | MinMax (m, c, x) -> F.fprintf fmt "%a(%d, %a)" pp_min_max m c Symbol.pp x - -let of_int : int -> t - = fun n -> Linear (n, SymLinear.empty) - -let zero = of_int 0 - -let one = of_int 1 - -let minus_one = of_int ~-1 - -let _255 = of_int 255 - -let of_sym : SymLinear.t -> t - = fun s -> Linear (0, s) - -let is_symbolic : t -> bool - = function - | MInf | PInf | Bot -> false - | Linear (_, se) -> not (SymLinear.is_empty se) - | MinMax _ -> true - -let opt_lift : ('a -> 'b -> bool) -> 'a option -> 'b option -> bool - = fun f a_opt b_opt -> - match a_opt, b_opt with - | None, _ - | _, None -> false - | Some a, Some b -> f a b - -let eq_symbol : Symbol.t -> t -> bool - = fun s -> function - | Linear (c, se) -> - Int.equal c 0 && opt_lift Symbol.eq (SymLinear.one_symbol se) (Some s) - | _ -> false - -let one_symbol : t -> Symbol.t option - = function - | Linear (c, se) when Int.equal c 0 -> SymLinear.one_symbol se - | _ -> None - -let is_one_symbol : t -> bool - = fun x -> one_symbol x <> None - -let use_symbol : Symbol.t -> t -> bool - = fun s -> function - | PInf | MInf | Bot -> false - | Linear (_, se) -> SymLinear.find s se <> 0 - | MinMax (_, _, s') -> Symbol.eq s s' - -let subst1 : t -> t -> Symbol.t -> t -> t - = fun default x s y -> - if not (use_symbol s x) then x else - match x, y with - | _, _ when eq_symbol s x -> y - | Linear (c1, se1), Linear (c2, se2) -> - let coeff = SymLinear.find s se1 in - let c' = c1 + coeff * c2 in - let se1 = SymLinear.add s 0 se1 in - let se' = SymLinear.plus se1 (SymLinear.mult_const se2 coeff) in - Linear (c', se') - | MinMax (Min, _, s'), MInf when Symbol.eq s s' -> MInf - | MinMax (Max, c, s'), MInf when Symbol.eq s s' -> - Linear (c, SymLinear.zero) - | MinMax (Max, _, s'), PInf when Symbol.eq s s' -> PInf - | MinMax (Min, c, s'), PInf when Symbol.eq s s' -> - Linear (c, SymLinear.zero) - | MinMax (Min, c1, s'), Linear (c2, se) - when Symbol.eq s s' && SymLinear.is_zero se -> - Linear (min c1 c2, SymLinear.zero) - | MinMax (Max, c1, s'), Linear (c2, se) - when Symbol.eq s s' && SymLinear.is_zero se -> - Linear (max c1 c2, SymLinear.zero) - | MinMax (m, c, s'), _ when Symbol.eq s s' && is_one_symbol y -> - (match one_symbol y with - | Some s'' -> MinMax (m, c, s'') - | _ -> assert false) - | MinMax (Min, c1, s'), MinMax (Min, c2, s'') when Symbol.eq s s' -> - MinMax (Min, min c1 c2, s'') - | MinMax (Max, c1, s'), MinMax (Max, c2, s'') when Symbol.eq s s' -> - MinMax (Max, max c1 c2, s'') - | _ -> default - -(* substitution symbols in ``x'' with respect to ``map'' *) -let subst : t -> t -> t SubstMap.t -> t - = fun default x map -> SubstMap.fold (fun s y x -> subst1 default x s y) map x - -let le : t -> t -> bool - = fun x y -> - assert (x <> Bot && y <> Bot); - match x, y with - | MInf, _ - | _, PInf -> true - | _, MInf - | PInf, _ -> false - | Linear (c0, x0), Linear (c1, x1) -> c0 <= c1 && SymLinear.eq x0 x1 - | MinMax (Min, c0, x0), MinMax (Min, c1, x1) - | MinMax (Max, c0, x0), MinMax (Max, c1, x1) -> c0 <= c1 && Symbol.eq x0 x1 - | MinMax (Min, c0, x0), Linear (c1, x1) -> - (c0 <= c1 && SymLinear.is_zero x1) - || (Int.equal c1 0 && opt_lift Symbol.eq (SymLinear.one_symbol x1) (Some x0)) - | Linear (c1, x1), MinMax (Max, c0, x0) -> - (c1 <= c0 && SymLinear.is_zero x1) - || (Int.equal c1 0 && opt_lift Symbol.eq (SymLinear.one_symbol x1) (Some x0)) - | MinMax (Min, c0, x0), MinMax (Max, c1, x1) -> c0 <= c1 || Symbol.eq x0 x1 - | _, _ -> false - -let lt : t -> t -> bool - = fun x y -> - assert (x <> Bot && y <> Bot); - match x, y with - | MInf, Linear _ - | MInf, MinMax _ - | MInf, PInf - | Linear _, PInf - | MinMax _, PInf -> true - | Linear (c0, x0), Linear (c1, x1) -> c0 < c1 && SymLinear.eq x0 x1 - | MinMax (Min, c0, _), Linear (c1, x1) -> c0 < c1 && SymLinear.is_zero x1 - | Linear (c1, x1), MinMax (Max, c0, _) -> c1 < c0 && SymLinear.is_zero x1 - | MinMax (Min, c0, _), MinMax (Max, c1, _) -> c0 < c1 - | _, _ -> false - -let gt : t -> t -> bool - = fun x y -> lt y x - -let eq : t -> t -> bool - = fun x y -> - if equal x Bot && equal y Bot then true else - if equal x Bot || equal y Bot then false else - le x y && le y x - -let min : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - if le x y then x else - if le y x then y else - match x, y with - | Linear (c0, x0), Linear (c1, x1) - when SymLinear.is_zero x0 && Int.equal c1 0 && SymLinear.is_one_symbol x1 -> - (match SymLinear.one_symbol x1 with - | Some x' -> MinMax (Min, c0, x') - | None -> assert false) - | Linear (c0, x0), Linear (c1, x1) - when SymLinear.is_zero x1 && Int.equal c0 0 && SymLinear.is_one_symbol x0 -> - (match SymLinear.one_symbol x0 with - | Some x' -> MinMax (Min, c1, x') - | None -> assert false) - | MinMax (Max, c0, _), Linear (c1, x1) - | Linear (c1, x1), MinMax (Max, c0, _) - when SymLinear.is_zero x1 && c0 < c1 -> Linear (c0, x1) - | _, _ -> MInf - -let max : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - if le x y then y else - if le y x then x else - match x, y with + [@@deriving compare] + + and min_max_t = Min | Max + + let equal = [%compare.equal : t] + + let pp_min_max : F.formatter -> min_max_t -> unit = + fun fmt -> function Min -> F.fprintf fmt "min" | Max -> F.fprintf fmt "max" + + let pp : F.formatter -> t -> unit = + fun fmt -> + function + | MInf + -> F.fprintf fmt "-oo" + | PInf + -> F.fprintf fmt "+oo" + | Bot + -> F.fprintf fmt "_|_" + | Linear (c, x) + -> if SymLinear.le x SymLinear.empty then F.fprintf fmt "%d" c + else if Int.equal c 0 then F.fprintf fmt "%a" SymLinear.pp x + else F.fprintf fmt "%a + %d" SymLinear.pp x c + | MinMax (m, c, x) + -> F.fprintf fmt "%a(%d, %a)" pp_min_max m c Symbol.pp x + + let of_int : int -> t = fun n -> Linear (n, SymLinear.empty) + + let zero = of_int 0 + + let one = of_int 1 + + let minus_one = of_int (-1) + + let _255 = of_int 255 + + let of_sym : SymLinear.t -> t = fun s -> Linear (0, s) + + let is_symbolic : t -> bool = function + | MInf | PInf | Bot + -> false + | Linear (_, se) + -> not (SymLinear.is_empty se) + | MinMax _ + -> true + + let opt_lift : ('a -> 'b -> bool) -> 'a option -> 'b option -> bool = + fun f a_opt b_opt -> + match (a_opt, b_opt) with None, _ | _, None -> false | Some a, Some b -> f a b + + let eq_symbol : Symbol.t -> t -> bool = + fun s -> + function + | Linear (c, se) + -> Int.equal c 0 && opt_lift Symbol.eq (SymLinear.one_symbol se) (Some s) + | _ + -> false + + let one_symbol : t -> Symbol.t option = function + | Linear (c, se) when Int.equal c 0 + -> SymLinear.one_symbol se + | _ + -> None + + let is_one_symbol : t -> bool = fun x -> one_symbol x <> None + + let use_symbol : Symbol.t -> t -> bool = + fun s -> + function + | PInf | MInf | Bot + -> false + | Linear (_, se) + -> SymLinear.find s se <> 0 + | MinMax (_, _, s') + -> Symbol.eq s s' + + let subst1 : t -> t -> Symbol.t -> t -> t = + fun default x s y -> + if not (use_symbol s x) then x + else + match (x, y) with + | _, _ when eq_symbol s x + -> y + | Linear (c1, se1), Linear (c2, se2) + -> let coeff = SymLinear.find s se1 in + let c' = c1 + coeff * c2 in + let se1 = SymLinear.add s 0 se1 in + let se' = SymLinear.plus se1 (SymLinear.mult_const se2 coeff) in + Linear (c', se') + | MinMax (Min, _, s'), MInf when Symbol.eq s s' + -> MInf + | MinMax (Max, c, s'), MInf when Symbol.eq s s' + -> Linear (c, SymLinear.zero) + | MinMax (Max, _, s'), PInf when Symbol.eq s s' + -> PInf + | MinMax (Min, c, s'), PInf when Symbol.eq s s' + -> Linear (c, SymLinear.zero) + | MinMax (Min, c1, s'), Linear (c2, se) when Symbol.eq s s' && SymLinear.is_zero se + -> Linear (min c1 c2, SymLinear.zero) + | MinMax (Max, c1, s'), Linear (c2, se) when Symbol.eq s s' && SymLinear.is_zero se + -> Linear (max c1 c2, SymLinear.zero) + | MinMax (m, c, s'), _ when Symbol.eq s s' && is_one_symbol y -> ( + match one_symbol y with Some s'' -> MinMax (m, c, s'') | _ -> assert false ) + | MinMax (Min, c1, s'), MinMax (Min, c2, s'') when Symbol.eq s s' + -> MinMax (Min, min c1 c2, s'') + | MinMax (Max, c1, s'), MinMax (Max, c2, s'') when Symbol.eq s s' + -> MinMax (Max, max c1 c2, s'') + | _ + -> default + + (* substitution symbols in ``x'' with respect to ``map'' *) + let subst : t -> t -> t SubstMap.t -> t = + fun default x map -> SubstMap.fold (fun s y x -> subst1 default x s y) map x + + let le : t -> t -> bool = + fun x y -> + assert (x <> Bot && y <> Bot) ; + match (x, y) with + | MInf, _ | _, PInf + -> true + | _, MInf | PInf, _ + -> false | Linear (c0, x0), Linear (c1, x1) - when SymLinear.is_zero x0 && Int.equal c1 0 && SymLinear.is_one_symbol x1 -> - (match SymLinear.one_symbol x1 with - | Some x' -> MinMax (Max, c0, x') - | None -> assert false) + -> c0 <= c1 && SymLinear.eq x0 x1 + | MinMax (Min, c0, x0), MinMax (Min, c1, x1) | MinMax (Max, c0, x0), MinMax (Max, c1, x1) + -> c0 <= c1 && Symbol.eq x0 x1 + | MinMax (Min, c0, x0), Linear (c1, x1) + -> c0 <= c1 && SymLinear.is_zero x1 + || Int.equal c1 0 && opt_lift Symbol.eq (SymLinear.one_symbol x1) (Some x0) + | Linear (c1, x1), MinMax (Max, c0, x0) + -> c1 <= c0 && SymLinear.is_zero x1 + || Int.equal c1 0 && opt_lift Symbol.eq (SymLinear.one_symbol x1) (Some x0) + | MinMax (Min, c0, x0), MinMax (Max, c1, x1) + -> c0 <= c1 || Symbol.eq x0 x1 + | _, _ + -> false + + let lt : t -> t -> bool = + fun x y -> + assert (x <> Bot && y <> Bot) ; + match (x, y) with + | MInf, Linear _ | MInf, MinMax _ | MInf, PInf | Linear _, PInf | MinMax _, PInf + -> true | Linear (c0, x0), Linear (c1, x1) - when SymLinear.is_zero x1 && Int.equal c0 0 && SymLinear.is_one_symbol x0 -> - (match SymLinear.one_symbol x0 with - | Some x' -> MinMax (Max, c1, x') - | None -> assert false) + -> c0 < c1 && SymLinear.eq x0 x1 | MinMax (Min, c0, _), Linear (c1, x1) - | Linear (c1, x1), MinMax (Min, c0, _) - when SymLinear.is_zero x1 && c0 > c1 -> Linear (c0, x1) - | _, _ -> PInf - -let widen_l : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - if equal x PInf || equal y PInf then failwith "Lower bound cannot be +oo." else - if le x y then x else - MInf - -let widen_u : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - if equal x MInf || equal y MInf then failwith "Upper bound cannot be -oo." else - if le y x then x else - PInf - -let initial : t - = of_int 0 - -let zero : t - = Linear (0, SymLinear.zero) - -let one : t - = Linear (1, SymLinear.zero) - -let mone : t - = Linear (-1, SymLinear.zero) - -let is_zero : t -> bool - = fun x -> - assert (x <> Bot); - match x with - | Linear (c, y) -> Int.equal c 0 && SymLinear.is_zero y - | _ -> false - -let is_const : t -> int option - = fun x -> - assert (x <> Bot); - match x with - | Linear (c, y) when SymLinear.is_zero y -> Some c - | _ -> None - -let plus_l : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - match x, y with - | _, _ when is_zero x -> y - | _, _ when is_zero y -> x - | Linear (c1, x1), Linear (c2, x2) -> Linear (c1 + c2, SymLinear.plus x1 x2) - | MinMax (Max, c1, _), Linear (c2, x2) - | Linear (c2, x2), MinMax (Max, c1, _) -> Linear (c1 + c2, x2) - | _, _ -> MInf - -let plus_u : t -> t -> t - = fun x y -> - assert (x <> Bot && y <> Bot); - match x, y with - | _, _ when is_zero x -> y - | _, _ when is_zero y -> x - | Linear (c1, x1), Linear (c2, x2) -> Linear (c1 + c2, SymLinear.plus x1 x2) - | MinMax (Min, c1, _), Linear (c2, x2) - | Linear (c2, x2), MinMax (Min, c1, _) -> Linear (c1 + c2, x2) - | _, _ -> PInf - -let mult_const : t -> int -> t option - = fun x n -> - assert (x <> Bot); - assert (n <> 0); - match x with - | MInf -> Some (if n > 0 then MInf else PInf) - | PInf -> Some (if n > 0 then PInf else MInf) - | Linear (c, x') -> Some (Linear (c * n, SymLinear.mult_const x' n)) - | _ -> None - -let div_const : t -> int -> t option - = fun x n -> - assert (x <> Bot); - if Int.equal n 0 then Some zero else + -> c0 < c1 && SymLinear.is_zero x1 + | Linear (c1, x1), MinMax (Max, c0, _) + -> c1 < c0 && SymLinear.is_zero x1 + | MinMax (Min, c0, _), MinMax (Max, c1, _) + -> c0 < c1 + | _, _ + -> false + + let gt : t -> t -> bool = fun x y -> lt y x + + let eq : t -> t -> bool = + fun x y -> + if equal x Bot && equal y Bot then true + else if equal x Bot || equal y Bot then false + else le x y && le y x + + let min : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + if le x y then x + else if le y x then y + else + match (x, y) with + | Linear (c0, x0), Linear (c1, x1) + when SymLinear.is_zero x0 && Int.equal c1 0 && SymLinear.is_one_symbol x1 -> ( + match SymLinear.one_symbol x1 with + | Some x' + -> MinMax (Min, c0, x') + | None + -> assert false ) + | Linear (c0, x0), Linear (c1, x1) + when SymLinear.is_zero x1 && Int.equal c0 0 && SymLinear.is_one_symbol x0 -> ( + match SymLinear.one_symbol x0 with + | Some x' + -> MinMax (Min, c1, x') + | None + -> assert false ) + | MinMax (Max, c0, _), Linear (c1, x1) + | Linear (c1, x1), MinMax (Max, c0, _) + when SymLinear.is_zero x1 && c0 < c1 + -> Linear (c0, x1) + | _, _ + -> MInf + + let max : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + if le x y then y + else if le y x then x + else + match (x, y) with + | Linear (c0, x0), Linear (c1, x1) + when SymLinear.is_zero x0 && Int.equal c1 0 && SymLinear.is_one_symbol x1 -> ( + match SymLinear.one_symbol x1 with + | Some x' + -> MinMax (Max, c0, x') + | None + -> assert false ) + | Linear (c0, x0), Linear (c1, x1) + when SymLinear.is_zero x1 && Int.equal c0 0 && SymLinear.is_one_symbol x0 -> ( + match SymLinear.one_symbol x0 with + | Some x' + -> MinMax (Max, c1, x') + | None + -> assert false ) + | MinMax (Min, c0, _), Linear (c1, x1) + | Linear (c1, x1), MinMax (Min, c0, _) + when SymLinear.is_zero x1 && c0 > c1 + -> Linear (c0, x1) + | _, _ + -> PInf + + let widen_l : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + if equal x PInf || equal y PInf then failwith "Lower bound cannot be +oo." + else if le x y then x + else MInf + + let widen_u : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + if equal x MInf || equal y MInf then failwith "Upper bound cannot be -oo." + else if le y x then x + else PInf + + let initial : t = of_int 0 + + let zero : t = Linear (0, SymLinear.zero) + + let one : t = Linear (1, SymLinear.zero) + + let mone : t = Linear (-1, SymLinear.zero) + + let is_zero : t -> bool = + fun x -> + assert (x <> Bot) ; + match x with Linear (c, y) -> Int.equal c 0 && SymLinear.is_zero y | _ -> false + + let is_const : t -> int option = + fun x -> + assert (x <> Bot) ; + match x with Linear (c, y) when SymLinear.is_zero y -> Some c | _ -> None + + let plus_l : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + match (x, y) with + | _, _ when is_zero x + -> y + | _, _ when is_zero y + -> x + | Linear (c1, x1), Linear (c2, x2) + -> Linear (c1 + c2, SymLinear.plus x1 x2) + | MinMax (Max, c1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (Max, c1, _) + -> Linear (c1 + c2, x2) + | _, _ + -> MInf + + let plus_u : t -> t -> t = + fun x y -> + assert (x <> Bot && y <> Bot) ; + match (x, y) with + | _, _ when is_zero x + -> y + | _, _ when is_zero y + -> x + | Linear (c1, x1), Linear (c2, x2) + -> Linear (c1 + c2, SymLinear.plus x1 x2) + | MinMax (Min, c1, _), Linear (c2, x2) | Linear (c2, x2), MinMax (Min, c1, _) + -> Linear (c1 + c2, x2) + | _, _ + -> PInf + + let mult_const : t -> int -> t option = + fun x n -> + assert (x <> Bot) ; + assert (n <> 0) ; match x with - | MInf -> Some (if n > 0 then MInf else PInf) - | PInf -> Some (if n > 0 then PInf else MInf) - | Linear (c, x') -> - if Int.equal (c mod n) 0 && SymLinear.is_mod_zero x' n then - Some (Linear (c / n, SymLinear.div_const x' n)) - else None - | _ -> None - -let neg : t -> t option - = function - | MInf -> Some PInf - | PInf -> Some MInf - | Linear (c, x) -> Some (Linear (-c, SymLinear.neg x)) - | MinMax _ -> None - | Bot -> assert false - -let make_min_max : min_max_t -> t -> t -> t option - = fun m x y -> - assert (x <> Bot && y <> Bot); - match x, y with - | Linear (cx, x'), Linear (cy, y') - when Int.equal cy 0 && SymLinear.is_zero x' && SymLinear.is_one_symbol y' -> - (match SymLinear.one_symbol y' with - | Some s -> Some (MinMax (m, cx, s)) - | None -> None) - | Linear (cx, x'), Linear (cy, y') - when Int.equal cx 0 && SymLinear.is_zero y' && SymLinear.is_one_symbol x' -> - (match SymLinear.one_symbol x' with - | Some s -> Some (MinMax (m, cy, s)) - | None -> None) - | _, _ -> None - -let make_min : t -> t -> t option - = make_min_max Min - -let make_max : t -> t -> t option - = make_min_max Max - -let get_symbols : t -> Symbol.t list - = function - | MInf | PInf -> [] - | Linear (_, se) -> SymLinear.get_symbols se - | MinMax (_, _, s) -> [s] - | Bot -> assert false - -let is_not_infty : t -> bool - = function - | MInf | PInf -> false - | _ -> true + | MInf + -> Some (if n > 0 then MInf else PInf) + | PInf + -> Some (if n > 0 then PInf else MInf) + | Linear (c, x') + -> Some (Linear (c * n, SymLinear.mult_const x' n)) + | _ + -> None + + let div_const : t -> int -> t option = + fun x n -> + assert (x <> Bot) ; + if Int.equal n 0 then Some zero + else + match x with + | MInf + -> Some (if n > 0 then MInf else PInf) + | PInf + -> Some (if n > 0 then PInf else MInf) + | Linear (c, x') + -> if Int.equal (c mod n) 0 && SymLinear.is_mod_zero x' n then + Some (Linear (c / n, SymLinear.div_const x' n)) + else None + | _ + -> None + + let neg : t -> t option = function + | MInf + -> Some PInf + | PInf + -> Some MInf + | Linear (c, x) + -> Some (Linear (-c, SymLinear.neg x)) + | MinMax _ + -> None + | Bot + -> assert false + + let make_min_max : min_max_t -> t -> t -> t option = + fun m x y -> + assert (x <> Bot && y <> Bot) ; + match (x, y) with + | Linear (cx, x'), Linear (cy, y') + when Int.equal cy 0 && SymLinear.is_zero x' && SymLinear.is_one_symbol y' -> ( + match SymLinear.one_symbol y' with Some s -> Some (MinMax (m, cx, s)) | None -> None ) + | Linear (cx, x'), Linear (cy, y') + when Int.equal cx 0 && SymLinear.is_zero y' && SymLinear.is_one_symbol x' -> ( + match SymLinear.one_symbol x' with Some s -> Some (MinMax (m, cy, s)) | None -> None ) + | _, _ + -> None + + let make_min : t -> t -> t option = make_min_max Min + + let make_max : t -> t -> t option = make_min_max Max + + let get_symbols : t -> Symbol.t list = function + | MInf | PInf + -> [] + | Linear (_, se) + -> SymLinear.get_symbols se + | MinMax (_, _, s) + -> [s] + | Bot + -> assert false + + let is_not_infty : t -> bool = function MInf | PInf -> false | _ -> true end -module ItvPure = -struct - type astate = Bound.t * Bound.t - [@@deriving compare] +module ItvPure = struct + type astate = Bound.t * Bound.t [@@deriving compare] type t = astate - let initial : t - = (Bound.initial, Bound.initial) + let initial : t = (Bound.initial, Bound.initial) - let lb : t -> Bound.t - = fst + let lb : t -> Bound.t = fst - let ub : t -> Bound.t - = snd + let ub : t -> Bound.t = snd - let is_finite : t -> bool - = fun (l, u) -> - match Bound.is_const l, Bound.is_const u with - Some _, Some _ -> true - | _, _ -> false + let is_finite : t -> bool = + fun (l, u) -> + match (Bound.is_const l, Bound.is_const u) with Some _, Some _ -> true | _, _ -> false - let make : Bound.t -> Bound.t -> t - = fun l u -> (l, u) + let make : Bound.t -> Bound.t -> t = fun l u -> (l, u) - let subst : t -> Bound.t SubstMap.t -> t - = fun x map -> - (Bound.subst Bound.MInf (lb x) map, Bound.subst Bound.PInf (ub x) map) + let subst : t -> Bound.t SubstMap.t -> t = + fun x map -> (Bound.subst Bound.MInf (lb x) map, Bound.subst Bound.PInf (ub x) map) - let (<=) : lhs:t -> rhs:t -> bool - = fun ~lhs:(l1, u1) ~rhs:(l2, u2) -> Bound.le l2 l1 && Bound.le u1 u2 + let ( <= ) : lhs:t -> rhs:t -> bool = + fun ~lhs:(l1, u1) ~rhs:(l2, u2) -> Bound.le l2 l1 && Bound.le u1 u2 - let join : t -> t -> t - = fun (l1, u1) (l2, u2) -> (Bound.min l1 l2, Bound.max u1 u2) + let join : t -> t -> t = fun (l1, u1) (l2, u2) -> (Bound.min l1 l2, Bound.max u1 u2) - let widen : prev:t -> next:t -> num_iters:int -> t - = fun ~prev:(l1, u1) ~next:(l2, u2) ~num_iters:_ -> - (Bound.widen_l l1 l2, Bound.widen_u u1 u2) + let widen : prev:t -> next:t -> num_iters:int -> t = + fun ~prev:(l1, u1) ~next:(l2, u2) ~num_iters:_ -> (Bound.widen_l l1 l2, Bound.widen_u u1 u2) - let pp : F.formatter -> t -> unit - = fun fmt (l, u) -> F.fprintf fmt "[%a, %a]" Bound.pp l Bound.pp u + let pp : F.formatter -> t -> unit = + fun fmt (l, u) -> F.fprintf fmt "[%a, %a]" Bound.pp l Bound.pp u let of_bound bound = (bound, bound) let of_int n = of_bound (Bound.of_int n) - let of_int_lit : IntLit.t -> t option - = fun s -> - match IntLit.to_int s with - | size -> Some (of_int size) - | exception _ -> None + let of_int_lit : IntLit.t -> t option = + fun s -> match IntLit.to_int s with size -> Some (of_int size) | exception _ -> None - let get_new_sym : Typ.Procname.t -> t - = fun pname -> + let get_new_sym : Typ.Procname.t -> t = + fun pname -> let lower = Bound.of_sym (SymLinear.get_new pname) in let upper = Bound.of_sym (SymLinear.get_new pname) in (lower, upper) - let make_sym : unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t - = fun ~unsigned pname new_sym_num -> + let make_sym : unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t = + fun ~unsigned pname new_sym_num -> let lower = - if unsigned then - Bound.MinMax (Bound.Max, 0, Symbol.make pname (new_sym_num ())) - else - Bound.of_sym (SymLinear.make pname (new_sym_num ())) + if unsigned then Bound.MinMax (Bound.Max, 0, Symbol.make pname (new_sym_num ())) + else Bound.of_sym (SymLinear.make pname (new_sym_num ())) in let upper = Bound.of_sym (SymLinear.make pname (new_sym_num ())) in (lower, upper) @@ -588,51 +577,44 @@ struct let unknown_bool = join false_sem true_sem - let is_true : t -> bool - = fun (l, u) -> Bound.le (Bound.of_int 1) l || Bound.le u (Bound.of_int (-1)) + let is_true : t -> bool = + fun (l, u) -> Bound.le (Bound.of_int 1) l || Bound.le u (Bound.of_int (-1)) - let is_false : t -> bool - = fun (l, u) -> Bound.is_zero l && Bound.is_zero u + let is_false : t -> bool = fun (l, u) -> Bound.is_zero l && Bound.is_zero u - let is_const : t -> int option - = fun (l, u) -> - match Bound.is_const l, Bound.is_const u with - | Some n, Some m when Int.equal n m -> Some n - | _, _ -> None + let is_const : t -> int option = + fun (l, u) -> + match (Bound.is_const l, Bound.is_const u) with + | Some n, Some m when Int.equal n m + -> Some n + | _, _ + -> None - let is_symbolic : t -> bool - = fun (lb, ub) -> Bound.is_symbolic lb || Bound.is_symbolic ub + let is_symbolic : t -> bool = fun (lb, ub) -> Bound.is_symbolic lb || Bound.is_symbolic ub - let is_ge_zero : t -> bool - = fun (lb, _) -> - if lb <> Bound.Bot then Bound.le Bound.zero lb else false + let is_ge_zero : t -> bool = + fun (lb, _) -> if lb <> Bound.Bot then Bound.le Bound.zero lb else false - let is_le_zero : t -> bool - = fun (_, ub) -> - if ub <> Bound.Bot then Bound.le ub Bound.zero else false + let is_le_zero : t -> bool = + fun (_, ub) -> if ub <> Bound.Bot then Bound.le ub Bound.zero else false - let neg : t -> t - = fun (l, u) -> + let neg : t -> t = + fun (l, u) -> let l' = Option.value ~default:Bound.MInf (Bound.neg u) in let u' = Option.value ~default:Bound.PInf (Bound.neg l) in (l', u') - let lnot : t -> t - = fun x -> - if is_true x then false_sem else - if is_false x then true_sem else - unknown_bool + let lnot : t -> t = + fun x -> if is_true x then false_sem else if is_false x then true_sem else unknown_bool - let plus : t -> t -> t - = fun (l1, u1) (l2, u2) -> (Bound.plus_l l1 l2, Bound.plus_u u1 u2) + let plus : t -> t -> t = fun (l1, u1) (l2, u2) -> (Bound.plus_l l1 l2, Bound.plus_u u1 u2) - let minus : t -> t -> t - = fun i1 i2 -> plus i1 (neg i2) + let minus : t -> t -> t = fun i1 i2 -> plus i1 (neg i2) - let mult_const : t -> int -> t - = fun (l, u) n -> - if Int.equal n 0 then zero else - if n > 0 then + let mult_const : t -> int -> t = + fun (l, u) n -> + if Int.equal n 0 then zero + else if n > 0 then let l' = Option.value ~default:Bound.MInf (Bound.mult_const l n) in let u' = Option.value ~default:Bound.PInf (Bound.mult_const u n) in (l', u') @@ -643,9 +625,9 @@ struct (* Returns a correct value only when all coefficients are divided by n without remainder. *) - let div_const : t -> int -> t - = fun (l, u) n -> - assert (n <> 0); + let div_const : t -> int -> t = + fun (l, u) n -> + assert (n <> 0) ; if n > 0 then let l' = Option.value ~default:Bound.MInf (Bound.div_const l n) in let u' = Option.value ~default:Bound.PInf (Bound.div_const u n) in @@ -655,247 +637,243 @@ struct let u' = Option.value ~default:Bound.PInf (Bound.div_const l n) in (l', u') - let mult : t -> t -> t - = fun x y -> - match is_const x, is_const y with - | _, Some n -> mult_const x n - | Some n, _ -> mult_const y n - | None, None -> top + let mult : t -> t -> t = + fun x y -> + match (is_const x, is_const y) with + | _, Some n + -> mult_const x n + | Some n, _ + -> mult_const y n + | None, None + -> top - let div : t -> t -> t - = fun x y -> - match is_const y with - | Some n when n <> 0 -> div_const x n - | _ -> top + let div : t -> t -> t = + fun x y -> match is_const y with Some n when n <> 0 -> div_const x n | _ -> top (* x % [0,0] does nothing. *) - let mod_sem : t -> t -> t - = fun x y -> - match is_const x, is_const y with - | _, Some 0 -> x - | Some n, Some m -> of_int (n mod m) - | _, Some m -> - let abs_m = abs m in - if is_ge_zero x then (Bound.zero, Bound.of_int (abs_m - 1)) else - if is_le_zero x then (Bound.of_int (-abs_m + 1), Bound.zero) else - (Bound.of_int (-abs_m + 1), Bound.of_int (abs_m - 1)) - | _, None -> top + let mod_sem : t -> t -> t = + fun x y -> + match (is_const x, is_const y) with + | _, Some 0 + -> x + | Some n, Some m + -> of_int (n mod m) + | _, Some m + -> let abs_m = abs m in + if is_ge_zero x then (Bound.zero, Bound.of_int (abs_m - 1)) + else if is_le_zero x then (Bound.of_int (-abs_m + 1), Bound.zero) + else (Bound.of_int (-abs_m + 1), Bound.of_int (abs_m - 1)) + | _, None + -> top (* x << [-1,-1] does nothing. *) - let shiftlt : t -> t -> t - = fun x y -> - match is_const y with - | Some n -> if n >= 0 then mult_const x (1 lsl n) else x - | None -> top + let shiftlt : t -> t -> t = + fun x y -> + match is_const y with Some n -> if n >= 0 then mult_const x (1 lsl n) else x | None -> top (* x >> [-1,-1] does nothing. *) - let shiftrt : t -> t -> t - = fun x y -> + let shiftrt : t -> t -> t = + fun x y -> match is_const y with - | Some n -> if n >= 0 && n < 63 then div_const x (1 lsl n) else x - | None -> top - - let lt_sem : t -> t -> t - = fun (l1, u1) (l2, u2) -> - if Bound.lt u1 l2 then true_sem else - if Bound.le u2 l1 then false_sem else - unknown_bool - - let gt_sem : t -> t -> t - = fun x y -> lt_sem y x - - let le_sem : t -> t -> t - = fun (l1, u1) (l2, u2) -> - if Bound.le u1 l2 then true_sem else - if Bound.lt u2 l1 then false_sem else - unknown_bool - - let ge_sem : t -> t -> t - = fun x y -> le_sem y x - - let eq_sem : t -> t -> t - = fun (l1, u1) (l2, u2) -> - if Bound.eq l1 u1 && Bound.eq u1 l2 && Bound.eq l2 u2 then true_sem else - if Bound.lt u1 l2 || Bound.lt u2 l1 then false_sem else - unknown_bool - - let ne_sem : t -> t -> t - = fun (l1, u1) (l2, u2) -> - if Bound.eq l1 u1 && Bound.eq u1 l2 && Bound.eq l2 u2 then false_sem else - if Bound.lt u1 l2 || Bound.lt u2 l1 then true_sem else - unknown_bool - - let land_sem : t -> t -> t - = fun x y -> - if is_true x && is_true y then true_sem else - if is_false x || is_false y then false_sem else - unknown_bool - - let lor_sem : t -> t -> t - = fun x y -> - if is_true x || is_true y then true_sem else - if is_false x && is_false y then false_sem else - unknown_bool - - let invalid : t -> bool - = fun (l, u) -> - Bound.equal l Bound.Bot || Bound.equal u Bound.Bot - || Bound.eq l Bound.PInf || Bound.eq u Bound.MInf || Bound.lt u l - - let prune_le : t -> t -> t - = fun x y -> - match x, y with - | (l1, u1), (_, u2) when Bound.equal u1 Bound.PInf -> (l1, u2) - | (l1, Bound.Linear (c1, s1)), (_, Bound.Linear (c2, s2)) - when SymLinear.eq s1 s2 -> - (l1, Bound.Linear (min c1 c2, s1)) - | (l1, Bound.Linear (c, se)), (_, u) - when SymLinear.is_zero se && Bound.is_one_symbol u -> - (match Bound.one_symbol u with - | Some s -> (l1, Bound.MinMax (Bound.Min, c, s)) - | None -> assert false) - | (l1, u), (_, Bound.Linear (c, se)) - when SymLinear.is_zero se && Bound.is_one_symbol u -> - (match Bound.one_symbol u with - | Some s -> (l1, Bound.MinMax (Bound.Min, c, s)) - | None -> assert false) + | Some n + -> if n >= 0 && n < 63 then div_const x (1 lsl n) else x + | None + -> top + + let lt_sem : t -> t -> t = + fun (l1, u1) (l2, u2) -> + if Bound.lt u1 l2 then true_sem else if Bound.le u2 l1 then false_sem else unknown_bool + + let gt_sem : t -> t -> t = fun x y -> lt_sem y x + + let le_sem : t -> t -> t = + fun (l1, u1) (l2, u2) -> + if Bound.le u1 l2 then true_sem else if Bound.lt u2 l1 then false_sem else unknown_bool + + let ge_sem : t -> t -> t = fun x y -> le_sem y x + + let eq_sem : t -> t -> t = + fun (l1, u1) (l2, u2) -> + if Bound.eq l1 u1 && Bound.eq u1 l2 && Bound.eq l2 u2 then true_sem + else if Bound.lt u1 l2 || Bound.lt u2 l1 then false_sem + else unknown_bool + + let ne_sem : t -> t -> t = + fun (l1, u1) (l2, u2) -> + if Bound.eq l1 u1 && Bound.eq u1 l2 && Bound.eq l2 u2 then false_sem + else if Bound.lt u1 l2 || Bound.lt u2 l1 then true_sem + else unknown_bool + + let land_sem : t -> t -> t = + fun x y -> + if is_true x && is_true y then true_sem + else if is_false x || is_false y then false_sem + else unknown_bool + + let lor_sem : t -> t -> t = + fun x y -> + if is_true x || is_true y then true_sem + else if is_false x && is_false y then false_sem + else unknown_bool + + let invalid : t -> bool = + fun (l, u) -> + Bound.equal l Bound.Bot || Bound.equal u Bound.Bot || Bound.eq l Bound.PInf + || Bound.eq u Bound.MInf || Bound.lt u l + + let prune_le : t -> t -> t = + fun x y -> + match (x, y) with + | (l1, u1), (_, u2) when Bound.equal u1 Bound.PInf + -> (l1, u2) + | (l1, Bound.Linear (c1, s1)), (_, Bound.Linear (c2, s2)) when SymLinear.eq s1 s2 + -> (l1, Bound.Linear (min c1 c2, s1)) + | (l1, Bound.Linear (c, se)), (_, u) when SymLinear.is_zero se && Bound.is_one_symbol u -> ( + match Bound.one_symbol u with + | Some s + -> (l1, Bound.MinMax (Bound.Min, c, s)) + | None + -> assert false ) + | (l1, u), (_, Bound.Linear (c, se)) when SymLinear.is_zero se && Bound.is_one_symbol u -> ( + match Bound.one_symbol u with + | Some s + -> (l1, Bound.MinMax (Bound.Min, c, s)) + | None + -> assert false ) | (l1, Bound.Linear (c1, se)), (_, Bound.MinMax (Bound.Min, c2, se')) | (l1, Bound.MinMax (Bound.Min, c1, se')), (_, Bound.Linear (c2, se)) - when SymLinear.is_zero se -> - (l1, Bound.MinMax (Bound.Min, min c1 c2, se')) - | (l1, Bound.MinMax (Bound.Min, c1, se1)), - (_, Bound.MinMax (Bound.Min, c2, se2)) - when Symbol.eq se1 se2 -> - (l1, Bound.MinMax (Bound.Min, min c1 c2, se1)) - | _ -> x - - let prune_ge : t -> t -> t - = fun x y -> - match x, y with - | (l1, u1), (l2, _) when Bound.equal l1 Bound.MInf -> (l2, u1) - | (Bound.Linear (c1, s1), u1), (Bound.Linear (c2, s2), _) - when SymLinear.eq s1 s2 -> - (Bound.Linear (max c1 c2, s1), u1) - | (Bound.Linear (c, se), u1), (l, _) - when SymLinear.is_zero se && Bound.is_one_symbol l -> - (match Bound.one_symbol l with - | Some s -> (Bound.MinMax (Bound.Max, c, s), u1) - | None -> assert false) - | (l, u1), (Bound.Linear (c, se), _) - when SymLinear.is_zero se && Bound.is_one_symbol l -> - (match Bound.one_symbol l with - | Some s -> (Bound.MinMax (Bound.Max, c, s), u1) - | None -> assert false) + when SymLinear.is_zero se + -> (l1, Bound.MinMax (Bound.Min, min c1 c2, se')) + | (l1, Bound.MinMax (Bound.Min, c1, se1)), (_, Bound.MinMax (Bound.Min, c2, se2)) + when Symbol.eq se1 se2 + -> (l1, Bound.MinMax (Bound.Min, min c1 c2, se1)) + | _ + -> x + + let prune_ge : t -> t -> t = + fun x y -> + match (x, y) with + | (l1, u1), (l2, _) when Bound.equal l1 Bound.MInf + -> (l2, u1) + | (Bound.Linear (c1, s1), u1), (Bound.Linear (c2, s2), _) when SymLinear.eq s1 s2 + -> (Bound.Linear (max c1 c2, s1), u1) + | (Bound.Linear (c, se), u1), (l, _) when SymLinear.is_zero se && Bound.is_one_symbol l -> ( + match Bound.one_symbol l with + | Some s + -> (Bound.MinMax (Bound.Max, c, s), u1) + | None + -> assert false ) + | (l, u1), (Bound.Linear (c, se), _) when SymLinear.is_zero se && Bound.is_one_symbol l -> ( + match Bound.one_symbol l with + | Some s + -> (Bound.MinMax (Bound.Max, c, s), u1) + | None + -> assert false ) | (Bound.Linear (c1, se), u1), (Bound.MinMax (Bound.Max, c2, se'), _) | (Bound.MinMax (Bound.Max, c1, se'), u1), (Bound.Linear (c2, se), _) - when SymLinear.is_zero se -> - (Bound.MinMax (Bound.Max, max c1 c2, se'), u1) - | (Bound.MinMax (Bound.Max, c1, se1), u1), - (Bound.MinMax (Bound.Max, c2, se2), _) - when Symbol.eq se1 se2 -> - (Bound.MinMax (Bound.Max, max c1 c2, se1), u1) - | _ -> x - - let prune_lt : t -> t -> t - = fun x y -> prune_le x (minus y one) - - let prune_gt : t -> t -> t - = fun x y -> prune_ge x (plus y one) - - let diff : t -> Bound.t -> t - = fun (l, u) b -> - if Bound.eq l b then (Bound.plus_l l Bound.one, u) else - if Bound.eq u b then (l, Bound.plus_u u Bound.mone) else - (l, u) - - let prune_zero : t -> t - = fun x -> diff x Bound.zero - - let prune_comp : Binop.t -> t -> t -> t option - = fun c x y -> - if invalid y then Some x else + when SymLinear.is_zero se + -> (Bound.MinMax (Bound.Max, max c1 c2, se'), u1) + | (Bound.MinMax (Bound.Max, c1, se1), u1), (Bound.MinMax (Bound.Max, c2, se2), _) + when Symbol.eq se1 se2 + -> (Bound.MinMax (Bound.Max, max c1 c2, se1), u1) + | _ + -> x + + let prune_lt : t -> t -> t = fun x y -> prune_le x (minus y one) + + let prune_gt : t -> t -> t = fun x y -> prune_ge x (plus y one) + + let diff : t -> Bound.t -> t = + fun (l, u) b -> + if Bound.eq l b then (Bound.plus_l l Bound.one, u) + else if Bound.eq u b then (l, Bound.plus_u u Bound.mone) + else (l, u) + + let prune_zero : t -> t = fun x -> diff x Bound.zero + + let prune_comp : Binop.t -> t -> t -> t option = + fun c x y -> + if invalid y then Some x + else let x = match c with - | Binop.Le -> prune_le x y - | Binop.Ge -> prune_ge x y - | Binop.Lt -> prune_lt x y - | Binop.Gt -> prune_gt x y - | _ -> assert false + | Binop.Le + -> prune_le x y + | Binop.Ge + -> prune_ge x y + | Binop.Lt + -> prune_lt x y + | Binop.Gt + -> prune_gt x y + | _ + -> assert false in if invalid x then None else Some x - let prune_eq : t -> t -> t option - = fun x y -> - match prune_comp Binop.Le x y with - | None -> None - | Some x' -> prune_comp Binop.Ge x' y + let prune_eq : t -> t -> t option = + fun x y -> + match prune_comp Binop.Le x y with None -> None | Some x' -> prune_comp Binop.Ge x' y - let prune_ne : t -> t -> t option - = fun x (l, u) -> - if invalid (l, u) then Some x else + let prune_ne : t -> t -> t option = + fun x (l, u) -> + if invalid (l, u) then Some x + else let x = if Bound.eq l u then diff x l else x in if invalid x then None else Some x - let get_symbols : t -> Symbol.t list - = fun (l, u) -> - List.append (Bound.get_symbols l) (Bound.get_symbols u) + let get_symbols : t -> Symbol.t list = + fun (l, u) -> List.append (Bound.get_symbols l) (Bound.get_symbols u) - let normalize : t -> t option - = fun (l, u) -> - if invalid (l,u) then None - else Some (l, u) + let normalize : t -> t option = fun (l, u) -> if invalid (l, u) then None else Some (l, u) - let has_bnd_bot : t -> bool - = fun (l, u) -> - Bound.equal l Bound.Bot || Bound.equal u Bound.Bot + let has_bnd_bot : t -> bool = fun (l, u) -> Bound.equal l Bound.Bot || Bound.equal u Bound.Bot end include AbstractDomain.BottomLifted (ItvPure) type t = astate -let compare : t -> t -> int - = fun x y -> - match x, y with - | Bottom, Bottom -> 0 - | Bottom, _ -> -1 - | _, Bottom -> 1 - | NonBottom x, NonBottom y -> ItvPure.compare_astate x y +let compare : t -> t -> int = + fun x y -> + match (x, y) with + | Bottom, Bottom + -> 0 + | Bottom, _ + -> -1 + | _, Bottom + -> 1 + | NonBottom x, NonBottom y + -> ItvPure.compare_astate x y let equal = [%compare.equal : t] let compare_astate = compare -let bot : t - = Bottom +let bot : t = Bottom -let top : t - = NonBottom ItvPure.top +let top : t = NonBottom ItvPure.top -let lb : t -> Bound.t - = function - | NonBottom x -> ItvPure.lb x - | _ -> raise (Failure "lower bound of bottom") +let lb : t -> Bound.t = function + | NonBottom x + -> ItvPure.lb x + | _ + -> raise (Failure "lower bound of bottom") -let ub : t -> Bound.t - = function - | NonBottom x -> ItvPure.ub x - | _ -> raise (Failure "upper bound of bottom") +let ub : t -> Bound.t = function + | NonBottom x + -> ItvPure.ub x + | _ + -> raise (Failure "upper bound of bottom") -let of_int : int -> astate - = fun n -> NonBottom (ItvPure.of_int n) +let of_int : int -> astate = fun n -> NonBottom (ItvPure.of_int n) let of_int_lit n = - try of_int (IntLit.to_int n) with - | _ -> top + try of_int (IntLit.to_int n) + with _ -> top -let is_bot : t -> bool - = fun x -> equal x Bottom +let is_bot : t -> bool = fun x -> equal x Bottom -let is_finite : t -> bool - = function - | NonBottom x -> ItvPure.is_finite x - | Bottom -> false +let is_finite : t -> bool = function NonBottom x -> ItvPure.is_finite x | Bottom -> false let false_sem = NonBottom ItvPure.false_sem @@ -913,144 +891,100 @@ let unknown_bool = NonBottom ItvPure.unknown_bool let zero = NonBottom ItvPure.zero -let make : Bound.t -> Bound.t -> t - = fun l u -> if Bound.lt u l then Bottom else NonBottom (ItvPure.make l u) - -let invalid : t -> bool - = function - | NonBottom x -> ItvPure.invalid x - | Bottom -> false - -let is_symbolic : t -> bool - = function - | NonBottom x -> ItvPure.is_symbolic x - | Bottom -> false - -let le : lhs:t -> rhs:t -> bool - = (<=) - -let eq : t -> t -> bool - = fun x y -> (<=) ~lhs:x ~rhs:y && (<=) ~lhs:y ~rhs:x - -let to_string : t -> string - = fun x -> - pp F.str_formatter x; - F.flush_str_formatter () - -let lift1 : (ItvPure.t -> ItvPure.t) -> t -> t - = fun f -> function - | Bottom -> Bottom - | NonBottom x -> NonBottom (f x) - -let lift1_opt : (ItvPure.t -> ItvPure.t option) -> t -> t - = fun f -> function - | Bottom -> Bottom - | NonBottom x -> - (match f x with - | None -> Bottom - | Some v -> NonBottom v) - -let lift2 : (ItvPure.t -> ItvPure.t -> ItvPure.t) -> t -> t -> t - = fun f x y -> - match x, y with - | Bottom, _ - | _, Bottom -> Bottom - | NonBottom x, NonBottom y -> NonBottom (f x y) +let make : Bound.t -> Bound.t -> t = + fun l u -> if Bound.lt u l then Bottom else NonBottom (ItvPure.make l u) -let lift2_opt : (ItvPure.t -> ItvPure.t -> ItvPure.t option) -> t -> t -> t - = fun f x y -> - match x, y with - | Bottom, _ - | _, Bottom -> Bottom +let invalid : t -> bool = function NonBottom x -> ItvPure.invalid x | Bottom -> false + +let is_symbolic : t -> bool = function NonBottom x -> ItvPure.is_symbolic x | Bottom -> false + +let le : lhs:t -> rhs:t -> bool = ( <= ) + +let eq : t -> t -> bool = fun x y -> ( <= ) ~lhs:x ~rhs:y && ( <= ) ~lhs:y ~rhs:x + +let to_string : t -> string = fun x -> pp F.str_formatter x ; F.flush_str_formatter () + +let lift1 : (ItvPure.t -> ItvPure.t) -> t -> t = + fun f -> function Bottom -> Bottom | NonBottom x -> NonBottom (f x) + +let lift1_opt : (ItvPure.t -> ItvPure.t option) -> t -> t = + fun f -> + function + | Bottom -> Bottom | NonBottom x -> match f x with None -> Bottom | Some v -> NonBottom v + +let lift2 : (ItvPure.t -> ItvPure.t -> ItvPure.t) -> t -> t -> t = + fun f x y -> + match (x, y) with + | Bottom, _ | _, Bottom + -> Bottom + | NonBottom x, NonBottom y + -> NonBottom (f x y) + +let lift2_opt : (ItvPure.t -> ItvPure.t -> ItvPure.t option) -> t -> t -> t = + fun f x y -> + match (x, y) with + | Bottom, _ | _, Bottom + -> Bottom | NonBottom x, NonBottom y -> - (match f x y with - | Some v -> NonBottom v - | None -> Bottom) + match f x y with Some v -> NonBottom v | None -> Bottom -let plus : t -> t -> t - = lift2 ItvPure.plus +let plus : t -> t -> t = lift2 ItvPure.plus -let minus : t -> t -> t - = lift2 ItvPure.minus +let minus : t -> t -> t = lift2 ItvPure.minus -let get_new_sym : Typ.Procname.t -> t - = fun pname -> NonBottom (ItvPure.get_new_sym pname) +let get_new_sym : Typ.Procname.t -> t = fun pname -> NonBottom (ItvPure.get_new_sym pname) -let make_sym : ?unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t - = fun ?(unsigned=false) pname new_sym_num -> NonBottom (ItvPure.make_sym ~unsigned pname new_sym_num) +let make_sym : ?unsigned:bool -> Typ.Procname.t -> (unit -> int) -> t = + fun ?(unsigned= false) pname new_sym_num -> + NonBottom (ItvPure.make_sym ~unsigned pname new_sym_num) -let neg : t -> t - = lift1 ItvPure.neg +let neg : t -> t = lift1 ItvPure.neg -let lnot : t -> t - = lift1 ItvPure.lnot +let lnot : t -> t = lift1 ItvPure.lnot -let mult : t -> t -> t - = lift2 ItvPure.mult +let mult : t -> t -> t = lift2 ItvPure.mult -let div : t -> t -> t - = lift2 ItvPure.div +let div : t -> t -> t = lift2 ItvPure.div -let mod_sem : t -> t -> t - = lift2 ItvPure.mod_sem +let mod_sem : t -> t -> t = lift2 ItvPure.mod_sem -let shiftlt : t -> t -> t - = lift2 ItvPure.shiftlt +let shiftlt : t -> t -> t = lift2 ItvPure.shiftlt -let shiftrt : t -> t -> t - = lift2 ItvPure.shiftrt +let shiftrt : t -> t -> t = lift2 ItvPure.shiftrt -let lt_sem : t -> t -> t - = lift2 ItvPure.lt_sem +let lt_sem : t -> t -> t = lift2 ItvPure.lt_sem -let gt_sem : t -> t -> t - = lift2 ItvPure.gt_sem +let gt_sem : t -> t -> t = lift2 ItvPure.gt_sem -let le_sem : t -> t -> t - = lift2 ItvPure.le_sem +let le_sem : t -> t -> t = lift2 ItvPure.le_sem -let ge_sem : t -> t -> t - = lift2 ItvPure.ge_sem +let ge_sem : t -> t -> t = lift2 ItvPure.ge_sem -let eq_sem : t -> t -> t - = lift2 ItvPure.eq_sem +let eq_sem : t -> t -> t = lift2 ItvPure.eq_sem -let ne_sem : t -> t -> t - = lift2 ItvPure.ne_sem +let ne_sem : t -> t -> t = lift2 ItvPure.ne_sem -let land_sem : t -> t -> t - = lift2 ItvPure.land_sem +let land_sem : t -> t -> t = lift2 ItvPure.land_sem -let lor_sem : t -> t -> t - = lift2 ItvPure.lor_sem +let lor_sem : t -> t -> t = lift2 ItvPure.lor_sem -let prune_zero : t -> t - = lift1 ItvPure.prune_zero +let prune_zero : t -> t = lift1 ItvPure.prune_zero -let prune_comp : Binop.t -> t -> t -> t - = fun comp -> lift2_opt (ItvPure.prune_comp comp) +let prune_comp : Binop.t -> t -> t -> t = fun comp -> lift2_opt (ItvPure.prune_comp comp) -let prune_eq : t -> t -> t - = lift2_opt ItvPure.prune_eq +let prune_eq : t -> t -> t = lift2_opt ItvPure.prune_eq -let prune_ne : t -> t -> t - = lift2_opt ItvPure.prune_ne +let prune_ne : t -> t -> t = lift2_opt ItvPure.prune_ne -let subst : t -> Bound.t SubstMap.t -> t - = fun x map -> - match x with - | NonBottom x' -> NonBottom (ItvPure.subst x' map) - | _ -> x +let subst : t -> Bound.t SubstMap.t -> t = + fun x map -> match x with NonBottom x' -> NonBottom (ItvPure.subst x' map) | _ -> x -let get_symbols : t -> Symbol.t list - = function - | Bottom -> [] - | NonBottom x -> ItvPure.get_symbols x +let get_symbols : t -> Symbol.t list = function + | Bottom + -> [] + | NonBottom x + -> ItvPure.get_symbols x -let normalize : t -> t - = lift1_opt ItvPure.normalize +let normalize : t -> t = lift1_opt ItvPure.normalize -let has_bnd_bot : t -> bool - = function - | Bottom -> false - | NonBottom x -> ItvPure.has_bnd_bot x +let has_bnd_bot : t -> bool = function Bottom -> false | NonBottom x -> ItvPure.has_bnd_bot x diff --git a/infer/src/checkers/AnnotReachabilityDomain.ml b/infer/src/checkers/AnnotReachabilityDomain.ml index 38e70f6cf..f61d67183 100644 --- a/infer/src/checkers/AnnotReachabilityDomain.ml +++ b/infer/src/checkers/AnnotReachabilityDomain.ml @@ -8,9 +8,6 @@ *) open! IStd - module CallSites = AbstractDomain.FiniteSet (CallSite) - module SinkMap = AbstractDomain.Map (Typ.Procname) (CallSites) - include AbstractDomain.Map (Annot) (SinkMap) diff --git a/infer/src/checkers/BoundedCallTree.ml b/infer/src/checkers/BoundedCallTree.ml index 0875607fd..09f93a138 100644 --- a/infer/src/checkers/BoundedCallTree.ml +++ b/infer/src/checkers/BoundedCallTree.ml @@ -8,88 +8,81 @@ *) open! IStd - module F = Format module L = Logging (** find transitive procedure calls for each procedure *) -module Domain = AbstractDomain.FiniteSet(Typ.Procname) +module Domain = AbstractDomain.FiniteSet (Typ.Procname) (* Store a single stacktree frame per method. That is, callees is always []. Instead, the expanded per-method summaries are directly stored in the output directory as JSON files and *only* for those methods that will be part of the final crashcontext.json. *) module SpecSummary = Summary.Make (struct - type payload = Stacktree_j.stacktree - - let update_payload frame (summary : Specs.summary) = - let payload = - { summary.payload with Specs.crashcontext_frame = Some frame } in - { summary with payload = payload } + type payload = Stacktree_j.stacktree - let read_payload (summary : Specs.summary) = - summary.payload.crashcontext_frame + let update_payload frame (summary: Specs.summary) = + let payload = {summary.payload with Specs.crashcontext_frame= Some frame} in + {summary with payload} - end) + let read_payload (summary: Specs.summary) = summary.payload.crashcontext_frame +end) -type extras_t = { - get_proc_desc : Typ.Procname.t -> Procdesc.t option; - stacktraces : Stacktrace.t list; -} +type extras_t = {get_proc_desc: Typ.Procname.t -> Procdesc.t option; stacktraces: Stacktrace.t list} let line_range_of_pdesc pdesc = let ploc = Procdesc.get_loc pdesc in let start_line = ploc.Location.line in - let end_line = Procdesc.fold_instrs + let end_line = + Procdesc.fold_instrs (fun acc _ instr -> - let new_loc = Sil.instr_get_loc instr in - max acc new_loc.Location.line) - start_line - pdesc in - { Stacktree_j.start_line; end_line } - -let stacktree_of_pdesc - pdesc - ?(loc=Procdesc.get_loc pdesc) - ?(callees=[]) - location_type = + let new_loc = Sil.instr_get_loc instr in + max acc new_loc.Location.line) + start_line pdesc + in + {Stacktree_j.start_line= start_line; end_line} + +let stacktree_of_pdesc pdesc ?(loc= Procdesc.get_loc pdesc) ?(callees= []) location_type = let procname = Procdesc.get_proc_name pdesc in let frame_loc = - Some { Stacktree_j.location_type = location_type; - file = SourceFile.to_string loc.Location.file; - line = Some loc.Location.line; - blame_range = [line_range_of_pdesc pdesc] } in - { Stacktree_j.method_name = Typ.Procname.to_unique_id procname; - location = frame_loc; - callees = callees } + Some + { Stacktree_j.location_type= location_type + ; file= SourceFile.to_string loc.Location.file + ; line= Some loc.Location.line + ; blame_range= [line_range_of_pdesc pdesc] } + in + {Stacktree_j.method_name= Typ.Procname.to_unique_id procname; location= frame_loc; callees} let stacktree_stub_of_procname procname = - { Stacktree_j.method_name = Typ.Procname.to_unique_id procname; - location = None; - callees = [] } + {Stacktree_j.method_name= Typ.Procname.to_unique_id procname; location= None; callees= []} module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = extras_t let stacktree_of_astate pdesc astate loc location_type get_proc_desc = let procs = Domain.elements astate in - let callees = List.map + let callees = + List.map ~f:(fun pn -> - match SpecSummary.read_summary pdesc pn with - | None -> - (match get_proc_desc pn with - | None -> stacktree_stub_of_procname pn - (* This can happen when the callee is in the same cluster/ buck + match SpecSummary.read_summary pdesc pn with + | None -> ( + match get_proc_desc pn with + | None + -> stacktree_stub_of_procname pn + (* This can happen when the callee is in the same cluster/ buck target, but it hasn't been checked yet. So we need both the inter-target lookup (SpecSummary) and the intra-target lookup (using get_proc_desc). *) - | Some callee_pdesc -> - stacktree_of_pdesc callee_pdesc "proc_start") - | Some stracktree -> stracktree ) - procs in + | Some callee_pdesc + -> stacktree_of_pdesc callee_pdesc "proc_start" ) + | Some stracktree + -> stracktree) + procs + in stacktree_of_pdesc pdesc ~loc ~callees location_type let output_json_summary pdesc astate loc location_type get_proc_desc = @@ -97,54 +90,50 @@ module TransferFunctions (CFG : ProcCfg.S) = struct let stacktree = stacktree_of_astate pdesc astate loc location_type get_proc_desc in let dir = Filename.concat Config.results_dir "crashcontext" in let suffix = F.sprintf "%s_%d" location_type loc.Location.line in - let fname = F.sprintf "%s.%s.json" - (Typ.Procname.to_filename caller) - suffix in + let fname = F.sprintf "%s.%s.json" (Typ.Procname.to_filename caller) suffix in let fpath = Filename.concat dir fname in - Utils.create_dir dir; - Ag_util.Json.to_file Stacktree_j.write_stacktree fpath stacktree + Utils.create_dir dir ; Ag_util.Json.to_file Stacktree_j.write_stacktree fpath stacktree let exec_instr astate proc_data _ = function - | Sil.Call (_, Const (Const.Cfun pn), _, loc, _) -> + | Sil.Call (_, Const Const.Cfun pn, _, loc, _) + -> ( let get_proc_desc = proc_data.ProcData.extras.get_proc_desc in let traces = proc_data.ProcData.extras.stacktraces in let caller = Procdesc.get_proc_name proc_data.ProcData.pdesc in let matches_proc frame = - let matches_class pname = match pname with - | Typ.Procname.Java java_proc -> - String.equal - frame.Stacktrace.class_str + let matches_class pname = + match pname with + | Typ.Procname.Java java_proc + -> String.equal frame.Stacktrace.class_str (Typ.Procname.java_get_class_name java_proc) - | Typ.Procname.ObjC_Cpp objc_cpp_prod -> - String.equal - frame.Stacktrace.class_str + | Typ.Procname.ObjC_Cpp objc_cpp_prod + -> String.equal frame.Stacktrace.class_str (Typ.Procname.objc_cpp_get_class_name objc_cpp_prod) - | Typ.Procname.C _ -> true (* Needed for test code. *) - | Typ.Procname.Block _ | Typ.Procname.Linters_dummy_method -> - failwith "Proc type not supported by crashcontext: block" in - String.equal frame.Stacktrace.method_str (Typ.Procname.get_method caller) && - matches_class caller in - let all_frames = List.concat - (List.map ~f:(fun trace -> trace.Stacktrace.frames) traces) in - begin - match List.find ~f:matches_proc all_frames with - | Some frame -> - let new_astate = Domain.add pn astate in - if Stacktrace.frame_matches_location frame loc then begin + | Typ.Procname.C _ + -> true (* Needed for test code. *) + | Typ.Procname.Block _ | Typ.Procname.Linters_dummy_method + -> failwith "Proc type not supported by crashcontext: block" + in + String.equal frame.Stacktrace.method_str (Typ.Procname.get_method caller) + && matches_class caller + in + let all_frames = List.concat (List.map ~f:(fun trace -> trace.Stacktrace.frames) traces) in + match List.find ~f:matches_proc all_frames with + | Some frame + -> let new_astate = Domain.add pn astate in + ( if Stacktrace.frame_matches_location frame loc then let pdesc = proc_data.ProcData.pdesc in - output_json_summary pdesc new_astate loc "call_site" get_proc_desc - end; - new_astate - | None -> - astate - end - | Sil.Call _ -> - (* We currently ignore calls through function pointers in C and + output_json_summary pdesc new_astate loc "call_site" get_proc_desc ) ; + new_astate + | None + -> astate ) + | Sil.Call _ + -> (* We currently ignore calls through function pointers in C and other potential special kinds of procedure calls to be added later, e.g. Java reflection. *) astate - | Sil.Load _ | Store _ | Prune _ | Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ -> - astate + | Sil.Load _ | Store _ | Prune _ | Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ + -> astate end module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) @@ -154,31 +143,32 @@ let loaded_stacktraces = Config.stacktraces_dir. *) let json_files_in_dir dir = let stacktrace_path_regexp = Str.regexp ".*\\.json" in - let path_matcher path = Str.string_match stacktrace_path_regexp path 0 in - DB.paths_matching dir path_matcher in - let filenames = match Config.stacktrace, Config.stacktraces_dir with - | None, None -> None - | Some fname, None -> Some [fname] - | None, Some dir -> Some (json_files_in_dir dir) - | Some fname, Some dir -> Some (fname :: (json_files_in_dir dir)) in + let path_matcher path = Str.string_match stacktrace_path_regexp path 0 in + DB.paths_matching dir path_matcher + in + let filenames = + match (Config.stacktrace, Config.stacktraces_dir) with + | None, None + -> None + | Some fname, None + -> Some [fname] + | None, Some dir + -> Some (json_files_in_dir dir) + | Some fname, Some dir + -> Some (fname :: json_files_in_dir dir) + in match filenames with - | None -> None - | Some files -> Some (List.map ~f:Stacktrace.of_json_file files) - -let checker { Callbacks.proc_desc; tenv; get_proc_desc; summary } : Specs.summary = - begin - match loaded_stacktraces with - | None -> failwith "Missing command line option. Either \ - '--stacktrace stack.json' or '--stacktrace-dir ./dir' \ - must be used when running '-a crashcontext'. This \ - options expects a JSON formated stack trace or a \ - directory containing multiple such traces, \ - respectively. See \ - tests/codetoanalyze/java/crashcontext/*.json for \ - examples of the expected format." - | Some stacktraces -> begin - let extras = { get_proc_desc; stacktraces; } in - ignore (Analyzer.exec_pdesc (ProcData.make proc_desc tenv extras) ~initial:Domain.empty) - end - end; + | None + -> None + | Some files + -> Some (List.map ~f:Stacktrace.of_json_file files) + +let checker {Callbacks.proc_desc; tenv; get_proc_desc; summary} : Specs.summary = + ( match loaded_stacktraces with + | None + -> failwith + "Missing command line option. Either '--stacktrace stack.json' or '--stacktrace-dir ./dir' must be used when running '-a crashcontext'. This options expects a JSON formated stack trace or a directory containing multiple such traces, respectively. See tests/codetoanalyze/java/crashcontext/*.json for examples of the expected format." + | Some stacktraces + -> let extras = {get_proc_desc; stacktraces} in + ignore (Analyzer.exec_pdesc (ProcData.make proc_desc tenv extras) ~initial:Domain.empty) ) ; summary diff --git a/infer/src/checkers/IdAccessPathMapDomain.ml b/infer/src/checkers/IdAccessPathMapDomain.ml index b4563a6fe..4a44868dc 100644 --- a/infer/src/checkers/IdAccessPathMapDomain.ml +++ b/infer/src/checkers/IdAccessPathMapDomain.ml @@ -8,54 +8,48 @@ *) open! IStd - module IdMap = Var.Map type astate = AccessPath.Raw.t IdMap.t include IdMap -let pp fmt astate = - IdMap.pp ~pp_value:AccessPath.Raw.pp fmt astate +let pp fmt astate = IdMap.pp ~pp_value:AccessPath.Raw.pp fmt astate let check_invariant ap1 ap2 = function - | Var.ProgramVar pvar when Pvar.is_ssa_frontend_tmp pvar -> - (* Sawja reuses temporary variables which sometimes breaks this invariant *) + | Var.ProgramVar pvar when Pvar.is_ssa_frontend_tmp pvar + -> (* Sawja reuses temporary variables which sometimes breaks this invariant *) () - | id -> - if not (AccessPath.Raw.equal ap1 ap2) - then - failwithf "Id %a maps to both %a and %a@." - Var.pp id - AccessPath.Raw.pp ap1 + | id + -> if not (AccessPath.Raw.equal ap1 ap2) then + failwithf "Id %a maps to both %a and %a@." Var.pp id AccessPath.Raw.pp ap1 AccessPath.Raw.pp ap2 -let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true +let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else IdMap.for_all (fun id lhs_ap -> - let rhs_has = IdMap.mem id rhs in - if rhs_has && Config.debug_exceptions - then check_invariant lhs_ap (IdMap.find id rhs) id; - rhs_has) + let rhs_has = IdMap.mem id rhs in + if rhs_has && Config.debug_exceptions then check_invariant lhs_ap (IdMap.find id rhs) id ; + rhs_has) lhs let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else IdMap.merge - (fun var ap1_opt ap2_opt -> match ap1_opt, ap2_opt with - | Some ap1, Some ap2 -> - if Config.debug_exceptions then check_invariant ap1 ap2 var; - ap1_opt - | Some _, None -> ap1_opt - | None, Some _ -> ap2_opt - | None, None -> None) - astate1 - astate2 - -let widen ~prev ~next ~num_iters:_ = - join prev next + (fun var ap1_opt ap2_opt -> + match (ap1_opt, ap2_opt) with + | Some ap1, Some ap2 + -> if Config.debug_exceptions then check_invariant ap1 ap2 var ; + ap1_opt + | Some _, None + -> ap1_opt + | None, Some _ + -> ap2_opt + | None, None + -> None) + astate1 astate2 + +let widen ~prev ~next ~num_iters:_ = join prev next diff --git a/infer/src/checkers/IdAccessPathMapDomain.mli b/infer/src/checkers/IdAccessPathMapDomain.mli index 72a18e583..4819b9149 100644 --- a/infer/src/checkers/IdAccessPathMapDomain.mli +++ b/infer/src/checkers/IdAccessPathMapDomain.mli @@ -15,6 +15,6 @@ module IdMap = Var.Map type astate = AccessPath.Raw.t IdMap.t -include (module type of IdMap) +include module type of IdMap include AbstractDomain.WithBottom with type astate := astate diff --git a/infer/src/checkers/NullabilityPreanalysis.ml b/infer/src/checkers/NullabilityPreanalysis.ml index 7bae8c45f..52683cae0 100644 --- a/infer/src/checkers/NullabilityPreanalysis.ml +++ b/infer/src/checkers/NullabilityPreanalysis.ml @@ -6,71 +6,66 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Module that define preanalysis to derive nullability annotations *) open! IStd - module F = Format module L = Logging -module FieldsAssignedInConstructors = - AbstractDomain.FiniteSet(struct - type t = Typ.Fieldname.t * Typ.t [@@deriving compare] +module FieldsAssignedInConstructors = AbstractDomain.FiniteSet (struct + type t = Typ.Fieldname.t * Typ.t [@@deriving compare] - let pp fmt (fieldname, typ) = - F.fprintf fmt "(%a, %a)" Typ.Fieldname.pp fieldname (Typ.pp_full Pp.text) typ - end) + let pp fmt (fieldname, typ) = + F.fprintf fmt "(%a, %a)" Typ.Fieldname.pp fieldname (Typ.pp_full Pp.text) typ +end) module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = FieldsAssignedInConstructors + type extras = Exp.t Ident.IdentHash.t let exp_is_null ids_map exp = match exp with - | Exp.Var id -> - (try - let exp = Ident.IdentHash.find ids_map id in - Exp.is_null_literal exp - with Not_found -> false) - | _ -> Exp.is_null_literal exp + | Exp.Var id -> ( + try + let exp = Ident.IdentHash.find ids_map id in + Exp.is_null_literal exp + with Not_found -> false ) + | _ + -> Exp.is_null_literal exp let is_self ids_map id = - try - match Ident.IdentHash.find ids_map id with - | Exp.Lvar var -> Pvar.is_self var - | _ -> false + try match Ident.IdentHash.find ids_map id with Exp.Lvar var -> Pvar.is_self var | _ -> false with Not_found -> false let exec_instr astate (proc_data: Exp.t Ident.IdentHash.t ProcData.t) _ instr = match instr with - | Sil.Load (id, exp, _, _) -> - Ident.IdentHash.add proc_data.extras id exp; - astate - | Sil.Store (Exp.Lfield (Exp.Var lhs_id, name, typ), exp_typ, rhs, _) -> - (match exp_typ.Typ.desc with (* block field of a ObjC class *) - | Typ.Tptr ({desc=Tfun _}, _) - when Typ.is_objc_class typ && - is_self proc_data.extras lhs_id && (* lhs is self, rhs is not null *) - not (exp_is_null proc_data.extras rhs) -> - FieldsAssignedInConstructors.add (name, typ) astate - | _ -> astate) - | _ -> astate + | Sil.Load (id, exp, _, _) + -> Ident.IdentHash.add proc_data.extras id exp ; astate + | Sil.Store (Exp.Lfield (Exp.Var lhs_id, name, typ), exp_typ, rhs, _) -> ( + match exp_typ.Typ.desc with + (* block field of a ObjC class *) + | Typ.Tptr ({desc= Tfun _}, _) + when Typ.is_objc_class typ && is_self proc_data.extras lhs_id + && (* lhs is self, rhs is not null *) + not (exp_is_null proc_data.extras rhs) + -> FieldsAssignedInConstructors.add (name, typ) astate + | _ + -> astate ) + | _ + -> astate end (* Tracks when block variables of ObjC classes have been assigned to in constructors *) module FieldsAssignedInConstructorsChecker = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions) - module AnalysisCfg = ProcCfg.Normal -let add_annot annot annot_name = - ({ Annot.class_name = annot_name; parameters = []; }, true) :: annot +let add_annot annot annot_name = ({Annot.class_name= annot_name; parameters= []}, true) :: annot -let add_nonnull_to_selected_field given_field ((fieldname, typ, annot) as field) = - if Typ.Fieldname.equal fieldname given_field && - not (Annotations.ia_is_nullable annot) then +let add_nonnull_to_selected_field given_field (fieldname, typ, annot as field) = + if Typ.Fieldname.equal fieldname given_field && not (Annotations.ia_is_nullable annot) then let new_annot = add_annot annot Annotations.nonnull in (fieldname, typ, new_annot) else field @@ -78,16 +73,18 @@ let add_nonnull_to_selected_field given_field ((fieldname, typ, annot) as field) let add_nonnull_to_fields fields tenv = let add_nonnull_to_field (field, typ) = match Typ.name typ with - | Some typ_name -> - (match Tenv.lookup tenv typ_name with - | Some { fields; statics; supers; methods; annots} -> - let fields_with_annot = - List.map ~f:(add_nonnull_to_selected_field field) fields in - ignore( - Tenv.mk_struct tenv - ~fields: fields_with_annot ~statics ~supers ~methods ~annots typ_name) - | None -> ()) - | None -> () in + | Some typ_name -> ( + match Tenv.lookup tenv typ_name with + | Some {fields; statics; supers; methods; annots} + -> let fields_with_annot = List.map ~f:(add_nonnull_to_selected_field field) fields in + ignore + (Tenv.mk_struct tenv ~fields:fields_with_annot ~statics ~supers ~methods ~annots + typ_name) + | None + -> () ) + | None + -> () + in FieldsAssignedInConstructors.iter add_nonnull_to_field fields let analysis cfg tenv = @@ -95,12 +92,17 @@ let analysis cfg tenv = let f domain pdesc = let proc_name = Procdesc.get_proc_name pdesc in if Typ.Procname.is_constructor proc_name then - match FieldsAssignedInConstructorsChecker.compute_post - (ProcData.make pdesc tenv (Ident.IdentHash.create 10)) ~initial with - | Some new_domain -> - FieldsAssignedInConstructors.union new_domain domain - | None -> domain - else domain in + match + FieldsAssignedInConstructorsChecker.compute_post + (ProcData.make pdesc tenv (Ident.IdentHash.create 10)) + ~initial + with + | Some new_domain + -> FieldsAssignedInConstructors.union new_domain domain + | None + -> domain + else domain + in let procs = Cfg.get_defined_procs cfg in let fields_assigned_in_constructor = List.fold ~f ~init:initial procs in add_nonnull_to_fields fields_assigned_in_constructor tenv diff --git a/infer/src/checkers/NullabilityPreanalysis.mli b/infer/src/checkers/NullabilityPreanalysis.mli index e9193d3c7..66942596b 100644 --- a/infer/src/checkers/NullabilityPreanalysis.mli +++ b/infer/src/checkers/NullabilityPreanalysis.mli @@ -6,10 +6,10 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Module that define preanalysis to derive nullability annotations *) open! IStd (* Analysis the cfg and updates the tenv with nullability annotations *) + val analysis : Cfg.cfg -> Tenv.t -> unit diff --git a/infer/src/checkers/NullabilitySuggest.ml b/infer/src/checkers/NullabilitySuggest.ml index d70729eaa..f9d7d8b30 100644 --- a/infer/src/checkers/NullabilitySuggest.ml +++ b/infer/src/checkers/NullabilitySuggest.ml @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module F = Format module L = Logging module MF = MarkupFormatter @@ -17,27 +16,25 @@ module UseDefChain = struct | DependsOn of (Location.t * AccessPath.Raw.t) | NullDefCompare of (Location.t * AccessPath.Raw.t) | NullDefAssign of (Location.t * AccessPath.Raw.t) - [@@deriving compare] + [@@deriving compare] - let (<=) ~lhs ~rhs = - compare_astate lhs rhs <= 0 + let ( <= ) ~lhs ~rhs = compare_astate lhs rhs <= 0 (* Keep only one chain in join/widen as we are going to report only one * trace to the user eventually. *) - let join lhs rhs = - if (<=) ~lhs ~rhs then rhs else lhs + let join lhs rhs = if ( <= ) ~lhs ~rhs then rhs else lhs - let widen ~prev ~next ~num_iters:_ = - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next let pp fmt = function - | NullDefAssign (loc, ap) -> - F.fprintf fmt "NullDefAssign(%a, %a)" Location.pp loc AccessPath.Raw.pp ap - | NullDefCompare (loc, ap) -> - F.fprintf fmt "NullDefCompare(%a, %a)" Location.pp loc AccessPath.Raw.pp ap - | DependsOn (loc, ap) -> - F.fprintf fmt "DependsOn(%a, %a)" Location.pp loc AccessPath.Raw.pp ap + | NullDefAssign (loc, ap) + -> F.fprintf fmt "NullDefAssign(%a, %a)" Location.pp loc AccessPath.Raw.pp ap + | NullDefCompare (loc, ap) + -> F.fprintf fmt "NullDefCompare(%a, %a)" Location.pp loc AccessPath.Raw.pp ap + | DependsOn (loc, ap) + -> F.fprintf fmt "DependsOn(%a, %a)" Location.pp loc AccessPath.Raw.pp ap end + module Domain = AbstractDomain.Map (AccessPath.Raw) (UseDefChain) type extras = ProcData.no_extras @@ -45,136 +42,126 @@ type extras = ProcData.no_extras module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type nonrec extras = extras let is_access_nullable ap proc_data = match AccessPath.Raw.get_field_and_annotation ap proc_data.ProcData.tenv with - | Some (_, annot_item) -> - Annotations.ia_is_nullable annot_item - | _ -> false + | Some (_, annot_item) + -> Annotations.ia_is_nullable annot_item + | _ + -> false let nullable_usedef_chain_of exp lhs astate loc = match exp with - | HilExp.Constant (Cint n) when IntLit.isnull n -> - Some (UseDefChain.NullDefAssign (loc, lhs)) - | HilExp.AccessPath ap -> - begin - try - match Domain.find ap astate with - | UseDefChain.NullDefCompare _ -> - (* Stop NullDefCompare from propagating here because we want to prevent + | HilExp.Constant Cint n when IntLit.isnull n + -> Some (UseDefChain.NullDefAssign (loc, lhs)) + | HilExp.AccessPath ap -> ( + try + match Domain.find ap astate with + | UseDefChain.NullDefCompare _ + -> (* Stop NullDefCompare from propagating here because we want to prevent * the checker from suggesting @Nullable on y in the following case: * if (x == null) ... else { y = x; } *) - None - | _ -> - Some (UseDefChain.DependsOn (loc, ap)) - with - | Not_found -> None - end - | _ -> None + None + | _ + -> Some (UseDefChain.DependsOn (loc, ap)) + with Not_found -> None ) + | _ + -> None let extract_null_compare_expr = function | HilExp.BinaryOperator ((Eq | Ne), HilExp.AccessPath ap, exp) - | HilExp.BinaryOperator ((Eq | Ne), exp, HilExp.AccessPath ap) -> - Option.some_if (HilExp.is_null_literal exp) ap - | _ -> None + | HilExp.BinaryOperator ((Eq | Ne), exp, HilExp.AccessPath ap) + -> Option.some_if (HilExp.is_null_literal exp) ap + | _ + -> None - let exec_instr (astate : Domain.astate) proc_data _ (instr : HilInstr.t) = + let exec_instr (astate: Domain.astate) proc_data _ (instr: HilInstr.t) = match instr with - | Assume (expr, _, _, loc) -> - begin - match extract_null_compare_expr expr with - | Some ap when not (is_access_nullable ap proc_data) -> - let udchain = UseDefChain.NullDefCompare (loc, ap) in - Domain.add ap udchain astate - | _ -> - astate - end - | Call _ -> - (* For now we just assume the callee always return non-null *) + | Assume (expr, _, _, loc) -> ( + match extract_null_compare_expr expr with + | Some ap when not (is_access_nullable ap proc_data) + -> let udchain = UseDefChain.NullDefCompare (loc, ap) in + Domain.add ap udchain astate + | _ + -> astate ) + | Call _ + -> (* For now we just assume the callee always return non-null *) astate - | Assign (lhs, rhs, loc) -> - if not (is_access_nullable lhs proc_data) then + | Assign (lhs, rhs, loc) + -> if not (is_access_nullable lhs proc_data) then match nullable_usedef_chain_of rhs lhs astate loc with - | Some udchain -> - Domain.add lhs udchain astate - | None -> - astate - else - astate + | Some udchain + -> Domain.add lhs udchain astate + | None + -> astate + else astate end -module Analyzer = - AbstractInterpreter.Make - (ProcCfg.Exceptional) - (LowerHil.Make (TransferFunctions)) +module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (LowerHil.Make (TransferFunctions)) let make_error_trace astate ap ud = let name_of ap = match AccessPath.Raw.get_last_access ap with - | Some (AccessPath.FieldAccess field_name) -> - "Field " ^ (Typ.Fieldname.to_flat_string field_name) - | Some (AccessPath.ArrayAccess _) -> - "Some array element" - | None -> - "Variable" + | Some AccessPath.FieldAccess field_name + -> "Field " ^ Typ.Fieldname.to_flat_string field_name + | Some AccessPath.ArrayAccess _ + -> "Some array element" + | None + -> "Variable" in let open UseDefChain in let rec error_trace_impl depth ap = function - | NullDefAssign (loc, src) -> - let msg = F.sprintf "%s is assigned null here" (name_of src) in + | NullDefAssign (loc, src) + -> let msg = F.sprintf "%s is assigned null here" (name_of src) in let ltr = [Errlog.make_trace_element depth loc msg []] in Some (loc, ltr) - | NullDefCompare (loc, src) -> - let msg = F.sprintf "%s is compared to null here" (name_of src) in + | NullDefCompare (loc, src) + -> let msg = F.sprintf "%s is compared to null here" (name_of src) in let ltr = [Errlog.make_trace_element depth loc msg []] in Some (loc, ltr) | DependsOn (loc, dep) -> - try - let ud' = Domain.find dep astate in - let msg = F.sprintf "%s could be assigned here" (name_of ap) in - let trace_elem = Errlog.make_trace_element depth loc msg [] in - Option.map (error_trace_impl (depth+1) dep ud') ~f:( - fun (_, trace) -> loc, trace_elem :: trace - ) - with - Not_found -> None + try + let ud' = Domain.find dep astate in + let msg = F.sprintf "%s could be assigned here" (name_of ap) in + let trace_elem = Errlog.make_trace_element depth loc msg [] in + Option.map (error_trace_impl (depth + 1) dep ud') ~f:(fun (_, trace) -> + (loc, trace_elem :: trace) ) + with Not_found -> None in error_trace_impl 0 ap ud let pretty_field_name proc_data field_name = match Procdesc.get_proc_name proc_data.ProcData.pdesc with - | Typ.Procname.Java jproc_name -> - let proc_class_name = Typ.Procname.java_get_class_name jproc_name in + | Typ.Procname.Java jproc_name + -> let proc_class_name = Typ.Procname.java_get_class_name jproc_name in let field_class_name = Typ.Fieldname.java_get_class field_name in - if String.equal proc_class_name field_class_name then - Typ.Fieldname.to_flat_string field_name - else - Typ.Fieldname.to_simplified_string field_name - | _ -> - (* This format is subject to change once this checker gets to run on C/Cpp/ObjC *) + if String.equal proc_class_name field_class_name then Typ.Fieldname.to_flat_string field_name + else Typ.Fieldname.to_simplified_string field_name + | _ + -> (* This format is subject to change once this checker gets to run on C/Cpp/ObjC *) Typ.Fieldname.to_string field_name -let checker { Callbacks.summary; proc_desc; tenv; } = - let report astate (proc_data : extras ProcData.t) = +let checker {Callbacks.summary; proc_desc; tenv} = + let report astate (proc_data: extras ProcData.t) = let report_access_path ap udchain = let issue_kind = Localise.to_issue_id Localise.field_should_be_nullable in match AccessPath.Raw.get_field_and_annotation ap proc_data.tenv with - | Some (field_name, _) -> + | Some (field_name, _) + -> ( let message = - F.asprintf "Field %a should be annotated with %a" - MF.pp_monospaced (pretty_field_name proc_data field_name) - MF.pp_monospaced "@Nullable" + F.asprintf "Field %a should be annotated with %a" MF.pp_monospaced + (pretty_field_name proc_data field_name) MF.pp_monospaced "@Nullable" in let exn = Exceptions.Checkers (issue_kind, Localise.verbatim_desc message) in - begin - match make_error_trace astate ap udchain with - | Some (loc, ltr) -> - Reporting.log_warning summary ~loc ~ltr exn - | None -> - Reporting.log_warning summary exn - end - | _ -> () + match make_error_trace astate ap udchain with + | Some (loc, ltr) + -> Reporting.log_warning summary ~loc ~ltr exn + | None + -> Reporting.log_warning summary exn ) + | _ + -> () in Domain.iter report_access_path astate in @@ -184,13 +171,10 @@ let checker { Callbacks.summary; proc_desc; tenv; } = summary else (* Assume all fields are not null in the beginning *) - let initial = Domain.empty, IdAccessPathMapDomain.empty in + let initial = (Domain.empty, IdAccessPathMapDomain.empty) in let proc_data = ProcData.make_default proc_desc tenv in match Analyzer.compute_post proc_data ~initial ~debug:false with - | Some (post, _) -> - report post proc_data; - summary - | None -> - failwithf - "Analyzer failed to compute post for %a" - Typ.Procname.pp proc_name + | Some (post, _) + -> report post proc_data ; summary + | None + -> failwithf "Analyzer failed to compute post for %a" Typ.Procname.pp proc_name diff --git a/infer/src/checkers/NullabilitySuggest.mli b/infer/src/checkers/NullabilitySuggest.mli index 4e3825c84..6a08b01ba 100644 --- a/infer/src/checkers/NullabilitySuggest.mli +++ b/infer/src/checkers/NullabilitySuggest.mli @@ -6,8 +6,8 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Module that suggest adding nullability annotations *) open! IStd -val checker: Callbacks.proc_callback_t + +val checker : Callbacks.proc_callback_t diff --git a/infer/src/checkers/Passthrough.ml b/infer/src/checkers/Passthrough.ml index a44a7647d..d7f403b84 100644 --- a/infer/src/checkers/Passthrough.ml +++ b/infer/src/checkers/Passthrough.ml @@ -8,28 +8,22 @@ *) open! IStd - module F = Format (* for now this is just a call site, but in the future we may add input access path, output kind, etc. depending on what we need *) -type t = - { - site : CallSite.t; - } -[@@deriving compare] - -let make site = - { site } - -let site t = - t.site - -let pp fmt s = - F.fprintf fmt "%a" CallSite.pp s.site - -module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) +type t = {site: CallSite.t} [@@deriving compare] + +let make site = {site} + +let site t = t.site + +let pp fmt s = F.fprintf fmt "%a" CallSite.pp s.site + +module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp +end) diff --git a/infer/src/checkers/Passthrough.mli b/infer/src/checkers/Passthrough.mli index fff95635a..1746a95bc 100644 --- a/infer/src/checkers/Passthrough.mli +++ b/infer/src/checkers/Passthrough.mli @@ -8,7 +8,6 @@ *) open! IStd - module F = Format type t [@@deriving compare] diff --git a/infer/src/checkers/SimpleChecker.ml b/infer/src/checkers/SimpleChecker.ml index d92ef6dbd..6e150c755 100644 --- a/infer/src/checkers/SimpleChecker.ml +++ b/infer/src/checkers/SimpleChecker.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -16,57 +15,57 @@ module L = Logging a type, comparison function, reporting function, and exec function into an analyzer *) module type Spec = sig - type astate (** what state do you want to propagate? *) + (** what state do you want to propagate? *) + type astate - (** implement the state the analysis should start from here *) val initial : astate + (** implement the state the analysis should start from here *) + val exec_instr : + astate -> Sil.instr -> Procdesc.Node.nodekind -> Typ.Procname.t -> Tenv.t -> astate (** implement how an instruction changes your state here. input is the previous state, current instruction, current node kind, current procedure and type environment. *) - val exec_instr : astate -> Sil.instr -> Procdesc.Node.nodekind -> Typ.Procname.t -> Tenv.t -> astate + val report : astate -> Location.t -> Typ.Procname.t -> unit (** log errors here. input is a state, location where the state occurs in the source, and the current procedure. *) - val report : astate -> Location.t -> Typ.Procname.t -> unit val compare : astate -> astate -> int end module type S = sig - (** add YourChecker.checker to registerCallbacks.ml to run your checker *) val checker : Callbacks.proc_callback_t + (** add YourChecker.checker to registerCallbacks.ml to run your checker *) end module Make (Spec : Spec) : S = struct - (* powerset domain over Spec.astate *) module Domain = struct - include - AbstractDomain.FiniteSet - (struct - type t = Spec.astate - let compare = Spec.compare - let pp _ _ = () - end) + include AbstractDomain.FiniteSet (struct + type t = Spec.astate + + let compare = Spec.compare + let pp _ _ = () + end) let widen ~prev ~next ~num_iters = let iters_befor_timeout = 1000 in (* failsafe for accidental non-finite height domains *) - if num_iters >= iters_befor_timeout - then + if num_iters >= iters_befor_timeout then failwith - ("Stopping analysis after 1000 iterations without convergence." ^ - "Make sure your domain is finite height.") + ( "Stopping analysis after 1000 iterations without convergence." + ^ "Make sure your domain is finite height." ) else widen ~prev ~next ~num_iters end module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = ProcData.no_extras let exec_instr astate_set proc_data node instr = @@ -74,32 +73,30 @@ module Make (Spec : Spec) : S = struct let pname = Procdesc.get_proc_name proc_data.ProcData.pdesc in Domain.fold (fun astate acc -> - Domain.add (Spec.exec_instr astate instr node_kind pname proc_data.ProcData.tenv) acc) - astate_set - Domain.empty + Domain.add (Spec.exec_instr astate instr node_kind pname proc_data.ProcData.tenv) acc) + astate_set Domain.empty end module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) - let checker { Callbacks.proc_desc; tenv; summary } : Specs.summary = + let checker {Callbacks.proc_desc; tenv; summary} : Specs.summary = let proc_name = Procdesc.get_proc_name proc_desc in let nodes = Procdesc.get_nodes proc_desc in let do_reporting node_id state = let astate_set = state.AbstractInterpreter.post in - if not (Domain.is_empty astate_set) - then + if not (Domain.is_empty astate_set) then (* should never fail since keys in the invariant map should always be real node id's *) let node = List.find_exn ~f:(fun node -> Procdesc.Node.equal_id node_id (Procdesc.Node.get_id node)) - nodes in + nodes + in Domain.iter - (fun astate -> - Spec.report astate (ProcCfg.Exceptional.loc node) proc_name) - astate_set in + (fun astate -> Spec.report astate (ProcCfg.Exceptional.loc node) proc_name) + astate_set + in let inv_map = - Analyzer.exec_pdesc (ProcData.make_default proc_desc tenv) ~initial:Domain.empty in - Analyzer.InvariantMap.iter do_reporting inv_map; - summary - + Analyzer.exec_pdesc (ProcData.make_default proc_desc tenv) ~initial:Domain.empty + in + Analyzer.InvariantMap.iter do_reporting inv_map ; summary end diff --git a/infer/src/checkers/SimpleChecker.mli b/infer/src/checkers/SimpleChecker.mli index 10ed0b4af..2681a9883 100644 --- a/infer/src/checkers/SimpleChecker.mli +++ b/infer/src/checkers/SimpleChecker.mli @@ -9,17 +9,21 @@ open! IStd -module type Spec = -sig +module type Spec = sig type astate + val initial : astate + val exec_instr : - astate -> - Sil.instr -> Procdesc.Node.nodekind -> Typ.Procname.t -> Tenv.t -> astate + astate -> Sil.instr -> Procdesc.Node.nodekind -> Typ.Procname.t -> Tenv.t -> astate + val report : astate -> Location.t -> Typ.Procname.t -> unit + val compare : astate -> astate -> int end -module type S = sig val checker : Callbacks.proc_callback_t end +module type S = sig + val checker : Callbacks.proc_callback_t +end -module Make : functor (Spec : Spec) -> S +module Make (Spec : Spec) : S diff --git a/infer/src/checkers/Sink.ml b/infer/src/checkers/Sink.ml index ebd6969de..48256caa7 100644 --- a/infer/src/checkers/Sink.ml +++ b/infer/src/checkers/Sink.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -29,39 +28,32 @@ end module Make (Kind : Kind) = struct module Kind = Kind - type t = - { - kind : Kind.t; - site : CallSite.t; - indexes : IntSet.t; - } [@@deriving compare] + type t = {kind: Kind.t; site: CallSite.t; indexes: IntSet.t} [@@deriving compare] - let kind t = - t.kind + let kind t = t.kind - let call_site t = - t.site + let call_site t = t.site - let indexes t = - t.indexes + let indexes t = t.indexes - let make ?(indexes=IntSet.empty) kind site = - { kind; site; indexes; } + let make ?(indexes= IntSet.empty) kind site = {kind; site; indexes} let get site actuals tenv = match Kind.get (CallSite.pname site) actuals tenv with - | Some (kind, indexes) -> Some { kind; site; indexes; } - | None -> None + | Some (kind, indexes) + -> Some {kind; site; indexes} + | None + -> None + + let with_callsite t callee_site = {t with site= callee_site} + + let pp fmt s = F.fprintf fmt "%a(%a)" Kind.pp s.kind CallSite.pp s.site - let with_callsite t callee_site = - { t with site = callee_site; } + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t - let pp fmt s = - F.fprintf fmt "%a(%a)" Kind.pp s.kind CallSite.pp s.site + let compare = compare - module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + let pp = pp + end) end diff --git a/infer/src/checkers/Sink.mli b/infer/src/checkers/Sink.mli index 5e985e823..009c9f20b 100644 --- a/infer/src/checkers/Sink.mli +++ b/infer/src/checkers/Sink.mli @@ -12,18 +12,18 @@ open! IStd module type Kind = sig include TraceElem.Kind + val get : Typ.Procname.t -> HilExp.t list -> Tenv.t -> (t * IntSet.t) option (** return Some kind if the given procname/actuals are a sink, None otherwise *) - val get : Typ.Procname.t -> HilExp.t list -> Tenv.t -> (t * IntSet.t) option end module type S = sig include TraceElem.S - (** return Some sink if the given call site/actuals are a sink, None otherwise *) val get : CallSite.t -> HilExp.t list -> Tenv.t -> t option + (** return Some sink if the given call site/actuals are a sink, None otherwise *) - (** return the indexes where taint can flow into the sink *) val indexes : t -> IntSet.t + (** return the indexes where taint can flow into the sink *) end module Make (Kind : Kind) : S with module Kind = Kind diff --git a/infer/src/checkers/SinkTrace.ml b/infer/src/checkers/SinkTrace.ml index ab305bacc..ea9cd1cae 100644 --- a/infer/src/checkers/SinkTrace.ml +++ b/infer/src/checkers/SinkTrace.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -26,23 +25,25 @@ module type S = sig val of_sink : Sink.t -> t val to_sink_loc_trace : - ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> - sink_path -> Errlog.loc_trace_elem list + ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> sink_path + -> Errlog.loc_trace_elem list end -module MakeSink(TraceElem : TraceElem.S) = struct +module MakeSink (TraceElem : TraceElem.S) = struct include TraceElem let get _ _ _ = None + let indexes _ = IntSet.empty end module Make (TraceElem : TraceElem.S) = struct - include Trace.Make(struct - module Source = Source.Dummy - module Sink = MakeSink(TraceElem) - let should_report _ _ = true - end) + include Trace.Make (struct + module Source = Source.Dummy + module Sink = MakeSink (TraceElem) + + let should_report _ _ = true + end) type sink_path = Passthroughs.t * (Sink.t * Passthroughs.t) list @@ -52,7 +53,7 @@ module Make (TraceElem : TraceElem.S) = struct let get_reportable_sink_paths t ~trace_of_pname = List.map - ~f:(fun (passthroughs, _, sinks) -> passthroughs, sinks) + ~f:(fun (passthroughs, _, sinks) -> (passthroughs, sinks)) (get_reportable_paths t ~trace_of_pname) let to_sink_loc_trace ?desc_of_sink ?sink_should_nest (passthroughs, sinks) = @@ -61,8 +62,8 @@ module Make (TraceElem : TraceElem.S) = struct let with_callsite t call_site = List.fold ~f:(fun t_acc sink -> - let callee_sink = Sink.with_callsite sink call_site in - add_sink callee_sink t_acc) + let callee_sink = Sink.with_callsite sink call_site in + add_sink callee_sink t_acc) ~init:empty (Sinks.elements (sinks t)) @@ -72,16 +73,16 @@ module Make (TraceElem : TraceElem.S) = struct let get_reportable_sink_path sink ~trace_of_pname = match get_reportable_sink_paths (of_sink sink) ~trace_of_pname with - | [] -> None - | [report] -> Some report - | _ -> failwithf "Should not get >1 report for 1 sink" + | [] + -> None + | [report] + -> Some report + | _ + -> failwithf "Should not get >1 report for 1 sink" let pp fmt t = let pp_passthroughs_if_not_empty fmt p = - if not (Passthroughs.is_empty p) then - F.fprintf fmt " via %a" Passthroughs.pp p in - F.fprintf - fmt - "%a%a" - Sinks.pp (sinks t) pp_passthroughs_if_not_empty (passthroughs t) + if not (Passthroughs.is_empty p) then F.fprintf fmt " via %a" Passthroughs.pp p + in + F.fprintf fmt "%a%a" Sinks.pp (sinks t) pp_passthroughs_if_not_empty (passthroughs t) end diff --git a/infer/src/checkers/SinkTrace.mli b/infer/src/checkers/SinkTrace.mli index 5e6b468cc..938c539cc 100644 --- a/infer/src/checkers/SinkTrace.mli +++ b/infer/src/checkers/SinkTrace.mli @@ -17,20 +17,20 @@ module type S = sig passthroughs for each callee *) type sink_path = Passthrough.Set.t * (Sink.t * Passthrough.Set.t) list - (** get a path for each of the reportable flows to a sink in this trace *) val get_reportable_sink_paths : t -> trace_of_pname:(Typ.Procname.t -> t) -> sink_path list + (** get a path for each of the reportable flows to a sink in this trace *) - (** get a report for a single sink *) val get_reportable_sink_path : Sink.t -> trace_of_pname:(Typ.Procname.t -> t) -> sink_path option + (** get a report for a single sink *) - (** update sink with the given call site *) val with_callsite : t -> CallSite.t -> t + (** update sink with the given call site *) val of_sink : Sink.t -> t val to_sink_loc_trace : - ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> - sink_path -> Errlog.loc_trace_elem list + ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> sink_path + -> Errlog.loc_trace_elem list end module MakeSink (TraceElem : TraceElem.S) : diff --git a/infer/src/checkers/Siof.ml b/infer/src/checkers/Siof.ml index b4df69fe5..2f868070b 100644 --- a/infer/src/checkers/Siof.ml +++ b/infer/src/checkers/Siof.ml @@ -8,103 +8,107 @@ *) open! IStd - module F = Format module L = Logging - module GlobalsAccesses = SiofTrace.GlobalsAccesses let methods_whitelist = QualifiedCppName.Match.of_fuzzy_qual_names Config.siof_safe_methods -let is_whitelisted (pname : Typ.Procname.t) = - Typ.Procname.get_qualifiers pname - |> QualifiedCppName.Match.match_qualifiers methods_whitelist +let is_whitelisted (pname: Typ.Procname.t) = + Typ.Procname.get_qualifiers pname |> QualifiedCppName.Match.match_qualifiers methods_whitelist -type siof_model = { - qual_name : string; (** (fuzzy) name of the method, eg "std::ios_base::Init::Init" *) - initialized_globals : string list; (** names of variables that are guaranteed to be initialized +type siof_model = + { qual_name: string (** (fuzzy) name of the method, eg "std::ios_base::Init::Init" *) + ; initialized_globals: string list + (** names of variables that are guaranteed to be initialized once the method is executed, eg ["std::cerr"] *) -} + } -let parse_siof_model (qual_name, initialized_globals) = { qual_name; initialized_globals; } +let parse_siof_model (qual_name, initialized_globals) = {qual_name; initialized_globals} -let models = List.map ~f:parse_siof_model [ - ("std::ios_base::Init::Init", [ - "std::cerr"; "std::wcerr"; - "std::cin"; "std::wcin"; - "std::clog"; "std::wclog"; - "std::cout"; "std::wcout"; - ]); - ] +let models = + List.map ~f:parse_siof_model + [ ( "std::ios_base::Init::Init" + , [ "std::cerr" + ; "std::wcerr" + ; "std::cin" + ; "std::wcin" + ; "std::clog" + ; "std::wclog" + ; "std::cout" + ; "std::wcout" ] ) ] let is_modelled = let models_matcher = - List.map models ~f:(fun {qual_name} -> qual_name) - |> QualifiedCppName.Match.of_fuzzy_qual_names in + List.map models ~f:(fun {qual_name} -> qual_name) |> QualifiedCppName.Match.of_fuzzy_qual_names + in fun pname -> - Typ.Procname.get_qualifiers pname - |> QualifiedCppName.Match.match_qualifiers models_matcher + Typ.Procname.get_qualifiers pname |> QualifiedCppName.Match.match_qualifiers models_matcher module Summary = Summary.Make (struct - type payload = SiofDomain.astate + type payload = SiofDomain.astate - let update_payload astate (summary : Specs.summary) = - { summary with payload = { summary.payload with siof = Some astate }} + let update_payload astate (summary: Specs.summary) = + {summary with payload= {summary.payload with siof= Some astate}} - let read_payload (summary : Specs.summary) = - summary.payload.siof - end) + let read_payload (summary: Specs.summary) = summary.payload.siof +end) module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = SiofDomain + type extras = ProcData.no_extras let is_compile_time_constructed pdesc pv = let init_pname = Pvar.get_initializer_pname pv in match Option.bind init_pname ~f:(Summary.read_summary pdesc) with - | Some (Domain.BottomSiofTrace.Bottom, _)-> - (* we analyzed the initializer for this global and found that it doesn't require any runtime + | Some (Domain.BottomSiofTrace.Bottom, _) + -> (* we analyzed the initializer for this global and found that it doesn't require any runtime initialization so cannot participate in SIOF *) true - | _ -> - false + | _ + -> false let get_globals pdesc loc e = let is_dangerous_global pv = - Pvar.is_global pv - && not (Pvar.is_static_local pv) - && not (Pvar.is_pod pv) - && not (Pvar.is_compile_constant pv) - && not (is_compile_time_constructed pdesc pv) in + Pvar.is_global pv && not (Pvar.is_static_local pv) && not (Pvar.is_pod pv) + && not (Pvar.is_compile_constant pv) && not (is_compile_time_constructed pdesc pv) + in let globals_accesses = - Exp.get_vars e |> snd |> List.filter ~f:is_dangerous_global - |> List.map ~f:(fun v -> (v, loc)) in + Exp.get_vars e |> snd |> List.filter ~f:is_dangerous_global |> List.map ~f:(fun v -> (v, loc)) + in GlobalsAccesses.of_list globals_accesses let filter_global_accesses initialized globals = let initialized_matcher = - Domain.VarNames.elements initialized - |> QualifiedCppName.Match.of_fuzzy_qual_names in + Domain.VarNames.elements initialized |> QualifiedCppName.Match.of_fuzzy_qual_names + in (* gvar \notin initialized, up to some fuzzing *) let f (gvar, _) = QualifiedCppName.of_qual_string (Pvar.to_string gvar) - |> Fn.non (QualifiedCppName.Match.match_qualifiers initialized_matcher) in + |> Fn.non (QualifiedCppName.Match.match_qualifiers initialized_matcher) + in GlobalsAccesses.filter f globals let add_globals astate outer_loc globals = - if GlobalsAccesses.is_empty globals then - astate + if GlobalsAccesses.is_empty globals then astate else - let trace = match fst astate with - | Domain.BottomSiofTrace.Bottom -> SiofTrace.empty - | Domain.BottomSiofTrace.NonBottom t -> t in + let trace = + match fst astate with + | Domain.BottomSiofTrace.Bottom + -> SiofTrace.empty + | Domain.BottomSiofTrace.NonBottom t + -> t + in (* filter out variables that are known to be already initialized *) let non_init_globals = let initialized = snd astate in - filter_global_accesses initialized globals in + filter_global_accesses initialized globals + in let globals_trace = - SiofTrace.add_sink (SiofTrace.make_access non_init_globals outer_loc) trace in + SiofTrace.add_sink (SiofTrace.make_access non_init_globals outer_loc) trace + in (Domain.BottomSiofTrace.NonBottom globals_trace, snd astate) let add_params_globals astate pdesc call_loc params = @@ -113,139 +117,151 @@ module TransferFunctions (CFG : ProcCfg.S) = struct |> add_globals astate (Procdesc.get_loc pdesc) let at_least_nonbottom = - Domain.join ((Domain.BottomSiofTrace.NonBottom SiofTrace.empty), Domain.VarNames.empty) + Domain.join (Domain.BottomSiofTrace.NonBottom SiofTrace.empty, Domain.VarNames.empty) - let exec_instr astate { ProcData.pdesc; } _ (instr : Sil.instr) = + let exec_instr astate {ProcData.pdesc} _ (instr: Sil.instr) = match instr with - | Load (_, exp, _, loc) - | Store (_, _, exp, loc) - | Prune (exp, loc, _, _) -> - let proc_loc = Procdesc.get_loc pdesc in + | Load (_, exp, _, loc) | Store (_, _, exp, loc) | Prune (exp, loc, _, _) + -> let proc_loc = Procdesc.get_loc pdesc in get_globals pdesc loc exp |> add_globals astate proc_loc - | Call (_, Const (Cfun callee_pname), _, _, _) when is_whitelisted callee_pname -> - at_least_nonbottom astate - | Call (_, Const (Cfun callee_pname), _, _, _) when is_modelled callee_pname -> - let init = List.find_map_exn models - ~f:(fun {qual_name; initialized_globals} -> - if QualifiedCppName.Match.of_fuzzy_qual_names [qual_name] - |> Fn.flip QualifiedCppName.Match.match_qualifiers - (Typ.Procname.get_qualifiers callee_pname) then - Some initialized_globals - else - None) in - Domain.join astate (Domain.BottomSiofTrace.NonBottom SiofTrace.empty, - Domain.VarNames.of_list init) - | Call (_, Const (Cfun callee_pname), _::params_without_self, loc, _) + | Call (_, Const Cfun callee_pname, _, _, _) when is_whitelisted callee_pname + -> at_least_nonbottom astate + | Call (_, Const Cfun callee_pname, _, _, _) when is_modelled callee_pname + -> let init = + List.find_map_exn models ~f:(fun {qual_name; initialized_globals} -> + if QualifiedCppName.Match.of_fuzzy_qual_names [qual_name] + |> Fn.flip QualifiedCppName.Match.match_qualifiers + (Typ.Procname.get_qualifiers callee_pname) + then Some initialized_globals + else None ) + in + Domain.join astate + (Domain.BottomSiofTrace.NonBottom SiofTrace.empty, Domain.VarNames.of_list init) + | Call (_, Const Cfun callee_pname, _ :: params_without_self, loc, _) when Typ.Procname.is_c_method callee_pname && Typ.Procname.is_constructor callee_pname - && Typ.Procname.is_constexpr callee_pname -> - add_params_globals astate pdesc loc params_without_self - | Call (_, Const (Cfun callee_pname), params, loc, _) -> - let callsite = CallSite.make callee_pname loc in - let callee_astate = match Summary.read_summary pdesc callee_pname with - | Some (Domain.BottomSiofTrace.NonBottom trace, initialized_globals) -> - let trace_without_initialized_globals = + && Typ.Procname.is_constexpr callee_pname + -> add_params_globals astate pdesc loc params_without_self + | Call (_, Const Cfun callee_pname, params, loc, _) + -> let callsite = CallSite.make callee_pname loc in + let callee_astate = + match Summary.read_summary pdesc callee_pname with + | Some (Domain.BottomSiofTrace.NonBottom trace, initialized_globals) + -> let trace_without_initialized_globals = let sinks_with_non_init_globals = - SiofTrace.Sinks.filter (fun sink -> + SiofTrace.Sinks.filter + (fun sink -> filter_global_accesses (snd astate) (SiofTrace.Sink.kind sink) - |> Fn.non GlobalsAccesses.is_empty) (SiofTrace.sinks trace) in - SiofTrace.update_sinks trace sinks_with_non_init_globals in - (Domain.BottomSiofTrace.NonBottom - (SiofTrace.with_callsite trace_without_initialized_globals callsite), - initialized_globals) - | Some ((Domain.BottomSiofTrace.Bottom, _) as astate) -> - astate - | None -> - (Domain.BottomSiofTrace.Bottom, Domain.VarNames.empty) in - add_params_globals astate pdesc loc params - |> Domain.join callee_astate - |> - (* make sure it's not Bottom: we made a function call so this needs initialization *) - at_least_nonbottom - | Call (_, _, params, loc, _) -> - add_params_globals astate pdesc loc params - |> - (* make sure it's not Bottom: we made a function call so this needs initialization *) - at_least_nonbottom - | Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ -> - astate + |> Fn.non GlobalsAccesses.is_empty) + (SiofTrace.sinks trace) + in + SiofTrace.update_sinks trace sinks_with_non_init_globals + in + ( Domain.BottomSiofTrace.NonBottom + (SiofTrace.with_callsite trace_without_initialized_globals callsite) + , initialized_globals ) + | Some (Domain.BottomSiofTrace.Bottom, _ as astate) + -> astate + | None + -> (Domain.BottomSiofTrace.Bottom, Domain.VarNames.empty) + in + add_params_globals astate pdesc loc params |> Domain.join callee_astate + |> (* make sure it's not Bottom: we made a function call so this needs initialization *) + at_least_nonbottom + | Call (_, _, params, loc, _) + -> add_params_globals astate pdesc loc params + |> (* make sure it's not Bottom: we made a function call so this needs initialization *) + at_least_nonbottom + | Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ + -> astate end module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions) let is_foreign tu_opt (v, _) = - match Pvar.get_translation_unit v, tu_opt with - | TUFile v_tu, Some current_tu -> - not (SourceFile.equal current_tu v_tu) - | TUExtern, Some _ -> - true - | _, None -> - invalid_arg "cannot be called with translation unit set to None" + match (Pvar.get_translation_unit v, tu_opt) with + | TUFile v_tu, Some current_tu + -> not (SourceFile.equal current_tu v_tu) + | TUExtern, Some _ + -> true + | _, None + -> invalid_arg "cannot be called with translation unit set to None" let report_siof summary trace pdesc gname loc = let tu_opt = let attrs = Procdesc.get_attributes pdesc in - attrs.ProcAttributes.translation_unit in + attrs.ProcAttributes.translation_unit + in let trace_of_pname pname = match Summary.read_summary pdesc pname with - | Some (SiofDomain.BottomSiofTrace.NonBottom summary, _) -> summary - | _ -> SiofTrace.empty in - + | Some (SiofDomain.BottomSiofTrace.NonBottom summary, _) + -> summary + | _ + -> SiofTrace.empty + in let report_one_path (passthroughs, path) = - let description, sink_path' = match path with - | [] -> assert false - | (final_sink, pt)::rest -> - let foreign_globals = - SiofTrace.Sink.kind final_sink |> GlobalsAccesses.filter (is_foreign tu_opt) in + let description, sink_path' = + match path with + | [] + -> assert false + | (final_sink, pt) :: rest + -> let foreign_globals = + SiofTrace.Sink.kind final_sink |> GlobalsAccesses.filter (is_foreign tu_opt) + in let final_sink' = let loc = CallSite.loc (SiofTrace.Sink.call_site final_sink) in - SiofTrace.make_access foreign_globals loc in + SiofTrace.make_access foreign_globals loc + in let description = F.asprintf "Initializer of %s accesses global variables from a different translation unit: %a" - gname - GlobalsAccesses.pp foreign_globals in - description, (passthroughs, (final_sink', pt)::rest) in + gname GlobalsAccesses.pp foreign_globals + in + (description, (passthroughs, (final_sink', pt) :: rest)) + in let ltr = SiofTrace.trace_of_error loc gname sink_path' in let msg = Localise.to_issue_id Localise.static_initialization_order_fiasco in let exn = Exceptions.Checkers (msg, Localise.verbatim_desc description) in - Reporting.log_error summary ~loc ~ltr exn in - + Reporting.log_error summary ~loc ~ltr exn + in let has_foreign_sink (_, path) = List.exists - ~f:(fun (sink, _) -> - GlobalsAccesses.exists (is_foreign tu_opt) - (SiofTrace.Sink.kind sink)) - path in - - SiofTrace.get_reportable_sink_paths trace ~trace_of_pname - |> List.filter ~f:has_foreign_sink + ~f:(fun (sink, _) -> GlobalsAccesses.exists (is_foreign tu_opt) (SiofTrace.Sink.kind sink)) + path + in + SiofTrace.get_reportable_sink_paths trace ~trace_of_pname |> List.filter ~f:has_foreign_sink |> List.iter ~f:report_one_path -let siof_check pdesc gname (summary : Specs.summary) = +let siof_check pdesc gname (summary: Specs.summary) = match summary.payload.siof with - | Some ((SiofDomain.BottomSiofTrace.NonBottom post, _)) -> - let attrs = Procdesc.get_attributes pdesc in - let all_globals = SiofTrace.Sinks.fold + | Some (SiofDomain.BottomSiofTrace.NonBottom post, _) + -> let attrs = Procdesc.get_attributes pdesc in + let all_globals = + SiofTrace.Sinks.fold (fun sink -> GlobalsAccesses.union (SiofTrace.Sink.kind sink)) - (SiofTrace.sinks post) GlobalsAccesses.empty in + (SiofTrace.sinks post) GlobalsAccesses.empty + in let tu_opt = let attrs = Procdesc.get_attributes pdesc in - attrs.ProcAttributes.translation_unit in + attrs.ProcAttributes.translation_unit + in if GlobalsAccesses.exists (is_foreign tu_opt) all_globals then - report_siof summary post pdesc gname attrs.ProcAttributes.loc; - | Some (SiofDomain.BottomSiofTrace.Bottom, _) | None -> - () + report_siof summary post pdesc gname attrs.ProcAttributes.loc + | Some (SiofDomain.BottomSiofTrace.Bottom, _) | None + -> () -let checker { Callbacks.proc_desc; tenv; summary; } : Specs.summary = +let checker {Callbacks.proc_desc; tenv; summary} : Specs.summary = let proc_data = ProcData.make_default proc_desc tenv in - let initial = SiofDomain.BottomSiofTrace.Bottom, SiofDomain.VarNames.empty in + let initial = (SiofDomain.BottomSiofTrace.Bottom, SiofDomain.VarNames.empty) in let updated_summary = match Analyzer.compute_post proc_data ~initial with - | Some post -> Summary.update_summary (SiofDomain.normalize post) summary - | None -> summary in - begin - match Typ.Procname.get_global_name_of_initializer (Procdesc.get_proc_name proc_desc) with - | Some gname -> siof_check proc_desc gname updated_summary - | None -> () - end; + | Some post + -> Summary.update_summary (SiofDomain.normalize post) summary + | None + -> summary + in + ( match Typ.Procname.get_global_name_of_initializer (Procdesc.get_proc_name proc_desc) with + | Some gname + -> siof_check proc_desc gname updated_summary + | None + -> () ) ; updated_summary diff --git a/infer/src/checkers/Siof.mli b/infer/src/checkers/Siof.mli index 0fecddd16..97f0d7573 100644 --- a/infer/src/checkers/Siof.mli +++ b/infer/src/checkers/Siof.mli @@ -9,4 +9,4 @@ open! IStd -val checker: Callbacks.proc_callback_t +val checker : Callbacks.proc_callback_t diff --git a/infer/src/checkers/SiofDomain.ml b/infer/src/checkers/SiofDomain.ml index 915a85a2b..56e2fe31c 100644 --- a/infer/src/checkers/SiofDomain.ml +++ b/infer/src/checkers/SiofDomain.ml @@ -8,34 +8,31 @@ *) open! IStd - -module VarNames = AbstractDomain.FiniteSet(String) - -module BottomSiofTrace = AbstractDomain.BottomLifted(SiofTrace) - -include AbstractDomain.Pair - (BottomSiofTrace) - (VarNames) +module VarNames = AbstractDomain.FiniteSet (String) +module BottomSiofTrace = AbstractDomain.BottomLifted (SiofTrace) +include AbstractDomain.Pair (BottomSiofTrace) (VarNames) (** group together procedure-local accesses *) -let normalize ((trace, initialized) as astate) = match trace with - | BottomSiofTrace.Bottom -> astate - | BottomSiofTrace.NonBottom trace -> - let elems = SiofTrace.Sinks.elements (SiofTrace.sinks trace) in - let (direct, indirect) = List.partition_tf ~f:SiofTrace.is_intraprocedural_access elems in +let normalize (trace, initialized as astate) = + match trace with + | BottomSiofTrace.Bottom + -> astate + | BottomSiofTrace.NonBottom trace + -> let elems = SiofTrace.Sinks.elements (SiofTrace.sinks trace) in + let direct, indirect = List.partition_tf ~f:SiofTrace.is_intraprocedural_access elems in match direct with - | [] | _::[] -> astate - | access::_ -> - (* [loc] should be the same for all local accesses: it's the loc of the enclosing + | [] | [_] + -> astate + | access :: _ + -> (* [loc] should be the same for all local accesses: it's the loc of the enclosing procdesc. Use the loc of the first access. *) let loc = CallSite.loc (SiofTrace.Sink.call_site access) in let kind = List.map ~f:SiofTrace.Sink.kind direct - |> List.fold - ~f:SiofTrace.GlobalsAccesses.union - ~init:SiofTrace.GlobalsAccesses.empty in + |> List.fold ~f:SiofTrace.GlobalsAccesses.union ~init:SiofTrace.GlobalsAccesses.empty + in let trace' = - SiofTrace.make_access kind loc::indirect - |> SiofTrace.Sinks.of_list - |> SiofTrace.update_sinks trace in + SiofTrace.make_access kind loc :: indirect |> SiofTrace.Sinks.of_list + |> SiofTrace.update_sinks trace + in (BottomSiofTrace.NonBottom trace', initialized) diff --git a/infer/src/checkers/SiofDomain.mli b/infer/src/checkers/SiofDomain.mli index 734e5c3ae..526cd018a 100644 --- a/infer/src/checkers/SiofDomain.mli +++ b/infer/src/checkers/SiofDomain.mli @@ -9,9 +9,9 @@ open! IStd -module VarNames : module type of AbstractDomain.FiniteSet(String) +module VarNames : module type of AbstractDomain.FiniteSet (String) -module BottomSiofTrace : module type of AbstractDomain.BottomLifted(SiofTrace) +module BottomSiofTrace : module type of AbstractDomain.BottomLifted (SiofTrace) (* The domain for the analysis is: @@ -28,9 +28,8 @@ module BottomSiofTrace : module type of AbstractDomain.BottomLifted(SiofTrace) - On the other hand, the set of variables that are guaranteed to be initialized when the function terminates (even before main() has started). For instance, this is the case for std::ios_base::Init::Init(). *) -include module type of AbstractDomain.Pair - (AbstractDomain.BottomLifted(SiofTrace)) - (VarNames) -(** group together procedure-local accesses *) +include module type of AbstractDomain.Pair (AbstractDomain.BottomLifted (SiofTrace)) (VarNames) + val normalize : astate -> astate +(** group together procedure-local accesses *) diff --git a/infer/src/checkers/SiofTrace.ml b/infer/src/checkers/SiofTrace.ml index 2f3904bfd..acad39f34 100644 --- a/infer/src/checkers/SiofTrace.ml +++ b/infer/src/checkers/SiofTrace.ml @@ -9,90 +9,86 @@ open! IStd open! PVariant - module F = Format module L = Logging module GlobalsAccesses = PrettyPrintable.MakePPSet (struct - type t = (Pvar.t * Location.t) - let compare (v1, l1) (v2, l2) = - (* compare by loc first to present reports in the right order *) - [%compare : (Location.t * Pvar.t)] (l1, v1) (l2, v2) - let pp fmt (v, _) = - F.fprintf fmt "%a|%a" Mangled.pp (Pvar.get_name v) - Pvar.pp_translation_unit (Pvar.get_translation_unit v) - end) + type t = Pvar.t * Location.t + + let compare (v1, l1) (v2, l2) = + (* compare by loc first to present reports in the right order *) + [%compare : Location.t * Pvar.t] (l1, v1) (l2, v2) + + let pp fmt (v, _) = + F.fprintf fmt "%a|%a" Mangled.pp (Pvar.get_name v) Pvar.pp_translation_unit + (Pvar.get_translation_unit v) +end) module TraceElem = struct module Kind = GlobalsAccesses - type t = { - site : CallSite.t; - kind: [`Call | `Access] * Kind.t; - } [@@deriving compare] + type t = {site: CallSite.t; kind: [`Call | `Access] * Kind.t} [@@deriving compare] - let call_site { site; } = site + let call_site {site} = site - let kind { kind; } = snd kind + let kind {kind} = snd kind - let make ?indexes:_ kind site = { kind = (`Call, kind); site; } + let make ?indexes:_ kind site = {kind= (`Call, kind); site} - let with_callsite { kind=(_, kind); } site = { kind=(`Call, kind); site; } + let with_callsite {kind= _, kind} site = {kind= (`Call, kind); site} - let pp fmt { site; kind; } = + let pp fmt {site; kind} = F.fprintf fmt "%saccess to %a" - (match fst kind with | `Call -> "indirect " | `Access -> "") - Kind.pp (snd kind); - match fst kind with - | `Call -> F.fprintf fmt " at %a" CallSite.pp site - | `Access -> () + (match fst kind with `Call -> "indirect " | `Access -> "") + Kind.pp (snd kind) ; + match fst kind with `Call -> F.fprintf fmt " at %a" CallSite.pp site | `Access -> () module Set = PrettyPrintable.MakePPSet (struct - (* Don't use nonrec due to https://github.com/janestreet/ppx_compare/issues/2 *) - (* type nonrec t = t [@@deriving compare]; *) - type nonrec t = t - let compare = compare - let pp = pp - end) + (* Don't use nonrec due to https://github.com/janestreet/ppx_compare/issues/2 *) + (* type nonrec t = t [@@deriving compare]; *) + type nonrec t = t + let compare = compare + + let pp = pp + end) end -include SinkTrace.Make(TraceElem) +include SinkTrace.Make (TraceElem) let make_access kind loc = let site = CallSite.make Typ.Procname.empty_block loc in - { TraceElem.kind = (`Access, kind); site; } + {TraceElem.kind= (`Access, kind); site} -let is_intraprocedural_access { TraceElem.kind=(kind, _); } = kind = `Access +let is_intraprocedural_access {TraceElem.kind= kind, _} = kind = `Access let trace_of_error loc gname path = let desc_of_sink sink = let callsite = Sink.call_site sink in - if is_intraprocedural_access sink then - Format.asprintf "%a" Sink.pp sink - else - Format.asprintf "call to %a" Typ.Procname.pp (CallSite.pname callsite) in + if is_intraprocedural_access sink then Format.asprintf "%a" Sink.pp sink + else Format.asprintf "call to %a" Typ.Procname.pp (CallSite.pname callsite) + in let sink_should_nest sink = not (is_intraprocedural_access sink) in let trace_elem_of_global = - Errlog.make_trace_element 0 loc - (Format.asprintf "initialization of %s" gname) - [] in + Errlog.make_trace_element 0 loc (Format.asprintf "initialization of %s" gname) [] + in let trace = let trace_with_set_of_globals = to_sink_loc_trace ~desc_of_sink ~sink_should_nest path in (* the last element of the trace gotten by [to_sink_loc_trace] contains a set of procedure-local accesses to globals. We want to remove it in exchange for as many trace elems as there are accesses. *) match (List.rev trace_with_set_of_globals, snd path) with - | telem::rest, ({TraceElem.kind = (`Access, globals)}, _)::_ -> - let nesting = telem.Errlog.lt_level in + | telem :: rest, ({TraceElem.kind= `Access, globals}, _) :: _ + -> let nesting = telem.Errlog.lt_level in let add_trace_elem_of_access err_trace (global, loc) = Errlog.make_trace_element nesting loc (Format.asprintf "access to %a" Mangled.pp (Pvar.get_name global)) [] - ::err_trace in - GlobalsAccesses.elements globals - |> List.fold ~f:add_trace_elem_of_access ~init:rest + :: err_trace + in + GlobalsAccesses.elements globals |> List.fold ~f:add_trace_elem_of_access ~init:rest |> List.rev - | _ -> trace_with_set_of_globals + | _ + -> trace_with_set_of_globals in - trace_elem_of_global::trace + trace_elem_of_global :: trace diff --git a/infer/src/checkers/SiofTrace.mli b/infer/src/checkers/SiofTrace.mli index a891e8225..a02546d57 100644 --- a/infer/src/checkers/SiofTrace.mli +++ b/infer/src/checkers/SiofTrace.mli @@ -9,7 +9,7 @@ open! IStd -module GlobalsAccesses : PrettyPrintable.PPSet with type elt = (Pvar.t * Location.t) +module GlobalsAccesses : PrettyPrintable.PPSet with type elt = Pvar.t * Location.t include SinkTrace.S with type Sink.Kind.t = GlobalsAccesses.t diff --git a/infer/src/checkers/Source.ml b/infer/src/checkers/Source.ml index 98e9d1053..9798ba450 100644 --- a/infer/src/checkers/Source.ml +++ b/infer/src/checkers/Source.ml @@ -8,12 +8,10 @@ *) open! IStd - module F = Format let all_formals_untainted pdesc = - let make_untainted (name, typ) = - name, typ, None in + let make_untainted (name, typ) = (name, typ, None) in List.map ~f:make_untainted (Procdesc.get_formals pdesc) module type Kind = sig @@ -29,17 +27,13 @@ end module type S = sig include TraceElem.S - type spec = - { - source : t; - index : int option; - } + type spec = {source: t; index: int option} val is_footprint : t -> bool val make_footprint : AccessPath.t -> Procdesc.t -> t - val get_footprint_access_path: t -> AccessPath.t option + val get_footprint_access_path : t -> AccessPath.t option val get : CallSite.t -> Tenv.t -> spec option @@ -50,86 +44,69 @@ module Make (Kind : Kind) = struct module Kind = Kind type kind = - | Normal of Kind.t (** known source returned directly or transitively from a callee *) - | Footprint of AccessPath.t (** unknown source read from the environment *) - [@@deriving compare] + | Normal of Kind.t (** known source returned directly or transitively from a callee *) + | Footprint of AccessPath.t (** unknown source read from the environment *) + [@@deriving compare] let pp_kind fmt = function - | Normal kind -> Kind.pp fmt kind - | Footprint ap -> F.fprintf fmt "Footprint(%a)" AccessPath.pp ap + | Normal kind + -> Kind.pp fmt kind + | Footprint ap + -> F.fprintf fmt "Footprint(%a)" AccessPath.pp ap - type t = - { - kind : kind; - site : CallSite.t; - } [@@deriving compare] + type t = {kind: kind; site: CallSite.t} [@@deriving compare] - type spec = - { - source : t; - index : int option; - } + type spec = {source: t; index: int option} - let is_footprint t = match t.kind with - | Footprint _ -> true - | _ -> false + let is_footprint t = match t.kind with Footprint _ -> true | _ -> false - let get_footprint_access_path t = match t.kind with - | Footprint ap -> Some ap - | _ -> None + let get_footprint_access_path t = match t.kind with Footprint ap -> Some ap | _ -> None - let call_site t = - t.site + let call_site t = t.site - let kind t = match t.kind with - | Normal kind -> kind - | Footprint _ -> Kind.unknown + let kind t = match t.kind with Normal kind -> kind | Footprint _ -> Kind.unknown - let make ?indexes:_ kind site = - { site; kind = Normal kind; } + let make ?indexes:_ kind site = {site; kind= Normal kind} let make_footprint ap pdesc = let kind = Footprint ap in let site = CallSite.make (Procdesc.get_proc_name pdesc) (Procdesc.get_loc pdesc) in - { site; kind; } + {site; kind} - let get site tenv = match Kind.get (CallSite.pname site) tenv with - | Some (kind, index) -> - let source = make kind site in - Some { source; index; } - | None -> - None + let get site tenv = + match Kind.get (CallSite.pname site) tenv with + | Some (kind, index) + -> let source = make kind site in + Some {source; index} + | None + -> None let get_tainted_formals pdesc tenv = let site = CallSite.make (Procdesc.get_proc_name pdesc) (Procdesc.get_loc pdesc) in List.map ~f:(fun (name, typ, kind_opt) -> - name, typ, Option.map kind_opt ~f:(fun kind -> make kind site)) + (name, typ, Option.map kind_opt ~f:(fun kind -> make kind site))) (Kind.get_tainted_formals pdesc tenv) - let pp fmt s = - F.fprintf fmt "%a(%a)" pp_kind s.kind CallSite.pp s.site + let pp fmt s = F.fprintf fmt "%a(%a)" pp_kind s.kind CallSite.pp s.site let with_callsite t callee_site = - if is_footprint t - then failwithf "Can't change the call site of footprint source %a" pp t; - { t with site = callee_site; } - - module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + if is_footprint t then failwithf "Can't change the call site of footprint source %a" pp t ; + {t with site= callee_site} + + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) end module Dummy = struct type t = unit [@@deriving compare] - type spec = - { - source : t; - index : int option; - } + type spec = {source: t; index: int option} let call_site _ = CallSite.dummy @@ -142,24 +119,29 @@ module Dummy = struct let is_footprint _ = false let make_footprint _ _ = assert false + let get_footprint_access_path _ = assert false let get _ _ = None - let get_tainted_formals pdesc _= - List.map ~f:(fun (name, typ) -> name, typ, None) (Procdesc.get_formals pdesc) + let get_tainted_formals pdesc _ = + List.map ~f:(fun (name, typ) -> (name, typ, None)) (Procdesc.get_formals pdesc) module Kind = struct type nonrec t = t + let compare = compare + let pp = pp end - module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) let with_callsite t _ = t end diff --git a/infer/src/checkers/Source.mli b/infer/src/checkers/Source.mli index f178bf830..217a9a1bb 100644 --- a/infer/src/checkers/Source.mli +++ b/infer/src/checkers/Source.mli @@ -9,49 +9,45 @@ open! IStd -(** specify that all the formals of the procdesc are not tainted *) val all_formals_untainted : Procdesc.t -> (Mangled.t * Typ.t * 'a option) list +(** specify that all the formals of the procdesc are not tainted *) module type Kind = sig include TraceElem.Kind - (** kind of an unknown source *) val unknown : t + (** kind of an unknown source *) - (** return Some (kind) if the procedure is a taint source, None otherwise *) val get : Typ.Procname.t -> Tenv.t -> (t * int option) option + (** return Some (kind) if the procedure is a taint source, None otherwise *) + val get_tainted_formals : Procdesc.t -> Tenv.t -> (Mangled.t * Typ.t * t option) list (** return each formal of the function paired with either Some(kind) if the formal is a taint source, or None if the formal is not a taint source *) - val get_tainted_formals : Procdesc.t -> Tenv.t -> (Mangled.t * Typ.t * t option) list end module type S = sig include TraceElem.S type spec = - { - source : t; - (** type of the returned source *) - index : int option; - (** index of the returned source if Some; return value if None *) - } + { source: t (** type of the returned source *) + ; index: int option (** index of the returned source if Some; return value if None *) } - (** return true if the current source is a footprint source *) val is_footprint : t -> bool + (** return true if the current source is a footprint source *) - (** create a footprint source for the value read from the given access path. *) val make_footprint : AccessPath.t -> Procdesc.t -> t + (** create a footprint source for the value read from the given access path. *) + val get_footprint_access_path : t -> AccessPath.t option (** return Some(access path) if the current source is a footprint source, None otherwise *) - val get_footprint_access_path: t -> AccessPath.t option - (** return Some (taint spec) if the call site is a taint source, None otherwise *) val get : CallSite.t -> Tenv.t -> spec option + (** return Some (taint spec) if the call site is a taint source, None otherwise *) + val get_tainted_formals : Procdesc.t -> Tenv.t -> (Mangled.t * Typ.t * t option) list (** return each formal of the function paired with either Some(source) if the formal is a taint source, or None if the formal is not a taint source *) - val get_tainted_formals : Procdesc.t -> Tenv.t -> (Mangled.t * Typ.t * t option) list end module Make (Kind : Kind) : S with module Kind = Kind diff --git a/infer/src/checkers/Stacktrace.ml b/infer/src/checkers/Stacktrace.ml index e786090e1..f12fa3475 100644 --- a/infer/src/checkers/Stacktrace.ml +++ b/infer/src/checkers/Stacktrace.ml @@ -10,106 +10,103 @@ (** Module for parsing stack traces and using them to guide Infer analysis *) open! IStd - module F = Format -type frame = { - class_str : string; - method_str : string; - file_str : string; - line_num : int option; -} +type frame = {class_str: string; method_str: string; file_str: string; line_num: int option} -type t = { - exception_name: string; - frames: frame list; -} +type t = {exception_name: string; frames: frame list} let new_line_regexp = Str.regexp "\n" (* Pre-compute the regular expression matchers used to parse: *) (* Stack frames into (procedure, location) tuples. *) let frame_regexp = Str.regexp "\t*at \\(.*\\)(\\(.*\\))" + (* procedures into class and method name. *) let procname_regexp = Str.regexp "\\(.*\\)\\.\\(.*\\)" + (* locations into file and line number information. *) let file_and_line_regexp = Str.regexp "\\(.*\\):\\([0-9]+\\)" + (* exception information lines into thread id and exception type *) let exception_regexp = Str.regexp "Exception in thread \"\\(.*\\)\" \\(.*\\)" -let make exception_name frames = { exception_name; frames; } +let make exception_name frames = {exception_name; frames} -let make_frame class_str method_str file_str line_num = - { class_str; method_str; file_str; line_num; } +let make_frame class_str method_str file_str line_num = {class_str; method_str; file_str; line_num} let frame_matches_location frame_obj loc = - let lfname = if SourceFile.is_invalid loc.Location.file then None - else Some (SourceFile.to_string loc.Location.file) in - let matches_file = Option.value_map lfname ~default:false - ~f:(String.is_suffix ~suffix:frame_obj.file_str) in - let matches_line = match frame_obj.line_num with - | None -> false - | Some line -> Int.equal line loc.Location.line in + let lfname = + if SourceFile.is_invalid loc.Location.file then None + else Some (SourceFile.to_string loc.Location.file) + in + let matches_file = + Option.value_map lfname ~default:false ~f:(String.is_suffix ~suffix:frame_obj.file_str) + in + let matches_line = + match frame_obj.line_num with None -> false | Some line -> Int.equal line loc.Location.line + in matches_file && matches_line let parse_stack_frame frame_str = (* separate the qualified method name and the parenthesized text/line number*) - ignore(Str.string_match frame_regexp frame_str 0); + ignore (Str.string_match frame_regexp frame_str 0) ; let qualified_procname = Str.matched_group 1 frame_str in let file_and_line = Str.matched_group 2 frame_str in (* separate the class name from the method name *) - ignore(Str.string_match procname_regexp qualified_procname 0); + ignore (Str.string_match procname_regexp qualified_procname 0) ; let class_str = Str.matched_group 1 qualified_procname in let method_str = Str.matched_group 2 qualified_procname in (* Native methods don't have debugging info *) if String.equal file_and_line "Native Method" then make_frame class_str method_str "Native Method" None - else begin + else (* Separate the filename and line number. note that a few methods might not have line number information, for those, file_and_line includes only the filename. *) let is_file_line = Str.string_match file_and_line_regexp file_and_line 0 in - let file_str, line_num = if is_file_line - then Str.matched_group 1 file_and_line, - Some (int_of_string (Str.matched_group 2 file_and_line)) - else file_and_line, None in + let file_str, line_num = + if is_file_line then + ( Str.matched_group 1 file_and_line + , Some (int_of_string (Str.matched_group 2 file_and_line)) ) + else (file_and_line, None) + in make_frame class_str method_str file_str line_num - end let parse_exception_line exception_line = - ignore(Str.string_match exception_regexp exception_line 0); + ignore (Str.string_match exception_regexp exception_line 0) ; let exception_name = Str.matched_group 2 exception_line in exception_name let of_string s = let lines = Str.split new_line_regexp s in match lines with - | exception_line :: trace -> - let exception_name = parse_exception_line exception_line in + | exception_line :: trace + -> let exception_name = parse_exception_line exception_line in let parsed = List.map ~f:parse_stack_frame trace in make exception_name parsed - | [] -> failwith "Empty stack trace" + | [] + -> failwith "Empty stack trace" let of_json filename json = let exception_name_key = "exception_type" in let frames_key = "stack_trace" in let extract_json_member key = match Yojson.Basic.Util.member key json with - | `Null -> failwithf "Missing key in supplied JSON data: %s (in file %s)" key filename - | item -> item in - let exception_name = - Yojson.Basic.Util.to_string (extract_json_member exception_name_key) in + | `Null + -> failwithf "Missing key in supplied JSON data: %s (in file %s)" key filename + | item + -> item + in + let exception_name = Yojson.Basic.Util.to_string (extract_json_member exception_name_key) in let frames = Yojson.Basic.Util.to_list (extract_json_member frames_key) - |> List.map ~f:Yojson.Basic.Util.to_string - |> List.map ~f:String.strip - |> List.filter ~f:(fun s -> s <> "") - |> List.map ~f:parse_stack_frame in + |> List.map ~f:Yojson.Basic.Util.to_string |> List.map ~f:String.strip + |> List.filter ~f:(fun s -> s <> "") |> List.map ~f:parse_stack_frame + in make exception_name frames let of_json_file filename = - try - of_json filename (Yojson.Basic.from_file filename) + try of_json filename (Yojson.Basic.from_file filename) with Sys_error msg | Yojson.Json_error msg -> - failwithf "Could not read or parse the supplied JSON stacktrace file %s :@\n %s" - filename msg + failwithf "Could not read or parse the supplied JSON stacktrace file %s :@\n %s" filename msg diff --git a/infer/src/checkers/Stacktrace.mli b/infer/src/checkers/Stacktrace.mli index 64b7c87d0..472708f31 100644 --- a/infer/src/checkers/Stacktrace.mli +++ b/infer/src/checkers/Stacktrace.mli @@ -11,17 +11,9 @@ open! IStd (** Module for parsing stack traces and using them to guide Infer analysis *) -type frame = { - class_str : string; - method_str : string; - file_str : string; - line_num : int option; -} - -type t = { - exception_name: string; - frames: frame list; -} +type frame = {class_str: string; method_str: string; file_str: string; line_num: int option} + +type t = {exception_name: string; frames: frame list} val make : string -> frame list -> t diff --git a/infer/src/checkers/ThreadSafety.ml b/infer/src/checkers/ThreadSafety.ml index 9f3dfcffa..3f0eafe4a 100644 --- a/infer/src/checkers/ThreadSafety.ml +++ b/infer/src/checkers/ThreadSafety.ml @@ -8,24 +8,22 @@ *) open! IStd - module F = Format module L = Logging module MF = MarkupFormatter module Summary = Summary.Make (struct - type payload = ThreadSafetyDomain.summary + type payload = ThreadSafetyDomain.summary - let update_payload post (summary : Specs.summary) = - { summary with payload = { summary.payload with threadsafety = Some post }} + let update_payload post (summary: Specs.summary) = + {summary with payload= {summary.payload with threadsafety= Some post}} - let read_payload (summary : Specs.summary) = - summary.payload.threadsafety - end) + let read_payload (summary: Specs.summary) = summary.payload.threadsafety +end) let is_owned access_path attribute_map = - ThreadSafetyDomain.AttributeMapDomain.has_attribute - access_path ThreadSafetyDomain.Attribute.unconditionally_owned attribute_map + ThreadSafetyDomain.AttributeMapDomain.has_attribute access_path + ThreadSafetyDomain.Attribute.unconditionally_owned attribute_map let container_write_string = "infer.dummy.__CONTAINERWRITE__" @@ -33,20 +31,23 @@ let container_write_string = "infer.dummy.__CONTAINERWRITE__" let get_container_write_desc sink = let (base_var, _), access_list = fst (ThreadSafetyDomain.TraceElem.kind sink) in let get_container_write_desc_ call_name container_name = - match String.chop_prefix (Typ.Fieldname.to_string call_name) ~prefix:container_write_string with - | Some call_name -> Some (container_name, call_name) - | None -> None in - + match + String.chop_prefix (Typ.Fieldname.to_string call_name) ~prefix:container_write_string + with + | Some call_name + -> Some (container_name, call_name) + | None + -> None + in match List.rev access_list with - | FieldAccess call_name :: FieldAccess container_name :: _ -> - get_container_write_desc_ call_name (Typ.Fieldname.to_string container_name) - | [FieldAccess call_name] -> - get_container_write_desc_ call_name (F.asprintf "%a" Var.pp base_var) - | _ -> - None + | (FieldAccess call_name) :: (FieldAccess container_name) :: _ + -> get_container_write_desc_ call_name (Typ.Fieldname.to_string container_name) + | [(FieldAccess call_name)] + -> get_container_write_desc_ call_name (F.asprintf "%a" Var.pp base_var) + | _ + -> None -let is_container_write_sink sink = - Option.is_some (get_container_write_desc sink) +let is_container_write_sink sink = Option.is_some (get_container_write_desc sink) (*Bit of redundancy with code in is_unprotected, might alter later *) let make_excluder locks threads = @@ -58,117 +59,113 @@ let make_excluder locks threads = module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = ThreadSafetyDomain + type extras = FormalMap.t - type lock_model = - | Lock - | Unlock - | LockedIfTrue - | NoEffect + type lock_model = Lock | Unlock | LockedIfTrue | NoEffect - type thread_model = - | Threaded - | Unknown - | ThreadedIfTrue + type thread_model = Threaded | Unknown | ThreadedIfTrue let is_thread_utils_type java_pname = - let pn = (Typ.Procname.java_get_class_name java_pname) in - String.is_suffix ~suffix:"ThreadUtils" pn - || String.is_suffix ~suffix:"ThreadUtil" pn + let pn = Typ.Procname.java_get_class_name java_pname in + String.is_suffix ~suffix:"ThreadUtils" pn || String.is_suffix ~suffix:"ThreadUtil" pn let is_thread_utils_method method_name_str = function - | Typ.Procname.Java java_pname -> - is_thread_utils_type java_pname + | Typ.Procname.Java java_pname + -> is_thread_utils_type java_pname && String.equal (Typ.Procname.java_get_method java_pname) method_name_str - | _ -> false + | _ + -> false let get_lock_model = let is_cpp_lock = - let matcher_lock = QualifiedCppName.Match.of_fuzzy_qual_names [ - "std::mutex::lock"; "std::unique_lock::lock"] in - let matcher_lock_constructor = QualifiedCppName.Match.of_fuzzy_qual_names [ - "std::lock_guard::lock_guard"; "std::unique_lock::unique_lock"] in + let matcher_lock = + QualifiedCppName.Match.of_fuzzy_qual_names ["std::mutex::lock"; "std::unique_lock::lock"] + in + let matcher_lock_constructor = + QualifiedCppName.Match.of_fuzzy_qual_names + ["std::lock_guard::lock_guard"; "std::unique_lock::unique_lock"] + in fun pname actuals -> QualifiedCppName.Match.match_qualifiers matcher_lock (Typ.Procname.get_qualifiers pname) - || (QualifiedCppName.Match.match_qualifiers matcher_lock_constructor - (Typ.Procname.get_qualifiers pname) - (* Passing additional parameter allows to defer the lock *) - && (Int.equal 2 (List.length actuals))) + || QualifiedCppName.Match.match_qualifiers matcher_lock_constructor + (Typ.Procname.get_qualifiers pname) + (* Passing additional parameter allows to defer the lock *) + && Int.equal 2 (List.length actuals) and is_cpp_unlock = - let matcher = QualifiedCppName.Match.of_fuzzy_qual_names - ["std::mutex::unlock"; "std::unique_lock::unlock"] in + let matcher = + QualifiedCppName.Match.of_fuzzy_qual_names + ["std::mutex::unlock"; "std::unique_lock::unlock"] + in fun pname -> QualifiedCppName.Match.match_qualifiers matcher (Typ.Procname.get_qualifiers pname) in fun pname actuals -> match pname with - | Typ.Procname.Java java_pname -> + | Typ.Procname.Java java_pname + -> ( if is_thread_utils_method "assertHoldsLock" (Typ.Procname.Java java_pname) then Lock else - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | ("java.util.concurrent.locks.Lock" - | "java.util.concurrent.locks.ReentrantLock" + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | ( ( "java.util.concurrent.locks.Lock" | "java.util.concurrent.locks.ReentrantLock" | "java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock" - | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock"), - ("lock" | "lockInterruptibly") -> - Lock - | ("java.util.concurrent.locks.Lock" - |"java.util.concurrent.locks.ReentrantLock" + | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock" ) + , ("lock" | "lockInterruptibly") ) + -> Lock + | ( ( "java.util.concurrent.locks.Lock" | "java.util.concurrent.locks.ReentrantLock" | "java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock" - | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock"), - "unlock" -> - Unlock - | ("java.util.concurrent.locks.Lock" - | "java.util.concurrent.locks.ReentrantLock" + | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock" ) + , "unlock" ) + -> Unlock + | ( ( "java.util.concurrent.locks.Lock" | "java.util.concurrent.locks.ReentrantLock" | "java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock" - | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock"), - "tryLock" -> - LockedIfTrue - | "com.facebook.buck.util.concurrent.AutoCloseableReadWriteUpdateLock", - ("readLock" | "updateLock" | "writeLock") -> - Lock - | _ -> - NoEffect - end - | (Typ.Procname.ObjC_Cpp _ as pname) when is_cpp_lock pname actuals -> - Lock - | (Typ.Procname.ObjC_Cpp _ as pname) when is_cpp_unlock pname -> - Unlock - | pname when Typ.Procname.equal pname BuiltinDecl.__set_locked_attribute -> - Lock - | pname when Typ.Procname.equal pname BuiltinDecl.__delete_locked_attribute -> - Unlock - | _ -> - NoEffect + | "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock" ) + , "tryLock" ) + -> LockedIfTrue + | ( "com.facebook.buck.util.concurrent.AutoCloseableReadWriteUpdateLock" + , ("readLock" | "updateLock" | "writeLock") ) + -> Lock + | _ + -> NoEffect ) + | Typ.Procname.ObjC_Cpp _ as pname when is_cpp_lock pname actuals + -> Lock + | Typ.Procname.ObjC_Cpp _ as pname when is_cpp_unlock pname + -> Unlock + | pname when Typ.Procname.equal pname BuiltinDecl.__set_locked_attribute + -> Lock + | pname when Typ.Procname.equal pname BuiltinDecl.__delete_locked_attribute + -> Unlock + | _ + -> NoEffect let get_thread_model = function - | Typ.Procname.Java java_pname -> - if is_thread_utils_type java_pname then + | Typ.Procname.Java java_pname + -> if is_thread_utils_type java_pname then match Typ.Procname.java_get_method java_pname with - | "assertMainThread" - | "checkOnMainThread" - | "assertOnUiThread" -> Threaded - | "isMainThread" - | "isUiThread" -> ThreadedIfTrue - | _ -> Unknown + | "assertMainThread" | "checkOnMainThread" | "assertOnUiThread" + -> Threaded + | "isMainThread" | "isUiThread" + -> ThreadedIfTrue + | _ + -> Unknown else Unknown (*Note we are not modelling assertOnNonUiThread or assertOnBackgroundThread. These treated as Unknown*) - | _ -> Unknown + | _ + -> Unknown let add_conditional_ownership_attribute access_path formal_map attribute_map attributes = match FormalMap.get_formal_index (fst access_path) formal_map with - | Some formal_index when not (is_owned access_path attribute_map) -> - Domain.AttributeSetDomain.add (Domain.Attribute.OwnedIf (Some formal_index)) attributes - | _ -> - attributes - + | Some formal_index when not (is_owned access_path attribute_map) + -> Domain.AttributeSetDomain.add (Domain.Attribute.OwnedIf (Some formal_index)) attributes + | _ + -> attributes let remove_ownership_attributes attributes = Domain.AttributeSetDomain.filter - (function | Domain.Attribute.OwnedIf _ -> false | _ -> true) + (function Domain.Attribute.OwnedIf _ -> false | _ -> true) attributes (* propagate attributes from the leaves to the root of an RHS Hil expression. @@ -177,35 +174,28 @@ module TransferFunctions (CFG : ProcCfg.S) = struct let open HilExp in let open Domain in match e with - | Constant _ -> - AttributeSetDomain.of_list [Attribute.unconditionally_owned; Attribute.Functional] - | AccessPath ap -> - begin - try AttributeMapDomain.find ap attribute_map - with Not_found -> AttributeSetDomain.empty - end + | Constant _ + -> AttributeSetDomain.of_list [Attribute.unconditionally_owned; Attribute.Functional] + | AccessPath ap + -> ( try AttributeMapDomain.find ap attribute_map + with Not_found -> AttributeSetDomain.empty ) |> add_conditional_ownership_attribute ap formal_map attribute_map - | Exception expr (* treat exceptions as transparent wrt attributes *) - | Cast(_, expr) -> - attributes_of_expr formal_map attribute_map expr - | UnaryOperator(_, expr, _) -> - attributes_of_expr formal_map attribute_map expr - |> remove_ownership_attributes - | BinaryOperator(_, expr1, expr2) -> - let attributes1 = attributes_of_expr formal_map attribute_map expr1 in + | Exception expr (* treat exceptions as transparent wrt attributes *) | Cast (_, expr) + -> attributes_of_expr formal_map attribute_map expr + | UnaryOperator (_, expr, _) + -> attributes_of_expr formal_map attribute_map expr |> remove_ownership_attributes + | BinaryOperator (_, expr1, expr2) + -> let attributes1 = attributes_of_expr formal_map attribute_map expr1 in let attributes2 = attributes_of_expr formal_map attribute_map expr2 in - AttributeSetDomain.join attributes1 attributes2 - |> remove_ownership_attributes - | Closure _ | Sizeof _ -> - AttributeSetDomain.empty + AttributeSetDomain.join attributes1 attributes2 |> remove_ownership_attributes + | Closure _ | Sizeof _ + -> AttributeSetDomain.empty (* will return true on x.f.g.h when x.f and x.f.g are owned, but not requiring x.f.g.h *) (* must not be called with an empty access list *) let all_prefixes_owned (base, accesses) attribute_map = let but_last_rev = List.rev accesses |> List.tl_exn in - let rec aux acc = function - | [] -> acc - | (_::tail) as all -> aux ((List.rev all)::acc) tail in + let rec aux acc = function [] -> acc | _ :: tail as all -> aux (List.rev all :: acc) tail in let prefixes = aux [] but_last_rev in List.for_all ~f:(fun ap -> is_owned (base, ap) attribute_map) prefixes @@ -214,18 +204,21 @@ module TransferFunctions (CFG : ProcCfg.S) = struct let lhs_root = fst lhs_access_path in let filter_on_globals = (* do not assign ownership to access paths rooted at globals *) - if Var.is_global (fst lhs_root) - then remove_ownership_attributes else Fn.id + if Var.is_global (fst lhs_root) then remove_ownership_attributes else Fn.id in let filter_on_lhs = (* do not assign ownership when lhs is not a single var or a single-field ap whose root is conditionally owned, or, all prefixes are owned *) match snd lhs_access_path with - | [] -> Fn.id - | [_] when FormalMap.is_formal lhs_root formal_map -> Fn.id - | _ when all_prefixes_owned lhs_access_path attribute_map -> Fn.id - | _ -> remove_ownership_attributes + | [] + -> Fn.id + | [_] when FormalMap.is_formal lhs_root formal_map + -> Fn.id + | _ when all_prefixes_owned lhs_access_path attribute_map + -> Fn.id + | _ + -> remove_ownership_attributes in let final_attributes = filter_on_globals rhs_attributes |> filter_on_lhs in Domain.AttributeMapDomain.add lhs_access_path final_attributes attribute_map @@ -233,610 +226,586 @@ module TransferFunctions (CFG : ProcCfg.S) = struct let propagate_return_attributes ret_opt ret_attributes actuals attribute_map formal_map = let open Domain in match ret_opt with - | None -> - attribute_map - | Some ret -> - let ownership_attributes, other_attributes = - AttributeSetDomain.partition (function | OwnedIf _ -> true | _ -> false) ret_attributes in + | None + -> attribute_map + | Some ret + -> let ownership_attributes, other_attributes = + AttributeSetDomain.partition (function OwnedIf _ -> true | _ -> false) ret_attributes + in let caller_return_attributes = match AttributeSetDomain.elements ownership_attributes with - | [] -> other_attributes - | [(OwnedIf None) as unconditionally_owned] -> - AttributeSetDomain.add unconditionally_owned other_attributes - | [OwnedIf (Some formal_index)] -> - begin - match List.nth actuals formal_index with - | Some (HilExp.AccessPath actual_ap) -> - if is_owned actual_ap attribute_map - then - AttributeSetDomain.add Attribute.unconditionally_owned other_attributes - else - add_conditional_ownership_attribute - actual_ap formal_map attribute_map other_attributes - | Some (HilExp.Constant _) -> - AttributeSetDomain.add Attribute.unconditionally_owned other_attributes - | _ -> + | [] + -> other_attributes + | [(OwnedIf None as unconditionally_owned)] + -> AttributeSetDomain.add unconditionally_owned other_attributes + | [(OwnedIf Some formal_index)] -> ( + match List.nth actuals formal_index with + | Some HilExp.AccessPath actual_ap + -> if is_owned actual_ap attribute_map then + AttributeSetDomain.add Attribute.unconditionally_owned other_attributes + else + add_conditional_ownership_attribute actual_ap formal_map attribute_map other_attributes - end - | _multiple_ownership_attributes -> - (* TODO: handle multiple ownership attributes *) - other_attributes in + | Some HilExp.Constant _ + -> AttributeSetDomain.add Attribute.unconditionally_owned other_attributes + | _ + -> other_attributes ) + | _multiple_ownership_attributes + -> (* TODO: handle multiple ownership attributes *) + other_attributes + in AttributeMapDomain.add (ret, []) caller_return_attributes attribute_map let is_unprotected is_locked is_threaded pdesc = - not is_locked && not is_threaded - && not (Procdesc.is_java_synchronized pdesc) + not is_locked && not is_threaded && not (Procdesc.is_java_synchronized pdesc) - - let add_access - exp loc access_kind accesses locks threads - attribute_map (proc_data : FormalMap.t ProcData.t) = + let add_access exp loc access_kind accesses locks threads attribute_map + (proc_data: FormalMap.t ProcData.t) = let open Domain in (* we don't want to warn on accesses to the field if it is (a) thread-confined, or (b) volatile *) let is_safe_access access prefix_path tenv = - match access, AccessPath.Raw.get_typ prefix_path tenv with - | AccessPath.FieldAccess fieldname, - Some ({Typ.desc=Tstruct typename} | {desc=Tptr ({desc=Tstruct typename}, _)}) -> - begin - match Tenv.lookup tenv typename with - | Some struct_typ -> - Annotations.struct_typ_has_annot struct_typ Annotations.ia_is_thread_confined || - Annotations.field_has_annot - fieldname struct_typ Annotations.ia_is_thread_confined || - Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_volatile - | None -> - false - end - | _ -> - false in + match (access, AccessPath.Raw.get_typ prefix_path tenv) with + | ( AccessPath.FieldAccess fieldname + , Some ({Typ.desc= Tstruct typename} | {desc= Tptr ({desc= Tstruct typename}, _)}) ) -> ( + match Tenv.lookup tenv typename with + | Some struct_typ + -> Annotations.struct_typ_has_annot struct_typ Annotations.ia_is_thread_confined + || Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_thread_confined + || Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_volatile + | None + -> false ) + | _ + -> false + in let rec add_field_accesses pre prefix_path access_acc = function - | [] -> - access_acc - | access :: access_list' -> - let kind = - if List.is_empty access_list' - then access_kind - else ThreadSafetyDomain.Access.Read in - let access_path = fst prefix_path, (snd prefix_path) @ [access] in + | [] + -> access_acc + | access :: access_list' + -> let kind = + if List.is_empty access_list' then access_kind else ThreadSafetyDomain.Access.Read + in + let access_path = (fst prefix_path, snd prefix_path @ [access]) in let access_acc' = - if is_owned prefix_path attribute_map || - is_safe_access access prefix_path proc_data.tenv - then - access_acc + if is_owned prefix_path attribute_map + || is_safe_access access prefix_path proc_data.tenv + then access_acc else (* TODO: I think there's a utility function for this somewhere *) let accesses = AccessDomain.get_accesses pre access_acc in let accesses' = PathDomain.add_sink (make_access access_path kind loc) accesses in - AccessDomain.add pre accesses' access_acc in - add_field_accesses pre access_path access_acc' access_list' in + AccessDomain.add pre accesses' access_acc + in + add_field_accesses pre access_path access_acc' access_list' + in let add_access_ acc (base, accesses) = - if List.is_empty accesses - then acc + if List.is_empty accesses then acc else let pre = - if is_unprotected locks threads proc_data.pdesc - then + if is_unprotected locks threads proc_data.pdesc then match FormalMap.get_formal_index base proc_data.extras with - | Some formal_index -> AccessPrecondition.Unprotected (Some formal_index) - | None -> AccessPrecondition.unprotected + | Some formal_index + -> AccessPrecondition.Unprotected (Some formal_index) + | None + -> AccessPrecondition.unprotected else AccessPrecondition.Protected - (make_excluder (locks || Procdesc.is_java_synchronized proc_data.pdesc) - threads) in - add_field_accesses pre (base, []) acc accesses in - + (make_excluder (locks || Procdesc.is_java_synchronized proc_data.pdesc) threads) + in + add_field_accesses pre (base, []) acc accesses + in List.fold ~f:add_access_ ~init:accesses (HilExp.get_access_paths exp) let has_return_annot predicate pn = - Annotations.pname_has_return_annot - pn - ~attrs_of_pname:Specs.proc_resolve_attributes - predicate + Annotations.pname_has_return_annot pn ~attrs_of_pname:Specs.proc_resolve_attributes predicate let is_functional pname = - let is_annotated_functional = - has_return_annot Annotations.ia_is_functional in + let is_annotated_functional = has_return_annot Annotations.ia_is_functional in let is_modeled_functional = function - | Typ.Procname.Java java_pname -> - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | "android.content.res.Resources", method_name -> - (* all methods of Resources are considered @Functional except for the ones in this + | Typ.Procname.Java java_pname -> ( + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | "android.content.res.Resources", method_name + -> (* all methods of Resources are considered @Functional except for the ones in this blacklist *) - let non_functional_resource_methods = [ - "getAssets"; - "getConfiguration"; - "getSystem"; - "newTheme"; - "openRawResource"; - "openRawResourceFd" - ] in - not (List.mem ~equal:String.equal non_functional_resource_methods method_name) - | _ -> - false - end - | _ -> - false in + let non_functional_resource_methods = + [ "getAssets" + ; "getConfiguration" + ; "getSystem" + ; "newTheme" + ; "openRawResource" + ; "openRawResourceFd" ] + in + not (List.mem ~equal:String.equal non_functional_resource_methods method_name) + | _ + -> false ) + | _ + -> false + in is_annotated_functional pname || is_modeled_functional pname let acquires_ownership pname tenv = let is_allocation pn = - Typ.Procname.equal pn BuiltinDecl.__new || Typ.Procname.equal pn BuiltinDecl.__new_array in + Typ.Procname.equal pn BuiltinDecl.__new || Typ.Procname.equal pn BuiltinDecl.__new_array + in (* identify library functions that maintain ownership invariants behind the scenes *) let is_owned_in_library = function - | Typ.Procname.Java java_pname -> - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | "javax.inject.Provider", "get" -> - (* in dependency injection, the library allocates fresh values behind the scenes *) - true - | ("java.lang.Class" | "java.lang.reflect.Constructor"), "newInstance" -> - (* reflection can perform allocations *) - true - | "java.lang.Object" , "clone" -> - (* cloning is like allocation *) - true - | "java.lang.ThreadLocal", "get" -> - (* ThreadLocal prevents sharing between threads behind the scenes *) - true - | ("android.app.Activity" | "android.view.View"), "findViewById" -> - (* assume findViewById creates fresh View's (note: not always true) *) - true - | "android.support.v4.util.Pools$Pool", "acquire" -> - (* a pool should own all of its objects *) - true - | _ -> - false - end - | _ -> - false in - is_allocation pname || - is_owned_in_library pname || - PatternMatch.override_exists is_owned_in_library tenv pname - - let is_container_write pn tenv = match pn with - | Typ.Procname.Java java_pname -> - let typename = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name java_pname) in + | Typ.Procname.Java java_pname -> ( + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | "javax.inject.Provider", "get" + -> (* in dependency injection, the library allocates fresh values behind the scenes *) + true + | ("java.lang.Class" | "java.lang.reflect.Constructor"), "newInstance" + -> (* reflection can perform allocations *) + true + | "java.lang.Object", "clone" + -> (* cloning is like allocation *) + true + | "java.lang.ThreadLocal", "get" + -> (* ThreadLocal prevents sharing between threads behind the scenes *) + true + | ("android.app.Activity" | "android.view.View"), "findViewById" + -> (* assume findViewById creates fresh View's (note: not always true) *) + true + | "android.support.v4.util.Pools$Pool", "acquire" + -> (* a pool should own all of its objects *) + true + | _ + -> false ) + | _ + -> false + in + is_allocation pname || is_owned_in_library pname + || PatternMatch.override_exists is_owned_in_library tenv pname + + let is_container_write pn tenv = + match pn with + | Typ.Procname.Java java_pname + -> let typename = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name java_pname) in let is_container_write_ typename _ = - match Typ.Name.name typename, Typ.Procname.java_get_method java_pname with - | ("android.util.SparseArray" | "android.support.v4.util.SparseArrayCompat"), - ("append" | "clear" | "delete" | "put" | "remove" | "removeAt" | "removeAtRange" - | "setValueAt") -> true - | "android.support.v4.util.SimpleArrayMap", - ("clear" | "ensureCapacity" | "put" | "putAll" | "remove" | "removeAt" - | "setValueAt") -> true - | "android.support.v4.util.Pools$SimplePool", - ("acquire" | "release") -> true - | "java.util.List", ("add" | "addAll" | "clear" | "remove" | "set") -> true - | "java.util.Map", ("clear" | "put" | "putAll" | "remove") -> true - | _ -> false in + match (Typ.Name.name typename, Typ.Procname.java_get_method java_pname) with + | ( ("android.util.SparseArray" | "android.support.v4.util.SparseArrayCompat") + , ( "append" | "clear" | "delete" | "put" | "remove" | "removeAt" | "removeAtRange" + | "setValueAt" ) ) + -> true + | ( "android.support.v4.util.SimpleArrayMap" + , ("clear" | "ensureCapacity" | "put" | "putAll" | "remove" | "removeAt" | "setValueAt") + ) + -> true + | "android.support.v4.util.Pools$SimplePool", ("acquire" | "release") + -> true + | "java.util.List", ("add" | "addAll" | "clear" | "remove" | "set") + -> true + | "java.util.Map", ("clear" | "put" | "putAll" | "remove") + -> true + | _ + -> false + in PatternMatch.supertype_exists tenv is_container_write_ typename - | _ -> - false + | _ + -> false - let is_threadsafe_collection pn tenv = match pn with - | Typ.Procname.Java java_pname -> - let typename = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name java_pname) in + let is_threadsafe_collection pn tenv = + match pn with + | Typ.Procname.Java java_pname + -> let typename = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name java_pname) in let aux tn _ = match Typ.Name.name tn with | "java.util.concurrent.ConcurrentMap" | "java.util.concurrent.CopyOnWriteArrayList" - | "android.support.v4.util.Pools$SynchronizedPool" -> true - | _ -> false in + | "android.support.v4.util.Pools$SynchronizedPool" + -> true + | _ + -> false + in PatternMatch.supertype_exists tenv aux typename - | _ -> false + | _ + -> false - let is_synchronized_container callee_pname ((_, (base_typ : Typ.t)), accesses) tenv = - if is_threadsafe_collection callee_pname tenv - then - true + let is_synchronized_container callee_pname ((_, (base_typ: Typ.t)), accesses) tenv = + if is_threadsafe_collection callee_pname tenv then true else let is_annotated_synchronized base_typename container_field tenv = match Tenv.lookup tenv base_typename with - | Some base_typ -> - Annotations.field_has_annot - container_field - base_typ Annotations.ia_is_synchronized_collection - | None -> - false in + | Some base_typ + -> Annotations.field_has_annot container_field base_typ + Annotations.ia_is_synchronized_collection + | None + -> false + in match List.rev accesses with - | AccessPath.FieldAccess base_field :: - AccessPath.FieldAccess container_field :: _-> - let base_typename = - Typ.Name.Java.from_string (Typ.Fieldname.java_get_class base_field) in + | (AccessPath.FieldAccess base_field) :: (AccessPath.FieldAccess container_field) :: _ + -> let base_typename = + Typ.Name.Java.from_string (Typ.Fieldname.java_get_class base_field) + in is_annotated_synchronized base_typename container_field tenv - | [AccessPath.FieldAccess container_field] -> - begin - match base_typ.desc with - | Typ.Tstruct base_typename | Tptr ({Typ.desc=Tstruct base_typename}, _) -> - is_annotated_synchronized base_typename container_field tenv - | _ -> - false - end - | _ -> - false + | [(AccessPath.FieldAccess container_field)] -> ( + match base_typ.desc with + | Typ.Tstruct base_typename | Tptr ({Typ.desc= Tstruct base_typename}, _) + -> is_annotated_synchronized base_typename container_field tenv + | _ + -> false ) + | _ + -> false let make_container_write callee_pname receiver_ap callee_loc tenv = (* create a dummy write that represents mutating the contents of the container *) let open Domain in let callee_accesses = - if is_synchronized_container callee_pname receiver_ap tenv - then - AccessDomain.empty + if is_synchronized_container callee_pname receiver_ap tenv then AccessDomain.empty else let dummy_fieldname = Typ.Fieldname.Java.from_string - (container_write_string ^ (Typ.Procname.get_method callee_pname)) in + (container_write_string ^ Typ.Procname.get_method callee_pname) + in let dummy_access_ap = - fst receiver_ap, (snd receiver_ap) @ [AccessPath.FieldAccess dummy_fieldname] in - AccessDomain.add_access - (Unprotected (Some 0)) - (make_access dummy_access_ap Write callee_loc) - AccessDomain.empty in + (fst receiver_ap, snd receiver_ap @ [AccessPath.FieldAccess dummy_fieldname]) + in + AccessDomain.add_access (Unprotected (Some 0)) + (make_access dummy_access_ap Write callee_loc) AccessDomain.empty + in Some (true, false, false, callee_accesses, AttributeSetDomain.empty) let get_summary caller_pdesc callee_pname actuals callee_loc tenv = - if is_container_write callee_pname tenv - then - let receiver_ap = match List.hd actuals with - | Some (HilExp.AccessPath receiver_ap) -> receiver_ap - | _ -> - failwithf - "Call to %a is marked as a container write, but has no receiver" - Typ.Procname.pp callee_pname in + if is_container_write callee_pname tenv then + let receiver_ap = + match List.hd actuals with + | Some HilExp.AccessPath receiver_ap + -> receiver_ap + | _ + -> failwithf "Call to %a is marked as a container write, but has no receiver" + Typ.Procname.pp callee_pname + in make_container_write callee_pname receiver_ap callee_loc tenv - else - Summary.read_summary caller_pdesc callee_pname + else Summary.read_summary caller_pdesc callee_pname (* return true if the given procname boxes a primitive type into a reference type *) let is_box = function - | Typ.Procname.Java java_pname -> - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | ("java.lang.Boolean" | - "java.lang.Byte" | - "java.lang.Char" | - "java.lang.Double" | - "java.lang.Float" | - "java.lang.Integer" | - "java.lang.Long" | - "java.lang.Short"), - "valueOf" -> true - | _ -> false - end - | _ -> - false + | Typ.Procname.Java java_pname -> ( + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | ( ( "java.lang.Boolean" | "java.lang.Byte" | "java.lang.Char" | "java.lang.Double" + | "java.lang.Float" | "java.lang.Integer" | "java.lang.Long" | "java.lang.Short" ) + , "valueOf" ) + -> true + | _ + -> false ) + | _ + -> false let add_reads exps loc accesses locks threads attribute_map proc_data = List.fold ~f:(fun acc exp -> add_access exp loc Read acc locks threads attribute_map proc_data) - exps - ~init:accesses - - let exec_instr - (astate : Domain.astate) - ({ ProcData.extras; tenv; pdesc; } as proc_data) - _ - (instr : HilInstr.t) = + exps ~init:accesses + + let exec_instr (astate: Domain.astate) ({ProcData.extras; tenv; pdesc} as proc_data) _ + (instr: HilInstr.t) = let open Domain in match instr with - | Call (Some ret_base, Direct procname, actuals, _, loc) - when acquires_ownership procname tenv -> - let accesses = - add_reads actuals loc astate.accesses astate.locks - astate.threads astate.attribute_map proc_data in + | Call (Some ret_base, Direct procname, actuals, _, loc) when acquires_ownership procname tenv + -> let accesses = + add_reads actuals loc astate.accesses astate.locks astate.threads astate.attribute_map + proc_data + in let attribute_map = - AttributeMapDomain.add_attribute - (ret_base, []) Attribute.unconditionally_owned astate.attribute_map in - { astate with accesses; attribute_map; } - - | Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) -> + AttributeMapDomain.add_attribute (ret_base, []) Attribute.unconditionally_owned + astate.attribute_map + in + {astate with accesses; attribute_map} + | Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) + -> ( let accesses = - add_reads actuals loc astate.accesses astate.locks - astate.threads astate.attribute_map proc_data in - let astate = { astate with accesses; } in + add_reads actuals loc astate.accesses astate.locks astate.threads astate.attribute_map + proc_data + in + let astate = {astate with accesses} in let astate = match get_thread_model callee_pname with - | Threaded -> - { astate with threads = true; } - | ThreadedIfTrue -> - begin - match ret_opt with - | Some ret_access_path -> - let attribute_map = - AttributeMapDomain.add_attribute - (ret_access_path, []) - (Choice Choice.OnMainThread) - astate.attribute_map in - { astate with attribute_map; } - | None -> - failwithf - "Procedure %a specified as returning boolean, but returns nothing" - Typ.Procname.pp callee_pname - end - | Unknown -> astate in + | Threaded + -> {astate with threads= true} + | ThreadedIfTrue -> ( + match ret_opt with + | Some ret_access_path + -> let attribute_map = + AttributeMapDomain.add_attribute (ret_access_path, []) + (Choice Choice.OnMainThread) astate.attribute_map + in + {astate with attribute_map} + | None + -> failwithf "Procedure %a specified as returning boolean, but returns nothing" + Typ.Procname.pp callee_pname ) + | Unknown + -> astate + in let astate_callee = (* assuming that modeled procedures do not have useful summaries *) - if is_thread_utils_method "assertMainThread" callee_pname - then - { astate with threads = true; } + if is_thread_utils_method "assertMainThread" callee_pname then {astate with threads= true} else match get_lock_model callee_pname actuals with - | Lock -> - { astate with locks = true; } - | Unlock -> - { astate with locks = false; } - | LockedIfTrue -> - begin - match ret_opt with - | Some ret_access_path -> - let attribute_map = - AttributeMapDomain.add_attribute - (ret_access_path, []) - (Choice Choice.LockHeld) - astate.attribute_map in - { astate with attribute_map; } - | None -> - failwithf - "Procedure %a specified as returning boolean, but returns nothing" - Typ.Procname.pp callee_pname - end + | Lock + -> {astate with locks= true} + | Unlock + -> {astate with locks= false} + | LockedIfTrue -> ( + match ret_opt with + | Some ret_access_path + -> let attribute_map = + AttributeMapDomain.add_attribute (ret_access_path, []) (Choice Choice.LockHeld) + astate.attribute_map + in + {astate with attribute_map} + | None + -> failwithf "Procedure %a specified as returning boolean, but returns nothing" + Typ.Procname.pp callee_pname ) | NoEffect -> - match get_summary pdesc callee_pname actuals loc tenv with - | Some (callee_thumbs_up, callee_threads, callee_locks, - callee_accesses, return_attributes) -> - let update_caller_accesses pre callee_accesses caller_accesses = - let combined_accesses = - PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc) - |> PathDomain.join (AccessDomain.get_accesses pre caller_accesses) in - AccessDomain.add pre combined_accesses caller_accesses in - let thumbs_up = callee_thumbs_up && astate.thumbs_up in - let locks = callee_locks || astate.locks in - let threads = callee_threads || astate.threads in - let unprotected = is_unprotected locks threads pdesc in - (* add [ownership_accesses] to the [accesses_acc] with a protected pre if + match get_summary pdesc callee_pname actuals loc tenv with + | Some + ( callee_thumbs_up + , callee_threads + , callee_locks + , callee_accesses + , return_attributes ) + -> let update_caller_accesses pre callee_accesses caller_accesses = + let combined_accesses = + PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc) + |> PathDomain.join (AccessDomain.get_accesses pre caller_accesses) + in + AccessDomain.add pre combined_accesses caller_accesses + in + let thumbs_up = callee_thumbs_up && astate.thumbs_up in + let locks = callee_locks || astate.locks in + let threads = callee_threads || astate.threads in + let unprotected = is_unprotected locks threads pdesc in + (* add [ownership_accesses] to the [accesses_acc] with a protected pre if [exp] is owned, and an appropriate unprotected pre otherwise *) - let add_ownership_access ownership_accesses actual_exp accesses_acc = - match actual_exp with - | HilExp.Constant _ -> - (* the actual is a constant, so it's owned in the caller. *) - accesses_acc - | HilExp.AccessPath actual_access_path -> - if is_owned actual_access_path astate.attribute_map - then - (* the actual passed to the current callee is owned. drop all the + let add_ownership_access ownership_accesses actual_exp accesses_acc = + match actual_exp with + | HilExp.Constant _ + -> (* the actual is a constant, so it's owned in the caller. *) + accesses_acc + | HilExp.AccessPath actual_access_path + -> if is_owned actual_access_path astate.attribute_map then + (* the actual passed to the current callee is owned. drop all the conditional accesses for that actual, since they're all safe *) - accesses_acc - else - let pre = - if unprotected - then - let base = fst actual_access_path in - match FormalMap.get_formal_index base extras with - | Some formal_index -> - (* the actual passed to the current callee is rooted in a + accesses_acc + else + let pre = + if unprotected then + let base = fst actual_access_path in + match FormalMap.get_formal_index base extras with + | Some formal_index + -> (* the actual passed to the current callee is rooted in a formal *) - AccessPrecondition.Unprotected (Some formal_index) - | None -> - match - AttributeMapDomain.get_conditional_ownership_index - actual_access_path - astate.attribute_map - with - | Some formal_index -> - (* access path conditionally owned if [formal_index] is + AccessPrecondition.Unprotected (Some formal_index) + | None -> + match + AttributeMapDomain.get_conditional_ownership_index + actual_access_path astate.attribute_map + with + | Some formal_index + -> (* access path conditionally owned if [formal_index] is owned *) - AccessPrecondition.Unprotected (Some formal_index) - | None -> - (* access path not rooted in a formal and not + AccessPrecondition.Unprotected (Some formal_index) + | None + -> (* access path not rooted in a formal and not conditionally owned *) - AccessPrecondition.unprotected - else - (* access protected by held lock *) - AccessPrecondition.Protected (make_excluder true threads) in - update_caller_accesses pre ownership_accesses accesses_acc - | _ -> - (* couldn't find access path, don't know if it's owned *) - update_caller_accesses - AccessPrecondition.unprotected ownership_accesses accesses_acc in - let accesses = - let update_accesses pre callee_accesses accesses_acc = match pre with - | AccessPrecondition.Protected _ -> - update_caller_accesses pre callee_accesses accesses_acc - | AccessPrecondition.Unprotected None -> - let pre' = - if unprotected - then pre - else AccessPrecondition.Protected (make_excluder true threads) in - update_caller_accesses pre' callee_accesses accesses_acc - | AccessPrecondition.Unprotected (Some index) -> - add_ownership_access - callee_accesses (List.nth_exn actuals index) accesses_acc in - AccessDomain.fold update_accesses callee_accesses astate.accesses in - let attribute_map = - propagate_return_attributes - ret_opt - return_attributes - actuals - astate.attribute_map - extras in - { thumbs_up; locks; threads; accesses; attribute_map; } - | None -> - let should_assume_returns_ownership (call_flags : CallFlags.t) actuals = - (* assume non-interface methods with no summary and no parameters return + AccessPrecondition.unprotected + else + (* access protected by held lock *) + AccessPrecondition.Protected (make_excluder true threads) + in + update_caller_accesses pre ownership_accesses accesses_acc + | _ + -> (* couldn't find access path, don't know if it's owned *) + update_caller_accesses AccessPrecondition.unprotected ownership_accesses + accesses_acc + in + let accesses = + let update_accesses pre callee_accesses accesses_acc = + match pre with + | AccessPrecondition.Protected _ + -> update_caller_accesses pre callee_accesses accesses_acc + | AccessPrecondition.Unprotected None + -> let pre' = + if unprotected then pre + else AccessPrecondition.Protected (make_excluder true threads) + in + update_caller_accesses pre' callee_accesses accesses_acc + | AccessPrecondition.Unprotected Some index + -> add_ownership_access callee_accesses (List.nth_exn actuals index) + accesses_acc + in + AccessDomain.fold update_accesses callee_accesses astate.accesses + in + let attribute_map = + propagate_return_attributes ret_opt return_attributes actuals + astate.attribute_map extras + in + {thumbs_up; locks; threads; accesses; attribute_map} + | None + -> let should_assume_returns_ownership (call_flags: CallFlags.t) actuals = + (* assume non-interface methods with no summary and no parameters return ownership *) - not (call_flags.cf_interface) && List.is_empty actuals in - if is_box callee_pname - then - match ret_opt, actuals with - | Some ret, HilExp.AccessPath actual_ap :: _ - when AttributeMapDomain.has_attribute - actual_ap Functional astate.attribute_map -> - (* TODO: check for constants, which are functional? *) - let attribute_map = - AttributeMapDomain.add_attribute - (ret, []) - Functional - astate.attribute_map in - { astate with attribute_map; } - | _ -> - astate - else if should_assume_returns_ownership call_flags actuals - then - match ret_opt with - | Some ret -> - let attribute_map = - AttributeMapDomain.add_attribute - (ret, []) - Attribute.unconditionally_owned - astate.attribute_map in - { astate with attribute_map; } - | None -> - astate - else - astate in - begin - match ret_opt with - | Some (_, { Typ.desc=Typ.Tint ILong | Tfloat FDouble }) -> - (* writes to longs and doubles are not guaranteed to be atomic in Java, so don't + not call_flags.cf_interface && List.is_empty actuals + in + if is_box callee_pname then + match (ret_opt, actuals) with + | Some ret, (HilExp.AccessPath actual_ap) :: _ + when AttributeMapDomain.has_attribute actual_ap Functional + astate.attribute_map + -> (* TODO: check for constants, which are functional? *) + let attribute_map = + AttributeMapDomain.add_attribute (ret, []) Functional + astate.attribute_map + in + {astate with attribute_map} + | _ + -> astate + else if should_assume_returns_ownership call_flags actuals then + match ret_opt with + | Some ret + -> let attribute_map = + AttributeMapDomain.add_attribute (ret, []) + Attribute.unconditionally_owned astate.attribute_map + in + {astate with attribute_map} + | None + -> astate + else astate + in + match ret_opt with + | Some (_, {Typ.desc= Typ.Tint ILong | Tfloat FDouble}) + -> (* writes to longs and doubles are not guaranteed to be atomic in Java, so don't bother tracking whether a returned long or float value is functional *) - astate_callee - | Some ret -> - let add_if_annotated predicate attribute attribute_map = - if PatternMatch.override_exists predicate tenv callee_pname - then - AttributeMapDomain.add_attribute (ret, []) attribute attribute_map - else - attribute_map in - let attribute_map = - add_if_annotated is_functional Functional astate_callee.attribute_map - |> add_if_annotated - (has_return_annot Annotations.ia_is_returns_ownership) - Domain.Attribute.unconditionally_owned in - { astate_callee with attribute_map; } - | _ -> - astate_callee - end - - | Assign (lhs_access_path, rhs_exp, loc) -> - let rhs_accesses = - add_access - rhs_exp loc Read astate.accesses astate.locks - astate.threads astate.attribute_map proc_data in + astate_callee + | Some ret + -> let add_if_annotated predicate attribute attribute_map = + if PatternMatch.override_exists predicate tenv callee_pname then + AttributeMapDomain.add_attribute (ret, []) attribute attribute_map + else attribute_map + in + let attribute_map = + add_if_annotated is_functional Functional astate_callee.attribute_map + |> add_if_annotated (has_return_annot Annotations.ia_is_returns_ownership) + Domain.Attribute.unconditionally_owned + in + {astate_callee with attribute_map} + | _ + -> astate_callee ) + | Assign (lhs_access_path, rhs_exp, loc) + -> let rhs_accesses = + add_access rhs_exp loc Read astate.accesses astate.locks astate.threads + astate.attribute_map proc_data + in let rhs_access_paths = HilExp.get_access_paths rhs_exp in let is_functional = - not (List.is_empty rhs_access_paths) && - List.for_all - ~f:(fun access_path -> - AttributeMapDomain.has_attribute access_path Functional astate.attribute_map) - rhs_access_paths in + not (List.is_empty rhs_access_paths) + && List.for_all + ~f:(fun access_path -> + AttributeMapDomain.has_attribute access_path Functional astate.attribute_map) + rhs_access_paths + in let accesses = - if is_functional - then + if is_functional then (* we want to forget about writes to @Functional fields altogether, otherwise we'll report spurious read/write races *) rhs_accesses else - add_access - (AccessPath lhs_access_path) - loc - Write - rhs_accesses - astate.locks - astate.threads - astate.attribute_map - proc_data in + add_access (AccessPath lhs_access_path) loc Write rhs_accesses astate.locks + astate.threads astate.attribute_map proc_data + in let attribute_map = - propagate_attributes lhs_access_path rhs_exp astate.attribute_map extras in - { astate with accesses; attribute_map; } - - | Assume (assume_exp, _, _, loc) -> - let rec eval_binop op var e1 e2 = - match eval_bexp var e1, eval_bexp var e2 with - | Some b1, Some b2 -> Some (op b1 b2) - | _ -> None + propagate_attributes lhs_access_path rhs_exp astate.attribute_map extras + in + {astate with accesses; attribute_map} + | Assume (assume_exp, _, _, loc) + -> let rec eval_binop op var e1 e2 = + match (eval_bexp var e1, eval_bexp var e2) with + | Some b1, Some b2 + -> Some (op b1 b2) + | _ + -> None (* return Some bool_value if the given boolean expression evaluates to bool_value when [var] is set to true. return None if it has free variables that stop us from evaluating it *) and eval_bexp var = function - | HilExp.AccessPath ap when AccessPath.Raw.equal ap var -> - Some true - | HilExp.Constant c -> - Some (not (Const.iszero_int_float c)) - | HilExp.UnaryOperator (Unop.LNot, e, _) -> - let b_opt = eval_bexp var e in + | HilExp.AccessPath ap when AccessPath.Raw.equal ap var + -> Some true + | HilExp.Constant c + -> Some (not (Const.iszero_int_float c)) + | HilExp.UnaryOperator (Unop.LNot, e, _) + -> let b_opt = eval_bexp var e in Option.map ~f:not b_opt - | HilExp.BinaryOperator (Binop.LAnd, e1, e2) -> - eval_binop (&&) var e1 e2 - | HilExp.BinaryOperator (Binop.LOr, e1, e2) -> - eval_binop (||) var e1 e2 - | HilExp.BinaryOperator (Binop.Eq, e1, e2) -> - eval_binop Bool.equal var e1 e2 - | HilExp.BinaryOperator (Binop.Ne, e1, e2) -> - eval_binop (<>) var e1 e2 - | _ -> - (* non-boolean expression; can't evaluate it *) - None in + | HilExp.BinaryOperator (Binop.LAnd, e1, e2) + -> eval_binop ( && ) var e1 e2 + | HilExp.BinaryOperator (Binop.LOr, e1, e2) + -> eval_binop ( || ) var e1 e2 + | HilExp.BinaryOperator (Binop.Eq, e1, e2) + -> eval_binop Bool.equal var e1 e2 + | HilExp.BinaryOperator (Binop.Ne, e1, e2) + -> eval_binop ( <> ) var e1 e2 + | _ + -> (* non-boolean expression; can't evaluate it *) + None + in let add_choice bool_value acc = function - | Choice.LockHeld -> - let locks = bool_value in - { acc with locks; } - | Choice.OnMainThread -> - let threads = bool_value in - { acc with threads; } in - + | Choice.LockHeld + -> let locks = bool_value in + {acc with locks} + | Choice.OnMainThread + -> let threads = bool_value in + {acc with threads} + in let accesses = - add_access - assume_exp loc Read astate.accesses astate.locks - astate.threads astate.attribute_map proc_data in + add_access assume_exp loc Read astate.accesses astate.locks astate.threads + astate.attribute_map proc_data + in let astate' = match HilExp.get_access_paths assume_exp with - | [access_path] -> + | [access_path] + -> ( let choices = AttributeMapDomain.get_choices access_path astate.attribute_map in - begin - match eval_bexp access_path assume_exp with - | Some bool_value -> - (* prune (prune_exp) can only evaluate to true if the choice is [bool_value]. + match eval_bexp access_path assume_exp with + | Some bool_value + -> (* prune (prune_exp) can only evaluate to true if the choice is [bool_value]. add the constraint that the the choice must be [bool_value] to the state *) - List.fold ~f:(add_choice bool_value) ~init:astate choices - | None -> - astate - end - | _ -> - astate in - { astate' with accesses; } + List.fold ~f:(add_choice bool_value) ~init:astate choices + | None + -> astate ) + | _ + -> astate + in + {astate' with accesses} | Call (_, Indirect _, _, _, _) -> - match Procdesc.get_proc_name pdesc with - | Typ.Procname.Java _ -> - failwithf "Unexpected indirect call instruction %a" HilInstr.pp instr - | _ -> - astate + match Procdesc.get_proc_name pdesc with + | Typ.Procname.Java _ + -> failwithf "Unexpected indirect call instruction %a" HilInstr.pp instr + | _ + -> astate end -module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (LowerHil.Make(TransferFunctions)) +module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (LowerHil.Make (TransferFunctions)) (* similarly, we assume that immutable classes safely encapsulate their state *) let is_immutable_collection_class class_name tenv = - let immutable_collections = [ - "com.google.common.collect.ImmutableCollection"; - "com.google.common.collect.ImmutableMap"; - "com.google.common.collect.ImmutableTable"; - ] in - PatternMatch.supertype_exists - tenv - (fun typename _ -> - List.mem ~equal:String.equal immutable_collections (Typ.Name.name typename)) + let immutable_collections = + [ "com.google.common.collect.ImmutableCollection" + ; "com.google.common.collect.ImmutableMap" + ; "com.google.common.collect.ImmutableTable" ] + in + PatternMatch.supertype_exists tenv + (fun typename _ -> List.mem ~equal:String.equal immutable_collections (Typ.Name.name typename)) class_name let is_call_to_immutable_collection_method tenv = function - | Typ.Procname.Java java_pname -> - is_immutable_collection_class (Typ.Procname.java_get_class_type_name java_pname) tenv - | _ -> - false + | Typ.Procname.Java java_pname + -> is_immutable_collection_class (Typ.Procname.java_get_class_type_name java_pname) tenv + | _ + -> false (* Methods in @ThreadConfined classes and methods annotated with @ThreadConfied are assumed to all run on the same thread. For the moment we won't warn on accesses resulting from use of such @@ -844,55 +813,48 @@ let is_call_to_immutable_collection_method tenv = function completely different classes that don't necessarily run on the same thread as the confined object. *) let is_thread_confined_method tenv pdesc = - Annotations.pdesc_return_annot_ends_with pdesc Annotations.thread_confined || - PatternMatch.check_current_class_attributes - Annotations.ia_is_thread_confined tenv (Procdesc.get_proc_name pdesc) + Annotations.pdesc_return_annot_ends_with pdesc Annotations.thread_confined + || PatternMatch.check_current_class_attributes Annotations.ia_is_thread_confined tenv + (Procdesc.get_proc_name pdesc) (* we don't want to warn on methods that run on the UI thread because they should always be single-threaded *) let runs_on_ui_thread proc_desc = (* assume that methods annotated with @UiThread, @OnEvent, @OnBind, @OnMount, @OnUnbind, @OnUnmount always run on the UI thread *) - Annotations.pdesc_has_return_annot - proc_desc - (fun annot -> Annotations.ia_is_ui_thread annot || - Annotations.ia_is_on_bind annot || - Annotations.ia_is_on_event annot || - Annotations.ia_is_on_mount annot || - Annotations.ia_is_on_unbind annot || - Annotations.ia_is_on_unmount annot) + Annotations.pdesc_has_return_annot proc_desc (fun annot -> + Annotations.ia_is_ui_thread annot || Annotations.ia_is_on_bind annot + || Annotations.ia_is_on_event annot || Annotations.ia_is_on_mount annot + || Annotations.ia_is_on_unbind annot || Annotations.ia_is_on_unmount annot ) let threadsafe_annotations = - Annotations.thread_safe :: - (ThreadSafetyConfig.AnnotationAliases.of_json Config.threadsafe_aliases) + Annotations.thread_safe :: ThreadSafetyConfig.AnnotationAliases.of_json Config.threadsafe_aliases (* returns true if the annotation is @ThreadSafe, @ThreadSafe(enableChecks = true), or is defined as an alias of @ThreadSafe in a .inferconfig file. *) let is_thread_safe item_annot = - let f ((annot : Annot.t), _) = + let f ((annot: Annot.t), _) = List.exists ~f:(fun annot_string -> - Annotations.annot_ends_with annot annot_string || - String.equal annot.class_name annot_string) - threadsafe_annotations && - match annot.Annot.parameters with - | ["false"] -> false - | _ -> true in + Annotations.annot_ends_with annot annot_string + || String.equal annot.class_name annot_string) + threadsafe_annotations + && match annot.Annot.parameters with ["false"] -> false | _ -> true + in List.exists ~f item_annot (* returns true if the annotation is @ThreadSafe(enableChecks = false) *) let is_assumed_thread_safe item_annot = let f (annot, _) = - Annotations.annot_ends_with annot Annotations.thread_safe && - match annot.Annot.parameters with - | ["false"] -> true - | _ -> false in + Annotations.annot_ends_with annot Annotations.thread_safe + && match annot.Annot.parameters with ["false"] -> true | _ -> false + in List.exists ~f item_annot let pdesc_is_assumed_thread_safe pdesc tenv = - is_assumed_thread_safe (Annotations.pdesc_get_return_annot pdesc) || - PatternMatch.check_current_class_attributes - is_assumed_thread_safe tenv (Procdesc.get_proc_name pdesc) + is_assumed_thread_safe (Annotations.pdesc_get_return_annot pdesc) + || PatternMatch.check_current_class_attributes is_assumed_thread_safe tenv + (Procdesc.get_proc_name pdesc) (* return true if we should compute a summary for the procedure. if this returns false, we won't analyze the procedure or report any warnings on it *) @@ -900,216 +862,214 @@ let pdesc_is_assumed_thread_safe pdesc tenv = find more bugs. this is just a temporary measure to avoid obvious false positives *) let should_analyze_proc pdesc tenv = let pn = Procdesc.get_proc_name pdesc in - not (Typ.Procname.is_class_initializer pn) && - not (FbThreadSafety.is_logging_method pn) && - not (is_call_to_immutable_collection_method tenv pn) && - not (pdesc_is_assumed_thread_safe pdesc tenv) + not (Typ.Procname.is_class_initializer pn) && not (FbThreadSafety.is_logging_method pn) + && not (is_call_to_immutable_collection_method tenv pn) + && not (pdesc_is_assumed_thread_safe pdesc tenv) let is_thread_safe_method pdesc tenv = PatternMatch.override_exists (fun pn -> - Annotations.pname_has_return_annot - pn - ~attrs_of_pname:Specs.proc_resolve_attributes - is_thread_safe) - tenv - (Procdesc.get_proc_name pdesc) + Annotations.pname_has_return_annot pn ~attrs_of_pname:Specs.proc_resolve_attributes + is_thread_safe) + tenv (Procdesc.get_proc_name pdesc) let empty_post = let initial_thumbs_up = true and initial_known_on_ui_thread = false and has_lock = false and return_attrs = ThreadSafetyDomain.AttributeSetDomain.empty in - (initial_thumbs_up, initial_known_on_ui_thread, has_lock, ThreadSafetyDomain.AccessDomain.empty, return_attrs) + ( initial_thumbs_up + , initial_known_on_ui_thread + , has_lock + , ThreadSafetyDomain.AccessDomain.empty + , return_attrs ) -let analyze_procedure { Callbacks.proc_desc; tenv; summary; } = +let analyze_procedure {Callbacks.proc_desc; tenv; summary} = let is_initializer tenv proc_name = - Typ.Procname.is_constructor proc_name || FbThreadSafety.is_custom_init tenv proc_name in + Typ.Procname.is_constructor proc_name || FbThreadSafety.is_custom_init tenv proc_name + in let open ThreadSafetyDomain in (* convert the abstract state to a summary by dropping the id map *) - if should_analyze_proc proc_desc tenv - then - begin - if not (Procdesc.did_preanalysis proc_desc) then Preanal.do_liveness proc_desc tenv; - let extras = FormalMap.make proc_desc in - let proc_data = ProcData.make proc_desc tenv extras in - let initial = - let threads = runs_on_ui_thread proc_desc || is_thread_confined_method tenv proc_desc in - if is_initializer tenv (Procdesc.get_proc_name proc_desc) - then - let add_owned_formal acc formal_index = - match FormalMap.get_formal_base formal_index extras with - | Some base -> - AttributeMapDomain.add_attribute (base, []) Attribute.unconditionally_owned acc - | None -> - acc in - let owned_formals = - (* if a constructer is called via DI, all of its formals will be freshly allocated + if should_analyze_proc proc_desc tenv then ( + if not (Procdesc.did_preanalysis proc_desc) then Preanal.do_liveness proc_desc tenv ; + let extras = FormalMap.make proc_desc in + let proc_data = ProcData.make proc_desc tenv extras in + let initial = + let threads = runs_on_ui_thread proc_desc || is_thread_confined_method tenv proc_desc in + if is_initializer tenv (Procdesc.get_proc_name proc_desc) then + let add_owned_formal acc formal_index = + match FormalMap.get_formal_base formal_index extras with + | Some base + -> AttributeMapDomain.add_attribute (base, []) Attribute.unconditionally_owned acc + | None + -> acc + in + let owned_formals = + (* if a constructer is called via DI, all of its formals will be freshly allocated and therefore owned. we assume that constructors annotated with @Inject will only be called via DI or using fresh parameters. *) - if Annotations.pdesc_has_return_annot proc_desc Annotations.ia_is_inject - then List.mapi ~f:(fun i _ -> i) (Procdesc.get_formals proc_desc) - else [0] (* express that the constructor owns [this] *) in - let attribute_map = - List.fold - ~f:add_owned_formal - owned_formals - ~init:ThreadSafetyDomain.empty.attribute_map in - { ThreadSafetyDomain.empty with attribute_map; threads; }, IdAccessPathMapDomain.empty - else - { ThreadSafetyDomain.empty with threads; }, IdAccessPathMapDomain.empty in - - match Analyzer.compute_post proc_data ~initial ~debug:false with - | Some ({ thumbs_up; threads; locks; accesses; attribute_map; }, _) -> - let return_var_ap = - AccessPath.of_pvar - (Pvar.get_ret_pvar (Procdesc.get_proc_name proc_desc)) - (Procdesc.get_ret_type proc_desc) in - let return_attributes = - try AttributeMapDomain.find return_var_ap attribute_map - with Not_found -> AttributeSetDomain.empty in - (* A hack for modeling lock_guard by releasing a + if Annotations.pdesc_has_return_annot proc_desc Annotations.ia_is_inject then + List.mapi ~f:(fun i _ -> i) (Procdesc.get_formals proc_desc) + else [0] + (* express that the constructor owns [this] *) + in + let attribute_map = + List.fold ~f:add_owned_formal owned_formals ~init:ThreadSafetyDomain.empty.attribute_map + in + ({ThreadSafetyDomain.empty with attribute_map; threads}, IdAccessPathMapDomain.empty) + else ({ThreadSafetyDomain.empty with threads}, IdAccessPathMapDomain.empty) + in + match Analyzer.compute_post proc_data ~initial ~debug:false with + | Some ({thumbs_up; threads; locks; accesses; attribute_map}, _) + -> let return_var_ap = + AccessPath.of_pvar + (Pvar.get_ret_pvar (Procdesc.get_proc_name proc_desc)) + (Procdesc.get_ret_type proc_desc) + in + let return_attributes = + try AttributeMapDomain.find return_var_ap attribute_map + with Not_found -> AttributeSetDomain.empty + in + (* A hack for modeling lock_guard by releasing a lock at the end of the procedure, as destructors are not modeled yet *) - let update_locks = match Procdesc.get_proc_name proc_desc with - | ObjC_Cpp _ when locks -> - let matcher = QualifiedCppName.Match.of_fuzzy_qual_names - ["std::lock_guard"; "std::unique_lock"] in - (* Unlock, if the procedure contains a local field + let update_locks = + match Procdesc.get_proc_name proc_desc with + | ObjC_Cpp _ when locks + -> let matcher = + QualifiedCppName.Match.of_fuzzy_qual_names ["std::lock_guard"; "std::unique_lock"] + in + (* Unlock, if the procedure contains a local field of type std::lock_guard or std::unique_lock *) - not (List.exists (Procdesc.get_locals proc_desc) ~f:(fun (_, ft) -> - Option.exists (Typ.name ft) ~f:(fun name -> - QualifiedCppName.Match.match_qualifiers matcher (Typ.Name.qual_name name)) - )) - | _ -> locks in - let post = thumbs_up, threads, update_locks, accesses, return_attributes in - Summary.update_summary post summary - | None -> - summary - end - else - Summary.update_summary empty_post summary + not + (List.exists (Procdesc.get_locals proc_desc) ~f:(fun (_, ft) -> + Option.exists (Typ.name ft) ~f:(fun name -> + QualifiedCppName.Match.match_qualifiers matcher (Typ.Name.qual_name name) + ) )) + | _ + -> locks + in + let post = (thumbs_up, threads, update_locks, accesses, return_attributes) in + Summary.update_summary post summary + | None + -> summary ) + else Summary.update_summary empty_post summary (* we assume two access paths can alias if their access parts are equal (we ignore the base). *) let can_alias access_path1 access_path2 = List.compare AccessPath.compare_access (snd access_path1) (snd access_path2) -module AccessListMap = Caml.Map.Make(struct - type t = AccessPath.Raw.t [@@deriving compare] - let compare = can_alias - end) +module AccessListMap = Caml.Map.Make (struct + type t = AccessPath.Raw.t [@@deriving compare] + + let compare = can_alias +end) let get_current_class_and_threadsafe_superclasses tenv pname = match pname with - | Typ.Procname.Java java_pname -> - let current_class = Typ.Procname.java_get_class_type_name java_pname in + | Typ.Procname.Java java_pname + -> let current_class = Typ.Procname.java_get_class_type_name java_pname in let thread_safe_annotated_classes = - PatternMatch.find_superclasses_with_attributes - is_thread_safe tenv current_class + PatternMatch.find_superclasses_with_attributes is_thread_safe tenv current_class in Some (current_class, thread_safe_annotated_classes) - | _ -> None (*shouldn't happen*) + | _ + -> None + +(*shouldn't happen*) (** The addendum message says that a superclass is marked @ThreadSafe, when the current class is not so marked*) let calculate_addendum_message tenv pname = match get_current_class_and_threadsafe_superclasses tenv pname with - | Some (current_class, thread_safe_annotated_classes) -> - if not (List.mem ~equal:Typ.Name.equal thread_safe_annotated_classes current_class) then + | Some (current_class, thread_safe_annotated_classes) + -> if not (List.mem ~equal:Typ.Name.equal thread_safe_annotated_classes current_class) then match thread_safe_annotated_classes with - | hd::_ -> - F.asprintf "@\n Note: Superclass %a is marked %a." - (MF.wrap_monospaced Typ.Name.pp) hd + | hd :: _ + -> F.asprintf "@\n Note: Superclass %a is marked %a." (MF.wrap_monospaced Typ.Name.pp) hd MF.pp_monospaced "@ThreadSafe" - | [] -> "" + | [] + -> "" else "" - | _ -> "" + | _ + -> "" let filter_by_access access_filter trace = let open ThreadSafetyDomain in - PathDomain.Sinks.filter access_filter (PathDomain.sinks trace) - |> PathDomain.update_sinks trace + PathDomain.Sinks.filter access_filter (PathDomain.sinks trace) |> PathDomain.update_sinks trace let get_all_accesses_with_pre pre_filter access_filter accesses = let open ThreadSafetyDomain in AccessDomain.fold (fun pre trace acc -> - if pre_filter pre - then PathDomain.join (filter_by_access access_filter trace) acc - else acc) - accesses - PathDomain.empty + if pre_filter pre then PathDomain.join (filter_by_access access_filter trace) acc else acc) + accesses PathDomain.empty let get_all_accesses = get_all_accesses_with_pre (fun _ -> true) let pp_container_access fmt (container_name, function_name) = - F.fprintf - fmt - "container %s via call to %s" - (MF.monospaced_to_string container_name) + F.fprintf fmt "container %s via call to %s" (MF.monospaced_to_string container_name) (MF.monospaced_to_string function_name) let pp_access fmt sink = match get_container_write_desc sink with - | Some container_write_desc -> - pp_container_access fmt container_write_desc - | None -> - let access_path, _ = ThreadSafetyDomain.PathDomain.Sink.kind sink in + | Some container_write_desc + -> pp_container_access fmt container_write_desc + | None + -> let access_path, _ = ThreadSafetyDomain.PathDomain.Sink.kind sink in F.fprintf fmt "%a" (MF.wrap_monospaced AccessPath.pp_access_list) (snd access_path) let desc_of_sink sink = match get_container_write_desc sink with - | Some container_write_desc -> - F.asprintf "%a" pp_container_access container_write_desc - | None -> - let sink_pname = CallSite.pname (ThreadSafetyDomain.PathDomain.Sink.call_site sink) in - if Typ.Procname.equal sink_pname Typ.Procname.empty_block - then F.asprintf "access to %a" pp_access sink + | Some container_write_desc + -> F.asprintf "%a" pp_container_access container_write_desc + | None + -> let sink_pname = CallSite.pname (ThreadSafetyDomain.PathDomain.Sink.call_site sink) in + if Typ.Procname.equal sink_pname Typ.Procname.empty_block then + F.asprintf "access to %a" pp_access sink else F.asprintf "call to %a" Typ.Procname.pp sink_pname let trace_of_pname orig_sink orig_pdesc callee_pname = let open ThreadSafetyDomain in let orig_access = PathDomain.Sink.kind orig_sink in match Summary.read_summary orig_pdesc callee_pname with - | Some (_, _, _, access_map, _) -> - get_all_accesses - (fun access -> - Int.equal (Access.compare (PathDomain.Sink.kind access) orig_access) 0) + | Some (_, _, _, access_map, _) + -> get_all_accesses + (fun access -> Int.equal (Access.compare (PathDomain.Sink.kind access) orig_access) 0) access_map - | _ -> - PathDomain.empty + | _ + -> PathDomain.empty let make_trace_with_conflicts conflicts original_path pdesc = let open ThreadSafetyDomain in - let loc_trace_of_path path = - PathDomain.to_sink_loc_trace ~desc_of_sink path in + let loc_trace_of_path path = PathDomain.to_sink_loc_trace ~desc_of_sink path in let make_trace_for_sink sink = let trace_of_pname = trace_of_pname sink pdesc in match PathDomain.get_reportable_sink_path sink ~trace_of_pname with - | Some path -> loc_trace_of_path path - | None -> [] in - + | Some path + -> loc_trace_of_path path + | None + -> [] + in let original_trace = loc_trace_of_path original_path in match conflicts with - | conflict_sink :: _ -> - (* create a trace for one of the conflicts and append it to the trace for the original sink *) + | conflict_sink :: _ + -> (* create a trace for one of the conflicts and append it to the trace for the original sink *) let conflict_trace = make_trace_for_sink conflict_sink in - let get_start_loc = function - | head :: _ -> head.Errlog.lt_loc - | [] -> Location.dummy in + let get_start_loc = function head :: _ -> head.Errlog.lt_loc | [] -> Location.dummy in let first_trace_spacer = - Errlog.make_trace_element - 0 (get_start_loc original_trace) "" [] in + Errlog.make_trace_element 0 (get_start_loc original_trace) "" [] + in let second_trace_spacer = - Errlog.make_trace_element - 0 (get_start_loc conflict_trace) "" [] in - (first_trace_spacer :: original_trace) @ (second_trace_spacer :: conflict_trace) - | [] -> - original_trace + Errlog.make_trace_element 0 (get_start_loc conflict_trace) "" [] + in + first_trace_spacer :: original_trace @ second_trace_spacer :: conflict_trace + | [] + -> original_trace let report_thread_safety_violation tenv pdesc ~make_description ~conflicts access = let open ThreadSafetyDomain in let pname = Procdesc.get_proc_name pdesc in - let report_one_path ((_, sinks) as path) = + let report_one_path (_, sinks as path) = let initial_sink, _ = List.last_exn sinks in let final_sink, _ = List.hd_exn sinks in let initial_sink_site = PathDomain.Sink.call_site initial_sink in @@ -1119,82 +1079,73 @@ let report_thread_safety_violation tenv pdesc ~make_description ~conflicts acces let msg = Localise.to_issue_id Localise.thread_safety_violation in let description = make_description tenv pname final_sink_site initial_sink_site final_sink in let exn = Exceptions.Checkers (msg, Localise.verbatim_desc description) in - Reporting.log_error_deprecated ~store_summary:true pname ~loc ~ltr exn in - + Reporting.log_error_deprecated ~store_summary:true pname ~loc ~ltr exn + in let trace_of_pname = trace_of_pname access pdesc in Option.iter ~f:report_one_path (PathDomain.get_reportable_sink_path access ~trace_of_pname) let pp_procname_short fmt = function - | Typ.Procname.Java java -> - F.fprintf fmt "%s.%s" - (Typ.Procname.java_get_class_name java) (Typ.Procname.java_get_method java) - | pname -> - Typ.Procname.pp fmt pname + | Typ.Procname.Java java + -> F.fprintf fmt "%s.%s" (Typ.Procname.java_get_class_name java) + (Typ.Procname.java_get_method java) + | pname + -> Typ.Procname.pp fmt pname let make_unprotected_write_description tenv pname final_sink_site initial_sink_site final_sink = - Format.asprintf - "Unprotected write. Non-private method %a%s %s %a outside of synchronization.%s" + Format.asprintf "Unprotected write. Non-private method %a%s %s %a outside of synchronization.%s" (MF.wrap_monospaced pp_procname_short) pname (if CallSite.equal final_sink_site initial_sink_site then "" else " indirectly") (if is_container_write_sink final_sink then "mutates" else "writes to field") - pp_access final_sink - (calculate_addendum_message tenv pname) - -let make_read_write_race_description - conflicts tenv pname final_sink_site initial_sink_site final_sink = - let race_with_main_thread = List.exists - ~f:(fun (_, _, threaded, _, _) -> threaded) - conflicts in + pp_access final_sink (calculate_addendum_message tenv pname) + +let make_read_write_race_description conflicts tenv pname final_sink_site initial_sink_site + final_sink = + let race_with_main_thread = List.exists ~f:(fun (_, _, threaded, _, _) -> threaded) conflicts in let conflicting_proc_names = - List.map - ~f:(fun (_, _, _, _, pdesc) -> Procdesc.get_proc_name pdesc) - conflicts - |> Typ.Procname.Set.of_list in + List.map ~f:(fun (_, _, _, _, pdesc) -> Procdesc.get_proc_name pdesc) conflicts + |> Typ.Procname.Set.of_list + in let pp_conflicts fmt conflicts = - if Int.equal (Typ.Procname.Set.cardinal conflicts) 1 - then Typ.Procname.pp fmt (Typ.Procname.Set.choose conflicts) - else Typ.Procname.Set.pp fmt conflicts in + if Int.equal (Typ.Procname.Set.cardinal conflicts) 1 then + Typ.Procname.pp fmt (Typ.Procname.Set.choose conflicts) + else Typ.Procname.Set.pp fmt conflicts + in let conflicts_description = Format.asprintf "Potentially races with writes in method%s %a. %s" (if Typ.Procname.Set.cardinal conflicting_proc_names > 1 then "s" else "") (MF.wrap_monospaced pp_conflicts) conflicting_proc_names - (if race_with_main_thread then - "\n Note: some of these write conflicts are confined to the UI or another thread, \ - but the current method is not specified to be. Consider adding synchronization \ - or a @ThreadConfined annotation to the current method." - else "") in + ( if race_with_main_thread then + "\n Note: some of these write conflicts are confined to the UI or another thread, but the current method is not specified to be. Consider adding synchronization or a @ThreadConfined annotation to the current method." + else "" ) + in Format.asprintf "Read/Write race. Non-private method %a%s reads from field %a. %s %s" (MF.wrap_monospaced pp_procname_short) pname (if CallSite.equal final_sink_site initial_sink_site then "" else " indirectly") - pp_access final_sink - conflicts_description - (calculate_addendum_message tenv pname) + pp_access final_sink conflicts_description (calculate_addendum_message tenv pname) (** type for remembering what we have already reported to avoid duplicates. our policy is to report each kind of access (read/write) to the same field reachable from the same procedure only once. in addition, if a call to a procedure (transitively) accesses multiple fields, we will only report one of each kind of access *) type reported = - { - reported_sites : CallSite.Set.t; - reported_writes : Typ.Procname.Set.t; - reported_reads : Typ.Procname.Set.t; - } + { reported_sites: CallSite.Set.t + ; reported_writes: Typ.Procname.Set.t + ; reported_reads: Typ.Procname.Set.t } let empty_reported = let reported_sites = CallSite.Set.empty in let reported_writes = Typ.Procname.Set.empty in let reported_reads = Typ.Procname.Set.empty in - { reported_sites; reported_reads; reported_writes; } + {reported_sites; reported_reads; reported_writes} (* return true if procedure is at an abstraction boundary or reporting has been explicitly requested via @ThreadSafe *) let should_report_on_proc proc_desc tenv = let proc_name = Procdesc.get_proc_name proc_desc in - is_thread_safe_method proc_desc tenv || - (not (Typ.Procname.java_is_autogen_method proc_name) && - Procdesc.get_access proc_desc <> PredSymb.Private && - not (Annotations.pdesc_return_annot_ends_with proc_desc Annotations.visibleForTesting)) + is_thread_safe_method proc_desc tenv + || not (Typ.Procname.java_is_autogen_method proc_name) + && Procdesc.get_access proc_desc <> PredSymb.Private + && not (Annotations.pdesc_return_annot_ends_with proc_desc Annotations.visibleForTesting) (** Report accesses that may race with each other. @@ -1226,180 +1177,167 @@ let should_report_on_proc proc_desc tenv = *) let report_unsafe_accesses aggregated_access_map = let open ThreadSafetyDomain in - let is_duplicate_report access pname { reported_sites; reported_writes; reported_reads; } = - CallSite.Set.mem (TraceElem.call_site access) reported_sites || - Typ.Procname.Set.mem - pname - (match snd (TraceElem.kind access) with - | Access.Write -> reported_writes - | Access.Read -> reported_reads) in + let is_duplicate_report access pname {reported_sites; reported_writes; reported_reads} = + CallSite.Set.mem (TraceElem.call_site access) reported_sites + || Typ.Procname.Set.mem pname + ( match snd (TraceElem.kind access) with + | Access.Write + -> reported_writes + | Access.Read + -> reported_reads ) + in let update_reported access pname reported = let reported_sites = CallSite.Set.add (TraceElem.call_site access) reported.reported_sites in match snd (TraceElem.kind access) with - | Access.Write -> - let reported_writes = Typ.Procname.Set.add pname reported.reported_writes in - { reported with reported_writes; reported_sites; } - | Access.Read -> - let reported_reads = Typ.Procname.Set.add pname reported.reported_reads in - { reported with reported_reads; reported_sites; } in + | Access.Write + -> let reported_writes = Typ.Procname.Set.add pname reported.reported_writes in + {reported with reported_writes; reported_sites} + | Access.Read + -> let reported_reads = Typ.Procname.Set.add pname reported.reported_reads in + {reported with reported_reads; reported_sites} + in let report_unsafe_access (access, pre, threaded, tenv, pdesc) accesses reported_acc = let pname = Procdesc.get_proc_name pdesc in - if is_duplicate_report access pname reported_acc - then - reported_acc + if is_duplicate_report access pname reported_acc then reported_acc else - match snd (TraceElem.kind access), pre with - | Access.Write, AccessPrecondition.Unprotected _ -> - begin - match Procdesc.get_proc_name pdesc with - | Java _ -> - if threaded - then - reported_acc - else - begin - (* unprotected write. warn. *) - report_thread_safety_violation - tenv - pdesc - ~make_description:make_unprotected_write_description - ~conflicts:[] - access; - update_reported access pname reported_acc - end - | _ -> - (* Do not report unprotected writes for ObjC_Cpp *) - reported_acc - end - | Access.Write, AccessPrecondition.Protected _ -> - (* protected write, do nothing *) + match (snd (TraceElem.kind access), pre) with + | Access.Write, AccessPrecondition.Unprotected _ -> ( + match Procdesc.get_proc_name pdesc with + | Java _ + -> if threaded then reported_acc + else ( + (* unprotected write. warn. *) + report_thread_safety_violation tenv pdesc + ~make_description:make_unprotected_write_description ~conflicts:[] access ; + update_reported access pname reported_acc ) + | _ + -> (* Do not report unprotected writes for ObjC_Cpp *) + reported_acc ) + | Access.Write, AccessPrecondition.Protected _ + -> (* protected write, do nothing *) reported_acc - | Access.Read, AccessPrecondition.Unprotected _ -> - (* unprotected read. report all writes as conflicts for java *) + | Access.Read, AccessPrecondition.Unprotected _ + -> (* unprotected read. report all writes as conflicts for java *) (* for c++ filter out unprotected writes *) - let is_cpp_protected_write pre = match pre with - | AccessPrecondition.Unprotected _ -> Typ.Procname.is_java pname - | AccessPrecondition.Protected _ -> true in + let is_cpp_protected_write pre = + match pre with + | AccessPrecondition.Unprotected _ + -> Typ.Procname.is_java pname + | AccessPrecondition.Protected _ + -> true + in let all_writes = List.filter ~f:(fun (other_access, pre, other_threaded, _, _) -> - TraceElem.is_write other_access && not (threaded && other_threaded) - && is_cpp_protected_write pre) - accesses in - if List.is_empty all_writes - then - reported_acc - else - begin - report_thread_safety_violation - tenv - pdesc - ~make_description:(make_read_write_race_description all_writes) - ~conflicts:(List.map ~f:(fun (access, _, _, _, _) -> access) all_writes) - access; - update_reported access pname reported_acc - end - | Access.Read, AccessPrecondition.Protected excl -> - (* protected read. + TraceElem.is_write other_access && not (threaded && other_threaded) + && is_cpp_protected_write pre) + accesses + in + if List.is_empty all_writes then reported_acc + else ( + report_thread_safety_violation tenv pdesc + ~make_description:(make_read_write_race_description all_writes) + ~conflicts:(List.map ~f:(fun (access, _, _, _, _) -> access) all_writes) + access ; + update_reported access pname reported_acc ) + | Access.Read, AccessPrecondition.Protected excl + -> (* protected read. report unprotected writes and opposite protected writes as conflicts Thread and Lock are opposites of one another, and Both has no opposite*) - let is_opposite = function - | Excluder.Lock , Excluder.Thread - -> true - | Excluder.Thread , Excluder.Lock - -> true - | _ , _ -> false in + let is_opposite = function + | Excluder.Lock, Excluder.Thread + -> true + | Excluder.Thread, Excluder.Lock + -> true + | _, _ + -> false + in let conflicting_writes = List.filter ~f:(fun (access, pre, _, _, _) -> - match pre with - | AccessPrecondition.Unprotected _ -> - TraceElem.is_write access - | AccessPrecondition.Protected other_excl - when is_opposite (excl , other_excl) -> - TraceElem.is_write access - | _ -> - false) - accesses in - if List.is_empty conflicting_writes - then - reported_acc - else - begin - (* protected read with conflicting unprotected write(s). warn. *) - report_thread_safety_violation - tenv - pdesc - ~make_description:(make_read_write_race_description conflicting_writes) - ~conflicts:(List.map ~f:(fun (access, _, _, _, _) -> access) conflicting_writes) - access; - update_reported access pname reported_acc - end in + match pre with + | AccessPrecondition.Unprotected _ + -> TraceElem.is_write access + | AccessPrecondition.Protected other_excl when is_opposite (excl, other_excl) + -> TraceElem.is_write access + | _ + -> false) + accesses + in + if List.is_empty conflicting_writes then reported_acc + else ( + (* protected read with conflicting unprotected write(s). warn. *) + report_thread_safety_violation tenv pdesc + ~make_description:(make_read_write_race_description conflicting_writes) + ~conflicts:(List.map ~f:(fun (access, _, _, _, _) -> access) conflicting_writes) + access ; + update_reported access pname reported_acc ) + in AccessListMap.fold (fun _ grouped_accesses reported_acc -> - (* reset the reported reads and writes for each memory location *) - let reported = - { reported_acc with reported_writes = Typ.Procname.Set.empty; - reported_reads = Typ.Procname.Set.empty; } in - let accessed_by_threadsafe_method = - List.exists - ~f:(fun (_, _, _, tenv, pdesc) -> is_thread_safe_method pdesc tenv) - grouped_accesses in - let class_has_mutex_member objc_cpp tenv = - let class_name = Typ.Procname.objc_cpp_get_class_type_name objc_cpp in - let matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["std::mutex"] in - Option.exists (Tenv.lookup tenv class_name) ~f:(fun class_str -> - (* check if the class contains a member of type std::mutex *) - List.exists (class_str.Typ.Struct.fields) ~f:(fun (_, ft, _) -> - Option.exists (Typ.name ft) ~f:(fun name -> - QualifiedCppName.Match.match_qualifiers matcher (Typ.Name.qual_name name)) - ) - ) in - let should_report pdesc tenv = - match Procdesc.get_proc_name pdesc with - | Java _ as pname -> - (* report if + (* reset the reported reads and writes for each memory location *) + let reported = + { reported_acc with + reported_writes= Typ.Procname.Set.empty; reported_reads= Typ.Procname.Set.empty } + in + let accessed_by_threadsafe_method = + List.exists + ~f:(fun (_, _, _, tenv, pdesc) -> is_thread_safe_method pdesc tenv) + grouped_accesses + in + let class_has_mutex_member objc_cpp tenv = + let class_name = Typ.Procname.objc_cpp_get_class_type_name objc_cpp in + let matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["std::mutex"] in + Option.exists (Tenv.lookup tenv class_name) ~f:(fun class_str -> + (* check if the class contains a member of type std::mutex *) + List.exists class_str.Typ.Struct.fields ~f:(fun (_, ft, _) -> + Option.exists (Typ.name ft) ~f:(fun name -> + QualifiedCppName.Match.match_qualifiers matcher (Typ.Name.qual_name name) ) ) ) + in + let should_report pdesc tenv = + match Procdesc.get_proc_name pdesc with + | Java _ as pname + -> (* report if - the method/class of the access is thread-safe (or an override or superclass is), or - any access is in a field marked thread-safe (or an override) *) - (accessed_by_threadsafe_method - || - let is_class_threadsafe = - not - (let current_class_marked_not_threadsafe = - PatternMatch.check_current_class_attributes - Annotations.ia_is_not_thread_safe tenv pname - in - current_class_marked_not_threadsafe) - && - (let current_class_or_super_marked_threadsafe = - match get_current_class_and_threadsafe_superclasses tenv pname with - | Some (_, thread_safe_annotated_classes) -> - not (List.is_empty thread_safe_annotated_classes) - | _ -> - false + ( accessed_by_threadsafe_method + || + let is_class_threadsafe = + not + (let current_class_marked_not_threadsafe = + PatternMatch.check_current_class_attributes Annotations.ia_is_not_thread_safe + tenv pname in - current_class_or_super_marked_threadsafe) + current_class_marked_not_threadsafe) + && + let current_class_or_super_marked_threadsafe = + match get_current_class_and_threadsafe_superclasses tenv pname with + | Some (_, thread_safe_annotated_classes) + -> not (List.is_empty thread_safe_annotated_classes) + | _ + -> false in - is_class_threadsafe) - && should_report_on_proc pdesc tenv - | ObjC_Cpp objc_cpp -> - (* do not report if a procedure is private *) - Procdesc.get_access pdesc <> PredSymb.Private && - (* report if the class has a mutex member *) - class_has_mutex_member objc_cpp tenv - | _ -> - false - in - let reportable_accesses = - List.filter ~f:(fun (_, _, _, tenv, pdesc) -> should_report pdesc tenv) grouped_accesses in - List.fold - ~f:(fun acc access -> report_unsafe_access access reportable_accesses acc) - reportable_accesses - ~init:reported) - aggregated_access_map - empty_reported + current_class_or_super_marked_threadsafe + in + is_class_threadsafe ) + && should_report_on_proc pdesc tenv + | ObjC_Cpp objc_cpp + -> (* do not report if a procedure is private *) + Procdesc.get_access pdesc <> PredSymb.Private + && (* report if the class has a mutex member *) + class_has_mutex_member objc_cpp tenv + | _ + -> false + in + let reportable_accesses = + List.filter ~f:(fun (_, _, _, tenv, pdesc) -> should_report pdesc tenv) grouped_accesses + in + List.fold + ~f:(fun acc access -> report_unsafe_access access reportable_accesses acc) + reportable_accesses ~init:reported) + aggregated_access_map empty_reported |> ignore (* equivalence relation computing whether two access paths may refer to the @@ -1407,44 +1345,37 @@ let report_unsafe_accesses aggregated_access_map = let may_alias tenv p1 p2 = let open Typ in let open AccessPath in - phys_equal p1 p2 || - match List.last_exn (snd p1), List.last_exn (snd p2) with - | FieldAccess _, ArrayAccess _ | ArrayAccess _, FieldAccess _ -> false + phys_equal p1 p2 + || + match (List.last_exn (snd p1), List.last_exn (snd p2)) with + | FieldAccess _, ArrayAccess _ | ArrayAccess _, FieldAccess _ + -> false (* fields in Java contain the class name /declaring/ them thus two fields can be aliases *iff* they are equal *) - | FieldAccess f1, FieldAccess f2 -> Typ.Fieldname.equal f1 f2 + | FieldAccess f1, FieldAccess f2 + -> Typ.Fieldname.equal f1 f2 (* if arrays of objects that have an inheritance rel then they can alias *) - | ArrayAccess {desc=Tptr ({desc=Tstruct tn1}, _)}, - ArrayAccess {desc=Tptr ({desc=Tstruct tn2}, _)} -> - PatternMatch.is_subtype tenv tn1 tn2 || - PatternMatch.is_subtype tenv tn2 tn1 + | ( ArrayAccess {desc= Tptr ({desc= Tstruct tn1}, _)} + , ArrayAccess {desc= Tptr ({desc= Tstruct tn2}, _)} ) + -> PatternMatch.is_subtype tenv tn1 tn2 || PatternMatch.is_subtype tenv tn2 tn1 (* primitive type arrays can alias if the prim. type is the same *) - | ArrayAccess t1, ArrayAccess t2 -> - equal_desc t1.desc t2.desc + | ArrayAccess t1, ArrayAccess t2 + -> equal_desc t1.desc t2.desc (* take a results table and quotient it by the may_alias relation *) let quotient_access_map acc_map = let rec aux acc m = - if AccessListMap.is_empty m then - acc + if AccessListMap.is_empty m then acc else let k, vals = AccessListMap.choose m in - let (_, _, _, tenv, _) = - List.find_exn vals - ~f:(fun (elem, _, _, _, _) -> - AccessPath.Raw.equal k (ThreadSafetyDomain.TraceElem.kind elem |> fst)) + let _, _, _, tenv, _ = + List.find_exn vals ~f:(fun (elem, _, _, _, _) -> + AccessPath.Raw.equal k (ThreadSafetyDomain.TraceElem.kind elem |> fst) ) in (* assumption: the tenv for k is sufficient for k' too *) - let (k_part, non_k_part) = - AccessListMap.partition (fun k' _ -> may_alias tenv k k') m in - if AccessListMap.is_empty k_part - then failwith "may_alias is not reflexive!"; - let k_accesses = - AccessListMap.fold - (fun _ v acc' -> List.append v acc') - k_part - [] - in + let k_part, non_k_part = AccessListMap.partition (fun k' _ -> may_alias tenv k k') m in + if AccessListMap.is_empty k_part then failwith "may_alias is not reflexive!" ; + let k_accesses = AccessListMap.fold (fun _ v acc' -> List.append v acc') k_part [] in let new_acc = AccessListMap.add k k_accesses acc in aux new_acc non_k_part in @@ -1455,41 +1386,44 @@ let quotient_access_map acc_map = that is auto-generated by Java. *) let should_filter_access (_, path) = let check_access_step = function - | AccessPath.ArrayAccess _ -> false - | AccessPath.FieldAccess fld -> - String.is_substring ~substring:"$SwitchMap" (Typ.Fieldname.to_string fld) in + | AccessPath.ArrayAccess _ + -> false + | AccessPath.FieldAccess fld + -> String.is_substring ~substring:"$SwitchMap" (Typ.Fieldname.to_string fld) + in List.exists path ~f:check_access_step (* create a map from [abstraction of a memory loc] -> accesses that may touch that memory loc. for now, our abstraction is an access path like x.f.g whose concretization is the set of memory cells that x.f.g may point to during execution *) let make_results_table file_env = - let aggregate_post (_ , threaded, _, accesses, _) tenv pdesc acc = + let aggregate_post (_, threaded, _, accesses, _) tenv pdesc acc = let open ThreadSafetyDomain in AccessDomain.fold (fun pre accesses acc -> - PathDomain.Sinks.fold - (fun access acc -> - let access_path, _ = TraceElem.kind access in - if should_filter_access access_path then acc else - let grouped_accesses = - try AccessListMap.find access_path acc - with Not_found -> [] in - AccessListMap.add - access_path - ((access, pre, threaded, tenv, pdesc) :: grouped_accesses) - acc) - (PathDomain.sinks accesses) - acc) - accesses - acc in + PathDomain.Sinks.fold + (fun access acc -> + let access_path, _ = TraceElem.kind access in + if should_filter_access access_path then acc + else + let grouped_accesses = + try AccessListMap.find access_path acc + with Not_found -> [] + in + AccessListMap.add access_path + ((access, pre, threaded, tenv, pdesc) :: grouped_accesses) acc) + (PathDomain.sinks accesses) acc) + accesses acc + in let aggregate_posts acc (_, tenv, proc_name, proc_desc) = match Summary.read_summary proc_desc proc_name with - | Some summary -> aggregate_post summary tenv proc_desc acc - | None -> acc in + | Some summary + -> aggregate_post summary tenv proc_desc acc + | None + -> acc + in List.fold ~f:aggregate_posts file_env ~init:AccessListMap.empty |> quotient_access_map (* Gathers results by analyzing all the methods in a file, then post-processes the results to check an (approximation of) thread safety *) -let file_analysis _ _ _ file_env = - report_unsafe_accesses (make_results_table file_env) +let file_analysis _ _ _ file_env = report_unsafe_accesses (make_results_table file_env) diff --git a/infer/src/checkers/ThreadSafetyConfig.ml b/infer/src/checkers/ThreadSafetyConfig.ml index 823fdacae..47e7fbc75 100644 --- a/infer/src/checkers/ThreadSafetyConfig.ml +++ b/infer/src/checkers/ThreadSafetyConfig.ml @@ -8,12 +8,12 @@ *) open! IStd - module F = Format module AnnotationAliases = struct - let of_json = function - | `List aliases -> List.map ~f:Yojson.Basic.Util.to_string aliases - | _ -> failwith "Couldn't parse thread-safety annotation aliases; expected list of strings" + | `List aliases + -> List.map ~f:Yojson.Basic.Util.to_string aliases + | _ + -> failwith "Couldn't parse thread-safety annotation aliases; expected list of strings" end diff --git a/infer/src/checkers/ThreadSafetyConfig.mli b/infer/src/checkers/ThreadSafetyConfig.mli index 363653e19..8555005b3 100644 --- a/infer/src/checkers/ThreadSafetyConfig.mli +++ b/infer/src/checkers/ThreadSafetyConfig.mli @@ -8,11 +8,9 @@ *) open! IStd - module F = Format (** List of annotations that should be considered aliases of @ThreadSafe *) module AnnotationAliases : sig - val of_json : Yojson.Basic.json -> string list end diff --git a/infer/src/checkers/ThreadSafetyDomain.ml b/infer/src/checkers/ThreadSafetyDomain.ml index 14a1ed175..74219383f 100644 --- a/infer/src/checkers/ThreadSafetyDomain.ml +++ b/infer/src/checkers/ThreadSafetyDomain.ml @@ -8,56 +8,47 @@ *) open! IStd - module F = Format module Access = struct - type kind = - | Read - | Write - [@@deriving compare] + type kind = Read | Write [@@deriving compare] type t = AccessPath.Raw.t * kind [@@deriving compare] - let pp fmt (access_path, access_kind) = match access_kind with - | Read -> F.fprintf fmt "Read of %a" AccessPath.Raw.pp access_path - | Write -> F.fprintf fmt "Write to %a" AccessPath.Raw.pp access_path + let pp fmt (access_path, access_kind) = + match access_kind with + | Read + -> F.fprintf fmt "Read of %a" AccessPath.Raw.pp access_path + | Write + -> F.fprintf fmt "Write to %a" AccessPath.Raw.pp access_path end module TraceElem = struct module Kind = Access - type t = { - site : CallSite.t; - kind : Kind.t; - } [@@deriving compare] + type t = {site: CallSite.t; kind: Kind.t} [@@deriving compare] - let is_read { kind; } = - match snd kind with - | Read -> true - | Write -> false + let is_read {kind} = match snd kind with Read -> true | Write -> false - let is_write { kind; } = - match snd kind with - | Read -> false - | Write -> true + let is_write {kind} = match snd kind with Read -> false | Write -> true - let call_site { site; } = site + let call_site {site} = site - let kind { kind; } = kind + let kind {kind} = kind - let make ?indexes:_ kind site = { kind; site; } + let make ?indexes:_ kind site = {kind; site} - let with_callsite t site = { t with site; } + let with_callsite t site = {t with site} - let pp fmt { site; kind; } = - F.fprintf fmt "%a at %a" Access.pp kind CallSite.pp site + let pp fmt {site; kind} = F.fprintf fmt "%a at %a" Access.pp kind CallSite.pp site module Set = PrettyPrintable.MakePPSet (struct - type nonrec t = t - let compare = compare - let pp = pp - end) + type nonrec t = t + + let compare = compare + + let pp = pp + end) end let make_access access_path access_kind loc = @@ -72,44 +63,42 @@ let make_access access_path access_kind loc = all branches. *) module LocksDomain = AbstractDomain.BooleanAnd - module ThreadsDomain = AbstractDomain.BooleanAnd - module ThumbsUpDomain = AbstractDomain.BooleanAnd - -module PathDomain = SinkTrace.Make(TraceElem) +module PathDomain = SinkTrace.Make (TraceElem) module Choice = struct - type t = - | OnMainThread - | LockHeld - [@@deriving compare] + type t = OnMainThread | LockHeld [@@deriving compare] let pp fmt = function - | OnMainThread -> F.fprintf fmt "OnMainThread" - | LockHeld -> F.fprintf fmt "LockHeld" + | OnMainThread + -> F.fprintf fmt "OnMainThread" + | LockHeld + -> F.fprintf fmt "LockHeld" end module Attribute = struct - type t = - | OwnedIf of int option - | Functional - | Choice of Choice.t - [@@deriving compare] + type t = OwnedIf of int option | Functional | Choice of Choice.t [@@deriving compare] let pp fmt = function - | OwnedIf None -> F.fprintf fmt "Owned" - | OwnedIf (Some formal_index) -> F.fprintf fmt "Owned if formal %d is owned" formal_index - | Functional -> F.fprintf fmt "Functional" - | Choice choice -> Choice.pp fmt choice + | OwnedIf None + -> F.fprintf fmt "Owned" + | OwnedIf Some formal_index + -> F.fprintf fmt "Owned if formal %d is owned" formal_index + | Functional + -> F.fprintf fmt "Functional" + | Choice choice + -> Choice.pp fmt choice let unconditionally_owned = OwnedIf None - module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) end module AttributeSetDomain = AbstractDomain.InvertedSet (Attribute.Set) @@ -118,67 +107,59 @@ module AttributeMapDomain = struct include AbstractDomain.InvertedMap (AccessPath.RawMap) (AttributeSetDomain) let has_attribute access_path attribute t = - try - find access_path t - |> AttributeSetDomain.mem attribute - with Not_found -> - false + try find access_path t |> AttributeSetDomain.mem attribute + with Not_found -> false let get_conditional_ownership_index access_path t = try let attributes = find access_path t in - (List.find_map - ~f:(function - | Attribute.OwnedIf ((Some _) as formal_index_opt) -> formal_index_opt - | _ -> None) - (AttributeSetDomain.elements attributes)) - with Not_found -> - None + List.find_map + ~f:(function + | Attribute.OwnedIf (Some _ as formal_index_opt) -> formal_index_opt | _ -> None) + (AttributeSetDomain.elements attributes) + with Not_found -> None let get_choices access_path t = try let attributes = find access_path t in - (List.filter_map - ~f:(function - | Attribute.Choice c -> Some c - | _ -> None) - (AttributeSetDomain.elements attributes)) - with Not_found -> - [] + List.filter_map + ~f:(function Attribute.Choice c -> Some c | _ -> None) + (AttributeSetDomain.elements attributes) + with Not_found -> [] let add_attribute access_path attribute t = let attribute_set = - (try find access_path t - with Not_found -> AttributeSetDomain.empty) - |> AttributeSetDomain.add attribute in + ( try find access_path t + with Not_found -> AttributeSetDomain.empty ) + |> AttributeSetDomain.add attribute + in add access_path attribute_set t end module Excluder = struct - type t = + type t = Thread | Lock | Both [@@deriving compare] + + let pp fmt = function | Thread + -> F.fprintf fmt "Thread" | Lock + -> F.fprintf fmt "Lock" | Both - [@@deriving compare] - - let pp fmt = function - | Thread -> F.fprintf fmt "Thread" - | Lock -> F.fprintf fmt "Lock" - | Both -> F.fprintf fmt "both Thread and Lock" + -> F.fprintf fmt "both Thread and Lock" end module AccessPrecondition = struct - type t = - | Protected of Excluder.t - | Unprotected of int option - [@@deriving compare] + type t = Protected of Excluder.t | Unprotected of int option [@@deriving compare] let unprotected = Unprotected None let pp fmt = function - | Protected excl -> F.fprintf fmt "ProtectedBy(%a)" Excluder.pp excl - | Unprotected (Some index) -> F.fprintf fmt "Unprotected(%d)" index - | Unprotected None -> F.fprintf fmt "Unprotected" + | Protected excl + -> F.fprintf fmt "ProtectedBy(%a)" Excluder.pp excl + | Unprotected Some index + -> F.fprintf fmt "Unprotected(%d)" index + | Unprotected None + -> F.fprintf fmt "Unprotected" end module AccessDomain = struct @@ -187,7 +168,8 @@ module AccessDomain = struct let add_access precondition access_path t = let precondition_accesses = try find precondition t - with Not_found -> PathDomain.empty in + with Not_found -> PathDomain.empty + in let precondition_accesses' = PathDomain.add_sink access_path precondition_accesses in add precondition precondition_accesses' t @@ -197,17 +179,18 @@ module AccessDomain = struct end type astate = - { - thumbs_up : ThumbsUpDomain.astate; - threads: ThreadsDomain.astate; - locks : LocksDomain.astate; - accesses : AccessDomain.astate; - attribute_map : AttributeMapDomain.astate; - } + { thumbs_up: ThumbsUpDomain.astate + ; threads: ThreadsDomain.astate + ; locks: LocksDomain.astate + ; accesses: AccessDomain.astate + ; attribute_map: AttributeMapDomain.astate } type summary = - ThumbsUpDomain.astate * ThreadsDomain.astate * LocksDomain.astate - * AccessDomain.astate * AttributeSetDomain.astate + ThumbsUpDomain.astate + * ThreadsDomain.astate + * LocksDomain.astate + * AccessDomain.astate + * AttributeSetDomain.astate let empty = let thumbs_up = true in @@ -215,59 +198,45 @@ let empty = let locks = false in let accesses = AccessDomain.empty in let attribute_map = AccessPath.RawMap.empty in - { thumbs_up; threads; locks; accesses; attribute_map; } + {thumbs_up; threads; locks; accesses; attribute_map} -let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true - else - ThreadsDomain.(<=) ~lhs:lhs.threads ~rhs:rhs.threads && - LocksDomain.(<=) ~lhs:lhs.locks ~rhs:rhs.locks && - AccessDomain.(<=) ~lhs:lhs.accesses ~rhs:rhs.accesses && - AttributeMapDomain.(<=) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map +let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true + else ThreadsDomain.( <= ) ~lhs:lhs.threads ~rhs:rhs.threads + && LocksDomain.( <= ) ~lhs:lhs.locks ~rhs:rhs.locks + && AccessDomain.( <= ) ~lhs:lhs.accesses ~rhs:rhs.accesses + && AttributeMapDomain.( <= ) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map let join astate1 astate2 = - if phys_equal astate1 astate2 - then - astate1 + if phys_equal astate1 astate2 then astate1 else let thumbs_up = ThreadsDomain.join astate1.thumbs_up astate2.thumbs_up in let threads = ThreadsDomain.join astate1.threads astate2.threads in let locks = LocksDomain.join astate1.locks astate2.locks in let accesses = AccessDomain.join astate1.accesses astate2.accesses in let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in - { thumbs_up; threads; locks; accesses; attribute_map; } + {thumbs_up; threads; locks; accesses; attribute_map} let widen ~prev ~next ~num_iters = - if phys_equal prev next - then - prev + if phys_equal prev next then prev else let thumbs_up = ThreadsDomain.widen ~prev:prev.thumbs_up ~next:next.thumbs_up ~num_iters in let threads = ThreadsDomain.widen ~prev:prev.threads ~next:next.threads ~num_iters in let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in let accesses = AccessDomain.widen ~prev:prev.accesses ~next:next.accesses ~num_iters in let attribute_map = - AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters in - { thumbs_up; threads; locks; accesses; attribute_map; } + AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters + in + {thumbs_up; threads; locks; accesses; attribute_map} -let pp_summary fmt (thumbs_up, threads, locks, - accesses, return_attributes) = - F.fprintf - fmt +let pp_summary fmt (thumbs_up, threads, locks, accesses, return_attributes) = + F.fprintf fmt "@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a@\n" - ThumbsUpDomain.pp thumbs_up - ThreadsDomain.pp threads - LocksDomain.pp locks - AccessDomain.pp accesses - AttributeSetDomain.pp return_attributes - -let pp fmt { thumbs_up; threads; locks; accesses; attribute_map; } = - F.fprintf - fmt + ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp + accesses AttributeSetDomain.pp return_attributes + +let pp fmt {thumbs_up; threads; locks; accesses; attribute_map} = + F.fprintf fmt "@\nThumbsUp: %a, Threads: %a, Locks: %a @\nAccesses %a @\nReturn Attributes: %a@\n" - ThumbsUpDomain.pp thumbs_up - ThreadsDomain.pp threads - LocksDomain.pp locks - AccessDomain.pp accesses - AttributeMapDomain.pp attribute_map + ThumbsUpDomain.pp thumbs_up ThreadsDomain.pp threads LocksDomain.pp locks AccessDomain.pp + accesses AttributeMapDomain.pp attribute_map diff --git a/infer/src/checkers/ThreadSafetyDomain.mli b/infer/src/checkers/ThreadSafetyDomain.mli index f7894329a..14b21ff3b 100644 --- a/infer/src/checkers/ThreadSafetyDomain.mli +++ b/infer/src/checkers/ThreadSafetyDomain.mli @@ -8,14 +8,10 @@ *) open! IStd - module F = Format module Access : sig - type kind = - | Read - | Write - [@@deriving compare] + type kind = Read | Write [@@deriving compare] type t = AccessPath.Raw.t * kind [@@deriving compare] @@ -41,14 +37,14 @@ module ThreadsDomain : AbstractDomain.S with type astate = bool module ThumbsUpDomain : AbstractDomain.S with type astate = bool -module PathDomain : module type of SinkTrace.Make(TraceElem) +module PathDomain : module type of SinkTrace.Make (TraceElem) (** attribute attached to a boolean variable specifying what it means when the boolean is true *) module Choice : sig type t = - | OnMainThread (** the current procedure is running on the main thread *) - | LockHeld (** a lock is currently held *) - [@@deriving compare] + | OnMainThread (** the current procedure is running on the main thread *) + | LockHeld (** a lock is currently held *) + [@@deriving compare] val pp : F.formatter -> t -> unit end @@ -56,15 +52,13 @@ end module Attribute : sig type t = | OwnedIf of int option - (** owned unconditionally if OwnedIf None, owned when formal at index i is owned otherwise *) - | Functional - (** holds a value returned from a callee marked @Functional *) - | Choice of Choice.t - (** holds a boolean choice variable *) - [@@deriving compare] + (** owned unconditionally if OwnedIf None, owned when formal at index i is owned otherwise *) + | Functional (** holds a value returned from a callee marked @Functional *) + | Choice of Choice.t (** holds a boolean choice variable *) + [@@deriving compare] - (** alias for OwnedIf None *) val unconditionally_owned : t + (** alias for OwnedIf None *) val pp : F.formatter -> t -> unit @@ -78,16 +72,15 @@ module AttributeMapDomain : sig val has_attribute : AccessPath.Raw.t -> Attribute.t -> astate -> bool - (** get the formal index of the the formal that must own the given access path (if any) *) val get_conditional_ownership_index : AccessPath.Raw.t -> astate -> int option + (** get the formal index of the the formal that must own the given access path (if any) *) - (** get the choice attributes associated with the given access path *) val get_choices : AccessPath.Raw.t -> astate -> Choice.t list + (** get the choice attributes associated with the given access path *) val add_attribute : AccessPath.Raw.t -> Attribute.t -> astate -> astate end - (** Excluders: Two things can provide for mutual exclusion: holding a lock, and knowing that you are on a particular thread. Here, we abstract it to "some" lock and "any particular" thread (typically, UI thread) @@ -97,12 +90,8 @@ end There is no need for a lattice relation between Thread, Lock and Both: you don't ever join Thread and Lock, rather, they are treated pointwise. **) -module Excluder: sig - type t = - | Thread - | Lock - | Both - [@@deriving compare] +module Excluder : sig + type t = Thread | Lock | Both [@@deriving compare] val pp : F.formatter -> t -> unit end @@ -110,16 +99,16 @@ end module AccessPrecondition : sig type t = | Protected of Excluder.t - (** access potentially protected for mutual exclusion by + (** access potentially protected for mutual exclusion by lock or thread or both *) | Unprotected of int option - (** access rooted in formal at index i. Safe if actual passed at index is owned (i.e., + (** access rooted in formal at index i. Safe if actual passed at index is owned (i.e., !owned(i) implies an unsafe access). Unprotected None means the access is unsafe unless a lock is held in the caller *) - [@@deriving compare] + [@@deriving compare] - (** type of an unprotected access *) val unprotected : t + (** type of an unprotected access *) val pp : F.formatter -> t -> unit end @@ -130,31 +119,31 @@ module AccessDomain : sig include module type of AbstractDomain.Map (AccessPrecondition) (PathDomain) (* add the given (access, precondition) pair to the map *) + val add_access : AccessPrecondition.t -> TraceElem.t -> astate -> astate (* get all accesses with the given precondition *) + val get_accesses : AccessPrecondition.t -> astate -> PathDomain.astate end type astate = - { - thumbs_up : ThreadsDomain.astate; - (** boolean that is true if we think we have a proof *) - threads : ThreadsDomain.astate; - (** boolean that is true if we know we are on UI/main thread *) - locks : LocksDomain.astate; - (** boolean that is true if a lock must currently be held *) - accesses : AccessDomain.astate; - (** read and writes accesses performed without ownership permissions *) - attribute_map : AttributeMapDomain.astate; - (** map of access paths to attributes such as owned, functional, ... *) - } + { thumbs_up: ThreadsDomain.astate (** boolean that is true if we think we have a proof *) + ; threads: ThreadsDomain.astate (** boolean that is true if we know we are on UI/main thread *) + ; locks: LocksDomain.astate (** boolean that is true if a lock must currently be held *) + ; accesses: AccessDomain.astate + (** read and writes accesses performed without ownership permissions *) + ; attribute_map: AttributeMapDomain.astate + (** map of access paths to attributes such as owned, functional, ... *) } (** same as astate, but without [id_map]/[owned] (since they are local) and with the addition of the attributes associated with the return value *) type summary = - ThumbsUpDomain.astate * ThreadsDomain.astate * LocksDomain.astate - * AccessDomain.astate * AttributeSetDomain.astate + ThumbsUpDomain.astate + * ThreadsDomain.astate + * LocksDomain.astate + * AccessDomain.astate + * AttributeSetDomain.astate include AbstractDomain.WithBottom with type astate := astate diff --git a/infer/src/checkers/Trace.ml b/infer/src/checkers/Trace.ml index a6babf93b..a85a222e9 100644 --- a/infer/src/checkers/Trace.ml +++ b/infer/src/checkers/Trace.ml @@ -8,22 +8,25 @@ *) open! IStd - module F = Format module L = Logging module type Spec = sig module Source : Source.S + module Sink : Sink.S - (** should a flow originating at source and entering sink be reported? *) val should_report : Source.t -> Sink.t -> bool + (** should a flow originating at source and entering sink be reported? *) end module type S = sig include Spec + type t + type astate = t + include AbstractDomain.WithBottom with type astate := astate module Sources = Source.Set @@ -37,40 +40,40 @@ module type S = sig val empty : t - (** get the sources of the trace. *) val sources : t -> Sources.t + (** get the sources of the trace. *) - (** get the sinks of the trace *) val sinks : t -> Sinks.t + (** get the sinks of the trace *) - (** get the passthroughs of the trace *) val passthroughs : t -> Passthroughs.t + (** get the passthroughs of the trace *) + val get_reports : ?cur_site:CallSite.t -> t -> (Source.t * Sink.t * Passthroughs.t) list (** get the reportable source-sink flows in this trace. specifying [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) - val get_reports : ?cur_site:CallSite.t -> t -> (Source.t * Sink.t * Passthroughs.t) list - (** get a path for each of the reportable source -> sink flows in this trace. specifying - [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) val get_reportable_paths : ?cur_site:CallSite.t -> t -> trace_of_pname:(Typ.Procname.t -> t) -> path list + (** get a path for each of the reportable source -> sink flows in this trace. specifying + [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) + val to_loc_trace : + ?desc_of_source:(Source.t -> string) -> ?source_should_nest:(Source.t -> bool) + -> ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> path + -> Errlog.loc_trace (** create a loc_trace from a path; [source_should_nest s] should be true when we are going one deeper into a call-chain, ie when lt_level should be bumper in the next loc_trace_elem, and similarly for [sink_should_nest] *) - val to_loc_trace : - ?desc_of_source:(Source.t -> string) -> ?source_should_nest:(Source.t -> bool) -> - ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> - path -> Errlog.loc_trace - (** create a trace from a source *) val of_source : Source.t -> t + (** create a trace from a source *) - (** ad a source to the current trace *) val add_source : Source.t -> t -> t + (** ad a source to the current trace *) - (** add a sink to the current trace. *) val add_sink : Sink.t -> t -> t + (** add a sink to the current trace. *) val update_sources : t -> Sources.t -> t @@ -78,11 +81,11 @@ module type S = sig val get_footprint_indexes : t -> IntSet.t - (** append the trace for given call site to the current caller trace *) val append : t -> t -> CallSite.t -> t + (** append the trace for given call site to the current caller trace *) - (** return true if this trace has no source or sink data *) val is_empty : t -> bool + (** return true if this trace has no source or sink data *) val compare : t -> t -> int @@ -90,14 +93,13 @@ module type S = sig val pp : F.formatter -> t -> unit - (** pretty-print a path in the context of the given procname *) val pp_path : Typ.Procname.t -> F.formatter -> path -> unit + (** pretty-print a path in the context of the given procname *) end (** Expand a trace element (i.e., a source or sink) into a list of trace elements bottoming out in the "original" trace element. The list is always non-empty. *) module Expander (TraceElem : TraceElem.S) = struct - let expand elem0 ~elems_passthroughs_of_pname ~filter_passthroughs = let rec expand_ elem (elems_passthroughs_acc, seen_acc) = let elem_site = TraceElem.call_site elem in @@ -105,43 +107,44 @@ module Expander (TraceElem : TraceElem.S) = struct let seen_acc' = CallSite.Set.add elem_site seen_acc in let elems, passthroughs = elems_passthroughs_of_pname (CallSite.pname elem_site) in let is_recursive callee_elem seen = - CallSite.Set.mem (TraceElem.call_site callee_elem) seen in + CallSite.Set.mem (TraceElem.call_site callee_elem) seen + in (* find sinks that are the same kind as the caller, but have a different procname *) let matching_elems = List.filter ~f:(fun callee_elem -> - [%compare.equal : TraceElem.Kind.t] (TraceElem.kind callee_elem) elem_kind && - not (is_recursive callee_elem seen_acc')) - elems in + [%compare.equal : TraceElem.Kind.t] (TraceElem.kind callee_elem) elem_kind + && not (is_recursive callee_elem seen_acc')) + elems + in (* arbitrarily pick one elem and explore it further *) match matching_elems with - | callee_elem :: _ -> - (* TODO: pick the shortest path to a sink here instead (t14242809) *) + | callee_elem :: _ + -> (* TODO: pick the shortest path to a sink here instead (t14242809) *) let filtered_passthroughs = - filter_passthroughs elem_site (TraceElem.call_site callee_elem) passthroughs in + filter_passthroughs elem_site (TraceElem.call_site callee_elem) passthroughs + in expand_ callee_elem ((elem, filtered_passthroughs) :: elems_passthroughs_acc, seen_acc') - | _ -> - (elem, Passthrough.Set.empty) :: elems_passthroughs_acc, seen_acc' in + | _ + -> ((elem, Passthrough.Set.empty) :: elems_passthroughs_acc, seen_acc') + in fst (expand_ elem0 ([], CallSite.Set.empty)) end module Make (Spec : Spec) = struct include Spec - module Sources = Source.Set module Sinks = Sink.Set module Passthroughs = Passthrough.Set - - module SourceExpander = Expander(Source) - module SinkExpander = Expander(Sink) + module SourceExpander = Expander (Source) + module SinkExpander = Expander (Sink) type t = - { - sources : Sources.t; (** last functions in the trace that returned tainted data *) - sinks : Sinks.t; - (** last callees in the trace that transitively called a tainted function (if any) *) - passthroughs : Passthrough.Set.t; (** calls that occurred between source and sink *) - } [@@deriving compare] + { sources: Sources.t (** last functions in the trace that returned tainted data *) + ; sinks: Sinks.t + (** last callees in the trace that transitively called a tainted function (if any) *) + ; passthroughs: Passthrough.Set.t (** calls that occurred between source and sink *) } + [@@deriving compare] let equal = [%compare.equal : t] @@ -150,281 +153,270 @@ module Make (Spec : Spec) = struct type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list let pp fmt t = - F.fprintf - fmt - "%a -> %a via %a" - Sources.pp t.sources Sinks.pp t.sinks Passthroughs.pp t.passthroughs + F.fprintf fmt "%a -> %a via %a" Sources.pp t.sources Sinks.pp t.sinks Passthroughs.pp + t.passthroughs - let sources t = - t.sources + let sources t = t.sources - let sinks t = - t.sinks + let sinks t = t.sinks - let passthroughs t = - t.passthroughs + let passthroughs t = t.passthroughs let is_empty t = (* sources empty => sinks empty and passthroughs empty *) Sources.is_empty t.sources let get_reports ?cur_site t = - if Sinks.is_empty t.sinks || Sources.is_empty t.sources - then - [] + if Sinks.is_empty t.sinks || Sources.is_empty t.sources then [] else - let should_report_at_site source sink = match cur_site with - | None -> - true - | Some call_site -> - (* report when: (1) [cur_site] introduces the sink, and (2) [cur_site] does not also + let should_report_at_site source sink = + match cur_site with + | None + -> true + | Some call_site + -> (* report when: (1) [cur_site] introduces the sink, and (2) [cur_site] does not also introduce the source. otherwise, we'll report paths that don't respect control flow. *) - CallSite.equal call_site (Sink.call_site sink) && - not (CallSite.equal call_site (Source.call_site source)) in - + CallSite.equal call_site (Sink.call_site sink) + && not (CallSite.equal call_site (Source.call_site source)) + in (* written to avoid closure allocations in hot code. change with caution. *) let report_source source sinks acc0 = let report_one sink acc = - if Spec.should_report source sink && should_report_at_site source sink - then (source, sink, t.passthroughs) :: acc - else acc in - Sinks.fold report_one sinks acc0 in - let report_sources source acc = - report_source source t.sinks acc in + if Spec.should_report source sink && should_report_at_site source sink then + (source, sink, t.passthroughs) :: acc + else acc + in + Sinks.fold report_one sinks acc0 + in + let report_sources source acc = report_source source t.sinks acc in Sources.fold report_sources t.sources [] let pp_path cur_pname fmt (cur_passthroughs, sources_passthroughs, sinks_passthroughs) = let pp_passthroughs fmt passthroughs = - if not (Passthrough.Set.is_empty passthroughs) - then F.fprintf fmt "(via %a)" Passthrough.Set.pp passthroughs in - + if not (Passthrough.Set.is_empty passthroughs) then + F.fprintf fmt "(via %a)" Passthrough.Set.pp passthroughs + in let pp_elems elem_to_callsite fmt elems_passthroughs = let pp_sep fmt () = F.fprintf fmt "@." in let pp_elem fmt (elem, passthroughs) = - F.fprintf - fmt - "|=> %a %a" - CallSite.pp (elem_to_callsite elem) pp_passthroughs passthroughs in - (F.pp_print_list ~pp_sep) pp_elem fmt elems_passthroughs in + F.fprintf fmt "|=> %a %a" CallSite.pp (elem_to_callsite elem) pp_passthroughs passthroughs + in + F.pp_print_list ~pp_sep pp_elem fmt elems_passthroughs + in let pp_sources = pp_elems Source.call_site in let pp_sinks = pp_elems Sink.call_site in - let original_source = fst (List.hd_exn sources_passthroughs) in let final_sink = fst (List.hd_exn sinks_passthroughs) in - F.fprintf - fmt - "Error: %a -> %a. Full trace:@.%a@.Current procedure %a %a@.%a" - Source.pp original_source - Sink.pp final_sink - pp_sources sources_passthroughs - Typ.Procname.pp cur_pname - pp_passthroughs cur_passthroughs - pp_sinks (List.rev sinks_passthroughs) + F.fprintf fmt "Error: %a -> %a. Full trace:@.%a@.Current procedure %a %a@.%a" Source.pp + original_source Sink.pp final_sink pp_sources sources_passthroughs Typ.Procname.pp cur_pname + pp_passthroughs cur_passthroughs pp_sinks (List.rev sinks_passthroughs) type passthrough_kind = - | Source (* passthroughs of a source *) - | Sink (* passthroughs of a sink *) - | Top_level (* passthroughs of a top-level source->sink path *) + | Source + (* passthroughs of a source *) + | Sink + (* passthroughs of a sink *) + | Top_level - let get_reportable_paths ?cur_site t ~trace_of_pname = + (* passthroughs of a top-level source->sink path *) + let get_reportable_paths ?cur_site t ~trace_of_pname = let filter_passthroughs_ passthrough_kind start_site end_site passthroughs = - let line_number call_site = - (CallSite.loc call_site).Location.line in + let line_number call_site = (CallSite.loc call_site).Location.line in let start_line = line_number start_site in let end_line = line_number end_site in let between_start_and_end passthrough = let passthrough_line = line_number (Passthrough.site passthrough) in match passthrough_kind with - | Source -> passthrough_line >= end_line - | Sink -> passthrough_line <= end_line - | Top_level -> passthrough_line >= start_line && passthrough_line <= end_line in - Passthrough.Set.filter between_start_and_end passthroughs in - + | Source + -> passthrough_line >= end_line + | Sink + -> passthrough_line <= end_line + | Top_level + -> passthrough_line >= start_line && passthrough_line <= end_line + in + Passthrough.Set.filter between_start_and_end passthroughs + in let expand_path source sink = let sources_of_pname pname = let trace = trace_of_pname pname in - Sources.elements (sources trace), passthroughs trace in + (Sources.elements (sources trace), passthroughs trace) + in let sinks_of_pname pname = let trace = trace_of_pname pname in - Sinks.elements (sinks trace), passthroughs trace in + (Sinks.elements (sinks trace), passthroughs trace) + in let sources_passthroughs = let filter_passthroughs = filter_passthroughs_ Source in - SourceExpander.expand - source ~elems_passthroughs_of_pname:sources_of_pname ~filter_passthroughs in + SourceExpander.expand source ~elems_passthroughs_of_pname:sources_of_pname + ~filter_passthroughs + in let sinks_passthroughs = let filter_passthroughs = filter_passthroughs_ Sink in - SinkExpander.expand - sink ~elems_passthroughs_of_pname:sinks_of_pname ~filter_passthroughs in - sources_passthroughs, sinks_passthroughs in - + SinkExpander.expand sink ~elems_passthroughs_of_pname:sinks_of_pname ~filter_passthroughs + in + (sources_passthroughs, sinks_passthroughs) + in List.map ~f:(fun (source, sink, passthroughs) -> - let sources_passthroughs, sinks_passthroughs = expand_path source sink in - let filtered_passthroughs = - filter_passthroughs_ - Top_level (Source.call_site source) (Sink.call_site sink) passthroughs in - filtered_passthroughs, sources_passthroughs, sinks_passthroughs) + let sources_passthroughs, sinks_passthroughs = expand_path source sink in + let filtered_passthroughs = + filter_passthroughs_ Top_level (Source.call_site source) (Sink.call_site sink) + passthroughs + in + (filtered_passthroughs, sources_passthroughs, sinks_passthroughs)) (get_reports ?cur_site t) let to_loc_trace - ?(desc_of_source=fun source -> - let callsite = Source.call_site source in - Format.asprintf "return from %a" Typ.Procname.pp (CallSite.pname callsite)) - ?(source_should_nest=(fun _ -> true)) - ?(desc_of_sink=fun sink -> - let callsite = Sink.call_site sink in - Format.asprintf "call to %a" Typ.Procname.pp (CallSite.pname callsite)) - ?(sink_should_nest=(fun _ -> true)) - (passthroughs, sources, sinks) = - + ?(desc_of_source= fun source -> + let callsite = Source.call_site source in + Format.asprintf "return from %a" Typ.Procname.pp + (CallSite.pname callsite)) ?(source_should_nest= fun _ -> true) + ?(desc_of_sink= fun sink -> + let callsite = Sink.call_site sink in + Format.asprintf "call to %a" Typ.Procname.pp (CallSite.pname callsite)) + ?(sink_should_nest= fun _ -> true) (passthroughs, sources, sinks) = let trace_elems_of_passthroughs lt_level passthroughs acc0 = let trace_elem_of_passthrough passthrough acc = let passthrough_site = Passthrough.site passthrough in - let desc = F.asprintf "flow through %a" Typ.Procname.pp (CallSite.pname passthrough_site) in - (Errlog.make_trace_element lt_level (CallSite.loc passthrough_site) desc []) :: acc in + let desc = + F.asprintf "flow through %a" Typ.Procname.pp (CallSite.pname passthrough_site) + in + Errlog.make_trace_element lt_level (CallSite.loc passthrough_site) desc [] :: acc + in (* sort passthroughs by ascending line number to create a coherent trace *) let sorted_passthroughs = List.sort ~cmp:(fun passthrough1 passthrough2 -> - let loc1 = CallSite.loc (Passthrough.site passthrough1) in - let loc2 = CallSite.loc (Passthrough.site passthrough2) in - Int.compare loc1.Location.line loc2.Location.line) - (Passthroughs.elements passthroughs) in - List.fold_right ~f:trace_elem_of_passthrough sorted_passthroughs ~init:acc0 in - + let loc1 = CallSite.loc (Passthrough.site passthrough1) in + let loc2 = CallSite.loc (Passthrough.site passthrough2) in + Int.compare loc1.Location.line loc2.Location.line) + (Passthroughs.elements passthroughs) + in + List.fold_right ~f:trace_elem_of_passthrough sorted_passthroughs ~init:acc0 + in let get_nesting should_nest elems start_nesting = let level = ref start_nesting in - let get_nesting_ ((elem, _) as pair) = - if should_nest elem - then incr level; - pair, !level in - List.map ~f:get_nesting_ (List.rev elems) in - + let get_nesting_ (elem, _ as pair) = + if should_nest elem then incr level ; + (pair, !level) + in + List.map ~f:get_nesting_ (List.rev elems) + in let trace_elems_of_path_elem call_site desc ~is_source ((elem, passthroughs), lt_level) acc = let desc = desc elem in let loc = CallSite.loc (call_site elem) in - if is_source - then + if is_source then let trace_elem = Errlog.make_trace_element lt_level loc desc [] in trace_elems_of_passthroughs (lt_level + 1) passthroughs (trace_elem :: acc) else let trace_elem = Errlog.make_trace_element (lt_level - 1) loc desc [] in - trace_elem :: (trace_elems_of_passthroughs lt_level passthroughs acc) in - + trace_elem :: trace_elems_of_passthroughs lt_level passthroughs acc + in let trace_elems_of_source = - trace_elems_of_path_elem Source.call_site desc_of_source ~is_source:true in + trace_elems_of_path_elem Source.call_site desc_of_source ~is_source:true + in let trace_elems_of_sink = - trace_elems_of_path_elem Sink.call_site desc_of_sink ~is_source:false in + trace_elems_of_path_elem Sink.call_site desc_of_sink ~is_source:false + in let sources_with_level = get_nesting source_should_nest sources (-1) in let sinks_with_level = get_nesting sink_should_nest sinks 0 in let trace_prefix = List.fold_right ~f:trace_elems_of_sink sinks_with_level ~init:[] - |> trace_elems_of_passthroughs 0 passthroughs in + |> trace_elems_of_passthroughs 0 passthroughs + in List.fold ~f:(fun acc source -> trace_elems_of_source source acc) - ~init:trace_prefix - sources_with_level + ~init:trace_prefix sources_with_level let of_source source = let sources = Sources.singleton source in let passthroughs = Passthroughs.empty in let sinks = Sinks.empty in - { sources; passthroughs; sinks; } + {sources; passthroughs; sinks} let add_source source t = let sources = Sources.add source t.sources in - { t with sources; } + {t with sources} let add_sink sink t = let sinks = Sinks.add sink t.sinks in - { t with sinks; } + {t with sinks} - let update_sources t sources = { t with sources } + let update_sources t sources = {t with sources} - let update_sinks t sinks = { t with sinks } + let update_sinks t sinks = {t with sinks} let get_footprint_index source = match Source.get_footprint_access_path source with - | Some access_path -> - AccessPath.get_footprint_index access_path - | None -> - None + | Some access_path + -> AccessPath.get_footprint_index access_path + | None + -> None let get_footprint_indexes trace = Sources.fold - (fun source acc -> match get_footprint_index source with - | Some footprint_index -> IntSet.add footprint_index acc - | None -> acc) - (sources trace) - IntSet.empty + (fun source acc -> + match get_footprint_index source with + | Some footprint_index + -> IntSet.add footprint_index acc + | None + -> acc) + (sources trace) IntSet.empty (** compute caller_trace + callee_trace *) let append caller_trace callee_trace callee_site = - if is_empty callee_trace - then caller_trace + if is_empty callee_trace then caller_trace else let non_footprint_callee_sources = - Sources.filter (fun source -> not (Source.is_footprint source)) callee_trace.sources in + Sources.filter (fun source -> not (Source.is_footprint source)) callee_trace.sources + in let sources = - if Sources.subset non_footprint_callee_sources caller_trace.sources - then + if Sources.subset non_footprint_callee_sources caller_trace.sources then caller_trace.sources else List.map ~f:(fun sink -> Source.with_callsite sink callee_site) (Sources.elements non_footprint_callee_sources) - |> Sources.of_list - |> Sources.union caller_trace.sources in - + |> Sources.of_list |> Sources.union caller_trace.sources + in let sinks = - if Sinks.subset callee_trace.sinks caller_trace.sinks - then - caller_trace.sinks + if Sinks.subset callee_trace.sinks caller_trace.sinks then caller_trace.sinks else List.map ~f:(fun sink -> Sink.with_callsite sink callee_site) (Sinks.elements callee_trace.sinks) - |> Sinks.of_list - |> Sinks.union caller_trace.sinks in - + |> Sinks.of_list |> Sinks.union caller_trace.sinks + in let passthroughs = - if Config.passthroughs - then - if phys_equal sources caller_trace.sources && phys_equal sinks caller_trace.sinks - then + if Config.passthroughs then + if phys_equal sources caller_trace.sources && phys_equal sinks caller_trace.sinks then (* this callee didn't add any new sources or any news sinks; it's just a passthrough *) Passthroughs.add (Passthrough.make callee_site) caller_trace.passthroughs - else - caller_trace.passthroughs - else - Passthroughs.empty in - - { sources; sinks; passthroughs; } + else caller_trace.passthroughs + else Passthroughs.empty + in + {sources; sinks; passthroughs} let empty = let sources = Sources.empty in let sinks = Sinks.empty in let passthroughs = Passthroughs.empty in - { sources; sinks; passthroughs; } + {sources; sinks; passthroughs} - let (<=) ~lhs ~rhs = - phys_equal lhs rhs || - (Sources.subset lhs.sources rhs.sources && - Sinks.subset lhs.sinks rhs.sinks && - Passthroughs.subset lhs.passthroughs rhs.passthroughs) + let ( <= ) ~lhs ~rhs = + phys_equal lhs rhs + || Sources.subset lhs.sources rhs.sources && Sinks.subset lhs.sinks rhs.sinks + && Passthroughs.subset lhs.passthroughs rhs.passthroughs let join t1 t2 = - if phys_equal t1 t2 - then t1 + if phys_equal t1 t2 then t1 else let sources = Sources.union t1.sources t2.sources in let sinks = Sinks.union t1.sinks t2.sinks in let passthroughs = Passthroughs.union t1.passthroughs t2.passthroughs in - { sources; sinks; passthroughs; } - - let widen ~prev ~next ~num_iters:_ = - join prev next + {sources; sinks; passthroughs} + let widen ~prev ~next ~num_iters:_ = join prev next end diff --git a/infer/src/checkers/Trace.mli b/infer/src/checkers/Trace.mli index e3238b676..73b57b999 100644 --- a/infer/src/checkers/Trace.mli +++ b/infer/src/checkers/Trace.mli @@ -8,22 +8,25 @@ *) open! IStd - module F = Format module L = Logging module type Spec = sig module Source : Source.S + module Sink : Sink.S - (** should a flow originating at source and entering sink be reported? *) val should_report : Source.t -> Sink.t -> bool + (** should a flow originating at source and entering sink be reported? *) end module type S = sig include Spec + type t + type astate = t + include AbstractDomain.WithBottom with type astate := astate module Sources = Source.Set @@ -35,57 +38,57 @@ module type S = sig both the source and sink stack *) type path = Passthroughs.t * (Source.t * Passthroughs.t) list * (Sink.t * Passthroughs.t) list - (** the empty trace *) val empty : t + (** the empty trace *) - (** get the sources of the trace. *) val sources : t -> Sources.t + (** get the sources of the trace. *) - (** get the sinks of the trace *) val sinks : t -> Sinks.t + (** get the sinks of the trace *) - (** get the passthroughs of the trace *) val passthroughs : t -> Passthroughs.t + (** get the passthroughs of the trace *) + val get_reports : ?cur_site:CallSite.t -> t -> (Source.t * Sink.t * Passthroughs.t) list (** get the reportable source-sink flows in this trace. specifying [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) - val get_reports : ?cur_site:CallSite.t -> t -> (Source.t * Sink.t * Passthroughs.t) list - (** get a path for each of the reportable source -> sink flows in this trace. specifying - [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) val get_reportable_paths : ?cur_site:CallSite.t -> t -> trace_of_pname:(Typ.Procname.t -> t) -> path list + (** get a path for each of the reportable source -> sink flows in this trace. specifying + [cur_site] restricts the reported paths to ones introduced by the call at [cur_site] *) + val to_loc_trace : + ?desc_of_source:(Source.t -> string) -> ?source_should_nest:(Source.t -> bool) + -> ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> path + -> Errlog.loc_trace (** create a loc_trace from a path; [source_should_nest s] should be true when we are going one deeper into a call-chain, ie when lt_level should be bumper in the next loc_trace_elem, and similarly for [sink_should_nest] *) - val to_loc_trace : - ?desc_of_source:(Source.t -> string) -> ?source_should_nest:(Source.t -> bool) -> - ?desc_of_sink:(Sink.t -> string) -> ?sink_should_nest:(Sink.t -> bool) -> - path -> Errlog.loc_trace - (** create a trace from a source *) val of_source : Source.t -> t + (** create a trace from a source *) - (** ad a source to the current trace *) val add_source : Source.t -> t -> t + (** ad a source to the current trace *) - (** add a sink to the current trace. *) val add_sink : Sink.t -> t -> t + (** add a sink to the current trace. *) val update_sources : t -> Sources.t -> t - (** replace sinks with new ones *) val update_sinks : t -> Sinks.t -> t + (** replace sinks with new ones *) - (** get the footprint indexes for all of the sources in the trace *) val get_footprint_indexes : t -> IntSet.t + (** get the footprint indexes for all of the sources in the trace *) - (** append the trace for given call site to the current caller trace *) val append : t -> t -> CallSite.t -> t + (** append the trace for given call site to the current caller trace *) - (** return true if this trace has no source or sink data *) val is_empty : t -> bool + (** return true if this trace has no source or sink data *) val compare : t -> t -> int @@ -93,8 +96,8 @@ module type S = sig val pp : F.formatter -> t -> unit - (** pretty-print a path in the context of the given procname *) val pp_path : Typ.Procname.t -> F.formatter -> path -> unit + (** pretty-print a path in the context of the given procname *) end module Make (Spec : Spec) : S with module Source = Spec.Source and module Sink = Spec.Sink diff --git a/infer/src/checkers/TraceElem.ml b/infer/src/checkers/TraceElem.ml index 70f976e52..f223b139b 100644 --- a/infer/src/checkers/TraceElem.ml +++ b/infer/src/checkers/TraceElem.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module type Kind = sig @@ -23,9 +22,11 @@ module type S = sig module Kind : Kind val call_site : t -> CallSite.t + val kind : t -> Kind.t val make : ?indexes:IntSet.t -> Kind.t -> CallSite.t -> t + val with_callsite : t -> CallSite.t -> t val pp : F.formatter -> t -> unit diff --git a/infer/src/checkers/accessPath.ml b/infer/src/checkers/accessPath.ml index 32033f9cb..0d7c69484 100644 --- a/infer/src/checkers/accessPath.ml +++ b/infer/src/checkers/accessPath.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format type _typ = Typ.t @@ -21,21 +20,19 @@ type base = Var.t * _typ [@@deriving compare] let equal_base = [%compare.equal : base] -type access = - | ArrayAccess of Typ.t - | FieldAccess of Typ.Fieldname.t -[@@deriving compare] +type access = ArrayAccess of Typ.t | FieldAccess of Typ.Fieldname.t [@@deriving compare] let equal_access = [%compare.equal : access] -let equal_access_list l1 l2 = Int.equal (List.compare compare_access l1 l2) 0 +let equal_access_list l1 l2 = Int.equal (List.compare compare_access l1 l2) 0 -let pp_base fmt (pvar, _) = - Var.pp fmt pvar +let pp_base fmt (pvar, _) = Var.pp fmt pvar let pp_access fmt = function - | FieldAccess field_name -> Typ.Fieldname.pp fmt field_name - | ArrayAccess _ -> F.fprintf fmt "[_]" + | FieldAccess field_name + -> Typ.Fieldname.pp fmt field_name + | ArrayAccess _ + -> F.fprintf fmt "[_]" let pp_access_list fmt accesses = let pp_sep _ _ = F.fprintf fmt "." in @@ -43,12 +40,14 @@ let pp_access_list fmt accesses = module Raw = struct type t = base * access list [@@deriving compare] + let equal = [%compare.equal : t] let truncate = function - | base, [] - | base, _ :: [] -> base, [] - | base, accesses -> base, List.rev (List.tl_exn (List.rev accesses)) + | base, [] | base, [_] + -> (base, []) + | base, accesses + -> (base, List.rev (List.tl_exn (List.rev accesses))) let lookup_field_type_annot tenv base_typ field_name = let lookup = Tenv.lookup tenv in @@ -56,20 +55,19 @@ module Raw = struct (* Get the type of an access, or None if the type cannot be determined *) let get_access_type tenv base_typ = function - | FieldAccess field_name -> - Option.map (lookup_field_type_annot tenv base_typ field_name) ~f:fst - | ArrayAccess array_typ -> - Some array_typ + | FieldAccess field_name + -> Option.map (lookup_field_type_annot tenv base_typ field_name) ~f:fst + | ArrayAccess array_typ + -> Some array_typ (* For field access, get the field name and the annotation associated with it * Return None if given an array access, or if the info cannot be obtained *) let get_access_field_annot tenv base_typ = function - | FieldAccess field_name -> - Option.map (lookup_field_type_annot tenv base_typ field_name) ~f:( - fun (_, annot) -> (field_name, annot) - ) - | ArrayAccess _ -> - None + | FieldAccess field_name + -> Option.map (lookup_field_type_annot tenv base_typ field_name) ~f:(fun (_, annot) -> + (field_name, annot) ) + | ArrayAccess _ + -> None (* Extract the last access of the given access path together with its base type. * Here the base type is defined to be the declaring class of the last accessed field, @@ -80,128 +78,121 @@ module Raw = struct * - for x, the base type of the last access is type(x) *) let last_access_info ((_, base_typ), accesses) tenv = let rec last_access_info_impl tenv base_typ = function - | [] -> - Some base_typ, None - | [last_access] -> - Some base_typ, Some last_access + | [] + -> (Some base_typ, None) + | [last_access] + -> (Some base_typ, Some last_access) | curr_access :: rest -> - match get_access_type tenv base_typ curr_access with - | Some access_typ -> - last_access_info_impl tenv access_typ rest - | None -> - None, None + match get_access_type tenv base_typ curr_access with + | Some access_typ + -> last_access_info_impl tenv access_typ rest + | None + -> (None, None) in last_access_info_impl tenv base_typ accesses - let get_last_access (_, accesses) = - List.last accesses + let get_last_access (_, accesses) = List.last accesses let get_field_and_annotation ap tenv = match last_access_info ap tenv with - | Some base_typ, Some access -> - get_access_field_annot tenv base_typ access - | _ -> None + | Some base_typ, Some access + -> get_access_field_annot tenv base_typ access + | _ + -> None let get_typ ap tenv = match last_access_info ap tenv with - | (Some _) as typ, None -> typ - | Some base_typ, Some access -> - get_access_type tenv base_typ access - | _ -> - None + | (Some _ as typ), None + -> typ + | Some base_typ, Some access + -> get_access_type tenv base_typ access + | _ + -> None let pp fmt = function - | base, [] -> pp_base fmt base - | base, accesses -> F.fprintf fmt "%a.%a" pp_base base pp_access_list accesses + | base, [] + -> pp_base fmt base + | base, accesses + -> F.fprintf fmt "%a.%a" pp_base base pp_access_list accesses end -type t = - | Abstracted of Raw.t - | Exact of Raw.t -[@@deriving compare] +type t = Abstracted of Raw.t | Exact of Raw.t [@@deriving compare] let equal = [%compare.equal : t] -let base_of_pvar pvar typ = - Var.of_pvar pvar, typ +let base_of_pvar pvar typ = (Var.of_pvar pvar, typ) -let base_of_id id typ = - Var.of_id id, typ +let base_of_id id typ = (Var.of_id id, typ) -let of_pvar pvar typ = - base_of_pvar pvar typ, [] +let of_pvar pvar typ = (base_of_pvar pvar typ, []) -let of_id id typ = - base_of_id id typ, [] +let of_id id typ = (base_of_id id typ, []) -let of_exp exp0 typ0 ~(f_resolve_id : Var.t -> Raw.t option) = +let of_exp exp0 typ0 ~(f_resolve_id: Var.t -> Raw.t option) = (* [typ] is the type of the last element of the access path (e.g., typeof(g) for x.f.g) *) let rec of_exp_ exp typ accesses acc = match exp with - | Exp.Var id -> - begin - match f_resolve_id (Var.of_id id) with - | Some (base, base_accesses) -> (base, base_accesses @ accesses) :: acc - | None -> (base_of_id id typ, accesses) :: acc - end - | Exp.Lvar pvar when Pvar.is_ssa_frontend_tmp pvar -> - begin - match f_resolve_id (Var.of_pvar pvar) with - | Some (base, base_accesses) -> (base, base_accesses @ accesses) :: acc - | None -> (base_of_pvar pvar typ, accesses) :: acc - end - | Exp.Lvar pvar -> - (base_of_pvar pvar typ, accesses) :: acc - | Exp.Lfield (root_exp, fld, root_exp_typ) -> - let field_access = FieldAccess fld in + | Exp.Var id -> ( + match f_resolve_id (Var.of_id id) with + | Some (base, base_accesses) + -> (base, base_accesses @ accesses) :: acc + | None + -> (base_of_id id typ, accesses) :: acc ) + | Exp.Lvar pvar when Pvar.is_ssa_frontend_tmp pvar -> ( + match f_resolve_id (Var.of_pvar pvar) with + | Some (base, base_accesses) + -> (base, base_accesses @ accesses) :: acc + | None + -> (base_of_pvar pvar typ, accesses) :: acc ) + | Exp.Lvar pvar + -> (base_of_pvar pvar typ, accesses) :: acc + | Exp.Lfield (root_exp, fld, root_exp_typ) + -> let field_access = FieldAccess fld in of_exp_ root_exp root_exp_typ (field_access :: accesses) acc - | Exp.Lindex (root_exp, _) -> - let array_access = ArrayAccess typ in + | Exp.Lindex (root_exp, _) + -> let array_access = ArrayAccess typ in let array_typ = Typ.mk (Tarray (typ, None, None)) in of_exp_ root_exp array_typ (array_access :: accesses) acc - | Exp.Cast (cast_typ, cast_exp) -> - of_exp_ cast_exp cast_typ [] acc - | Exp.UnOp (_, unop_exp, _) -> - of_exp_ unop_exp typ [] acc - | Exp.Exn exn_exp -> - of_exp_ exn_exp typ [] acc - | Exp.BinOp (_, exp1, exp2) -> - of_exp_ exp1 typ [] acc - |> of_exp_ exp2 typ [] - | Exp.Const _ | Closure _ | Sizeof _ -> - (* trying to make access path from an invalid expression *) - acc in + | Exp.Cast (cast_typ, cast_exp) + -> of_exp_ cast_exp cast_typ [] acc + | Exp.UnOp (_, unop_exp, _) + -> of_exp_ unop_exp typ [] acc + | Exp.Exn exn_exp + -> of_exp_ exn_exp typ [] acc + | Exp.BinOp (_, exp1, exp2) + -> of_exp_ exp1 typ [] acc |> of_exp_ exp2 typ [] + | Exp.Const _ | Closure _ | Sizeof _ + -> (* trying to make access path from an invalid expression *) + acc + in of_exp_ exp0 typ0 [] [] -let of_lhs_exp lhs_exp typ ~(f_resolve_id : Var.t -> Raw.t option) = - match of_exp lhs_exp typ ~f_resolve_id with - | [lhs_ap] -> Some lhs_ap - | _ -> None +let of_lhs_exp lhs_exp typ ~(f_resolve_id: Var.t -> Raw.t option) = + match of_exp lhs_exp typ ~f_resolve_id with [lhs_ap] -> Some lhs_ap | _ -> None -let append (base, old_accesses) new_accesses = - base, old_accesses @ new_accesses +let append (base, old_accesses) new_accesses = (base, old_accesses @ new_accesses) let with_base base = function - | Exact (_, accesses) -> Exact (base, accesses) - | Abstracted (_, accesses) -> Abstracted (base, accesses) + | Exact (_, accesses) + -> Exact (base, accesses) + | Abstracted (_, accesses) + -> Abstracted (base, accesses) let rec is_prefix_path path1 path2 = - if phys_equal path1 path2 - then true - else - match path1, path2 with - | [], _ -> true - | _, [] -> false - | access1 :: p1, access2 :: p2 -> equal_access access1 access2 && is_prefix_path p1 p2 - -let is_prefix ((base1, path1) as ap1) ((base2, path2) as ap2) = - if phys_equal ap1 ap2 - then true + if phys_equal path1 path2 then true else - equal_base base1 base2 && is_prefix_path path1 path2 + match (path1, path2) with + | [], _ + -> true + | _, [] + -> false + | access1 :: p1, access2 :: p2 + -> equal_access access1 access2 && is_prefix_path p1 p2 -let extract = function - | Exact ap | Abstracted ap -> ap +let is_prefix (base1, path1 as ap1) (base2, path2 as ap2) = + if phys_equal ap1 ap2 then true else equal_base base1 base2 && is_prefix_path path1 path2 + +let extract = function Exact ap | Abstracted ap -> ap let to_footprint formal_index access_path = let _, base_typ = fst (extract access_path) in @@ -210,37 +201,43 @@ let to_footprint formal_index access_path = let get_footprint_index access_path = let raw_access_path = extract access_path in match raw_access_path with - | (Var.LogicalVar id, _), _ when Ident.is_footprint id -> - Some (Ident.get_stamp id) - | _ -> - None + | (Var.LogicalVar id, _), _ when Ident.is_footprint id + -> Some (Ident.get_stamp id) + | _ + -> None + +let is_exact = function Exact _ -> true | Abstracted _ -> false + +let ( <= ) ~lhs ~rhs = + match (lhs, rhs) with + | Abstracted _, Exact _ + -> false + | Exact lhs_ap, Exact rhs_ap + -> Raw.equal lhs_ap rhs_ap + | (Exact lhs_ap | Abstracted lhs_ap), Abstracted rhs_ap + -> is_prefix rhs_ap lhs_ap + +let pp fmt = function + | Exact access_path + -> Raw.pp fmt access_path + | Abstracted access_path + -> F.fprintf fmt "%a*" Raw.pp access_path -let is_exact = function - | Exact _ -> true - | Abstracted _ -> false +module BaseMap = PrettyPrintable.MakePPMap (struct + type t = base -let (<=) ~lhs ~rhs = - match lhs, rhs with - | Abstracted _, Exact _ -> false - | Exact lhs_ap, Exact rhs_ap -> Raw.equal lhs_ap rhs_ap - | (Exact lhs_ap | Abstracted lhs_ap), Abstracted rhs_ap -> is_prefix rhs_ap lhs_ap + let compare = compare_base -let pp fmt = function - | Exact access_path -> Raw.pp fmt access_path - | Abstracted access_path -> F.fprintf fmt "%a*" Raw.pp access_path + let pp = pp_base +end) -module BaseMap = PrettyPrintable.MakePPMap(struct - type t = base - let compare = compare_base - let pp = pp_base - end) +module AccessMap = PrettyPrintable.MakePPMap (struct + type t = access -module AccessMap = PrettyPrintable.MakePPMap(struct - type t = access - let compare = compare_access - let pp = pp_access - end) + let compare = compare_access -module RawSet = PrettyPrintable.MakePPSet(Raw) + let pp = pp_access +end) -module RawMap = PrettyPrintable.MakePPMap(Raw) +module RawSet = PrettyPrintable.MakePPSet (Raw) +module RawMap = PrettyPrintable.MakePPMap (Raw) diff --git a/infer/src/checkers/accessPath.mli b/infer/src/checkers/accessPath.mli index 771d180bd..e9f345236 100644 --- a/infer/src/checkers/accessPath.mli +++ b/infer/src/checkers/accessPath.mli @@ -14,29 +14,31 @@ open! IStd type base = Var.t * Typ.t [@@deriving compare] type access = - | ArrayAccess of Typ.t (* array element type. index is unknown *) - | FieldAccess of Typ.Fieldname.t (* field name *) -[@@deriving compare] + | ArrayAccess of Typ.t + (* array element type. index is unknown *) + | FieldAccess of Typ.Fieldname.t + (* field name *) + [@@deriving compare] module Raw : sig (** root var, and a list of accesses. closest to the root var is first that is, x.f.g is representedas (x, [f; g]) *) type t = base * access list [@@deriving compare] + val truncate : t -> t (** remove the last access of the access path if the access list is non-empty. returns the original access path if the access list is empty *) - val truncate : t -> t - (** get the last access in the list. returns None if the list is empty *) val get_last_access : t -> access option + (** get the last access in the list. returns None if the list is empty *) + val get_field_and_annotation : t -> Tenv.t -> (Typ.Fieldname.t * Annot.Item.t) option (** get the field name and the annotation of the last access in the list of accesses if the list is non-empty and the last access is a field access *) - val get_field_and_annotation : t -> Tenv.t -> (Typ.Fieldname.t * Annot.Item.t) option + val get_typ : t -> Tenv.t -> Typ.t option (** get the typ of the last access in the list of accesses if the list is non-empty, or the base if the list is empty. that is, for x.f.g, return typ(g), and for x, return typ(x) *) - val get_typ : t -> Tenv.t -> Typ.t option val equal : t -> t -> bool @@ -44,9 +46,9 @@ module Raw : sig end type t = - | Abstracted of Raw.t (** abstraction of heap reachable from an access path, e.g. x.f* *) - | Exact of Raw.t (** precise representation of an access path, e.g. x.f.g *) -[@@deriving compare] + | Abstracted of Raw.t (** abstraction of heap reachable from an access path, e.g. x.f* *) + | Exact of Raw.t (** precise representation of an access path, e.g. x.f.g *) + [@@deriving compare] val equal_base : base -> base -> bool @@ -56,54 +58,54 @@ val equal_access_list : access list -> access list -> bool val equal : t -> t -> bool -(** create a base from a pvar *) val base_of_pvar : Pvar.t -> Typ.t -> base +(** create a base from a pvar *) -(** create a base from an ident *) val base_of_id : Ident.t -> Typ.t -> base +(** create a base from an ident *) -(** create an access path from a pvar *) val of_pvar : Pvar.t -> Typ.t -> Raw.t +(** create an access path from a pvar *) -(** create an access path from an ident *) val of_id : Ident.t -> Typ.t -> Raw.t +(** create an access path from an ident *) -(** extract the raw access paths that occur in [exp], resolving identifiers using [f_resolve_id] *) val of_exp : Exp.t -> Typ.t -> f_resolve_id:(Var.t -> Raw.t option) -> Raw.t list +(** extract the raw access paths that occur in [exp], resolving identifiers using [f_resolve_id] *) -(** convert [lhs_exp] to a raw access path, resolving identifiers using [f_resolve_id] *) val of_lhs_exp : Exp.t -> Typ.t -> f_resolve_id:(Var.t -> Raw.t option) -> Raw.t option +(** convert [lhs_exp] to a raw access path, resolving identifiers using [f_resolve_id] *) -(** replace the base var with a footprint variable rooted at formal index [formal_index] *) val to_footprint : int -> t -> t +(** replace the base var with a footprint variable rooted at formal index [formal_index] *) +val get_footprint_index : t -> int option (** return the formal index associated with the base of this access path if there is one, or None otherwise *) -val get_footprint_index : t -> int option +val append : Raw.t -> access list -> Raw.t (** append new accesses to an existing access path; e.g., `append_access x.f [g, h]` produces `x.f.g.h` *) -val append : Raw.t -> access list -> Raw.t +val with_base : base -> t -> t (** swap base of existing access path for [base_var] (e.g., `with_base_bvar x y.f.g` produces `x.f.g` *) -val with_base : base -> t -> t -(** return true if [ap1] is a prefix of [ap2]. returns true for equal access paths *) val is_prefix : Raw.t -> Raw.t -> bool +(** return true if [ap1] is a prefix of [ap2]. returns true for equal access paths *) val pp_access : Format.formatter -> access -> unit val pp_access_list : Format.formatter -> access list -> unit -(** extract a raw access path from its wrapper *) val extract : t -> Raw.t +(** extract a raw access path from its wrapper *) -(** return true if [t] is an exact representation of an access path, false if it's an abstraction *) val is_exact : t -> bool +(** return true if [t] is an exact representation of an access path, false if it's an abstraction *) +val ( <= ) : lhs:t -> rhs:t -> bool (** return true if \gamma(lhs) \subseteq \gamma(rhs) *) -val (<=) : lhs:t -> rhs:t -> bool val pp_base : Format.formatter -> base -> unit diff --git a/infer/src/checkers/accessPathDomains.ml b/infer/src/checkers/accessPathDomains.ml index 589021a6e..db8662a7a 100644 --- a/infer/src/checkers/accessPathDomains.ml +++ b/infer/src/checkers/accessPathDomains.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module Set = struct @@ -25,7 +24,7 @@ module Set = struct let normalize aps = APSet.filter (fun lhs -> - not (APSet.exists (fun rhs -> not (phys_equal lhs rhs) && AccessPath.(<=) ~lhs ~rhs) aps)) + not (APSet.exists (fun rhs -> not (phys_equal lhs rhs) && AccessPath.( <= ) ~lhs ~rhs) aps)) aps let add = APSet.add @@ -33,34 +32,32 @@ module Set = struct let of_list = APSet.of_list let mem ap aps = - APSet.mem ap aps || APSet.exists (fun other_ap -> AccessPath.(<=) ~lhs:ap ~rhs:other_ap) aps + APSet.mem ap aps || APSet.exists (fun other_ap -> AccessPath.( <= ) ~lhs:ap ~rhs:other_ap) aps let mem_fuzzy ap aps = let has_overlap ap1 ap2 = - AccessPath.(<=) ~lhs:ap1 ~rhs:ap2 || AccessPath.(<=) ~lhs:ap2 ~rhs:ap1 in + AccessPath.( <= ) ~lhs:ap1 ~rhs:ap2 || AccessPath.( <= ) ~lhs:ap2 ~rhs:ap1 + in APSet.mem ap aps || APSet.exists (has_overlap ap) aps - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else - let rhs_contains lhs_ap = - mem lhs_ap rhs in + let rhs_contains lhs_ap = mem lhs_ap rhs in APSet.subset lhs rhs || APSet.for_all rhs_contains lhs - let join aps1 aps2 = - if phys_equal aps1 aps2 - then aps1 - else APSet.union aps1 aps2 + let join aps1 aps2 = if phys_equal aps1 aps2 then aps1 else APSet.union aps1 aps2 let widen ~prev ~next ~num_iters:_ = - if phys_equal prev next - then prev + if phys_equal prev next then prev else - let abstract_access_path ap aps = match ap with - | AccessPath.Exact exact_ap -> APSet.add (AccessPath.Abstracted exact_ap) aps - | AccessPath.Abstracted _ -> APSet.add ap aps in + let abstract_access_path ap aps = + match ap with + | AccessPath.Exact exact_ap + -> APSet.add (AccessPath.Abstracted exact_ap) aps + | AccessPath.Abstracted _ + -> APSet.add ap aps + in let diff_aps = APSet.diff next prev in - APSet.fold abstract_access_path diff_aps APSet.empty - |> join prev + APSet.fold abstract_access_path diff_aps APSet.empty |> join prev end diff --git a/infer/src/checkers/accessPathDomains.mli b/infer/src/checkers/accessPathDomains.mli index 85f5120e0..6daa425b8 100644 --- a/infer/src/checkers/accessPathDomains.mli +++ b/infer/src/checkers/accessPathDomains.mli @@ -21,18 +21,18 @@ module Set : sig val of_list : AccessPath.t list -> astate + val mem : AccessPath.t -> astate -> bool (** return true if \gamma({ap}) \subseteq \gamma(aps). note: this is worst-case linear in the size of the set *) - val mem : AccessPath.t -> astate -> bool + val mem_fuzzy : AccessPath.t -> astate -> bool (** more permissive version of [mem]; return true if \gamma({a}) \cap \gamma(aps) != {}. note: this is worst-case linear in the size of the set *) - val mem_fuzzy : AccessPath.t -> astate -> bool val add : AccessPath.t -> astate -> astate + val normalize : astate -> astate (** simplify an access path set to its canonical representation by eliminating redundancies between (1) pairs of abstracted access_paths, and (2) exact access paths and abstracted access paths. warning: this is quadratic in the size of the set! use sparingly *) - val normalize : astate -> astate end diff --git a/infer/src/checkers/accessTree.ml b/infer/src/checkers/accessTree.ml index 3207e2e09..6f68cde53 100644 --- a/infer/src/checkers/accessTree.ml +++ b/infer/src/checkers/accessTree.ml @@ -8,19 +8,17 @@ *) open! IStd - module F = Format module L = Logging module type S = sig module TraceDomain : AbstractDomain.WithBottom + module AccessMap = AccessPath.AccessMap module BaseMap = AccessPath.BaseMap type node = TraceDomain.astate * tree - and tree = - | Subtree of node AccessMap.t - | Star + and tree = Subtree of node AccessMap.t | Star type t = node BaseMap.t @@ -58,178 +56,169 @@ module Make (TraceDomain : AbstractDomain.WithBottom) = struct module AccessMap = AccessPath.AccessMap module BaseMap = AccessPath.BaseMap - type node = TraceDomain.astate * tree - and tree = - | Subtree of node AccessMap.t - | Star + type node = (TraceDomain.astate * tree) + and tree = Subtree of node AccessMap.t | Star type t = node BaseMap.t + type astate = t let empty = BaseMap.empty - let make_node trace subtree = - trace, Subtree subtree + let make_node trace subtree = (trace, Subtree subtree) - let empty_node = - make_node TraceDomain.empty AccessMap.empty + let empty_node = make_node TraceDomain.empty AccessMap.empty - let make_normal_leaf trace = - make_node trace AccessMap.empty + let make_normal_leaf trace = make_node trace AccessMap.empty - let make_starred_leaf trace = - trace, Star + let make_starred_leaf trace = (trace, Star) let make_access_node base_trace access trace = make_node base_trace (AccessMap.singleton access (make_normal_leaf trace)) (** find all of the traces in the subtree and join them with [orig_trace] *) let rec join_all_traces orig_trace = function - | Subtree subtree -> - let join_all_traces_ orig_trace tree = + | Subtree subtree + -> let join_all_traces_ orig_trace tree = let node_join_traces _ (trace, node) trace_acc = - join_all_traces (TraceDomain.join trace_acc trace) node in - AccessMap.fold node_join_traces tree orig_trace in + join_all_traces (TraceDomain.join trace_acc trace) node + in + AccessMap.fold node_join_traces tree orig_trace + in join_all_traces_ orig_trace subtree - | Star -> - orig_trace + | Star + -> orig_trace let get_node ap tree = let rec accesses_get_node access_list trace tree = - match access_list, tree with - | _, Star -> - trace, Star - | [], (Subtree _ as tree) -> - trace, tree - | access :: accesses, Subtree subtree -> - let access_trace, access_subtree = AccessMap.find access subtree in - accesses_get_node accesses access_trace access_subtree in + match (access_list, tree) with + | _, Star + -> (trace, Star) + | [], (Subtree _ as tree) + -> (trace, tree) + | access :: accesses, Subtree subtree + -> let access_trace, access_subtree = AccessMap.find access subtree in + accesses_get_node accesses access_trace access_subtree + in let get_node_ base accesses tree = let base_trace, base_tree = BaseMap.find base tree in - accesses_get_node accesses base_trace base_tree in + accesses_get_node accesses base_trace base_tree + in let base, accesses = AccessPath.extract ap in match get_node_ base accesses tree with - | trace, subtree -> - if AccessPath.is_exact ap - then Some (trace, subtree) + | trace, subtree + -> if AccessPath.is_exact ap then Some (trace, subtree) else (* input query was [ap]*, and [trace] is the trace associated with [ap]. get the traces associated with the children of [ap] in [tree] and join them with [trace] *) Some (join_all_traces trace subtree, subtree) - | exception Not_found -> - None - - let get_trace ap tree = - Option.map ~f:fst (get_node ap tree) - - let rec access_tree_lteq ((lhs_trace, lhs_tree) as lhs) ((rhs_trace, rhs_tree) as rhs) = - if phys_equal lhs rhs - then true - else - TraceDomain.(<=) ~lhs:lhs_trace ~rhs:rhs_trace && - match lhs_tree, rhs_tree with - | Subtree lhs_subtree, Subtree rhs_subtree -> - AccessMap.for_all + | exception Not_found + -> None + + let get_trace ap tree = Option.map ~f:fst (get_node ap tree) + + let rec access_tree_lteq (lhs_trace, lhs_tree as lhs) (rhs_trace, rhs_tree as rhs) = + if phys_equal lhs rhs then true + else TraceDomain.( <= ) ~lhs:lhs_trace ~rhs:rhs_trace + && + match (lhs_tree, rhs_tree) with + | Subtree lhs_subtree, Subtree rhs_subtree + -> AccessMap.for_all (fun k lhs_v -> - try - let rhs_v = AccessMap.find k rhs_subtree in - access_tree_lteq lhs_v rhs_v - with Not_found -> false) + try + let rhs_v = AccessMap.find k rhs_subtree in + access_tree_lteq lhs_v rhs_v + with Not_found -> false) lhs_subtree - | _, Star -> - true - | Star, Subtree _ -> - false - - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + | _, Star + -> true + | Star, Subtree _ + -> false + + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else BaseMap.for_all (fun k lhs_v -> - try - let rhs_v = BaseMap.find k rhs in - access_tree_lteq lhs_v rhs_v - with Not_found -> false) + try + let rhs_v = BaseMap.find k rhs in + access_tree_lteq lhs_v rhs_v + with Not_found -> false) lhs - let node_join_ f_node_merge f_trace_merge ((trace1, tree1) as node1) ((trace2, tree2) as node2) = - if phys_equal node1 node2 - then node1 + let node_join_ f_node_merge f_trace_merge (trace1, tree1 as node1) (trace2, tree2 as node2) = + if phys_equal node1 node2 then node1 else let trace' = f_trace_merge trace1 trace2 in (* note: this is much-uglified by address equality optimization checks. skip to the else cases for the actual semantics *) - match tree1, tree2 with - | Subtree subtree1, Subtree subtree2 -> - let tree' = AccessMap.merge (fun _ v1 v2 -> f_node_merge v1 v2) subtree1 subtree2 in - if phys_equal trace' trace1 && phys_equal tree' subtree1 - then node1 - else if phys_equal trace' trace2 && phys_equal tree' subtree2 - then node2 - else trace', Subtree tree' - | Star, t -> - (* vacuum up all the traces associated with the subtree t and join them with trace' *) + match (tree1, tree2) with + | Subtree subtree1, Subtree subtree2 + -> let tree' = AccessMap.merge (fun _ v1 v2 -> f_node_merge v1 v2) subtree1 subtree2 in + if phys_equal trace' trace1 && phys_equal tree' subtree1 then node1 + else if phys_equal trace' trace2 && phys_equal tree' subtree2 then node2 + else (trace', Subtree tree') + | Star, t + -> (* vacuum up all the traces associated with the subtree t and join them with trace' *) let trace'' = join_all_traces trace' t in - if phys_equal trace'' trace1 - then node1 - else trace'', Star - | t, Star -> - (* same as above, but kind-of duplicated to allow address equality optimization *) + if phys_equal trace'' trace1 then node1 else (trace'', Star) + | t, Star + -> (* same as above, but kind-of duplicated to allow address equality optimization *) let trace'' = join_all_traces trace' t in - if phys_equal trace'' trace2 - then node2 - else trace'', Star + if phys_equal trace'' trace2 then node2 else (trace'', Star) - let rec node_join node1 node2 = - node_join_ node_merge TraceDomain.join node1 node2 + let rec node_join node1 node2 = node_join_ node_merge TraceDomain.join node1 node2 and node_merge node1_opt node2_opt = - match node1_opt, node2_opt with - | Some node1, Some node2 -> - let joined_node = node_join node1 node2 in - if phys_equal joined_node node1 - then node1_opt - else if phys_equal joined_node node2 - then node2_opt + match (node1_opt, node2_opt) with + | Some node1, Some node2 + -> let joined_node = node_join node1 node2 in + if phys_equal joined_node node1 then node1_opt + else if phys_equal joined_node node2 then node2_opt else Some joined_node - | None, node_opt | node_opt, None -> - node_opt + | None, node_opt | node_opt, None + -> node_opt (* helper for [add_access]. [last_trace] is the trace associated with [tree] in the parent. *) let access_tree_add_trace ~node_to_add ~seen_array_access ~is_exact accesses node = let rec access_tree_add_trace_ ~seen_array_access accesses node = - match accesses, node with - | [], (trace, tree) -> - begin - match is_exact, seen_array_access with - | true, false -> - (* adding x.f, do strong update on both subtree and its traces *) - node_to_add - | true, true -> - (* adding x[_], do weak update on subtree and on its immediate trace *) - node_join node_to_add node - | _ -> - (* adding x.f* or x[_]*, join with traces of subtree and replace it with * *) - let node_trace, node_tree = node_to_add in - let trace' = join_all_traces (TraceDomain.join trace node_trace) tree in - make_starred_leaf (join_all_traces trace' node_tree) - end - | _, (_, Star) -> - node_join node_to_add node - | access :: accesses, (trace, Subtree subtree) -> - let access_node = + match (accesses, node) with + | [], (trace, tree) -> ( + match (is_exact, seen_array_access) with + | true, false + -> (* adding x.f, do strong update on both subtree and its traces *) + node_to_add + | true, true + -> (* adding x[_], do weak update on subtree and on its immediate trace *) + node_join node_to_add node + | _ + -> (* adding x.f* or x[_]*, join with traces of subtree and replace it with * *) + let node_trace, node_tree = node_to_add in + let trace' = join_all_traces (TraceDomain.join trace node_trace) tree in + make_starred_leaf (join_all_traces trace' node_tree) ) + | _, (_, Star) + -> node_join node_to_add node + | access :: accesses, (trace, Subtree subtree) + -> let access_node = try AccessMap.find access subtree - with Not_found -> make_normal_leaf TraceDomain.empty in + with Not_found -> make_normal_leaf TraceDomain.empty + in (* once we encounter a subtree rooted in an array access, we have to do weak updates in the entire subtree. the reason: if I do x[i].f.g = , then x[j].f.g = , I don't want to overwrite . instead, I should get |_| *) - let seen_array_access = seen_array_access || match access with - | AccessPath.ArrayAccess _ -> true - | AccessPath.FieldAccess _ -> false in + let seen_array_access = + seen_array_access + || + match access with + | AccessPath.ArrayAccess _ + -> true + | AccessPath.FieldAccess _ + -> false + in let access_node' = access_tree_add_trace_ ~seen_array_access accesses access_node in - trace, Subtree (AccessMap.add access access_node' subtree) in + (trace, Subtree (AccessMap.add access access_node' subtree)) + in access_tree_add_trace_ ~seen_array_access accesses node let add_node ap node_to_add tree = @@ -237,83 +226,78 @@ module Make (TraceDomain : AbstractDomain.WithBottom) = struct let is_exact = AccessPath.is_exact ap in let base_node = try BaseMap.find base tree - with Not_found -> make_normal_leaf TraceDomain.empty in + with Not_found -> make_normal_leaf TraceDomain.empty + in let base_node' = - access_tree_add_trace ~node_to_add ~seen_array_access:false ~is_exact accesses base_node in + access_tree_add_trace ~node_to_add ~seen_array_access:false ~is_exact accesses base_node + in BaseMap.add base base_node' tree - let add_trace ap trace tree = - add_node ap (make_normal_leaf trace) tree + let add_trace ap trace tree = add_node ap (make_normal_leaf trace) tree let join tree1 tree2 = - if phys_equal tree1 tree2 - then tree1 + if phys_equal tree1 tree2 then tree1 else BaseMap.merge (fun _ n1 n2 -> node_merge n1 n2) tree1 tree2 let rec access_map_fold_ f base accesses m acc = AccessMap.fold (fun access node acc -> node_fold_ f base (accesses @ [access]) node acc) m acc - and node_fold_ f base accesses ((_, tree) as node) acc = - let cur_ap_raw = base, accesses in + + and node_fold_ f base accesses (_, tree as node) acc = + let cur_ap_raw = (base, accesses) in match tree with - | Subtree access_map -> - let acc' = f acc (AccessPath.Exact cur_ap_raw) node in + | Subtree access_map + -> let acc' = f acc (AccessPath.Exact cur_ap_raw) node in access_map_fold_ f base accesses access_map acc' - | Star -> - f acc (AccessPath.Abstracted cur_ap_raw) node + | Star + -> f acc (AccessPath.Abstracted cur_ap_raw) node - let node_fold (f : 'a -> AccessPath.t -> node -> 'a) base node acc = - node_fold_ f base [] node acc + let node_fold (f: 'a -> AccessPath.t -> node -> 'a) base node acc = node_fold_ f base [] node acc - let fold (f : 'a -> AccessPath.t -> node -> 'a) tree acc_ = + let fold (f: 'a -> AccessPath.t -> node -> 'a) tree acc_ = BaseMap.fold (fun base node acc -> node_fold f base node acc) tree acc_ - let trace_fold (f : 'a -> AccessPath.t -> TraceDomain.astate -> 'a) = - let f_ acc ap (trace, _) = - f acc ap trace in + let trace_fold (f: 'a -> AccessPath.t -> TraceDomain.astate -> 'a) = + let f_ acc ap (trace, _) = f acc ap trace in fold f_ (* replace the normal leaves of [node] with starred leaves *) - let rec node_add_stars ((trace, tree) as node) = match tree with - | Subtree subtree -> - if AccessMap.is_empty subtree - then make_starred_leaf trace + let rec node_add_stars (trace, tree as node) = + match tree with + | Subtree subtree + -> if AccessMap.is_empty subtree then make_starred_leaf trace else let subtree' = AccessMap.map node_add_stars subtree in - if phys_equal subtree' subtree - then node - else trace, Subtree subtree' - | Star -> node + if phys_equal subtree' subtree then node else (trace, Subtree subtree') + | Star + -> node let widen ~prev ~next ~num_iters = - if phys_equal prev next - then prev + if phys_equal prev next then prev else - let trace_widen prev next = - TraceDomain.widen ~prev ~next ~num_iters in + let trace_widen prev next = TraceDomain.widen ~prev ~next ~num_iters in let rec node_widen prev_node_opt next_node_opt = - match prev_node_opt, next_node_opt with - | Some prev_node, Some next_node -> - let widened_node = node_join_ node_widen trace_widen prev_node next_node in - if phys_equal widened_node prev_node - then prev_node_opt - else if phys_equal widened_node next_node - then next_node_opt - else Some widened_node - | None, Some next_node -> - let widened_node = node_add_stars next_node in - if phys_equal widened_node next_node - then next_node_opt + match (prev_node_opt, next_node_opt) with + | Some prev_node, Some next_node + -> let widened_node = node_join_ node_widen trace_widen prev_node next_node in + if phys_equal widened_node prev_node then prev_node_opt + else if phys_equal widened_node next_node then next_node_opt else Some widened_node - | Some _, None | None, None -> - prev_node_opt in + | None, Some next_node + -> let widened_node = node_add_stars next_node in + if phys_equal widened_node next_node then next_node_opt else Some widened_node + | Some _, None | None, None + -> prev_node_opt + in BaseMap.merge (fun _ prev_node next_node -> node_widen prev_node next_node) prev next let rec pp_node fmt (trace, subtree) = let pp_subtree fmt = function - | Subtree access_map -> AccessMap.pp ~pp_value:pp_node fmt access_map - | Star -> F.fprintf fmt "*" in + | Subtree access_map + -> AccessMap.pp ~pp_value:pp_node fmt access_map + | Star + -> F.fprintf fmt "*" + in F.fprintf fmt "(%a, %a)" TraceDomain.pp trace pp_subtree subtree - let pp fmt base_tree = - BaseMap.pp ~pp_value:pp_node fmt base_tree + let pp fmt base_tree = BaseMap.pp ~pp_value:pp_node fmt base_tree end diff --git a/infer/src/checkers/accessTree.mli b/infer/src/checkers/accessTree.mli index a77b1a251..38868486c 100644 --- a/infer/src/checkers/accessTree.mli +++ b/infer/src/checkers/accessTree.mli @@ -12,13 +12,16 @@ open! IStd (** tree of (trace, access path) associations organized by structure of access paths *) module type S = sig module TraceDomain : AbstractDomain.WithBottom + module AccessMap = AccessPath.AccessMap module BaseMap = AccessPath.BaseMap type node = TraceDomain.astate * tree + and tree = - | Subtree of node AccessMap.t (** map from access -> nodes. a leaf is encoded as an empty map *) - | Star (** special leaf for starred access paths *) + | Subtree of node AccessMap.t + (** map from access -> nodes. a leaf is encoded as an empty map *) + | Star (** special leaf for starred access paths *) (** map from base var -> access subtree. Here's how to represent a few different kinds of trace * access path associations: @@ -38,36 +41,36 @@ module type S = sig val make_node : TraceDomain.astate -> node AccessMap.t -> node - (** for testing only *) val make_access_node : TraceDomain.astate -> AccessPath.access -> TraceDomain.astate -> node + (** for testing only *) - (** create a leaf node with no successors *) val make_normal_leaf : TraceDomain.astate -> node + (** create a leaf node with no successors *) - (** create a leaf node with a wildcard successor *) val make_starred_leaf : TraceDomain.astate -> node + (** create a leaf node with a wildcard successor *) - (** retrieve the node associated with the given access path *) val get_node : AccessPath.t -> t -> node option + (** retrieve the node associated with the given access path *) - (** retrieve the trace associated with the given access path *) val get_trace : AccessPath.t -> t -> TraceDomain.astate option + (** retrieve the trace associated with the given access path *) + val add_node : AccessPath.t -> node -> t -> t (** add the given access path to the tree and associate its last access with with the given node. if any of the accesses in the path are not already present in the tree, they will be added with with empty traces associated with each of the inner nodes. *) - val add_node : AccessPath.t -> node -> t -> t + val add_trace : AccessPath.t -> TraceDomain.astate -> t -> t (** add the given access path to the tree and associate its last access with with the given trace. if any of the accesses in the path are not already present in the tree, they will be added with with empty traces associated with each of the inner nodes. *) - val add_trace : AccessPath.t -> TraceDomain.astate -> t -> t - (** join two nodes *) val node_join : node -> node -> node + (** join two nodes *) - (** apply a function to each (access path, node) pair in the tree. *) val fold : ('a -> AccessPath.t -> node -> 'a) -> t -> 'a -> 'a + (** apply a function to each (access path, node) pair in the tree. *) val trace_fold : ('a -> AccessPath.t -> TraceDomain.astate -> 'a) -> t -> 'a -> 'a diff --git a/infer/src/checkers/addressTaken.ml b/infer/src/checkers/addressTaken.ml index 1c68a04b4..25aebdfe6 100644 --- a/infer/src/checkers/addressTaken.ml +++ b/infer/src/checkers/addressTaken.ml @@ -9,41 +9,46 @@ open! IStd -module Domain = AbstractDomain.FiniteSet(struct - include Pvar - let pp = pp Pp.text - end) +module Domain = AbstractDomain.FiniteSet (struct + include Pvar + + let pp = pp Pp.text +end) module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = ProcData.no_extras - let rec add_address_taken_pvars exp astate = match exp with - | Exp.Lvar pvar -> - Domain.add pvar astate - | Exp.Cast (_, e) | UnOp (_, e, _) | Lfield (e, _, _) -> - add_address_taken_pvars e astate - | Exp.BinOp (_, e1, e2) | Lindex (e1, e2) -> - add_address_taken_pvars e1 astate - |> add_address_taken_pvars e2 + let rec add_address_taken_pvars exp astate = + match exp with + | Exp.Lvar pvar + -> Domain.add pvar astate + | Exp.Cast (_, e) | UnOp (_, e, _) | Lfield (e, _, _) + -> add_address_taken_pvars e astate + | Exp.BinOp (_, e1, e2) | Lindex (e1, e2) + -> add_address_taken_pvars e1 astate |> add_address_taken_pvars e2 | Exp.Exn _ | Exp.Closure _ | Exp.Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) - | Exp.Var _ | Exp.Sizeof _ -> - astate + | Exp.Var _ + | Exp.Sizeof _ + -> astate let exec_instr astate _ _ = function - | Sil.Store (_, {desc=Tptr _}, rhs_exp, _) -> - add_address_taken_pvars rhs_exp astate - | Sil.Call (_, _, actuals, _, _) -> - let add_actual_by_ref astate_acc = function - | actual_exp, {Typ.desc=Tptr _} -> add_address_taken_pvars actual_exp astate_acc - | _ -> astate_acc in + | Sil.Store (_, {desc= Tptr _}, rhs_exp, _) + -> add_address_taken_pvars rhs_exp astate + | Sil.Call (_, _, actuals, _, _) + -> let add_actual_by_ref astate_acc = function + | actual_exp, {Typ.desc= Tptr _} + -> add_address_taken_pvars actual_exp astate_acc + | _ + -> astate_acc + in List.fold ~f:add_actual_by_ref ~init:astate actuals - | Sil.Store _ | Load _ | Prune _ | Nullify _ | Abstract _ | Remove_temps _ - | Declare_locals _ -> - astate + | Sil.Store _ | Load _ | Prune _ | Nullify _ | Abstract _ | Remove_temps _ | Declare_locals _ + -> astate end module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) diff --git a/infer/src/checkers/annotationReachability.ml b/infer/src/checkers/annotationReachability.ml index 4ad14a87f..5becc2388 100644 --- a/infer/src/checkers/annotationReachability.ml +++ b/infer/src/checkers/annotationReachability.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging module MF = MarkupFormatter @@ -20,179 +19,170 @@ module Domain = struct module TrackingDomain = AbstractDomain.BottomLifted (TrackingVar) include AbstractDomain.Pair (AnnotReachabilityDomain) (TrackingDomain) - let add_call_site annot sink call_site ((annot_map, previous_vstate) as astate) = + let add_call_site annot sink call_site (annot_map, previous_vstate as astate) = match previous_vstate with - | TrackingDomain.Bottom -> astate - | TrackingDomain.NonBottom _ -> - let sink_map = + | TrackingDomain.Bottom + -> astate + | TrackingDomain.NonBottom _ + -> let sink_map = try AnnotReachabilityDomain.find annot annot_map - with Not_found -> AnnotReachabilityDomain.SinkMap.empty in + with Not_found -> AnnotReachabilityDomain.SinkMap.empty + in let sink_map' = - if AnnotReachabilityDomain.SinkMap.mem sink sink_map - then sink_map + if AnnotReachabilityDomain.SinkMap.mem sink sink_map then sink_map else let singleton = AnnotReachabilityDomain.CallSites.singleton call_site in - AnnotReachabilityDomain.SinkMap.singleton sink singleton in - if phys_equal sink_map' sink_map - then astate + AnnotReachabilityDomain.SinkMap.singleton sink singleton + in + if phys_equal sink_map' sink_map then astate else (AnnotReachabilityDomain.add annot sink_map' annot_map, previous_vstate) - let stop_tracking (annot_map, _ : astate) = - (annot_map, TrackingDomain.Bottom) + let stop_tracking ((annot_map, _): astate) = (annot_map, TrackingDomain.Bottom) - let add_tracking_var var ((annot_map, previous_vstate) as astate) = + let add_tracking_var var (annot_map, previous_vstate as astate) = match previous_vstate with - | TrackingDomain.Bottom -> astate - | TrackingDomain.NonBottom vars -> - (annot_map, TrackingDomain.NonBottom (TrackingVar.add var vars)) + | TrackingDomain.Bottom + -> astate + | TrackingDomain.NonBottom vars + -> (annot_map, TrackingDomain.NonBottom (TrackingVar.add var vars)) - let remove_tracking_var var ((annot_map, previous_vstate) as astate) = + let remove_tracking_var var (annot_map, previous_vstate as astate) = match previous_vstate with - | TrackingDomain.Bottom -> astate - | TrackingDomain.NonBottom vars -> - (annot_map, TrackingDomain.NonBottom (TrackingVar.remove var vars)) + | TrackingDomain.Bottom + -> astate + | TrackingDomain.NonBottom vars + -> (annot_map, TrackingDomain.NonBottom (TrackingVar.remove var vars)) let is_tracked_var var (_, vstate) = match vstate with - | TrackingDomain.Bottom -> false - | TrackingDomain.NonBottom vars -> - TrackingVar.mem var vars - + | TrackingDomain.Bottom + -> false + | TrackingDomain.NonBottom vars + -> TrackingVar.mem var vars end module Summary = Summary.Make (struct - type payload = AnnotReachabilityDomain.astate + type payload = AnnotReachabilityDomain.astate - let update_payload annot_map (summary : Specs.summary) = - { summary with payload = { summary.payload with annot_map = Some annot_map }} + let update_payload annot_map (summary: Specs.summary) = + {summary with payload= {summary.payload with annot_map= Some annot_map}} - let read_payload (summary : Specs.summary) = - summary.payload.annot_map - end) + let read_payload (summary: Specs.summary) = summary.payload.annot_map +end) (* Warning name when a performance critical method directly or indirectly calls a method annotatd as expensive *) -let calls_expensive_method = - "CHECKERS_CALLS_EXPENSIVE_METHOD" +let calls_expensive_method = "CHECKERS_CALLS_EXPENSIVE_METHOD" (* Warning name when a performance critical method directly or indirectly calls a method allocating memory *) -let allocates_memory = - "CHECKERS_ALLOCATES_MEMORY" +let allocates_memory = "CHECKERS_ALLOCATES_MEMORY" (* Warning name for the subtyping rule: method not annotated as expensive cannot be overridden by a method annotated as expensive *) -let expensive_overrides_unexpensive = - "CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED" +let expensive_overrides_unexpensive = "CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED" let annotation_reachability_error = "CHECKERS_ANNOTATION_REACHABILITY_ERROR" let is_modeled_expensive tenv = function - | Typ.Procname.Java proc_name_java as proc_name -> - not (BuiltinDecl.is_declared proc_name) && + | Typ.Procname.Java proc_name_java as proc_name + -> not (BuiltinDecl.is_declared proc_name) + && let is_subclass = - let classname = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name proc_name_java) in - PatternMatch.is_subtype_of_str tenv classname in + let classname = + Typ.Name.Java.from_string (Typ.Procname.java_get_class_name proc_name_java) + in + PatternMatch.is_subtype_of_str tenv classname + in Inferconfig.modeled_expensive_matcher is_subclass proc_name - | _ -> - false + | _ + -> false let is_allocator tenv pname = match pname with - | Typ.Procname.Java pname_java -> - let is_throwable () = - let class_name = - Typ.Name.Java.from_string (Typ.Procname.java_get_class_name pname_java) in - PatternMatch.is_throwable tenv class_name in - Typ.Procname.is_constructor pname - && not (BuiltinDecl.is_declared pname) + | Typ.Procname.Java pname_java + -> let is_throwable () = + let class_name = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name pname_java) in + PatternMatch.is_throwable tenv class_name + in + Typ.Procname.is_constructor pname && not (BuiltinDecl.is_declared pname) && not (is_throwable ()) - | _ -> - false + | _ + -> false let check_attributes check tenv pname = - PatternMatch.check_class_attributes check tenv pname || - Annotations.pname_has_return_annot pname ~attrs_of_pname:Specs.proc_resolve_attributes check + PatternMatch.check_class_attributes check tenv pname + || Annotations.pname_has_return_annot pname ~attrs_of_pname:Specs.proc_resolve_attributes check let method_overrides is_annotated tenv pname = PatternMatch.override_exists (fun pn -> is_annotated tenv pn) tenv pname let method_has_annot annot tenv pname = let has_annot ia = Annotations.ia_ends_with ia annot.Annot.class_name in - if Annotations.annot_ends_with annot dummy_constructor_annot - then is_allocator tenv pname - else if Annotations.annot_ends_with annot Annotations.expensive - then check_attributes has_annot tenv pname || is_modeled_expensive tenv pname + if Annotations.annot_ends_with annot dummy_constructor_annot then is_allocator tenv pname + else if Annotations.annot_ends_with annot Annotations.expensive then + check_attributes has_annot tenv pname || is_modeled_expensive tenv pname else check_attributes has_annot tenv pname -let method_overrides_annot annot tenv pname = - method_overrides (method_has_annot annot) tenv pname +let method_overrides_annot annot tenv pname = method_overrides (method_has_annot annot) tenv pname let lookup_annotation_calls caller_pdesc annot pname = match Ondemand.analyze_proc_name ~propagate_exceptions:false caller_pdesc pname with - | Some { Specs.payload = { Specs.annot_map = Some annot_map; }; } -> - begin - try - AnnotReachabilityDomain.find annot annot_map - with Not_found -> - AnnotReachabilityDomain.SinkMap.empty - end - | _ -> - AnnotReachabilityDomain.SinkMap.empty + | Some {Specs.payload= {Specs.annot_map= Some annot_map}} -> ( + try AnnotReachabilityDomain.find annot annot_map + with Not_found -> AnnotReachabilityDomain.SinkMap.empty ) + | _ + -> AnnotReachabilityDomain.SinkMap.empty let update_trace loc trace = if Location.equal loc Location.dummy then trace - else - Errlog.make_trace_element 0 loc "" [] :: trace + else Errlog.make_trace_element 0 loc "" [] :: trace -let string_of_pname = - Typ.Procname.to_simplified_string ~withclass:true +let string_of_pname = Typ.Procname.to_simplified_string ~withclass:true -let report_allocation_stack - src_annot summary fst_call_loc trace stack_str constructor_pname call_loc = +let report_allocation_stack src_annot summary fst_call_loc trace stack_str constructor_pname + call_loc = let pname = Specs.get_proc_name summary in let final_trace = List.rev (update_trace call_loc trace) in let constr_str = string_of_pname constructor_pname in let description = - Format.asprintf - "Method %a annotated with %a allocates %a via %a" - MF.pp_monospaced (Typ.Procname.to_simplified_string pname) - MF.pp_monospaced ("@" ^ src_annot) - MF.pp_monospaced constr_str - MF.pp_monospaced (stack_str ^ ("new "^constr_str)) in - let exn = - Exceptions.Checkers (allocates_memory, Localise.verbatim_desc description) in + Format.asprintf "Method %a annotated with %a allocates %a via %a" MF.pp_monospaced + (Typ.Procname.to_simplified_string pname) MF.pp_monospaced ("@" ^ src_annot) MF.pp_monospaced + constr_str MF.pp_monospaced + (stack_str ^ "new " ^ constr_str) + in + let exn = Exceptions.Checkers (allocates_memory, Localise.verbatim_desc description) in Reporting.log_error summary ~loc:fst_call_loc ~ltr:final_trace exn let report_annotation_stack src_annot snk_annot src_summary loc trace stack_str snk_pname call_loc = let src_pname = Specs.get_proc_name src_summary in - if String.equal snk_annot dummy_constructor_annot - then report_allocation_stack src_annot src_summary loc trace stack_str snk_pname call_loc + if String.equal snk_annot dummy_constructor_annot then + report_allocation_stack src_annot src_summary loc trace stack_str snk_pname call_loc else let final_trace = List.rev (update_trace call_loc trace) in let exp_pname_str = string_of_pname snk_pname in let description = - Format.asprintf - "Method %a annotated with %a calls %a where %a is annotated with %a" - MF.pp_monospaced (Typ.Procname.to_simplified_string src_pname) - MF.pp_monospaced ("@" ^ src_annot) - MF.pp_monospaced (stack_str ^ exp_pname_str) - MF.pp_monospaced exp_pname_str - MF.pp_monospaced ("@" ^ snk_annot) in + Format.asprintf "Method %a annotated with %a calls %a where %a is annotated with %a" + MF.pp_monospaced (Typ.Procname.to_simplified_string src_pname) MF.pp_monospaced + ("@" ^ src_annot) MF.pp_monospaced (stack_str ^ exp_pname_str) MF.pp_monospaced + exp_pname_str MF.pp_monospaced ("@" ^ snk_annot) + in let msg = - if String.equal src_annot Annotations.performance_critical - then calls_expensive_method - else annotation_reachability_error in - let exn = - Exceptions.Checkers (msg, Localise.verbatim_desc description) in + if String.equal src_annot Annotations.performance_critical then calls_expensive_method + else annotation_reachability_error + in + let exn = Exceptions.Checkers (msg, Localise.verbatim_desc description) in Reporting.log_error src_summary ~loc ~ltr:final_trace exn let report_call_stack summary end_of_stack lookup_next_calls report call_site sink_map = (* TODO: stop using this; we can use the call site instead *) let lookup_location pname = match Specs.get_summary pname with - | None -> Location.dummy - | Some summary -> summary.Specs.attributes.ProcAttributes.loc in + | None + -> Location.dummy + | Some summary + -> summary.Specs.attributes.ProcAttributes.loc + in let rec loop fst_call_loc visited_pnames (trace, stack_str) (callee_pname, call_loc) = if end_of_stack callee_pname then report summary fst_call_loc trace stack_str callee_pname call_loc @@ -204,42 +194,36 @@ let report_call_stack summary end_of_stack lookup_next_calls report call_site si let new_trace = update_trace call_loc trace |> update_trace callee_def_loc in let unseen_callees, updated_callees = AnnotReachabilityDomain.SinkMap.fold - (fun _ call_sites ((unseen, visited) as accu) -> - try - let call_site = AnnotReachabilityDomain.CallSites.choose call_sites in - let p = CallSite.pname call_site in - let loc = CallSite.loc call_site in - if Typ.Procname.Set.mem p visited then accu - else ((p, loc) :: unseen, Typ.Procname.Set.add p visited) - with Not_found -> accu) - next_calls - ([], visited_pnames) in - List.iter ~f:(loop fst_call_loc updated_callees (new_trace, new_stack_str)) unseen_callees in + (fun _ call_sites (unseen, visited as accu) -> + try + let call_site = AnnotReachabilityDomain.CallSites.choose call_sites in + let p = CallSite.pname call_site in + let loc = CallSite.loc call_site in + if Typ.Procname.Set.mem p visited then accu + else ((p, loc) :: unseen, Typ.Procname.Set.add p visited) + with Not_found -> accu) + next_calls ([], visited_pnames) + in + List.iter ~f:(loop fst_call_loc updated_callees (new_trace, new_stack_str)) unseen_callees + in AnnotReachabilityDomain.SinkMap.iter (fun _ call_sites -> - try - let fst_call_site = AnnotReachabilityDomain.CallSites.choose call_sites in - let fst_callee_pname = CallSite.pname fst_call_site in - let fst_call_loc = CallSite.loc fst_call_site in - let start_trace = update_trace (CallSite.loc call_site) [] in - loop fst_call_loc Typ.Procname.Set.empty (start_trace, "") (fst_callee_pname, fst_call_loc) - with Not_found -> ()) + try + let fst_call_site = AnnotReachabilityDomain.CallSites.choose call_sites in + let fst_callee_pname = CallSite.pname fst_call_site in + let fst_call_loc = CallSite.loc fst_call_site in + let start_trace = update_trace (CallSite.loc call_site) [] in + loop fst_call_loc Typ.Procname.Set.empty (start_trace, "") (fst_callee_pname, fst_call_loc) + with Not_found -> ()) sink_map -let report_src_snk_path { Callbacks.proc_desc; tenv; summary } sink_map snk_annot src_annot = +let report_src_snk_path {Callbacks.proc_desc; tenv; summary} sink_map snk_annot src_annot = let proc_name = Procdesc.get_proc_name proc_desc in let loc = Procdesc.get_loc proc_desc in - if method_overrides_annot src_annot tenv proc_name - then - let f_report = - report_annotation_stack src_annot.Annot.class_name snk_annot.Annot.class_name in - report_call_stack - summary - (method_has_annot snk_annot tenv) - (lookup_annotation_calls proc_desc snk_annot) - f_report - (CallSite.make proc_name loc) - sink_map + if method_overrides_annot src_annot tenv proc_name then + let f_report = report_annotation_stack src_annot.Annot.class_name snk_annot.Annot.class_name in + report_call_stack summary (method_has_annot snk_annot tenv) + (lookup_annotation_calls proc_desc snk_annot) f_report (CallSite.make proc_name loc) sink_map let report_src_snk_paths proc_data annot_map src_annot_list snk_annot = try @@ -249,19 +233,17 @@ let report_src_snk_paths proc_data annot_map src_annot_list snk_annot = (* New implementation starts here *) -let annotation_of_str annot_str = - { Annot.class_name = annot_str; parameters = []; } +let annotation_of_str annot_str = {Annot.class_name= annot_str; parameters= []} module AnnotationSpec = struct type predicate = Tenv.t -> Typ.Procname.t -> bool - type t = { - source_predicate: predicate; - sink_predicate: predicate; - sanitizer_predicate: predicate; - sink_annotation: Annot.t; - report: Callbacks.proc_callback_args -> AnnotReachabilityDomain.astate -> unit; - } + type t = + { source_predicate: predicate + ; sink_predicate: predicate + ; sanitizer_predicate: predicate + ; sink_annotation: Annot.t + ; report: Callbacks.proc_callback_args -> AnnotReachabilityDomain.astate -> unit } (* The default sanitizer does not sanitize anything *) let default_sanitizer _ _ = false @@ -271,111 +253,114 @@ module AnnotationSpec = struct end module StandardAnnotationSpec = struct - let from_annotations src_annots snk_annot = AnnotationSpec.{ - source_predicate = (fun tenv pname -> - List.exists src_annots ~f:(fun a -> method_overrides_annot a tenv pname)); - sink_predicate = (fun tenv pname -> + let from_annotations src_annots snk_annot = + let open AnnotationSpec in + { source_predicate= + (fun tenv pname -> + List.exists src_annots ~f:(fun a -> method_overrides_annot a tenv pname)) + ; sink_predicate= + (fun tenv pname -> let has_annot ia = Annotations.ia_ends_with ia snk_annot.Annot.class_name in - check_attributes has_annot tenv pname - ); - sanitizer_predicate = default_sanitizer; - sink_annotation = snk_annot; - report = (fun proc_data annot_map -> - report_src_snk_paths proc_data annot_map src_annots snk_annot) + check_attributes has_annot tenv pname) + ; sanitizer_predicate= default_sanitizer + ; sink_annotation= snk_annot + ; report= + (fun proc_data annot_map -> report_src_snk_paths proc_data annot_map src_annots snk_annot) } end module NoAllocationAnnotationSpec = struct let no_allocation_annot = annotation_of_str Annotations.no_allocation + let constructor_annot = annotation_of_str dummy_constructor_annot - let spec = AnnotationSpec.{ - source_predicate = (fun tenv pname -> - method_overrides_annot no_allocation_annot tenv pname); - sink_predicate = (fun tenv pname -> - is_allocator tenv pname); - sanitizer_predicate = (fun tenv pname -> - check_attributes Annotations.ia_is_ignore_allocations tenv pname); - sink_annotation = constructor_annot; - report = (fun proc_data annot_map -> - report_src_snk_paths proc_data annot_map [no_allocation_annot] constructor_annot); - } + let spec = + let open AnnotationSpec in + { source_predicate= (fun tenv pname -> method_overrides_annot no_allocation_annot tenv pname) + ; sink_predicate= (fun tenv pname -> is_allocator tenv pname) + ; sanitizer_predicate= + (fun tenv pname -> check_attributes Annotations.ia_is_ignore_allocations tenv pname) + ; sink_annotation= constructor_annot + ; report= + (fun proc_data annot_map -> + report_src_snk_paths proc_data annot_map [no_allocation_annot] constructor_annot) } end module ExpensiveAnnotationSpec = struct let performance_critical_annot = annotation_of_str Annotations.performance_critical + let expensive_annot = annotation_of_str Annotations.expensive - let is_expensive tenv pname = - check_attributes Annotations.ia_is_expensive tenv pname + let is_expensive tenv pname = check_attributes Annotations.ia_is_expensive tenv pname - let method_is_expensive tenv pname = - is_modeled_expensive tenv pname || is_expensive tenv pname + let method_is_expensive tenv pname = is_modeled_expensive tenv pname || is_expensive tenv pname - let check_expensive_subtyping_rules { Callbacks.proc_desc; tenv; summary } overridden_pname = + let check_expensive_subtyping_rules {Callbacks.proc_desc; tenv; summary} overridden_pname = let proc_name = Procdesc.get_proc_name proc_desc in let loc = Procdesc.get_loc proc_desc in if not (method_is_expensive tenv overridden_pname) then let description = - Format.asprintf - "Method %a overrides unannotated method %a and cannot be annotated with %a" - MF.pp_monospaced (Typ.Procname.to_string proc_name) - MF.pp_monospaced (Typ.Procname.to_string overridden_pname) - MF.pp_monospaced ("@" ^ Annotations.expensive) in + Format.asprintf "Method %a overrides unannotated method %a and cannot be annotated with %a" + MF.pp_monospaced (Typ.Procname.to_string proc_name) MF.pp_monospaced + (Typ.Procname.to_string overridden_pname) MF.pp_monospaced ("@" ^ Annotations.expensive) + in let exn = - Exceptions.Checkers - (expensive_overrides_unexpensive, Localise.verbatim_desc description) in + Exceptions.Checkers (expensive_overrides_unexpensive, Localise.verbatim_desc description) + in Reporting.log_error summary ~loc exn - let spec = AnnotationSpec.{ - source_predicate = is_expensive; - sink_predicate = (fun tenv pname -> + let spec = + let open AnnotationSpec in + { source_predicate= is_expensive + ; sink_predicate= + (fun tenv pname -> let has_annot ia = Annotations.ia_ends_with ia expensive_annot.class_name in - check_attributes has_annot tenv pname || is_modeled_expensive tenv pname - ); - sanitizer_predicate = default_sanitizer; - sink_annotation = expensive_annot; - report = (fun ({ Callbacks.tenv; proc_desc } as proc_data) astate -> + check_attributes has_annot tenv pname || is_modeled_expensive tenv pname) + ; sanitizer_predicate= default_sanitizer + ; sink_annotation= expensive_annot + ; report= + (fun ({Callbacks.tenv; proc_desc} as proc_data) astate -> let proc_name = Procdesc.get_proc_name proc_desc in if is_expensive tenv proc_name then - PatternMatch.override_iter (check_expensive_subtyping_rules proc_data) tenv proc_name; - report_src_snk_paths proc_data astate [performance_critical_annot] expensive_annot - ); - } + PatternMatch.override_iter (check_expensive_subtyping_rules proc_data) tenv proc_name ; + report_src_snk_paths proc_data astate [performance_critical_annot] expensive_annot) } end (* parse user-defined specs from .inferconfig *) let parse_user_defined_specs = function - | `List user_specs -> - let parse_user_spec json = + | `List user_specs + -> let parse_user_spec json = let open Yojson.Basic in let sources = Util.member "sources" json |> Util.to_list |> List.map ~f:Util.to_string in let sinks = Util.member "sink" json |> Util.to_string in - sources, sinks in + (sources, sinks) + in List.map ~f:parse_user_spec user_specs - | _ -> - [] + | _ + -> [] let annot_specs = let user_defined_specs = let specs = parse_user_defined_specs Config.annotation_reachability_custom_pairs in List.map specs ~f:(fun (src_annots, snk_annot) -> - StandardAnnotationSpec.from_annotations - (List.map ~f:annotation_of_str src_annots) (annotation_of_str snk_annot)) + StandardAnnotationSpec.from_annotations (List.map ~f:annotation_of_str src_annots) + (annotation_of_str snk_annot) ) in - ExpensiveAnnotationSpec.spec :: - NoAllocationAnnotationSpec.spec :: - (StandardAnnotationSpec.from_annotations - [annotation_of_str Annotations.any_thread ; annotation_of_str Annotations.for_non_ui_thread] - (annotation_of_str Annotations.ui_thread)) :: - (StandardAnnotationSpec.from_annotations - [annotation_of_str Annotations.ui_thread ; annotation_of_str Annotations.for_ui_thread] - (annotation_of_str Annotations.for_non_ui_thread)) :: - user_defined_specs + ExpensiveAnnotationSpec.spec + :: NoAllocationAnnotationSpec.spec + :: StandardAnnotationSpec.from_annotations + [ annotation_of_str Annotations.any_thread + ; annotation_of_str Annotations.for_non_ui_thread ] + (annotation_of_str Annotations.ui_thread) + :: StandardAnnotationSpec.from_annotations + [annotation_of_str Annotations.ui_thread; annotation_of_str Annotations.for_ui_thread] + (annotation_of_str Annotations.for_non_ui_thread) + :: user_defined_specs module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = ProcData.no_extras (* This is specific to the @NoAllocation and @PerformanceCritical checker @@ -383,86 +368,82 @@ module TransferFunctions (CFG : ProcCfg.S) = struct rarely to not affect the performances *) let is_unlikely pname = match pname with - | Typ.Procname.Java java_pname -> - String.equal (Typ.Procname.java_get_method java_pname) "unlikely" - | _ -> false + | Typ.Procname.Java java_pname + -> String.equal (Typ.Procname.java_get_method java_pname) "unlikely" + | _ + -> false let is_tracking_exp astate = function - | Exp.Var id -> Domain.is_tracked_var (Var.of_id id) astate - | Exp.Lvar pvar -> Domain.is_tracked_var (Var.of_pvar pvar) astate - | _ -> false + | Exp.Var id + -> Domain.is_tracked_var (Var.of_id id) astate + | Exp.Lvar pvar + -> Domain.is_tracked_var (Var.of_pvar pvar) astate + | _ + -> false let prunes_tracking_var astate = function - | Exp.BinOp (Binop.Eq, lhs, rhs) - when is_tracking_exp astate lhs -> - Exp.equal rhs Exp.one - | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq, lhs, rhs), _) - when is_tracking_exp astate lhs -> - Exp.equal rhs Exp.zero - | _ -> - false + | Exp.BinOp (Binop.Eq, lhs, rhs) when is_tracking_exp astate lhs + -> Exp.equal rhs Exp.one + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq, lhs, rhs), _) when is_tracking_exp astate lhs + -> Exp.equal rhs Exp.zero + | _ + -> false let check_call tenv callee_pname caller_pname call_site astate = - List.fold - ~init:astate + List.fold ~init:astate ~f:(fun astate (spec: AnnotationSpec.t) -> - if spec.sink_predicate tenv callee_pname && - not (spec.sanitizer_predicate tenv caller_pname) - then Domain.add_call_site spec.sink_annotation callee_pname call_site astate - else astate) + if spec.sink_predicate tenv callee_pname + && not (spec.sanitizer_predicate tenv caller_pname) + then Domain.add_call_site spec.sink_annotation callee_pname call_site astate + else astate) annot_specs let merge_callee_map call_site pdesc callee_pname astate = match Summary.read_summary pdesc callee_pname with - | None -> astate - | Some callee_call_map -> - let add_call_site annot sink calls astate = - if AnnotReachabilityDomain.CallSites.is_empty calls - then astate - else Domain.add_call_site annot sink call_site astate in + | None + -> astate + | Some callee_call_map + -> let add_call_site annot sink calls astate = + if AnnotReachabilityDomain.CallSites.is_empty calls then astate + else Domain.add_call_site annot sink call_site astate + in AnnotReachabilityDomain.fold (fun annot sink_map astate -> - AnnotReachabilityDomain.SinkMap.fold - (add_call_site annot) - sink_map - astate) - callee_call_map - astate - - let exec_instr astate { ProcData.pdesc; tenv; } _ = function - | Sil.Call (Some (id, _), Const (Cfun callee_pname), _, _, _) - when is_unlikely callee_pname -> - Domain.add_tracking_var (Var.of_id id) astate - | Sil.Call (_, Const (Cfun callee_pname), _, call_loc, _) -> - let caller_pname = Procdesc.get_proc_name pdesc in + AnnotReachabilityDomain.SinkMap.fold (add_call_site annot) sink_map astate) + callee_call_map astate + + let exec_instr astate {ProcData.pdesc; tenv} _ = function + | Sil.Call (Some (id, _), Const Cfun callee_pname, _, _, _) when is_unlikely callee_pname + -> Domain.add_tracking_var (Var.of_id id) astate + | Sil.Call (_, Const Cfun callee_pname, _, call_loc, _) + -> let caller_pname = Procdesc.get_proc_name pdesc in let call_site = CallSite.make callee_pname call_loc in check_call tenv callee_pname caller_pname call_site astate |> merge_callee_map call_site pdesc callee_pname - | Sil.Load (id, exp, _, _) - when is_tracking_exp astate exp -> - Domain.add_tracking_var (Var.of_id id) astate - | Sil.Store (Exp.Lvar pvar, _, exp, _) - when is_tracking_exp astate exp -> - Domain.add_tracking_var (Var.of_pvar pvar) astate - | Sil.Store (Exp.Lvar pvar, _, _, _) -> - Domain.remove_tracking_var (Var.of_pvar pvar) astate - | Sil.Prune (exp, _, _, _) - when prunes_tracking_var astate exp -> - Domain.stop_tracking astate - | Sil.Call (None, _, _, _, _) -> - failwith "Expecting a return identifier" - | _ -> - astate + | Sil.Load (id, exp, _, _) when is_tracking_exp astate exp + -> Domain.add_tracking_var (Var.of_id id) astate + | Sil.Store (Exp.Lvar pvar, _, exp, _) when is_tracking_exp astate exp + -> Domain.add_tracking_var (Var.of_pvar pvar) astate + | Sil.Store (Exp.Lvar pvar, _, _, _) + -> Domain.remove_tracking_var (Var.of_pvar pvar) astate + | Sil.Prune (exp, _, _, _) when prunes_tracking_var astate exp + -> Domain.stop_tracking astate + | Sil.Call (None, _, _, _, _) + -> failwith "Expecting a return identifier" + | _ + -> astate end module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) -let checker ({ Callbacks.proc_desc; tenv; summary} as callback) : Specs.summary = + +let checker ({Callbacks.proc_desc; tenv; summary} as callback) : Specs.summary = let initial = - (AnnotReachabilityDomain.empty, Domain.TrackingDomain.NonBottom Domain.TrackingVar.empty) in + (AnnotReachabilityDomain.empty, Domain.TrackingDomain.NonBottom Domain.TrackingVar.empty) + in let proc_data = ProcData.make_default proc_desc tenv in match Analyzer.compute_post proc_data ~initial with - | Some (annot_map, _) -> - List.iter annot_specs ~f:(fun (spec: AnnotationSpec.t) -> spec.report callback annot_map); + | Some (annot_map, _) + -> List.iter annot_specs ~f:(fun (spec: AnnotationSpec.t) -> spec.report callback annot_map) ; Summary.update_summary annot_map summary - | None -> - summary + | None + -> summary diff --git a/infer/src/checkers/annotations.ml b/infer/src/checkers/annotations.ml index afdaa9949..393420d98 100644 --- a/infer/src/checkers/annotations.ml +++ b/infer/src/checkers/annotations.ml @@ -8,65 +8,111 @@ *) open! IStd - module F = Format module L = Logging (** Annotations. *) let any_thread = "AnyThread" + let bind = "Bind" + let bind_view = "BindView" + let bind_array = "BindArray" + let bind_bitmap = "BindBitmap" + let bind_drawable = "BindDrawable" + let bind_string = "BindString" + let camel_nonnull = "NonNull" + let expensive = "Expensive" + let false_on_null = "FalseOnNull" + let for_ui_thread = "ForUiThread" + let for_non_ui_thread = "ForNonUiThread" + let functional = "Functional" + let guarded_by = "GuardedBy" + let ignore_allocations = "IgnoreAllocations" + let initializer_ = "Initializer" + let inject = "Inject" + let inject_view = "InjectView" + let integrity_source = "IntegritySource" + let integrity_sink = "IntegritySink" + let mutable_ = "Mutable" + let nonnull = "Nonnull" + let no_allocation = "NoAllocation" + let nullable = "Nullable" + let on_bind = "OnBind" + let on_event = "OnEvent" + let on_mount = "OnMount" + let on_unbind = "OnUnbind" + let on_unmount = "OnUnmount" + let notnull = "NotNull" + let not_thread_safe = "NotThreadSafe" + let performance_critical = "PerformanceCritical" + let present = "Present" + let privacy_source = "PrivacySource" + let privacy_sink = "PrivacySink" + let propagates_nullable = "PropagatesNullable" + let returns_ownership = "ReturnsOwnership" + let synchronized_collection = "SynchronizedCollection" + let strict = "com.facebook.infer.annotation.Strict" + let suppress_lint = "SuppressLint" + let suppress_view_nullability = "SuppressViewNullability" + let thread_confined = "ThreadConfined" + let thread_safe = "ThreadSafe" + let true_on_null = "TrueOnNull" + let ui_thread = "UiThread" + let verify_annotation = "com.facebook.infer.annotation.Verify" + let visibleForTesting = "VisibleForTesting" + let volatile = "volatile" -let ia_has_annotation_with (ia: Annot.Item.t) (predicate: Annot.t -> bool): bool = +let ia_has_annotation_with (ia: Annot.Item.t) (predicate: Annot.t -> bool) : bool = List.exists ~f:(fun (a, _) -> predicate a) ia -let ma_has_annotation_with ((ia, ial) : Annot.Method.t) (predicate: Annot.t -> bool): bool = +let ma_has_annotation_with ((ia, ial): Annot.Method.t) (predicate: Annot.t -> bool) : bool = let has_annot a = ia_has_annotation_with a predicate in has_annot ia || List.exists ~f:has_annot ial @@ -74,21 +120,18 @@ let ma_has_annotation_with ((ia, ial) : Annot.Method.t) (predicate: Annot.t -> b is equal to [ann_name] *) let annot_ends_with annot ann_name = match String.rsplit2 annot.Annot.class_name ~on:'.' with - | None -> String.equal annot.Annot.class_name ann_name - | Some (_, annot_class_name) -> String.equal annot_class_name ann_name + | None + -> String.equal annot.Annot.class_name ann_name + | Some (_, annot_class_name) + -> String.equal annot_class_name ann_name -let class_name_matches s ((annot : Annot.t), _) = - String.equal s annot.class_name +let class_name_matches s ((annot: Annot.t), _) = String.equal s annot.class_name -let ia_ends_with ia ann_name = - List.exists ~f:(fun (a, _) -> annot_ends_with a ann_name) ia +let ia_ends_with ia ann_name = List.exists ~f:(fun (a, _) -> annot_ends_with a ann_name) ia -let ia_contains ia ann_name = - List.exists ~f:(class_name_matches ann_name) ia +let ia_contains ia ann_name = List.exists ~f:(class_name_matches ann_name) ia -let ia_get ia ann_name = - List.find ~f:(class_name_matches ann_name) ia |> - Option.map ~f:fst +let ia_get ia ann_name = List.find ~f:(class_name_matches ann_name) ia |> Option.map ~f:fst let pdesc_has_parameter_annot pdesc predicate = let _, param_annotations = (Procdesc.get_attributes pdesc).ProcAttributes.method_annotation in @@ -97,8 +140,7 @@ let pdesc_has_parameter_annot pdesc predicate = let pdesc_get_return_annot pdesc = fst (Procdesc.get_attributes pdesc).ProcAttributes.method_annotation -let pdesc_has_return_annot pdesc predicate = - predicate (pdesc_get_return_annot pdesc) +let pdesc_has_return_annot pdesc predicate = predicate (pdesc_get_return_annot pdesc) let pdesc_return_annot_ends_with pdesc annot = pdesc_has_return_annot pdesc (fun ia -> ia_ends_with ia annot) @@ -107,147 +149,104 @@ let pdesc_return_annot_ends_with pdesc annot = but doing so creates a circular dependency *) let pname_has_return_annot pname ~attrs_of_pname predicate = match attrs_of_pname pname with - | Some attributes -> predicate (fst attributes.ProcAttributes.method_annotation) - | None -> false + | Some attributes + -> predicate (fst attributes.ProcAttributes.method_annotation) + | None + -> false -let field_has_annot fieldname (struct_typ : Typ.Struct.t) predicate = +let field_has_annot fieldname (struct_typ: Typ.Struct.t) predicate = let fld_has_taint_annot (fname, _, annot) = - Typ.Fieldname.equal fieldname fname && predicate annot in - List.exists ~f:fld_has_taint_annot struct_typ.fields || - List.exists ~f:fld_has_taint_annot struct_typ.statics + Typ.Fieldname.equal fieldname fname && predicate annot + in + List.exists ~f:fld_has_taint_annot struct_typ.fields + || List.exists ~f:fld_has_taint_annot struct_typ.statics -let struct_typ_has_annot (struct_typ : Typ.Struct.t) predicate = - predicate struct_typ.annots +let struct_typ_has_annot (struct_typ: Typ.Struct.t) predicate = predicate struct_typ.annots -let ia_is_not_thread_safe ia = - ia_ends_with ia not_thread_safe +let ia_is_not_thread_safe ia = ia_ends_with ia not_thread_safe -let ia_is_propagates_nullable ia = - ia_ends_with ia propagates_nullable +let ia_is_propagates_nullable ia = ia_ends_with ia propagates_nullable -let ia_is_nullable ia = - ia_ends_with ia nullable || - ia_is_propagates_nullable ia +let ia_is_nullable ia = ia_ends_with ia nullable || ia_is_propagates_nullable ia -let ia_is_present ia = - ia_ends_with ia present +let ia_is_present ia = ia_ends_with ia present -let ia_is_nonnull ia = - List.exists - ~f:(ia_ends_with ia) - [nonnull; notnull; camel_nonnull] +let ia_is_nonnull ia = List.exists ~f:(ia_ends_with ia) [nonnull; notnull; camel_nonnull] -let ia_is_false_on_null ia = - ia_ends_with ia false_on_null +let ia_is_false_on_null ia = ia_ends_with ia false_on_null -let ia_is_returns_ownership ia = - ia_ends_with ia returns_ownership +let ia_is_returns_ownership ia = ia_ends_with ia returns_ownership -let ia_is_synchronized_collection ia = - ia_ends_with ia synchronized_collection +let ia_is_synchronized_collection ia = ia_ends_with ia synchronized_collection -let ia_is_true_on_null ia = - ia_ends_with ia true_on_null +let ia_is_true_on_null ia = ia_ends_with ia true_on_null -let ia_is_initializer ia = - ia_ends_with ia initializer_ +let ia_is_initializer ia = ia_ends_with ia initializer_ -let ia_is_volatile ia = - ia_contains ia volatile +let ia_is_volatile ia = ia_contains ia volatile let field_injector_readwrite_list = - [ - inject_view; - bind; - bind_view; - bind_array; - bind_bitmap; - bind_drawable; - bind_string; - suppress_view_nullability; - ] - -let field_injector_readonly_list = - inject - :: - field_injector_readwrite_list + [ inject_view + ; bind + ; bind_view + ; bind_array + ; bind_bitmap + ; bind_drawable + ; bind_string + ; suppress_view_nullability ] + +let field_injector_readonly_list = inject :: field_injector_readwrite_list (** Annotations for readonly injectors. The injector framework initializes the field but does not write null into it. *) let ia_is_field_injector_readonly ia = - List.exists - ~f:(ia_ends_with ia) - field_injector_readonly_list + List.exists ~f:(ia_ends_with ia) field_injector_readonly_list (** Annotations for read-write injectors. The injector framework initializes the field and can write null into it. *) let ia_is_field_injector_readwrite ia = - List.exists - ~f:(ia_ends_with ia) - field_injector_readwrite_list + List.exists ~f:(ia_ends_with ia) field_injector_readwrite_list -let ia_is_mutable ia = - ia_ends_with ia mutable_ +let ia_is_mutable ia = ia_ends_with ia mutable_ -let ia_get_strict ia = - ia_get ia strict +let ia_get_strict ia = ia_get ia strict -let ia_is_verify ia = - ia_contains ia verify_annotation +let ia_is_verify ia = ia_contains ia verify_annotation -let ia_is_expensive ia = - ia_ends_with ia expensive +let ia_is_expensive ia = ia_ends_with ia expensive -let ia_is_functional ia = - ia_ends_with ia functional +let ia_is_functional ia = ia_ends_with ia functional -let ia_is_performance_critical ia = - ia_ends_with ia performance_critical +let ia_is_performance_critical ia = ia_ends_with ia performance_critical -let ia_is_no_allocation ia = - ia_ends_with ia no_allocation +let ia_is_no_allocation ia = ia_ends_with ia no_allocation -let ia_is_ignore_allocations ia = - ia_ends_with ia ignore_allocations +let ia_is_ignore_allocations ia = ia_ends_with ia ignore_allocations -let ia_is_inject ia = - ia_ends_with ia inject +let ia_is_inject ia = ia_ends_with ia inject -let ia_is_suppress_lint ia = - ia_ends_with ia suppress_lint +let ia_is_suppress_lint ia = ia_ends_with ia suppress_lint -let ia_is_on_event ia = - ia_ends_with ia on_event +let ia_is_on_event ia = ia_ends_with ia on_event -let ia_is_on_bind ia = - ia_ends_with ia on_bind +let ia_is_on_bind ia = ia_ends_with ia on_bind -let ia_is_on_mount ia = - ia_ends_with ia on_mount +let ia_is_on_mount ia = ia_ends_with ia on_mount -let ia_is_on_unbind ia = - ia_ends_with ia on_unbind +let ia_is_on_unbind ia = ia_ends_with ia on_unbind -let ia_is_on_unmount ia = - ia_ends_with ia on_unmount +let ia_is_on_unmount ia = ia_ends_with ia on_unmount -let ia_is_privacy_source ia = - ia_ends_with ia privacy_source +let ia_is_privacy_source ia = ia_ends_with ia privacy_source -let ia_is_privacy_sink ia = - ia_ends_with ia privacy_sink +let ia_is_privacy_sink ia = ia_ends_with ia privacy_sink -let ia_is_integrity_source ia = - ia_ends_with ia integrity_source +let ia_is_integrity_source ia = ia_ends_with ia integrity_source -let ia_is_integrity_sink ia = - ia_ends_with ia integrity_sink +let ia_is_integrity_sink ia = ia_ends_with ia integrity_sink -let ia_is_guarded_by ia = - ia_ends_with ia guarded_by +let ia_is_guarded_by ia = ia_ends_with ia guarded_by -let ia_is_ui_thread ia = - ia_ends_with ia ui_thread +let ia_is_ui_thread ia = ia_ends_with ia ui_thread -let ia_is_thread_confined ia = - ia_ends_with ia thread_confined +let ia_is_thread_confined ia = ia_ends_with ia thread_confined diff --git a/infer/src/checkers/annotations.mli b/infer/src/checkers/annotations.mli index 7862d9f4d..91c8b4591 100644 --- a/infer/src/checkers/annotations.mli +++ b/infer/src/checkers/annotations.mli @@ -12,29 +12,45 @@ open! IStd (** Annotations. *) val any_thread : string + val expensive : string + val no_allocation : string + val nullable : string + val nonnull : string + val on_bind : string + val performance_critical : string + val present : string + val for_non_ui_thread : string + val for_ui_thread : string + val guarded_by : string + val strict : string + val suppress_lint : string + val thread_confined : string + val thread_safe : string + val ui_thread : string + val visibleForTesting : string +val annot_ends_with : Annot.t -> string -> bool (** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package, is equal to [ann_name] *) -val annot_ends_with : Annot.t -> string -> bool -(** Check if there is an annotation in [ia] which ends with the given name *) val ia_ends_with : Annot.Item.t -> string -> bool +(** Check if there is an annotation in [ia] which ends with the given name *) val ia_contains : Annot.Item.t -> string -> bool @@ -43,73 +59,101 @@ val ia_has_annotation_with : Annot.Item.t -> (Annot.t -> bool) -> bool val ia_get_strict : Annot.Item.t -> Annot.t option val ia_is_false_on_null : Annot.Item.t -> bool + val ia_is_initializer : Annot.Item.t -> bool +val ia_is_field_injector_readonly : Annot.Item.t -> bool (** Annotations for readonly injectors. The injector framework initializes the field but does not write null into it. *) -val ia_is_field_injector_readonly : Annot.Item.t -> bool +val ia_is_field_injector_readwrite : Annot.Item.t -> bool (** Annotations for read-write injectors. The injector framework initializes the field and can write null into it. *) -val ia_is_field_injector_readwrite : Annot.Item.t -> bool val ia_is_mutable : Annot.Item.t -> bool + val ia_is_nonnull : Annot.Item.t -> bool + val ia_is_nullable : Annot.Item.t -> bool + val ia_is_present : Annot.Item.t -> bool + val ia_is_true_on_null : Annot.Item.t -> bool + val ia_is_verify : Annot.Item.t -> bool + val ia_is_expensive : Annot.Item.t -> bool + val ia_is_functional : Annot.Item.t -> bool + val ia_is_performance_critical : Annot.Item.t -> bool + val ia_is_propagates_nullable : Annot.Item.t -> bool + val ia_is_no_allocation : Annot.Item.t -> bool + val ia_is_ignore_allocations : Annot.Item.t -> bool + val ia_is_inject : Annot.Item.t -> bool + val ia_is_suppress_lint : Annot.Item.t -> bool + val ia_is_on_event : Annot.Item.t -> bool + val ia_is_on_bind : Annot.Item.t -> bool + val ia_is_on_mount : Annot.Item.t -> bool + val ia_is_on_unbind : Annot.Item.t -> bool + val ia_is_on_unmount : Annot.Item.t -> bool + val ia_is_privacy_source : Annot.Item.t -> bool + val ia_is_privacy_sink : Annot.Item.t -> bool + val ia_is_integrity_source : Annot.Item.t -> bool + val ia_is_integrity_sink : Annot.Item.t -> bool + val ia_is_guarded_by : Annot.Item.t -> bool + val ia_is_not_thread_safe : Annot.Item.t -> bool + val ia_is_returns_ownership : Annot.Item.t -> bool + val ia_is_synchronized_collection : Annot.Item.t -> bool + val ia_is_thread_confined : Annot.Item.t -> bool + val ia_is_ui_thread : Annot.Item.t -> bool + val ia_is_volatile : Annot.Item.t -> bool +val pdesc_has_parameter_annot : Procdesc.t -> (Annot.Item.t -> bool) -> bool (** return true if the given predicate evaluates to true on an annotation of one of [pdesc]'s parameters *) -val pdesc_has_parameter_annot : Procdesc.t -> (Annot.Item.t -> bool) -> bool -(** get the list of annotations on the return value of [pdesc] *) val pdesc_get_return_annot : Procdesc.t -> Annot.Item.t +(** get the list of annotations on the return value of [pdesc] *) +val pdesc_has_return_annot : Procdesc.t -> (Annot.Item.t -> bool) -> bool (** return true if the given predicate evaluates to true on the annotation of [pdesc]'s return value *) -val pdesc_has_return_annot : Procdesc.t -> (Annot.Item.t -> bool) -> bool +val pname_has_return_annot : + Typ.Procname.t -> attrs_of_pname:(Typ.Procname.t -> ProcAttributes.t option) + -> (Annot.Item.t -> bool) -> bool (** return true if the given predicate evaluates to true on the annotation of [pname]'s return value. the function [attrs_of_pname] should resolve the proc attributes of [pname]. Specs.proc_resolve_attributes is a good choice for this resolution function. *) -val pname_has_return_annot : - Typ.Procname.t -> - attrs_of_pname:(Typ.Procname.t -> ProcAttributes.t option) -> - (Annot.Item.t -> bool) -> - bool -(** return true if [pdesc]'s return value is annotated with a value ending with the given string *) val pdesc_return_annot_ends_with : Procdesc.t -> string -> bool +(** return true if [pdesc]'s return value is annotated with a value ending with the given string *) val ma_has_annotation_with : Annot.Method.t -> (Annot.t -> bool) -> bool val field_has_annot : Typ.Fieldname.t -> Typ.Struct.t -> (Annot.Item.t -> bool) -> bool -(** return true if the given predicate evaluates to true on some annotation of [struct_typ] *) val struct_typ_has_annot : Typ.Struct.t -> (Annot.Item.t -> bool) -> bool +(** return true if the given predicate evaluates to true on some annotation of [struct_typ] *) diff --git a/infer/src/checkers/constantPropagation.ml b/infer/src/checkers/constantPropagation.ml index ac0ffa0b3..9d6a053a1 100644 --- a/infer/src/checkers/constantPropagation.ml +++ b/infer/src/checkers/constantPropagation.ml @@ -8,139 +8,132 @@ *) open! IStd - module L = Logging - let string_widening_limit = 1000 + let verbose = false (* Merge two constant maps by adding keys as necessary *) let merge_values _ c1_opt c2_opt = - match c1_opt, c2_opt with - | Some (Some c1), Some (Some c2) when Const.equal c1 c2 -> Some (Some c1) - | Some c, None - | None, Some c -> Some c - | _ -> Some None + match (c1_opt, c2_opt) with + | Some Some c1, Some Some c2 when Const.equal c1 c2 + -> Some (Some c1) + | Some c, None | None, Some c + -> Some c + | _ + -> Some None module ConstantMap = Exp.Map (** Dataflow struct *) -module ConstantFlow = Dataflow.MakeDF(struct - type t = (Const.t option) ConstantMap.t [@@deriving compare] - - let equal = [%compare.equal : t] - - let pp fmt constants = - let pp_key fmt = Exp.pp fmt in - let print_kv k = function - | Some v -> Format.fprintf fmt " %a -> %a@." pp_key k (Const.pp Pp.text) v - | _ -> Format.fprintf fmt " %a -> None@." pp_key k in - Format.fprintf fmt "[@."; - ConstantMap.iter print_kv constants; - Format.fprintf fmt "]@." - - let join = ConstantMap.merge merge_values - - let proc_throws _ = Dataflow.DontKnow - - let do_node _ node constants = - - let do_instr constants instr = - try - let update key value constants = - ConstantMap.merge - merge_values - constants - (ConstantMap.add key value ConstantMap.empty) in - - let has_class pn name = match pn with - | Typ.Procname.Java pn_java -> - String.equal (Typ.Procname.java_get_class_name pn_java) name - | _ -> - false in - let has_method pn name = match pn with - | Typ.Procname.Java pn_java -> - String.equal (Typ.Procname.java_get_method pn_java) name - | _ -> - false in - - match instr with - | Sil.Load (i, Exp.Lvar p, _, _) -> (* tmp = var *) - update (Exp.Var i) (ConstantMap.find (Exp.Lvar p) constants) constants - - | Sil.Store (Exp.Lvar p, _, Exp.Const c, _) -> (* var = const *) - update (Exp.Lvar p) (Some c) constants - - | Sil.Store (Exp.Lvar p, _, Exp.Var i, _) -> (* var = tmp *) - update (Exp.Lvar p) (ConstantMap.find (Exp.Var i) constants) constants - - (* Handle propagation of string with StringBuilder. Does not handle null case *) - | Sil.Call (_, Exp.Const (Const.Cfun pn), (Exp.Var sb, _):: [], _, _) - when has_class pn "java.lang.StringBuilder" - && has_method pn "" -> (* StringBuilder. *) - update (Exp.Var sb) (Some (Const.Cstr "")) constants - - | Sil.Call (Some (i, _), Exp.Const (Const.Cfun pn), (Exp.Var i1, _):: [], _, _) - when has_class pn "java.lang.StringBuilder" - && has_method pn "toString" -> (* StringBuilder.toString *) - update (Exp.Var i) (ConstantMap.find (Exp.Var i1) constants) constants - - | Sil.Call - (Some (i, _), Exp.Const (Const.Cfun pn), (Exp.Var i1, _):: (Exp.Var i2, _):: [], _, _) - when has_class pn "java.lang.StringBuilder" - && has_method pn "append" -> (* StringBuilder.append *) - (match - ConstantMap.find (Exp.Var i1) constants, - ConstantMap.find (Exp.Var i2) constants with - | Some (Const.Cstr s1), Some (Const.Cstr s2) -> - begin - let s = s1 ^ s2 in - let u = - if String.length s < string_widening_limit then - Some (Const.Cstr s) - else - None in - update (Exp.Var i) u constants - end - | _ -> constants) - - | _ -> constants - with Not_found -> constants in - - if verbose then - begin - L.(debug Analysis Verbose) "Node %i:" (Procdesc.Node.get_id node :> int); - L.(debug Analysis Verbose) "%a" pp constants; - List.iter - ~f:(fun instr -> L.(debug Analysis Verbose) "%a@." (Sil.pp_instr Pp.text) instr) - (Procdesc.Node.get_instrs node) - end; - let constants = - List.fold - ~f:do_instr - ~init:constants - (Procdesc.Node.get_instrs node) in - if verbose then L.(debug Analysis Verbose) "%a@\n@." pp constants; - [constants], [constants] - end) +module ConstantFlow = Dataflow.MakeDF (struct + type t = Const.t option ConstantMap.t [@@deriving compare] + + let equal = [%compare.equal : t] + + let pp fmt constants = + let pp_key fmt = Exp.pp fmt in + let print_kv k = function + | Some v + -> Format.fprintf fmt " %a -> %a@." pp_key k (Const.pp Pp.text) v + | _ + -> Format.fprintf fmt " %a -> None@." pp_key k + in + Format.fprintf fmt "[@." ; ConstantMap.iter print_kv constants ; Format.fprintf fmt "]@." + + let join = ConstantMap.merge merge_values + + let proc_throws _ = Dataflow.DontKnow + + let do_node _ node constants = + let do_instr constants instr = + try + let update key value constants = + ConstantMap.merge merge_values constants (ConstantMap.add key value ConstantMap.empty) + in + let has_class pn name = + match pn with + | Typ.Procname.Java pn_java + -> String.equal (Typ.Procname.java_get_class_name pn_java) name + | _ + -> false + in + let has_method pn name = + match pn with + | Typ.Procname.Java pn_java + -> String.equal (Typ.Procname.java_get_method pn_java) name + | _ + -> false + in + match instr with + | Sil.Load (i, Exp.Lvar p, _, _) + -> (* tmp = var *) + update (Exp.Var i) (ConstantMap.find (Exp.Lvar p) constants) constants + | Sil.Store (Exp.Lvar p, _, Exp.Const c, _) + -> (* var = const *) + update (Exp.Lvar p) (Some c) constants + | Sil.Store (Exp.Lvar p, _, Exp.Var i, _) + -> (* var = tmp *) + update (Exp.Lvar p) (ConstantMap.find (Exp.Var i) constants) constants + (* Handle propagation of string with StringBuilder. Does not handle null case *) + | Sil.Call (_, Exp.Const Const.Cfun pn, [(Exp.Var sb, _)], _, _) + when has_class pn "java.lang.StringBuilder" && has_method pn "" + -> (* StringBuilder. *) + update (Exp.Var sb) (Some (Const.Cstr "")) constants + | Sil.Call (Some (i, _), Exp.Const Const.Cfun pn, [(Exp.Var i1, _)], _, _) + when has_class pn "java.lang.StringBuilder" && has_method pn "toString" + -> (* StringBuilder.toString *) + update (Exp.Var i) (ConstantMap.find (Exp.Var i1) constants) constants + | Sil.Call (Some (i, _), Exp.Const Const.Cfun pn, [(Exp.Var i1, _); (Exp.Var i2, _)], _, _) + when has_class pn "java.lang.StringBuilder" && has_method pn "append" -> ( + match + (* StringBuilder.append *) + (ConstantMap.find (Exp.Var i1) constants, ConstantMap.find (Exp.Var i2) constants) + with + | Some Const.Cstr s1, Some Const.Cstr s2 + -> let s = s1 ^ s2 in + let u = + if String.length s < string_widening_limit then Some (Const.Cstr s) else None + in + update (Exp.Var i) u constants + | _ + -> constants ) + | _ + -> constants + with Not_found -> constants + in + if verbose then ( + L.(debug Analysis Verbose) "Node %i:" (Procdesc.Node.get_id node :> int) ; + L.(debug Analysis Verbose) "%a" pp constants ; + List.iter + ~f:(fun instr -> L.(debug Analysis Verbose) "%a@." (Sil.pp_instr Pp.text) instr) + (Procdesc.Node.get_instrs node) ) ; + let constants = List.fold ~f:do_instr ~init:constants (Procdesc.Node.get_instrs node) in + if verbose then L.(debug Analysis Verbose) "%a@\n@." pp constants ; + ([constants], [constants]) +end) let run tenv proc_desc = let transitions = ConstantFlow.run tenv proc_desc ConstantMap.empty in let get_constants node = match transitions node with - | ConstantFlow.Transition (_, post_states, _) -> ConstantFlow.join post_states ConstantMap.empty - | ConstantFlow.Dead_state -> ConstantMap.empty in + | ConstantFlow.Transition (_, post_states, _) + -> ConstantFlow.join post_states ConstantMap.empty + | ConstantFlow.Dead_state + -> ConstantMap.empty + in get_constants type const_map = Procdesc.Node.t -> Exp.t -> Const.t option (** Build a const map lazily. *) let build_const_map tenv pdesc = - let const_map = lazy (run tenv pdesc) in + let const_map = (lazy (run tenv pdesc)) in let f node exp = try - let map = (Lazy.force const_map) node in + let map = Lazy.force const_map node in ConstantMap.find exp map - with Not_found -> None in + with Not_found -> None + in f diff --git a/infer/src/checkers/constantPropagation.mli b/infer/src/checkers/constantPropagation.mli index a5328e087..4a4ba33e4 100644 --- a/infer/src/checkers/constantPropagation.mli +++ b/infer/src/checkers/constantPropagation.mli @@ -11,5 +11,5 @@ open! IStd type const_map = Procdesc.Node.t -> Exp.t -> Const.t option -(** Build a const map lazily. *) val build_const_map : Tenv.t -> Procdesc.t -> const_map +(** Build a const map lazily. *) diff --git a/infer/src/checkers/copyPropagation.ml b/infer/src/checkers/copyPropagation.ml index 3c723701c..382e08b3f 100644 --- a/infer/src/checkers/copyPropagation.ml +++ b/infer/src/checkers/copyPropagation.ml @@ -8,38 +8,38 @@ *) open! IStd - module F = Format module L = Logging module Domain = struct include Var.Map + type astate = Var.t Var.Map.t (* return true if the key-value bindings in [rhs] are a subset of the key-value bindings in [lhs] *) - let (<=) ~lhs ~rhs = - if phys_equal lhs rhs - then true + let ( <= ) ~lhs ~rhs = + if phys_equal lhs rhs then true else Var.Map.for_all (fun k v -> - try Var.equal v (Var.Map.find k lhs) - with Not_found -> false) + try Var.equal v (Var.Map.find k lhs) + with Not_found -> false) rhs let join astate1 astate2 = - if phys_equal astate1 astate2 - then astate1 + if phys_equal astate1 astate2 then astate1 else - let keep_if_same _ v1_opt v2_opt = match v1_opt, v2_opt with - | Some v1, Some v2 -> - if Var.equal v1 v2 then Some v1 else None - | _ -> None in + let keep_if_same _ v1_opt v2_opt = + match (v1_opt, v2_opt) with + | Some v1, Some v2 + -> if Var.equal v1 v2 then Some v1 else None + | _ + -> None + in Var.Map.merge keep_if_same astate1 astate2 - let widen ~prev ~next ~num_iters:_= - join prev next + let widen ~prev ~next ~num_iters:_ = join prev next let pp fmt astate = let pp_value = Var.pp in @@ -47,65 +47,64 @@ module Domain = struct let gen var1 var2 astate = (* don't add tautological copies *) - if Var.equal var1 var2 - then astate - else Var.Map.add var1 var2 astate + if Var.equal var1 var2 then astate else Var.Map.add var1 var2 astate let kill_copies_with_var var astate = let do_kill lhs_var rhs_var astate_acc = - if Var.equal var lhs_var - then astate_acc (* kill copies vith [var] on lhs *) - else - if Var.equal var rhs_var - then (* delete [var] = [rhs_var] copy, but add [lhs_var] = M(rhs_var) copy*) + if Var.equal var lhs_var then astate_acc (* kill copies vith [var] on lhs *) + else if Var.equal var rhs_var then + (* delete [var] = [rhs_var] copy, but add [lhs_var] = M(rhs_var) copy*) try Var.Map.add lhs_var (Var.Map.find rhs_var astate) astate_acc with Not_found -> astate_acc else (* copy is unaffected by killing [var]; keep it *) - Var.Map.add lhs_var rhs_var astate_acc in + Var.Map.add lhs_var rhs_var astate_acc + in Var.Map.fold do_kill astate Var.Map.empty (* kill the previous binding for [lhs_var], and add a new [lhs_var] -> [rhs_var] binding *) let kill_then_gen lhs_var rhs_var astate = let already_has_binding lhs_var rhs_var astate = try Var.equal rhs_var (Var.Map.find lhs_var astate) - with Not_found -> false in - if Var.equal lhs_var rhs_var || already_has_binding lhs_var rhs_var astate - then astate (* already have this binding; no need to kill or gen *) - else - kill_copies_with_var lhs_var astate - |> gen lhs_var rhs_var + with Not_found -> false + in + if Var.equal lhs_var rhs_var || already_has_binding lhs_var rhs_var astate then astate + (* already have this binding; no need to kill or gen *) + else kill_copies_with_var lhs_var astate |> gen lhs_var rhs_var end module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = ProcData.no_extras let exec_instr astate _ _ = function - | Sil.Load (lhs_id, Exp.Lvar rhs_pvar, _, _) when not (Pvar.is_global rhs_pvar) -> - Domain.gen (Var.of_id lhs_id) (Var.of_pvar rhs_pvar) astate - | Sil.Store (Exp.Lvar lhs_pvar, _, Exp.Var rhs_id, _) when not (Pvar.is_global lhs_pvar) -> - Domain.kill_then_gen (Var.of_pvar lhs_pvar) (Var.of_id rhs_id) astate - | Sil.Store (Exp.Lvar lhs_pvar, _, _, _) -> - (* non-copy assignment; can only kill *) + | Sil.Load (lhs_id, Exp.Lvar rhs_pvar, _, _) when not (Pvar.is_global rhs_pvar) + -> Domain.gen (Var.of_id lhs_id) (Var.of_pvar rhs_pvar) astate + | Sil.Store (Exp.Lvar lhs_pvar, _, Exp.Var rhs_id, _) when not (Pvar.is_global lhs_pvar) + -> Domain.kill_then_gen (Var.of_pvar lhs_pvar) (Var.of_id rhs_id) astate + | Sil.Store (Exp.Lvar lhs_pvar, _, _, _) + -> (* non-copy assignment; can only kill *) Domain.kill_copies_with_var (Var.of_pvar lhs_pvar) astate | Sil.Load _ (* lhs = *rhs where rhs isn't a pvar (or is a global). in any case, not a copy *) (* note: since logical vars can't be reassigned, don't need to kill bindings for lhs id *) - | Sil.Store (Var _, _, _, _) -> - (* *lhs = rhs. not a copy, and not a write to lhs *) + | Sil.Store (Var _, _, _, _) + -> (* *lhs = rhs. not a copy, and not a write to lhs *) astate - | Sil.Call (ret_id, _, actuals, _, _) -> - let kill_ret_id (id,_) = - Domain.kill_copies_with_var (Var.of_id id) astate in + | Sil.Call (ret_id, _, actuals, _, _) + -> let kill_ret_id (id, _) = Domain.kill_copies_with_var (Var.of_id id) astate in let kill_actuals_by_ref astate_acc = function - | (Exp.Lvar pvar, {Typ.desc=Tptr _}) -> Domain.kill_copies_with_var (Var.of_pvar pvar) astate_acc - | _ -> astate_acc in + | Exp.Lvar pvar, {Typ.desc= Tptr _} + -> Domain.kill_copies_with_var (Var.of_pvar pvar) astate_acc + | _ + -> astate_acc + in let astate' = Option.value_map ~f:kill_ret_id ~default:astate ret_id in - if Config.curr_language_is Config.Java - then astate' (* Java doesn't have pass-by-reference *) + if Config.curr_language_is Config.Java then astate' + (* Java doesn't have pass-by-reference *) else List.fold ~f:kill_actuals_by_ref ~init:astate' actuals - | Sil.Store _ | Prune _ | Nullify _ | Abstract _ | Remove_temps _ | Declare_locals _ -> - (* none of these can assign to program vars or logical vars *) + | Sil.Store _ | Prune _ | Nullify _ | Abstract _ | Remove_temps _ | Declare_locals _ + -> (* none of these can assign to program vars or logical vars *) astate end diff --git a/infer/src/checkers/dataflow.ml b/infer/src/checkers/dataflow.ml index b2896fc76..342d878e7 100644 --- a/infer/src/checkers/dataflow.ml +++ b/infer/src/checkers/dataflow.ml @@ -8,184 +8,177 @@ *) open! IStd - module L = Logging type throws = - | DontKnow (** May or may not throw an exception. *) - | Throws (** Definitely throws an exception. *) - | DoesNotThrow (** Does not throw an exception. *) + | DontKnow (** May or may not throw an exception. *) + | Throws (** Definitely throws an exception. *) + | DoesNotThrow (** Does not throw an exception. *) (** Module type used to define the state component for a dataflow algorithm. *) module type DFStateType = sig (** Type for state. *) type t - (** Equality between states. *) val equal : t -> t -> bool + (** Equality between states. *) - (** Join two states (the old one is the first parameter). *) val join : t -> t -> t + (** Join two states (the old one is the first parameter). *) + val do_node : Tenv.t -> Procdesc.Node.t -> t -> t list * t list (** Perform a state transition on a node. *) - val do_node : Tenv.t -> Procdesc.Node.t -> t -> (t list) * (t list) - (** Can proc throw an exception? *) val proc_throws : Typ.Procname.t -> throws + (** Can proc throw an exception? *) end (** Type for the dataflow API. *) module type DF = sig type t + type state - type transition = - | Dead_state - | Transition of state * state list * state list + + type transition = Dead_state | Transition of state * state list * state list val join : state list -> state -> state - val run : Tenv.t -> Procdesc.t -> state -> (Procdesc.Node.t -> transition) + + val run : Tenv.t -> Procdesc.t -> state -> Procdesc.Node.t -> transition end (** Determine if the node can throw an exception. *) -let node_throws pdesc node (proc_throws : Typ.Procname.t -> throws) : throws = +let node_throws pdesc node (proc_throws: Typ.Procname.t -> throws) : throws = let instr_throws instr = let is_return pvar = let ret_pvar = Procdesc.get_ret_var pdesc in - Pvar.equal pvar ret_pvar in + Pvar.equal pvar ret_pvar + in match instr with - | Sil.Store (Exp.Lvar pvar, _, Exp.Exn _, _) when is_return pvar -> - (* assignment to return variable is an artifact of a throw instruction *) + | Sil.Store (Exp.Lvar pvar, _, Exp.Exn _, _) when is_return pvar + -> (* assignment to return variable is an artifact of a throw instruction *) Throws - | Sil.Call (_, Exp.Const (Const.Cfun callee_pn), _, _, _) - when BuiltinDecl.is_declared callee_pn -> - if Typ.Procname.equal callee_pn BuiltinDecl.__cast - then DontKnow - else DoesNotThrow - | Sil.Call (_, Exp.Const (Const.Cfun callee_pn), _, _, _) -> - proc_throws callee_pn - | _ -> - DoesNotThrow in - + | Sil.Call (_, Exp.Const Const.Cfun callee_pn, _, _, _) when BuiltinDecl.is_declared callee_pn + -> if Typ.Procname.equal callee_pn BuiltinDecl.__cast then DontKnow else DoesNotThrow + | Sil.Call (_, Exp.Const Const.Cfun callee_pn, _, _, _) + -> proc_throws callee_pn + | _ + -> DoesNotThrow + in let res = ref DoesNotThrow in - let update_res throws = match !res, throws with - | DontKnow, DontKnow -> res := DontKnow - | Throws, _ - | _, Throws -> res := Throws - | DoesNotThrow, t - | t, DoesNotThrow -> res := t in + let update_res throws = + match (!res, throws) with + | DontKnow, DontKnow + -> res := DontKnow + | Throws, _ | _, Throws + -> res := Throws + | DoesNotThrow, t | t, DoesNotThrow + -> res := t + in let do_instr instr = update_res (instr_throws instr) in - - List.iter ~f:do_instr (Procdesc.Node.get_instrs node); + List.iter ~f:do_instr (Procdesc.Node.get_instrs node) ; !res (** Create an instance of the dataflow algorithm given a state parameter. *) -module MakeDF(St: DFStateType) : DF with type state = St.t = struct +module MakeDF (St : DFStateType) : DF with type state = St.t = struct module S = Procdesc.NodeSet module H = Procdesc.NodeHash type worklist = S.t + type statemap = St.t H.t - type statelistmap = (St.t list) H.t - type t = { - mutable worklist: worklist; - pre_states : statemap; - post_states : statelistmap; - exn_states : statelistmap; - proc_desc : Procdesc.t - } + + type statelistmap = St.t list H.t + + type t = + { mutable worklist: worklist + ; pre_states: statemap + ; post_states: statelistmap + ; exn_states: statelistmap + ; proc_desc: Procdesc.t } + type state = St.t - type transition = - | Dead_state - | Transition of state * state list * state list - let join states initial_state = - List.fold - ~f:St.join - ~init:initial_state - states + type transition = Dead_state | Transition of state * state list * state list + + let join states initial_state = List.fold ~f:St.join ~init:initial_state states (** Propagate [new_state] to all the nodes immediately reachable. *) - let propagate t node states_succ states_exn (throws : throws) = + let propagate t node states_succ states_exn (throws: throws) = let propagate_to_dest new_state dest_node = let push_state s = - H.replace t.pre_states dest_node s; - t.worklist <- S.add dest_node t.worklist in + H.replace t.pre_states dest_node s ; + t.worklist <- S.add dest_node t.worklist + in try let dest_state = H.find t.pre_states dest_node in let dest_joined = St.join dest_state new_state in - if not (St.equal dest_state dest_joined) then - push_state dest_joined - with Not_found -> push_state new_state in - + if not (St.equal dest_state dest_joined) then push_state dest_joined + with Not_found -> push_state new_state + in let succ_nodes = Procdesc.Node.get_succs node in let exn_nodes = Procdesc.Node.get_exn node in if throws <> Throws then - List.iter - ~f:(fun s -> List.iter ~f:(propagate_to_dest s) succ_nodes) - states_succ; + List.iter ~f:(fun s -> List.iter ~f:(propagate_to_dest s) succ_nodes) states_succ ; if throws <> DoesNotThrow then - List.iter - ~f:(fun s -> List.iter ~f:(propagate_to_dest s) exn_nodes) - states_exn; - - H.replace t.post_states node states_succ; + List.iter ~f:(fun s -> List.iter ~f:(propagate_to_dest s) exn_nodes) states_exn ; + H.replace t.post_states node states_succ ; H.replace t.exn_states node states_exn (** Run the worklist-based dataflow algorithm. *) let run tenv proc_desc state = - let t = let start_node = Procdesc.get_start_node proc_desc in let init_set = S.singleton start_node in let init_statemap = let m = H.create 1 in - H.replace m start_node state; m in - { - worklist = init_set; - pre_states = init_statemap; - post_states = H.create 0; - exn_states = H.create 0; - proc_desc = proc_desc - } in - + H.replace m start_node state ; m + in + { worklist= init_set + ; pre_states= init_statemap + ; post_states= H.create 0 + ; exn_states= H.create 0 + ; proc_desc } + in let () = - while (not (S.is_empty t.worklist)) do + while not (S.is_empty t.worklist) do let node = S.min_elt t.worklist in - t.worklist <- S.remove node t.worklist; + t.worklist <- S.remove node t.worklist ; try let state = H.find t.pre_states node in let states_succ, states_exn = St.do_node tenv node state in propagate t node states_succ states_exn (node_throws proc_desc node St.proc_throws) with Not_found -> () - done in - + done + in let transitions node = - try - Transition - (H.find t.pre_states node, H.find t.post_states node, H.find t.exn_states node) - with Not_found -> Dead_state in - + try Transition (H.find t.pre_states node, H.find t.post_states node, H.find t.exn_states node) + with Not_found -> Dead_state + in transitions +end -end (* MakeDF *) +(* MakeDF *) (** Example dataflow callback: compute the the distance from a node to the start node. *) -let callback_test_dataflow { Callbacks.proc_desc; tenv; summary } = +let callback_test_dataflow {Callbacks.proc_desc; tenv; summary} = let verbose = false in - let module DFCount = MakeDF(struct - type t = int - let equal = Int.equal - let join n m = if Int.equal n 0 then m else n - let do_node _ n s = - if verbose then - L.(debug Analysis Verbose) "visiting node %a with state %d@." Procdesc.Node.pp n s; - [s + 1], [s + 1] - let proc_throws _ = DoesNotThrow - end) in + let module DFCount = MakeDF (struct + type t = int + + let equal = Int.equal + + let join n m = if Int.equal n 0 then m else n + + let do_node _ n s = + if verbose then + L.(debug Analysis Verbose) "visiting node %a with state %d@." Procdesc.Node.pp n s ; + ([s + 1], [s + 1]) + + let proc_throws _ = DoesNotThrow + end) in let transitions = DFCount.run tenv proc_desc 0 in let do_node node = - match transitions node with - | DFCount.Transition _ -> () - | DFCount.Dead_state -> () in - List.iter ~f:do_node (Procdesc.get_nodes proc_desc); + match transitions node with DFCount.Transition _ -> () | DFCount.Dead_state -> () + in + List.iter ~f:do_node (Procdesc.get_nodes proc_desc) ; summary diff --git a/infer/src/checkers/dataflow.mli b/infer/src/checkers/dataflow.mli index 941bd55d2..15b17a801 100644 --- a/infer/src/checkers/dataflow.mli +++ b/infer/src/checkers/dataflow.mli @@ -10,43 +10,44 @@ open! IStd type throws = - | DontKnow (** May or may not throw an exception. *) - | Throws (** Definitely throws an exception. *) - | DoesNotThrow (** Does not throw an exception. *) + | DontKnow (** May or may not throw an exception. *) + | Throws (** Definitely throws an exception. *) + | DoesNotThrow (** Does not throw an exception. *) (** Module type used to define the state component for a dataflow algorithm. *) module type DFStateType = sig (** Type for state. *) type t - (** Equality between states. *) val equal : t -> t -> bool + (** Equality between states. *) - (** Join two states (the old one is the first parameter). *) val join : t -> t -> t + (** Join two states (the old one is the first parameter). *) + val do_node : Tenv.t -> Procdesc.Node.t -> t -> t list * t list (** Perform a state transition on a node. *) - val do_node : Tenv.t -> Procdesc.Node.t -> t -> (t list) * (t list) - (** Can proc throw an exception? *) val proc_throws : Typ.Procname.t -> throws + (** Can proc throw an exception? *) end (** Type for the dataflow API. *) module type DF = sig type t + type state - type transition = - | Dead_state - | Transition of state * state list * state list + + type transition = Dead_state | Transition of state * state list * state list + val join : state list -> state -> state + val run : Tenv.t -> Procdesc.t -> state -> Procdesc.Node.t -> transition (** Run the dataflow analysis on a procedure starting from the given state. Returns a function to lookup the results of the analysis on every node *) - val run : Tenv.t -> Procdesc.t -> state -> (Procdesc.Node.t -> transition) end (** Functor to create an instance of a dataflow analysis. *) -module MakeDF(St: DFStateType) : DF with type state = St.t +module MakeDF (St : DFStateType) : DF with type state = St.t val callback_test_dataflow : Callbacks.proc_callback_t diff --git a/infer/src/checkers/fragmentRetainsViewChecker.ml b/infer/src/checkers/fragmentRetainsViewChecker.ml index 8f6f4aa53..61ecb5492 100644 --- a/infer/src/checkers/fragmentRetainsViewChecker.ml +++ b/infer/src/checkers/fragmentRetainsViewChecker.ml @@ -17,48 +17,48 @@ let report_error fragment_typ fld fld_typ summary pdesc = let pname = Procdesc.get_proc_name pdesc in let retained_view = "CHECKERS_FRAGMENT_RETAINS_VIEW" in let description = Localise.desc_fragment_retains_view fragment_typ fld fld_typ pname in - let exn = Exceptions.Checkers (retained_view, description) in + let exn = Exceptions.Checkers (retained_view, description) in let loc = Procdesc.get_loc pdesc in Reporting.log_error summary ~loc exn -let callback_fragment_retains_view_java - pname_java { Callbacks.proc_desc; summary; tenv } = +let callback_fragment_retains_view_java pname_java {Callbacks.proc_desc; summary; tenv} = (* TODO: complain if onDestroyView is not defined, yet the Fragment has View fields *) (* TODO: handle fields nullified in callees in the same file *) - let is_on_destroy_view = String.equal (Typ.Procname.java_get_method pname_java) "onDestroyView" in - let fld_typ_is_view typ = match typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct tname}, _) -> AndroidFramework.is_view tenv tname - | _ -> false in + let is_on_destroy_view = + String.equal (Typ.Procname.java_get_method pname_java) "onDestroyView" + in + let fld_typ_is_view typ = + match typ.Typ.desc with + | Typ.Tptr ({desc= Tstruct tname}, _) + -> AndroidFramework.is_view tenv tname + | _ + -> false + in (* is [fldname] a View type declared by [class_typename]? *) let is_declared_view_typ class_typename (fldname, fld_typ, _) = let fld_classname = Typ.Name.Java.from_string (Typ.Fieldname.java_get_class fldname) in - Typ.Name.equal fld_classname class_typename && fld_typ_is_view fld_typ in + Typ.Name.equal fld_classname class_typename && fld_typ_is_view fld_typ + in if is_on_destroy_view then - begin - let class_typename = - Typ.Name.Java.from_string (Typ.Procname.java_get_class_name pname_java) in - match Tenv.lookup tenv class_typename with - | Some { fields } when AndroidFramework.is_fragment tenv class_typename -> - let declared_view_fields = - List.filter ~f:(is_declared_view_typ class_typename) fields in - let fields_nullified = PatternMatch.get_fields_nullified proc_desc in - (* report if a field is declared by C, but not nulled out in C.onDestroyView *) - List.iter - ~f:(fun (fname, fld_typ, _) -> - if not (Typ.Fieldname.Set.mem fname fields_nullified) then - report_error - (Typ.mk (Tstruct class_typename)) fname fld_typ summary proc_desc) - declared_view_fields - | _ -> () - end + let class_typename = Typ.Name.Java.from_string (Typ.Procname.java_get_class_name pname_java) in + match Tenv.lookup tenv class_typename with + | Some {fields} when AndroidFramework.is_fragment tenv class_typename + -> let declared_view_fields = List.filter ~f:(is_declared_view_typ class_typename) fields in + let fields_nullified = PatternMatch.get_fields_nullified proc_desc in + (* report if a field is declared by C, but not nulled out in C.onDestroyView *) + List.iter + ~f:(fun (fname, fld_typ, _) -> + if not (Typ.Fieldname.Set.mem fname fields_nullified) then + report_error (Typ.mk (Tstruct class_typename)) fname fld_typ summary proc_desc) + declared_view_fields + | _ + -> () -let callback_fragment_retains_view ({ Callbacks.summary } as args) : Specs.summary = +let callback_fragment_retains_view ({Callbacks.summary} as args) : Specs.summary = let proc_name = Specs.get_proc_name summary in - begin - match proc_name with - | Typ.Procname.Java pname_java -> - callback_fragment_retains_view_java pname_java args - | _ -> - () - end; + ( match proc_name with + | Typ.Procname.Java pname_java + -> callback_fragment_retains_view_java pname_java args + | _ + -> () ) ; summary diff --git a/infer/src/checkers/idenv.ml b/infer/src/checkers/idenv.ml index 5fd60d746..014e1d0ad 100644 --- a/infer/src/checkers/idenv.ml +++ b/infer/src/checkers/idenv.ml @@ -12,49 +12,38 @@ open! IStd (** Environment for temporary identifiers used in instructions. Lazy implementation: only created when actually used. *) - -type t = (Exp.t Ident.IdentHash.t) Lazy.t +type t = Exp.t Ident.IdentHash.t Lazy.t let create_ proc_desc = let map = Ident.IdentHash.create 1 in - let do_instr _ = function - | Sil.Load (id, e, _, _) -> - Ident.IdentHash.add map id e - | _ -> () in - Procdesc.iter_instrs do_instr proc_desc; - map + let do_instr _ = function Sil.Load (id, e, _, _) -> Ident.IdentHash.add map id e | _ -> () in + Procdesc.iter_instrs do_instr proc_desc ; map (* lazy implementation, only create when used *) let create proc_desc = - let map = lazy (create_ proc_desc) in + let map = (lazy (create_ proc_desc)) in map let lookup map_ id = let map = Lazy.force map_ in - try - Some (Ident.IdentHash.find map id) + try Some (Ident.IdentHash.find map id) with Not_found -> None -let expand_expr idenv e = match e with - | Exp.Var id -> - (match lookup idenv id with - | Some e' -> e' - | None -> e) - | _ -> e +let expand_expr idenv e = + match e with Exp.Var id -> ( match lookup idenv id with Some e' -> e' | None -> e ) | _ -> e let expand_expr_temps idenv node _exp = let exp = expand_expr idenv _exp in match exp with - | Exp.Lvar pvar when Pvar.is_frontend_tmp pvar -> - (match Errdesc.find_program_variable_assignment node pvar with - | None -> exp - | Some (_, id) -> - expand_expr idenv (Exp.Var id)) - | _ -> exp + | Exp.Lvar pvar when Pvar.is_frontend_tmp pvar -> ( + match Errdesc.find_program_variable_assignment node pvar with + | None + -> exp + | Some (_, id) + -> expand_expr idenv (Exp.Var id) ) + | _ + -> exp (** Return true if the expression is a temporary variable introduced by the front-end. *) let exp_is_temp idenv e = - match expand_expr idenv e with - | Exp.Lvar pvar -> - Pvar.is_frontend_tmp pvar - | _ -> false + match expand_expr idenv e with Exp.Lvar pvar -> Pvar.is_frontend_tmp pvar | _ -> false diff --git a/infer/src/checkers/idenv.mli b/infer/src/checkers/idenv.mli index 3a3351284..2925312c5 100644 --- a/infer/src/checkers/idenv.mli +++ b/infer/src/checkers/idenv.mli @@ -12,14 +12,15 @@ open! IStd (** Environment for temporary identifiers used in instructions. Lazy implementation: only created when actually used. *) - type t val create : Procdesc.t -> t + val lookup : t -> Ident.t -> Exp.t option + val expand_expr : t -> Exp.t -> Exp.t val exp_is_temp : t -> Exp.t -> bool -(** Stronger version of expand_expr which also expands a temporary variable. *) val expand_expr_temps : t -> Procdesc.Node.t -> Exp.t -> Exp.t +(** Stronger version of expand_expr which also expands a temporary variable. *) diff --git a/infer/src/checkers/immutableChecker.ml b/infer/src/checkers/immutableChecker.ml index a10a2c28a..42141ddf4 100644 --- a/infer/src/checkers/immutableChecker.ml +++ b/infer/src/checkers/immutableChecker.ml @@ -8,48 +8,42 @@ *) open! IStd - module L = Logging module F = Format (** Check an implicit cast when returning an immutable collection from a method whose type is mutable. *) let check_immutable_cast tenv curr_pname curr_pdesc typ_expected typ_found_opt loc : unit = match typ_found_opt with - | Some typ_found -> - begin - let casts = - [ - "java.util.List", "com.google.common.collect.ImmutableList"; - "java.util.Map", "com.google.common.collect.ImmutableMap"; - "java.util.Set", "com.google.common.collect.ImmutableSet" - ] in - let in_casts expected given = - List.exists ~f:(fun (x, y) -> - String.equal (Typ.Name.name expected) x - && String.equal (Typ.Name.name given) y - ) casts in - match PatternMatch.type_get_class_name typ_expected, - PatternMatch.type_get_class_name typ_found with - | Some name_expected, Some name_given -> - if in_casts name_expected name_given then - begin - let description = - Format.asprintf - "Method %s returns %a but the return type is %a. \ - Make sure that users of this method do not try to modify the collection." - (Typ.Procname.to_simplified_string curr_pname) - Typ.Name.pp name_given - Typ.Name.pp name_expected in - Checkers.ST.report_error tenv - curr_pname - curr_pdesc - Localise.checkers_immutable_cast - loc - description - end - | _ -> () - end - | None -> () + | Some typ_found + -> ( + let casts = + [ ("java.util.List", "com.google.common.collect.ImmutableList") + ; ("java.util.Map", "com.google.common.collect.ImmutableMap") + ; ("java.util.Set", "com.google.common.collect.ImmutableSet") ] + in + let in_casts expected given = + List.exists + ~f:(fun (x, y) -> + String.equal (Typ.Name.name expected) x && String.equal (Typ.Name.name given) y) + casts + in + match + (PatternMatch.type_get_class_name typ_expected, PatternMatch.type_get_class_name typ_found) + with + | Some name_expected, Some name_given + -> if in_casts name_expected name_given then + let description = + Format.asprintf + "Method %s returns %a but the return type is %a. Make sure that users of this method do not try to modify the collection." + (Typ.Procname.to_simplified_string curr_pname) Typ.Name.pp name_given Typ.Name.pp + name_expected + in + Checkers.ST.report_error tenv curr_pname curr_pdesc Localise.checkers_immutable_cast + loc description + | _ + -> () ) + | None + -> () let callback_check_immutable_cast ({Callbacks.tenv} as args) = Eradicate.callback_check_return_type (check_immutable_cast tenv) args diff --git a/infer/src/checkers/liveness.ml b/infer/src/checkers/liveness.ml index c287aca80..905b1842b 100644 --- a/infer/src/checkers/liveness.ml +++ b/infer/src/checkers/liveness.ml @@ -8,57 +8,53 @@ *) open! IStd - module F = Format module L = Logging (** backward analysis for computing set of maybe-live variables at each program point *) -module Domain = AbstractDomain.FiniteSet(Var) +module Domain = AbstractDomain.FiniteSet (Var) (* compilers 101-style backward transfer functions for liveness analysis. gen a variable when it is read, kill the variable when it is assigned *) module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type extras = ProcData.no_extras (* add all of the vars read in [exp] to the live set *) let exp_add_live exp astate = - let (ids, pvars) = Exp.get_vars exp in + let ids, pvars = Exp.get_vars exp in let astate' = - List.fold - ~f:(fun astate_acc id -> Domain.add (Var.of_id id) astate_acc) - ~init:astate - ids in + List.fold ~f:(fun astate_acc id -> Domain.add (Var.of_id id) astate_acc) ~init:astate ids + in List.fold ~f:(fun astate_acc pvar -> Domain.add (Var.of_pvar pvar) astate_acc) - ~init:astate' - pvars + ~init:astate' pvars let exec_instr astate _ _ = function - | Sil.Load (lhs_id, rhs_exp, _, _) -> - Domain.remove (Var.of_id lhs_id) astate - |> exp_add_live rhs_exp - | Sil.Store (Lvar lhs_pvar, _, rhs_exp, _) -> - let astate' = - if Pvar.is_global lhs_pvar - then astate (* never kill globals *) - else Domain.remove (Var.of_pvar lhs_pvar) astate in + | Sil.Load (lhs_id, rhs_exp, _, _) + -> Domain.remove (Var.of_id lhs_id) astate |> exp_add_live rhs_exp + | Sil.Store (Lvar lhs_pvar, _, rhs_exp, _) + -> let astate' = + if Pvar.is_global lhs_pvar then astate (* never kill globals *) + else Domain.remove (Var.of_pvar lhs_pvar) astate + in exp_add_live rhs_exp astate' - | Sil.Store (lhs_exp, _, rhs_exp, _) -> - exp_add_live lhs_exp astate - |> exp_add_live rhs_exp - | Sil.Prune (exp, _, _, _) -> - exp_add_live exp astate - | Sil.Call (ret_id, call_exp, params, _, _) -> - Option.value_map ~f:(fun (ret_id, _) -> Domain.remove (Var.of_id ret_id) astate) + | Sil.Store (lhs_exp, _, rhs_exp, _) + -> exp_add_live lhs_exp astate |> exp_add_live rhs_exp + | Sil.Prune (exp, _, _, _) + -> exp_add_live exp astate + | Sil.Call (ret_id, call_exp, params, _, _) + -> Option.value_map + ~f:(fun (ret_id, _) -> Domain.remove (Var.of_id ret_id) astate) ~default:astate ret_id |> exp_add_live call_exp - |> (fun x -> List.fold_right ~f:exp_add_live (List.map ~f:fst params) ~init:x) - | Sil.Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ -> - astate + |> fun x -> List.fold_right ~f:exp_add_live (List.map ~f:fst params) ~init:x + | Sil.Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ + -> astate end module Analyzer = - AbstractInterpreter.Make (ProcCfg.Backward(ProcCfg.Exceptional)) (TransferFunctions) + AbstractInterpreter.Make (ProcCfg.Backward (ProcCfg.Exceptional)) (TransferFunctions) diff --git a/infer/src/checkers/printfArgs.ml b/infer/src/checkers/printfArgs.ml index 0acf1c2b6..0efe49561 100644 --- a/infer/src/checkers/printfArgs.ml +++ b/infer/src/checkers/printfArgs.ml @@ -8,197 +8,184 @@ *) open! IStd - module L = Logging module F = Format -type printf_signature = { - unique_id: string; - format_pos: int; - fixed_pos: int list; - vararg_pos: int option -} +type printf_signature = + {unique_id: string; format_pos: int; fixed_pos: int list; vararg_pos: int option} let printf_like_functions = ref - [ - { unique_id = "java.io.PrintStream.printf(java.lang.String,java.lang.Object[]):java.io.PrintStream"; - format_pos = 1; - fixed_pos = []; - vararg_pos = Some 2 }; - { unique_id = "java.io.PrintStream.printf(java.lang.Locale,java.lang.String,java.lang.Object[]):java.io.PrintStream"; - format_pos = 2; - fixed_pos = []; - vararg_pos = Some 3 }; - { unique_id = "java.lang.String(java.lang.String,java.lang.Object[]):java.lang.String"; - format_pos = 1; - fixed_pos = []; - vararg_pos = Some 2 }; - { unique_id = "java.lang.String(java.lang.Locale,java.lang.String,java.lang.Object[]):java.lang.String"; - format_pos = 2; - fixed_pos = []; - vararg_pos = Some 3 }; - ] - -let printf_like_function - (proc_name: Typ.Procname.t): printf_signature option = + [ { unique_id= + "java.io.PrintStream.printf(java.lang.String,java.lang.Object[]):java.io.PrintStream" + ; format_pos= 1 + ; fixed_pos= [] + ; vararg_pos= Some 2 } + ; { unique_id= + "java.io.PrintStream.printf(java.lang.Locale,java.lang.String,java.lang.Object[]):java.io.PrintStream" + ; format_pos= 2 + ; fixed_pos= [] + ; vararg_pos= Some 3 } + ; { unique_id= "java.lang.String(java.lang.String,java.lang.Object[]):java.lang.String" + ; format_pos= 1 + ; fixed_pos= [] + ; vararg_pos= Some 2 } + ; { unique_id= + "java.lang.String(java.lang.Locale,java.lang.String,java.lang.Object[]):java.lang.String" + ; format_pos= 2 + ; fixed_pos= [] + ; vararg_pos= Some 3 } ] + +let printf_like_function (proc_name: Typ.Procname.t) : printf_signature option = List.find ~f:(fun printf -> String.equal printf.unique_id (Typ.Procname.to_unique_id proc_name)) !printf_like_functions -let default_format_type_name - (format_type: string): string = +let default_format_type_name (format_type: string) : string = match format_type with - | "d" | "i" | "u" | "x" | "X" | "o" -> "java.lang.Integer" - | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" -> "java.lang.Double" - | "c" -> "java.lang.Character" - | "b" -> "java.lang.Boolean" - | "s" -> "java.lang.String" - | "h" | "H" -> "java.lang.Object" - | _ -> "unknown" - -let format_type_matches_given_type - (format_type: string) - (given_type: string): bool = + | "d" | "i" | "u" | "x" | "X" | "o" + -> "java.lang.Integer" + | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" + -> "java.lang.Double" + | "c" + -> "java.lang.Character" + | "b" + -> "java.lang.Boolean" + | "s" + -> "java.lang.String" + | "h" | "H" + -> "java.lang.Object" + | _ + -> "unknown" + +let format_type_matches_given_type (format_type: string) (given_type: string) : bool = match format_type with - | "d" | "i" | "u" | "x" | "X" | "o" -> - List.mem - ~equal:String.equal - ["java.lang.Integer"; "java.lang.Long"; "java.lang.Short"; "java.lang.Byte"] - given_type - | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" -> - List.mem - ~equal:String.equal - ["java.lang.Double"; "java.lang.Float"] - given_type - | "c" -> String.equal given_type "java.lang.Character" - | "b" | "h" | "H" | "s" -> true (* accepts pretty much anything, even null *) - | _ -> false + | "d" | "i" | "u" | "x" | "X" | "o" + -> List.mem ~equal:String.equal + ["java.lang.Integer"; "java.lang.Long"; "java.lang.Short"; "java.lang.Byte"] given_type + | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" + -> List.mem ~equal:String.equal ["java.lang.Double"; "java.lang.Float"] given_type + | "c" + -> String.equal given_type "java.lang.Character" + | "b" | "h" | "H" | "s" + -> true (* accepts pretty much anything, even null *) + | _ + -> false (* The format string and the nvar for the fixed arguments and the nvar of the varargs array *) -let format_arguments - (printf: printf_signature) - (args: (Exp.t * Typ.t) list): (string option * (Exp.t list) * (Exp.t option)) = - - let format_string = match List.nth_exn args printf.format_pos with - | Exp.Const (Const.Cstr fmt), _ -> Some fmt - | _ -> None in - - let fixed_nvars = List.map - ~f:(fun i -> fst (List.nth_exn args i)) - printf.fixed_pos in - let varargs_nvar = match printf.vararg_pos with - | Some pos -> Some (fst (List.nth_exn args pos)) - | None -> None in - - format_string, fixed_nvars, varargs_nvar +let format_arguments (printf: printf_signature) (args: (Exp.t * Typ.t) list) + : string option * Exp.t list * Exp.t option = + let format_string = + match List.nth_exn args printf.format_pos with + | Exp.Const Const.Cstr fmt, _ + -> Some fmt + | _ + -> None + in + let fixed_nvars = List.map ~f:(fun i -> fst (List.nth_exn args i)) printf.fixed_pos in + let varargs_nvar = + match printf.vararg_pos with Some pos -> Some (fst (List.nth_exn args pos)) | None -> None + in + (format_string, fixed_nvars, varargs_nvar) (* Extract type names from format string *) -let rec format_string_type_names - (fmt_string: string) - (start: int): string list = +let rec format_string_type_names (fmt_string: string) (start: int) : string list = try - let fmt_re = Str.regexp "%[0-9]*\\.?[0-9]*[A-mo-z]" in (* matches '%2.1d' etc. *) + let fmt_re = Str.regexp "%[0-9]*\\.?[0-9]*[A-mo-z]" in + (* matches '%2.1d' etc. *) let _ = Str.search_forward fmt_re fmt_string start in let fmt_match = Str.matched_string fmt_string in - let fmt_type = String.sub fmt_match ~pos:((String.length fmt_match) - 1) ~len:1 in - fmt_type:: format_string_type_names fmt_string (Str.match_end ()) + let fmt_type = String.sub fmt_match ~pos:(String.length fmt_match - 1) ~len:1 in + fmt_type :: format_string_type_names fmt_string (Str.match_end ()) with Not_found -> [] -let check_printf_args_ok - tenv - (node: Procdesc.Node.t) - (instr: Sil.instr) - (proc_name: Typ.Procname.t) - (proc_desc: Procdesc.t) - summary - : unit = - +let check_printf_args_ok tenv (node: Procdesc.Node.t) (instr: Sil.instr) + (proc_name: Typ.Procname.t) (proc_desc: Procdesc.t) summary : unit = (* Check if format string lines up with arguments *) let rec check_type_names instr_loc n_arg instr_proc_name fmt_type_names arg_type_names = let instr_name = Typ.Procname.to_simplified_string instr_proc_name in let instr_line = Location.to_string instr_loc in match (fmt_type_names, arg_type_names) with - | ft:: fs, gt:: gs -> - if not (format_type_matches_given_type ft gt) then - let description = Printf.sprintf + | ft :: fs, gt :: gs + -> if not (format_type_matches_given_type ft gt) then + let description = + Printf.sprintf "%s at line %s: parameter %d is expected to be of type %s but %s was given." - instr_name - instr_line - n_arg - (default_format_type_name ft) - gt in + instr_name instr_line n_arg (default_format_type_name ft) gt + in let exn = Exceptions.Checkers (description, Localise.verbatim_desc description) in Reporting.log_error summary ~loc:instr_loc exn - else - check_type_names instr_loc (n_arg + 1) instr_proc_name fs gs - | [], [] -> () - | _ -> - let description = Printf.sprintf - "format string arguments don't mach provided arguments in %s at line %s" - instr_name - instr_line in + else check_type_names instr_loc (n_arg + 1) instr_proc_name fs gs + | [], [] + -> () + | _ + -> let description = + Printf.sprintf "format string arguments don't mach provided arguments in %s at line %s" + instr_name instr_line + in let exn = Exceptions.Checkers (description, Localise.verbatim_desc description) in - Reporting.log_error summary ~loc:instr_loc exn in - + Reporting.log_error summary ~loc:instr_loc exn + in (* Get the array ivar for a given nvar *) let rec array_ivar instrs nvar = - match instrs, nvar with - | Sil.Load (id, Exp.Lvar iv, _, _):: _, Exp.Var nid - when Ident.equal id nid -> iv - | _:: is, _ -> array_ivar is nvar - | _ -> raise Not_found in - + match (instrs, nvar) with + | (Sil.Load (id, Exp.Lvar iv, _, _)) :: _, Exp.Var nid when Ident.equal id nid + -> iv + | _ :: is, _ + -> array_ivar is nvar + | _ + -> raise Not_found + in let rec fixed_nvar_type_name instrs nvar = match nvar with | Exp.Var nid -> ( - match instrs with - | Sil.Load (id, Exp.Lvar _, t, _):: _ - when Ident.equal id nid -> PatternMatch.get_type_name t - | _:: is -> fixed_nvar_type_name is nvar - | _ -> raise Not_found) - | Exp.Const c -> PatternMatch.java_get_const_type_name c - | _ -> raise (Failure "Could not resolve fixed type name") in - + match instrs with + | (Sil.Load (id, Exp.Lvar _, t, _)) :: _ when Ident.equal id nid + -> PatternMatch.get_type_name t + | _ :: is + -> fixed_nvar_type_name is nvar + | _ + -> raise Not_found ) + | Exp.Const c + -> PatternMatch.java_get_const_type_name c + | _ + -> raise (Failure "Could not resolve fixed type name") + in match instr with - | Sil.Call (_, Exp.Const (Const.Cfun pn), args, cl, _) -> ( - match printf_like_function pn with - | Some printf -> ( - try - let fmt, fixed_nvars, array_nvar = format_arguments printf args in - let instrs = Procdesc.Node.get_instrs node in - let fixed_nvar_type_names = List.map ~f:(fixed_nvar_type_name instrs) fixed_nvars in - let vararg_ivar_type_names = match array_nvar with - | Some nvar -> ( - let ivar = array_ivar instrs nvar in - PatternMatch.get_vararg_type_names tenv node ivar) - | None -> [] in - match fmt with - | Some fmt -> - check_type_names - cl - (printf.format_pos + 1) - pn - (format_string_type_names fmt 0) - (fixed_nvar_type_names@ vararg_ivar_type_names) - | None -> - Checkers.ST.report_error tenv - proc_name - proc_desc - Localise.checkers_printf_args - cl - "Format string must be string literal" - with e -> - L.internal_error - "%s Exception when analyzing %s: %s@." - (Localise.to_issue_id Localise.checkers_printf_args) - (Typ.Procname.to_string proc_name) - (Exn.to_string e)) - | None -> ()) - | _ -> () - -let callback_printf_args { Callbacks.tenv; proc_desc; summary } : Specs.summary = + | Sil.Call (_, Exp.Const Const.Cfun pn, args, cl, _) -> ( + match printf_like_function pn with + | Some printf -> ( + try + let fmt, fixed_nvars, array_nvar = format_arguments printf args in + let instrs = Procdesc.Node.get_instrs node in + let fixed_nvar_type_names = List.map ~f:(fixed_nvar_type_name instrs) fixed_nvars in + let vararg_ivar_type_names = + match array_nvar with + | Some nvar + -> let ivar = array_ivar instrs nvar in + PatternMatch.get_vararg_type_names tenv node ivar + | None + -> [] + in + match fmt with + | Some fmt + -> check_type_names cl (printf.format_pos + 1) pn (format_string_type_names fmt 0) + (fixed_nvar_type_names @ vararg_ivar_type_names) + | None + -> Checkers.ST.report_error tenv proc_name proc_desc Localise.checkers_printf_args cl + "Format string must be string literal" + with e -> + L.internal_error "%s Exception when analyzing %s: %s@." + (Localise.to_issue_id Localise.checkers_printf_args) (Typ.Procname.to_string proc_name) + (Exn.to_string e) ) + | None + -> () ) + | _ + -> () + +let callback_printf_args {Callbacks.tenv; proc_desc; summary} : Specs.summary = let proc_name = Procdesc.get_proc_name proc_desc in Procdesc.iter_instrs - (fun n i -> check_printf_args_ok tenv n i proc_name proc_desc summary) proc_desc; + (fun n i -> check_printf_args_ok tenv n i proc_name proc_desc summary) + proc_desc ; summary diff --git a/infer/src/checkers/printfArgs.mli b/infer/src/checkers/printfArgs.mli index 6ae0b9039..95867f96e 100644 --- a/infer/src/checkers/printfArgs.mli +++ b/infer/src/checkers/printfArgs.mli @@ -9,4 +9,4 @@ open! IStd -val callback_printf_args: Callbacks.proc_callback_t +val callback_printf_args : Callbacks.proc_callback_t diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml index 8962c302c..481144bcc 100644 --- a/infer/src/checkers/registerCheckers.ml +++ b/infer/src/checkers/registerCheckers.ml @@ -15,59 +15,67 @@ module L = Logging module F = Format (* make sure SimpleChecker.ml is not dead code *) -let () = if false then (let module SC = SimpleChecker.Make in ()) +let () = if false then let module SC = SimpleChecker.Make in () -type callback = - | Procedure of Callbacks.proc_callback_t - | Cluster of Callbacks.cluster_callback_t +type callback = Procedure of Callbacks.proc_callback_t | Cluster of Callbacks.cluster_callback_t -let checkers = [ - "annotation reachability", Config.annotation_reachability, - [Procedure AnnotationReachability.checker, Config.Java]; - "biabduction", Config.biabduction, - [Procedure Interproc.analyze_procedure, Config.Clang; - Procedure Interproc.analyze_procedure, Config.Java]; - "buffer overrun", Config.bufferoverrun, - [Procedure BufferOverrunChecker.checker, Config.Clang; - Procedure BufferOverrunChecker.checker, Config.Java]; - "crashcontext", Config.crashcontext, [Procedure BoundedCallTree.checker, Config.Java]; - "eradicate", Config.eradicate, [Procedure Eradicate.callback_eradicate, Config.Java]; - "fragment retains view", Config.fragment_retains_view, - [Procedure FragmentRetainsViewChecker.callback_fragment_retains_view, Config.Java]; - "immutable cast", Config.immutable_cast, - [Procedure ImmutableChecker.callback_check_immutable_cast, Config.Java]; - "printf args", Config.printf_args, [Procedure PrintfArgs.callback_printf_args, Config.Java]; - "nullable suggestion", Config.suggest_nullable, - [Procedure NullabilitySuggest.checker, Config.Java]; - "quandary", Config.quandary, - [Procedure JavaTaintAnalysis.checker, Config.Java; - Procedure ClangTaintAnalysis.checker, Config.Clang]; - "repeated calls", Config.repeated_calls, - [Procedure RepeatedCallsChecker.callback_check_repeated_calls, Config.Java]; - "resource leak", Config.resource_leak, - [Procedure ResourceLeaks.checker, Config.Java]; - "SIOF", Config.siof, [Procedure Siof.checker, Config.Clang]; - "thread safety", Config.threadsafety, - [Procedure ThreadSafety.analyze_procedure, Config.Clang; - Procedure ThreadSafety.analyze_procedure, Config.Java; - Cluster ThreadSafety.file_analysis, Config.Clang; - Cluster ThreadSafety.file_analysis, Config.Java] -] +let checkers = + [ ( "annotation reachability" + , Config.annotation_reachability + , [(Procedure AnnotationReachability.checker, Config.Java)] ) + ; ( "biabduction" + , Config.biabduction + , [ (Procedure Interproc.analyze_procedure, Config.Clang) + ; (Procedure Interproc.analyze_procedure, Config.Java) ] ) + ; ( "buffer overrun" + , Config.bufferoverrun + , [ (Procedure BufferOverrunChecker.checker, Config.Clang) + ; (Procedure BufferOverrunChecker.checker, Config.Java) ] ) + ; ("crashcontext", Config.crashcontext, [(Procedure BoundedCallTree.checker, Config.Java)]) + ; ("eradicate", Config.eradicate, [(Procedure Eradicate.callback_eradicate, Config.Java)]) + ; ( "fragment retains view" + , Config.fragment_retains_view + , [(Procedure FragmentRetainsViewChecker.callback_fragment_retains_view, Config.Java)] ) + ; ( "immutable cast" + , Config.immutable_cast + , [(Procedure ImmutableChecker.callback_check_immutable_cast, Config.Java)] ) + ; ("printf args", Config.printf_args, [(Procedure PrintfArgs.callback_printf_args, Config.Java)]) + ; ( "nullable suggestion" + , Config.suggest_nullable + , [(Procedure NullabilitySuggest.checker, Config.Java)] ) + ; ( "quandary" + , Config.quandary + , [ (Procedure JavaTaintAnalysis.checker, Config.Java) + ; (Procedure ClangTaintAnalysis.checker, Config.Clang) ] ) + ; ( "repeated calls" + , Config.repeated_calls + , [(Procedure RepeatedCallsChecker.callback_check_repeated_calls, Config.Java)] ) + ; ("resource leak", Config.resource_leak, [(Procedure ResourceLeaks.checker, Config.Java)]) + ; ("SIOF", Config.siof, [(Procedure Siof.checker, Config.Clang)]) + ; ( "thread safety" + , Config.threadsafety + , [ (Procedure ThreadSafety.analyze_procedure, Config.Clang) + ; (Procedure ThreadSafety.analyze_procedure, Config.Java) + ; (Cluster ThreadSafety.file_analysis, Config.Clang) + ; (Cluster ThreadSafety.file_analysis, Config.Java) ] ) ] let register () = let register_one (_, active, callbacks) = - let register_callback (callback, language) = match callback with - | Procedure procedure_cb -> - Callbacks.register_procedure_callback (Some language) procedure_cb - | Cluster cluster_cb -> - Callbacks.register_cluster_callback (Some language) cluster_cb in - if active then List.iter ~f:register_callback callbacks in + let register_callback (callback, language) = + match callback with + | Procedure procedure_cb + -> Callbacks.register_procedure_callback (Some language) procedure_cb + | Cluster cluster_cb + -> Callbacks.register_cluster_callback (Some language) cluster_cb + in + if active then List.iter ~f:register_callback callbacks + in List.iter ~f:register_one checkers let pp_active_checkers fmt () = let has_active = ref false in - List.iter checkers ~f:(fun (name, active, _) -> if active then ( - Format.fprintf fmt "%s%s" (if !has_active then ", " else "") name; - has_active := true - )); + List.iter checkers ~f:(fun (name, active, _) -> + if active then ( + Format.fprintf fmt "%s%s" (if !has_active then ", " else "") name ; + has_active := true ) ) ; if not !has_active then Format.fprintf fmt "none" diff --git a/infer/src/checkers/repeatedCallsChecker.ml b/infer/src/checkers/repeatedCallsChecker.ml index 5567bb971..565bc1d22 100644 --- a/infer/src/checkers/repeatedCallsChecker.ml +++ b/infer/src/checkers/repeatedCallsChecker.ml @@ -8,53 +8,43 @@ *) open! IStd - module L = Logging module F = Format - (** Extension for the repeated calls check. *) -module RepeatedCallsExtension : Eradicate.ExtensionT = -struct - module InstrSet = - Caml.Set.Make(struct - type t = Sil.instr - let compare i1 i2 = match i1, i2 with - | Sil.Call (_, e1, etl1, _, cf1), Sil.Call (_, e2, etl2, _, cf2) -> - (* ignore return ids and call flags *) - [%compare: Exp.t * (Exp.t * Typ.t) list * CallFlags.t] - (e1, etl1, cf1) (e2, etl2, cf2) - | _ -> Sil.compare_instr i1 i2 - end) +module RepeatedCallsExtension : Eradicate.ExtensionT = struct + module InstrSet = Caml.Set.Make (struct + type t = Sil.instr + + let compare i1 i2 = + match (i1, i2) with + | Sil.Call (_, e1, etl1, _, cf1), Sil.Call (_, e2, etl2, _, cf2) + -> (* ignore return ids and call flags *) + [%compare : Exp.t * (Exp.t * Typ.t) list * CallFlags.t] (e1, etl1, cf1) (e2, etl2, cf2) + | _ + -> Sil.compare_instr i1 i2 + end) type extension = InstrSet.t let empty = InstrSet.empty - let join calls1 calls2 = - InstrSet.inter calls1 calls2 + let join calls1 calls2 = InstrSet.inter calls1 calls2 let pp fmt calls = let pp_call instr = F.fprintf fmt " %a@\n" (Sil.pp_instr Pp.text) instr in - if not (InstrSet.is_empty calls) then - begin - F.fprintf fmt "Calls:@\n"; - InstrSet.iter pp_call calls; - end + if not (InstrSet.is_empty calls) then ( F.fprintf fmt "Calls:@\n" ; InstrSet.iter pp_call calls ) let get_old_call instr calls = - try - Some (InstrSet.find instr calls) + try Some (InstrSet.find instr calls) with Not_found -> None - let add_call instr calls = - if InstrSet.mem instr calls then calls - else InstrSet.add instr calls + let add_call instr calls = if InstrSet.mem instr calls then calls else InstrSet.add instr calls type paths = - | AllPaths (** Check on all paths *) - | SomePath (** Check if some path exists *) - [@@deriving compare] + | AllPaths (** Check on all paths *) + | SomePath (** Check if some path exists *) + [@@deriving compare] let equal_paths = [%compare.equal : paths] @@ -62,112 +52,115 @@ struct If [paths] is AllPaths, check if an allocation happens on all paths. If [paths] is SomePath, check if a path with an allocation exists. *) let proc_performs_allocation tenv pdesc paths : Location.t option = - let node_allocates node : Location.t option = let found = ref None in let proc_is_new pn = - Typ.Procname.equal pn BuiltinDecl.__new || - Typ.Procname.equal pn BuiltinDecl.__new_array in + Typ.Procname.equal pn BuiltinDecl.__new || Typ.Procname.equal pn BuiltinDecl.__new_array + in let do_instr instr = match instr with - | Sil.Call (_, Exp.Const (Const.Cfun pn), _, loc, _) when proc_is_new pn -> - found := Some loc - | _ -> () in - List.iter ~f:do_instr (Procdesc.Node.get_instrs node); - !found in - - let module DFAllocCheck = Dataflow.MakeDF(struct - type t = Location.t option [@@deriving compare] - let equal = [%compare.equal : t] - let join_ paths_ l1o l2o = (* join with left priority *) - match l1o, l2o with - | None, None -> - None - | Some loc, None - | None, Some loc -> - if equal_paths paths_ AllPaths then None else Some loc - | Some loc1, Some _ -> - Some loc1 (* left priority *) - let join = join_ paths - let do_node _ node lo1 = - let lo2 = node_allocates node in - let lo' = (* use left priority join to implement transfer function *) - join_ SomePath lo1 lo2 in - [lo'], [lo'] - let proc_throws _ = Dataflow.DontKnow - end) in - + | Sil.Call (_, Exp.Const Const.Cfun pn, _, loc, _) when proc_is_new pn + -> found := Some loc + | _ + -> () + in + List.iter ~f:do_instr (Procdesc.Node.get_instrs node) ; + !found + in + let module DFAllocCheck = Dataflow.MakeDF (struct + type t = Location.t option [@@deriving compare] + + let equal = [%compare.equal : t] + + let join_ paths_ l1o l2o = + (* join with left priority *) + match (l1o, l2o) with + | None, None + -> None + | Some loc, None | None, Some loc + -> if equal_paths paths_ AllPaths then None else Some loc + | Some loc1, Some _ + -> Some loc1 + + (* left priority *) + let join = join_ paths + + let do_node _ node lo1 = + let lo2 = node_allocates node in + let lo' = + (* use left priority join to implement transfer function *) + join_ SomePath lo1 lo2 + in + ([lo'], [lo']) + + let proc_throws _ = Dataflow.DontKnow + end) in let transitions = DFAllocCheck.run tenv pdesc None in match transitions (Procdesc.get_exit_node pdesc) with - | DFAllocCheck.Transition (loc, _, _) -> loc - | DFAllocCheck.Dead_state -> None + | DFAllocCheck.Transition (loc, _, _) + -> loc + | DFAllocCheck.Dead_state + -> None (** Check repeated calls to the same procedure. *) let check_instr tenv get_proc_desc curr_pname curr_pdesc extension instr normalized_etl = - (* Arguments are not temporary variables. *) let arguments_not_temp args = - let filter_arg (e, _) = match e with - | Exp.Lvar pvar -> - (* same temporary variable does not imply same value *) + let filter_arg (e, _) = + match e with + | Exp.Lvar pvar + -> (* same temporary variable does not imply same value *) not (Pvar.is_frontend_tmp pvar) - | _ -> true in - List.for_all ~f:filter_arg args in - + | _ + -> true + in + List.for_all ~f:filter_arg args + in match instr with - | Sil.Call (Some _ as ret_id, Exp.Const (Const.Cfun callee_pname), _, loc, call_flags) - when arguments_not_temp normalized_etl -> - let instr_normalized_args = Sil.Call ( - ret_id, - Exp.Const (Const.Cfun callee_pname), - normalized_etl, - loc, - call_flags) in + | Sil.Call ((Some _ as ret_id), Exp.Const Const.Cfun callee_pname, _, loc, call_flags) + when arguments_not_temp normalized_etl + -> let instr_normalized_args = + Sil.Call (ret_id, Exp.Const (Const.Cfun callee_pname), normalized_etl, loc, call_flags) + in let report proc_desc = match get_old_call instr_normalized_args extension with - | Some (Sil.Call (_, _, _, loc_old, _)) -> - begin - match proc_performs_allocation tenv proc_desc AllPaths with - | Some alloc_loc -> - let description = - Format.asprintf "call to %s seen before on line %d (may allocate at %a:%d)" - (Typ.Procname.to_simplified_string callee_pname) - loc_old.Location.line - SourceFile.pp alloc_loc.Location.file - alloc_loc.Location.line in - Checkers.ST.report_error tenv - curr_pname curr_pdesc Localise.checkers_repeated_calls loc description - | None -> () - end - | _ -> () in - - let () = match get_proc_desc callee_pname with - | None -> () - | Some proc_desc -> - if Procdesc.is_defined proc_desc - then report proc_desc in + | Some Sil.Call (_, _, _, loc_old, _) -> ( + match proc_performs_allocation tenv proc_desc AllPaths with + | Some alloc_loc + -> let description = + Format.asprintf "call to %s seen before on line %d (may allocate at %a:%d)" + (Typ.Procname.to_simplified_string callee_pname) loc_old.Location.line + SourceFile.pp alloc_loc.Location.file alloc_loc.Location.line + in + Checkers.ST.report_error tenv curr_pname curr_pdesc + Localise.checkers_repeated_calls loc description + | None + -> () ) + | _ + -> () + in + let () = + match get_proc_desc callee_pname with + | None + -> () + | Some proc_desc + -> if Procdesc.is_defined proc_desc then report proc_desc + in add_call instr_normalized_args extension - | _ -> extension + | _ + -> extension - let ext = - { - TypeState.empty = empty; - check_instr = check_instr; - join = join; - pp = pp; - } + let ext = {TypeState.empty= empty; check_instr; join; pp} let update_payload _ payload = payload -end (* CheckRepeatedCalls *) +end + +(* CheckRepeatedCalls *) -module MainRepeatedCalls = - Eradicate.Build(RepeatedCallsExtension) +module MainRepeatedCalls = Eradicate.Build (RepeatedCallsExtension) let callback_check_repeated_calls callback_args = let checks = - { - TypeCheck.eradicate = false; - check_extension = Config.repeated_calls; - check_ret_type = []; - } in + {TypeCheck.eradicate= false; check_extension= Config.repeated_calls; check_ret_type= []} + in MainRepeatedCalls.callback checks callback_args diff --git a/infer/src/clang/ALVar.ml b/infer/src/clang/ALVar.ml index 209dcdd3c..b5b514e4b 100644 --- a/infer/src/clang/ALVar.ml +++ b/infer/src/clang/ALVar.ml @@ -7,115 +7,85 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module L = Logging -type keyword = - | Doc_url - | Message - | Mode - | Name - | Report_when - | Severity - | Suggestion +type keyword = Doc_url | Message | Mode | Name | Report_when | Severity | Suggestion -type formula_id = Formula_id of string[@@deriving compare] +type formula_id = Formula_id of string [@@deriving compare] type alexp = | Const of string | Regexp of string | Var of string | FId of formula_id -[@@deriving compare] + [@@deriving compare] -type t = alexp[@@deriving compare] +type t = alexp [@@deriving compare] let equal = [%compare.equal : t] let formula_id_to_string fid = - match fid with - | Formula_id s -> s + match fid + with Formula_id s -> s let alexp_to_string e = - match e with - | Const s - | Regexp s - | Var s - | FId (Formula_id s) -> s + match e + with Const s | Regexp s | Var s | FId Formula_id s -> s let keyword_to_string k = match k with - | Doc_url -> "doc_url" - | Message -> "message" - | Mode -> "mode" - | Name -> "name_hum_readable" - | Report_when -> "report_when" - | Severity -> "severity" - | Suggestion -> "suggestion" - -let is_report_when_keyword k = - match k with - | Report_when -> true - | _ -> false + | Doc_url + -> "doc_url" + | Message + -> "message" + | Mode + -> "mode" + | Name + -> "name_hum_readable" + | Report_when + -> "report_when" + | Severity + -> "severity" + | Suggestion + -> "suggestion" -let is_message_keyword k = - match k with - | Message -> true - | _ -> false +let is_report_when_keyword k = match k with Report_when -> true | _ -> false -let is_suggestion_keyword k = - match k with - | Suggestion -> true - | _ -> false +let is_message_keyword k = match k with Message -> true | _ -> false -let is_severity_keyword k = - match k with - | Severity -> true - | _ -> false +let is_suggestion_keyword k = match k with Suggestion -> true | _ -> false -let is_mode_keyword k = - match k with - | Mode -> true - | _ -> false +let is_severity_keyword k = match k with Severity -> true | _ -> false -let is_doc_url_keyword k = - match k with - | Doc_url -> true - | _ -> false +let is_mode_keyword k = match k with Mode -> true | _ -> false -let is_name_keyword k = - match k with - | Name -> true - | _ -> false +let is_doc_url_keyword k = match k with Doc_url -> true | _ -> false + +let is_name_keyword k = match k with Name -> true | _ -> false (* true if and only if a substring of container matches the regular expression defined by contained *) let str_match_regex container re = let rexp = Str.regexp re in - try - Str.search_forward rexp container 0 >= 0 + try Str.search_forward rexp container 0 >= 0 with Not_found -> false let compare_str_with_alexp s ae = match ae with - | Const s' - | Var s' -> - String.equal s s' - | Regexp re -> - str_match_regex s re - | _ -> - L.(debug Linters Medium) "[WARNING]: ALVAR expression '%s' is not a constant/var or regexp@\n" - (alexp_to_string ae); + | Const s' | Var s' + -> String.equal s s' + | Regexp re + -> str_match_regex s re + | _ + -> L.(debug Linters Medium) + "[WARNING]: ALVAR expression '%s' is not a constant/var or regexp@\n" (alexp_to_string ae) ; false +module FormulaIdMap = Caml.Map.Make (struct + type t = formula_id [@@deriving compare] +end) -module FormulaIdMap = Caml.Map.Make ( - struct - type t = formula_id[@@deriving compare] - end) - -module VarMap = Caml.Map.Make ( - struct - type t = string[@@deriving compare] - end) +module VarMap = Caml.Map.Make (struct + type t = string [@@deriving compare] +end) diff --git a/infer/src/clang/ALVar.mli b/infer/src/clang/ALVar.mli index 94d4d1894..de1c82124 100644 --- a/infer/src/clang/ALVar.mli +++ b/infer/src/clang/ALVar.mli @@ -6,24 +6,14 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd -type keyword = - | Doc_url - | Message - | Mode - | Name - | Report_when - | Severity - | Suggestion +type keyword = Doc_url | Message | Mode | Name | Report_when | Severity | Suggestion type formula_id = Formula_id of string -type alexp = - | Const of string - | Regexp of string - | Var of string - | FId of formula_id +type alexp = Const of string | Regexp of string | Var of string | FId of formula_id type t = alexp diff --git a/infer/src/clang/CLintersContext.ml b/infer/src/clang/CLintersContext.ml index c0c2e59dc..d83da90db 100644 --- a/infer/src/clang/CLintersContext.ml +++ b/infer/src/clang/CLintersContext.ml @@ -9,33 +9,28 @@ open! IStd -type if_context = { - within_responds_to_selector_block : string list; - ios_version_guard : string list -} +type if_context = {within_responds_to_selector_block: string list; ios_version_guard: string list} -type context = { - translation_unit_context : CFrontend_config.translation_unit_context; - current_method : Clang_ast_t.decl option; - in_synchronized_block: bool; - (** True if the translation unit contains an ObjC class impl that's a subclass +type context = + { translation_unit_context: CFrontend_config.translation_unit_context + ; current_method: Clang_ast_t.decl option + ; in_synchronized_block: bool + (** True if the translation unit contains an ObjC class impl that's a subclass of CKComponent or CKComponentController. *) - is_ck_translation_unit: bool; - (** If inside an objc class impl, contains the objc class impl decl. *) - current_objc_impl : Clang_ast_t.decl option; - (** True if inside an objc static factory method (a class-level initializer, like +new) *) - in_objc_static_factory_method : bool; - et_evaluation_node : string option; - if_context : if_context option; -} + ; is_ck_translation_unit: bool + (** If inside an objc class impl, contains the objc class impl decl. *) + ; current_objc_impl: Clang_ast_t.decl option + (** True if inside an objc static factory method (a class-level initializer, like +new) *) + ; in_objc_static_factory_method: bool + ; et_evaluation_node: string option + ; if_context: if_context option } -let empty translation_unit_context = { - current_method = None; - translation_unit_context; - in_synchronized_block = false; - is_ck_translation_unit = false; - current_objc_impl = None; - in_objc_static_factory_method = false; - et_evaluation_node = None; - if_context = None; -} +let empty translation_unit_context = + { current_method= None + ; translation_unit_context + ; in_synchronized_block= false + ; is_ck_translation_unit= false + ; current_objc_impl= None + ; in_objc_static_factory_method= false + ; et_evaluation_node= None + ; if_context= None } diff --git a/infer/src/clang/CProcname.ml b/infer/src/clang/CProcname.ml index ba2a1b064..8f5f580a5 100644 --- a/infer/src/clang/CProcname.ml +++ b/infer/src/clang/CProcname.ml @@ -16,85 +16,115 @@ let rec get_mangled_method_name function_decl_info method_decl_info = work. *) let open Clang_ast_t in match method_decl_info.xmdi_overriden_methods with - | [] -> function_decl_info.fdi_mangled_name - | base1_dr :: _ -> - (let base1 = match CAst_utils.get_decl base1_dr.dr_decl_pointer with - | Some b -> b - | _ -> assert false in - match base1 with - | CXXMethodDecl (_, _, _, fdi, mdi) - | CXXConstructorDecl (_, _, _, fdi, mdi) - | CXXConversionDecl (_, _, _, fdi, mdi) - | CXXDestructorDecl (_, _, _, fdi, mdi) -> - get_mangled_method_name fdi mdi - | _ -> assert false) - -let get_template_info tenv (fdi : Clang_ast_t.function_decl_info) : Typ.template_spec_info = + | [] + -> function_decl_info.fdi_mangled_name + | base1_dr :: _ + -> let base1 = + match CAst_utils.get_decl base1_dr.dr_decl_pointer with Some b -> b | _ -> assert false + in + match base1 with + | CXXMethodDecl (_, _, _, fdi, mdi) + | CXXConstructorDecl (_, _, _, fdi, mdi) + | CXXConversionDecl (_, _, _, fdi, mdi) + | CXXDestructorDecl (_, _, _, fdi, mdi) + -> get_mangled_method_name fdi mdi + | _ + -> assert false + +let get_template_info tenv (fdi: Clang_ast_t.function_decl_info) : Typ.template_spec_info = match fdi.fdi_template_specialization with - | Some spec_info -> Typ.Template ( - List.map spec_info.tsi_specialization_args ~f:(function - | `Type qual_type -> Some (CType_decl.qual_type_to_sil_type tenv qual_type) - | _ -> None)) - | None -> Typ.NoTemplate + | Some spec_info + -> Typ.Template + (List.map spec_info.tsi_specialization_args ~f:(function + | `Type qual_type + -> Some (CType_decl.qual_type_to_sil_type tenv qual_type) + | _ + -> None )) + | None + -> Typ.NoTemplate let is_decl_info_generic_model {Clang_ast_t.di_attributes} = let f = function - | Clang_ast_t.AnnotateAttr {ai_parameters=[_; name; _]} - when String.equal name "__infer_generic_model" -> true - | _ -> false in + | Clang_ast_t.AnnotateAttr {ai_parameters= [_; name; _]} + when String.equal name "__infer_generic_model" + -> true + | _ + -> false + in List.exists ~f di_attributes let mk_c_function translation_unit_context ?tenv name function_decl_info_opt = let file = match function_decl_info_opt with - | Some (decl_info, function_decl_info) -> - (match function_decl_info.Clang_ast_t.fdi_storage_class with - | Some "static" -> - let file_opt = (fst decl_info.Clang_ast_t.di_source_range).Clang_ast_t.sl_file |> Option.map ~f:SourceFile.from_abs_path in - let file_to_hex src = SourceFile.to_string src |> Utils.string_crc_hex32 in - Option.value_map ~f:file_to_hex ~default:"" file_opt - | _ -> "") - | None -> "" in - let mangled_opt = match function_decl_info_opt with - | Some (_, function_decl_info) -> function_decl_info.Clang_ast_t.fdi_mangled_name - | _ -> None in - let mangled_name = match mangled_opt with - | Some m when CGeneral_utils.is_cpp_translation translation_unit_context -> m - | _ -> "" in - let template_info, is_generic_model = match function_decl_info_opt, tenv with - | Some (decl_info, function_decl_info), Some t -> - get_template_info t function_decl_info, is_decl_info_generic_model decl_info - | _ -> Typ.NoTemplate, false in + | Some (decl_info, function_decl_info) -> ( + match function_decl_info.Clang_ast_t.fdi_storage_class with + | Some "static" + -> let file_opt = + (fst decl_info.Clang_ast_t.di_source_range).Clang_ast_t.sl_file + |> Option.map ~f:SourceFile.from_abs_path + in + let file_to_hex src = SourceFile.to_string src |> Utils.string_crc_hex32 in + Option.value_map ~f:file_to_hex ~default:"" file_opt + | _ + -> "" ) + | None + -> "" + in + let mangled_opt = + match function_decl_info_opt with + | Some (_, function_decl_info) + -> function_decl_info.Clang_ast_t.fdi_mangled_name + | _ + -> None + in + let mangled_name = + match mangled_opt with + | Some m when CGeneral_utils.is_cpp_translation translation_unit_context + -> m + | _ + -> "" + in + let template_info, is_generic_model = + match (function_decl_info_opt, tenv) with + | Some (decl_info, function_decl_info), Some t + -> (get_template_info t function_decl_info, is_decl_info_generic_model decl_info) + | _ + -> (Typ.NoTemplate, false) + in let mangled = file ^ mangled_name in if String.is_empty mangled then Typ.Procname.from_string_c_fun (QualifiedCppName.to_qual_string name) - else - Typ.Procname.C (Typ.Procname.c name mangled template_info ~is_generic_model) + else Typ.Procname.C (Typ.Procname.c name mangled template_info ~is_generic_model) let mk_cpp_method ?tenv class_name method_name ?meth_decl mangled = let open Clang_ast_t in - let method_kind = match meth_decl with - | Some (Clang_ast_t.CXXConstructorDecl (_, _, _, _, {xmdi_is_constexpr})) -> - Typ.Procname.CPPConstructor (mangled, xmdi_is_constexpr) - | _ -> - Typ.Procname.CPPMethod mangled in - let template_info, is_generic_model = match meth_decl with - | Some (CXXMethodDecl (di, _, _, fdi, _)) - | Some (CXXConstructorDecl (di, _, _, fdi, _)) - | Some (CXXConversionDecl (di, _, _, fdi, _)) - | Some (CXXDestructorDecl (di, _, _, fdi, _)) -> ( - let templ_info = match tenv with - | Some t -> get_template_info t fdi - | None -> Typ.NoTemplate in - let is_gen_model = is_decl_info_generic_model di || - (* read whether parent class is annoatated as generic model *) - di.di_parent_pointer - |> Option.value_map ~f:CAst_utils.get_decl ~default:None - |> Option.map ~f:Clang_ast_proj.get_decl_tuple - |> Option.value_map ~f:is_decl_info_generic_model ~default:false in - templ_info, is_gen_model - ) - | _ -> Typ.NoTemplate, false in + let method_kind = + match meth_decl with + | Some Clang_ast_t.CXXConstructorDecl (_, _, _, _, {xmdi_is_constexpr}) + -> Typ.Procname.CPPConstructor (mangled, xmdi_is_constexpr) + | _ + -> Typ.Procname.CPPMethod mangled + in + let template_info, is_generic_model = + match meth_decl with + | Some CXXMethodDecl (di, _, _, fdi, _) + | Some CXXConstructorDecl (di, _, _, fdi, _) + | Some CXXConversionDecl (di, _, _, fdi, _) + | Some CXXDestructorDecl (di, _, _, fdi, _) + -> let templ_info = + match tenv with Some t -> get_template_info t fdi | None -> Typ.NoTemplate + in + let is_gen_model = + is_decl_info_generic_model di + || (* read whether parent class is annoatated as generic model *) + di.di_parent_pointer |> Option.value_map ~f:CAst_utils.get_decl ~default:None + |> Option.map ~f:Clang_ast_proj.get_decl_tuple + |> Option.value_map ~f:is_decl_info_generic_model ~default:false + in + (templ_info, is_gen_model) + | _ + -> (Typ.NoTemplate, false) + in Typ.Procname.ObjC_Cpp (Typ.Procname.objc_cpp class_name method_name method_kind template_info ~is_generic_model) @@ -104,35 +134,33 @@ let mk_objc_method class_typename method_name method_kind = ~is_generic_model:false) let block_procname_with_index defining_proc i = - Config.anonymous_block_prefix ^ - (Typ.Procname.to_string defining_proc) ^ - Config.anonymous_block_num_sep ^ - (string_of_int i) + Config.anonymous_block_prefix ^ Typ.Procname.to_string defining_proc + ^ Config.anonymous_block_num_sep ^ string_of_int i (* Global counter for anonymous block*) let block_counter = ref 0 let get_next_block_pvar defining_proc = - let name = block_procname_with_index defining_proc (!block_counter +1) in + let name = block_procname_with_index defining_proc (!block_counter + 1) in Pvar.mk_tmp name defining_proc -let reset_block_counter () = - block_counter := 0 +let reset_block_counter () = block_counter := 0 let get_fresh_block_index () = - block_counter := !block_counter +1; + block_counter := !block_counter + 1 ; !block_counter let mk_fresh_block_procname defining_proc = let name = block_procname_with_index defining_proc (get_fresh_block_index ()) in Typ.Procname.mangled_objc_block name - let get_class_typename ?tenv method_decl_info = let class_ptr = Option.value_exn method_decl_info.Clang_ast_t.di_parent_pointer in match CAst_utils.get_decl class_ptr with - | Some class_decl -> CType_decl.get_record_typename ?tenv class_decl - | None -> assert false + | Some class_decl + -> CType_decl.get_record_typename ?tenv class_decl + | None + -> assert false module NoAstDecl = struct let c_function_of_string translation_unit_context tenv name = @@ -149,26 +177,29 @@ end let from_decl translation_unit_context ?tenv meth_decl = let open Clang_ast_t in match meth_decl with - | FunctionDecl (decl_info, name_info, _, fdi) -> - let name = CAst_utils.get_qualified_name name_info in + | FunctionDecl (decl_info, name_info, _, fdi) + -> let name = CAst_utils.get_qualified_name name_info in let function_info = Some (decl_info, fdi) in mk_c_function translation_unit_context ?tenv name function_info | CXXMethodDecl (decl_info, name_info, _, fdi, mdi) | CXXConstructorDecl (decl_info, name_info, _, fdi, mdi) | CXXConversionDecl (decl_info, name_info, _, fdi, mdi) - | CXXDestructorDecl (decl_info, name_info, _, fdi, mdi) -> - let mangled = get_mangled_method_name fdi mdi in + | CXXDestructorDecl (decl_info, name_info, _, fdi, mdi) + -> let mangled = get_mangled_method_name fdi mdi in let method_name = CAst_utils.get_unqualified_name name_info in let class_typename = get_class_typename ?tenv decl_info in mk_cpp_method ?tenv class_typename method_name ~meth_decl mangled - | ObjCMethodDecl (decl_info, name_info, mdi) -> - let class_typename = get_class_typename ?tenv decl_info in + | ObjCMethodDecl (decl_info, name_info, mdi) + -> let class_typename = get_class_typename ?tenv decl_info in let method_name = name_info.Clang_ast_t.ni_name in let is_instance = mdi.Clang_ast_t.omdi_is_instance_method in let method_kind = Typ.Procname.objc_method_kind_of_bool is_instance in mk_objc_method class_typename method_name method_kind - | BlockDecl _ -> - let name = Config.anonymous_block_prefix ^ Config.anonymous_block_num_sep ^ - (string_of_int (get_fresh_block_index ())) in + | BlockDecl _ + -> let name = + Config.anonymous_block_prefix ^ Config.anonymous_block_num_sep + ^ string_of_int (get_fresh_block_index ()) + in Typ.Procname.mangled_objc_block name - | _ -> assert false + | _ + -> assert false diff --git a/infer/src/clang/CProcname.mli b/infer/src/clang/CProcname.mli index a22921bb2..10d55b00a 100644 --- a/infer/src/clang/CProcname.mli +++ b/infer/src/clang/CProcname.mli @@ -9,10 +9,10 @@ open! IStd -(** Given decl, return its procname. This function should be used for all procedures - present in original AST *) val from_decl : CFrontend_config.translation_unit_context -> ?tenv:Tenv.t -> Clang_ast_t.decl -> Typ.Procname.t +(** Given decl, return its procname. This function should be used for all procedures + present in original AST *) (** WARNING: functions from this module should not be used if full decl is available in AST *) module NoAstDecl : sig @@ -21,17 +21,16 @@ module NoAstDecl : sig val cpp_method_of_string : Tenv.t -> Typ.Name.t -> string -> Typ.Procname.t - val objc_method_of_string_kind : Typ.Name.t -> string -> Typ.Procname.objc_cpp_method_kind -> - Typ.Procname.t - + val objc_method_of_string_kind : + Typ.Name.t -> string -> Typ.Procname.objc_cpp_method_kind -> Typ.Procname.t end +val mk_fresh_block_procname : Typ.Procname.t -> Typ.Procname.t (** Makes a fresh name for a block defined inside the defining procedure. It updates the global block_counter *) -val mk_fresh_block_procname : Typ.Procname.t -> Typ.Procname.t +val get_next_block_pvar : Typ.Procname.t -> Pvar.t (** Returns the next fresh name for a block defined inside the defining procedure It does not update the global block_counter *) -val get_next_block_pvar : Typ.Procname.t -> Pvar.t val reset_block_counter : unit -> unit diff --git a/infer/src/clang/CType.ml b/infer/src/clang/CType.ml index 04ae1100f..605384e62 100644 --- a/infer/src/clang/CType.ml +++ b/infer/src/clang/CType.ml @@ -13,67 +13,74 @@ open! IStd module L = Logging -let add_pointer_to_typ typ = - Typ.mk (Tptr(typ, Typ.Pk_pointer)) +let add_pointer_to_typ typ = Typ.mk (Tptr (typ, Typ.Pk_pointer)) let remove_pointer_to_typ typ = - match typ.Typ.desc with - | Typ.Tptr(typ, Typ.Pk_pointer) -> typ - | _ -> typ + match typ.Typ.desc with Typ.Tptr (typ, Typ.Pk_pointer) -> typ | _ -> typ let objc_classname_of_type typ = match typ.Typ.desc with - | Typ.Tstruct name -> name - | Typ.Tfun _ -> Typ.Name.Objc.from_string CFrontend_config.objc_object - | _ -> - L.(debug Capture Verbose) - "Classname of type cannot be extracted in type %s" (Typ.to_string typ); + | Typ.Tstruct name + -> name + | Typ.Tfun _ + -> Typ.Name.Objc.from_string CFrontend_config.objc_object + | _ + -> L.(debug Capture Verbose) + "Classname of type cannot be extracted in type %s" (Typ.to_string typ) ; Typ.Name.Objc.from_string "undefined" let is_class typ = match typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct name}, _) -> - String.equal (Typ.Name.name name) CFrontend_config.objc_class - | _ -> false + | Typ.Tptr ({desc= Tstruct name}, _) + -> String.equal (Typ.Name.name name) CFrontend_config.objc_class + | _ + -> false -let rec return_type_of_function_qual_type (qual_type : Clang_ast_t.qual_type) = +let rec return_type_of_function_qual_type (qual_type: Clang_ast_t.qual_type) = let open Clang_ast_t in match CAst_utils.get_type qual_type.qt_type_ptr with | Some FunctionProtoType (_, function_type_info, _) - | Some FunctionNoProtoType (_, function_type_info) -> - function_type_info.Clang_ast_t.fti_return_type - | Some BlockPointerType (_, in_qual) -> - return_type_of_function_qual_type in_qual - | Some _ -> - L.(debug Capture Verbose) "Warning: Type pointer %s is not a function type." - (Clang_ast_extend.type_ptr_to_string qual_type.qt_type_ptr); - {qual_type with qt_type_ptr=Clang_ast_extend.ErrorType} - | None -> - L.(debug Capture Verbose) "Warning: Type pointer %s not found." - (Clang_ast_extend.type_ptr_to_string qual_type.qt_type_ptr); - {qual_type with qt_type_ptr=Clang_ast_extend.ErrorType} - -let return_type_of_function_type qual_type = - return_type_of_function_qual_type qual_type + | Some FunctionNoProtoType (_, function_type_info) + -> function_type_info.Clang_ast_t.fti_return_type + | Some BlockPointerType (_, in_qual) + -> return_type_of_function_qual_type in_qual + | Some _ + -> L.(debug Capture Verbose) + "Warning: Type pointer %s is not a function type." + (Clang_ast_extend.type_ptr_to_string qual_type.qt_type_ptr) ; + {qual_type with qt_type_ptr= Clang_ast_extend.ErrorType} + | None + -> L.(debug Capture Verbose) + "Warning: Type pointer %s not found." + (Clang_ast_extend.type_ptr_to_string qual_type.qt_type_ptr) ; + {qual_type with qt_type_ptr= Clang_ast_extend.ErrorType} +let return_type_of_function_type qual_type = return_type_of_function_qual_type qual_type let is_block_type {Clang_ast_t.qt_type_ptr} = let open Clang_ast_t in match CAst_utils.get_desugared_type qt_type_ptr with - | Some BlockPointerType _ -> true - | _ -> false + | Some BlockPointerType _ + -> true + | _ + -> false let is_reference_type {Clang_ast_t.qt_type_ptr} = match CAst_utils.get_desugared_type qt_type_ptr with - | Some Clang_ast_t.LValueReferenceType _ -> true - | Some Clang_ast_t.RValueReferenceType _ -> true - | _ -> false + | Some Clang_ast_t.LValueReferenceType _ + -> true + | Some Clang_ast_t.RValueReferenceType _ + -> true + | _ + -> false (* To be called with strings of format "*" *) let get_name_from_type_pointer custom_type_pointer = match Str.split (Str.regexp "*") custom_type_pointer with - | [pointer_type_info; class_name] -> pointer_type_info, class_name - | _ -> assert false + | [pointer_type_info; class_name] + -> (pointer_type_info, class_name) + | _ + -> assert false (* let rec get_type_list nn ll = diff --git a/infer/src/clang/CType_decl.ml b/infer/src/clang/CType_decl.ml index 0a04d87fb..9798a32b5 100644 --- a/infer/src/clang/CType_decl.ml +++ b/infer/src/clang/CType_decl.ml @@ -14,39 +14,51 @@ open! IStd module L = Logging let add_predefined_objc_types tenv = - ignore (Tenv.mk_struct tenv (CType_to_sil_type.get_builtin_objc_typename `ObjCClass)); + ignore (Tenv.mk_struct tenv (CType_to_sil_type.get_builtin_objc_typename `ObjCClass)) ; ignore (Tenv.mk_struct tenv (CType_to_sil_type.get_builtin_objc_typename `ObjCId)) -let add_predefined_types tenv = - add_predefined_objc_types tenv +let add_predefined_types tenv = add_predefined_objc_types tenv let create_c_record_typename opt_type = match opt_type with - | `Type s -> - (let buf = Str.split (Str.regexp "[ \t]+") s in - match buf with - | "struct":: _ -> Typ.Name.C.from_qual_name - | "class":: _ -> Typ.Name.Cpp.from_qual_name Typ.NoTemplate - | "union":: _ -> Typ.Name.C.union_from_qual_name - | _ -> Typ.Name.C.from_qual_name) - | _ -> assert false + | `Type s + -> ( + let buf = Str.split (Str.regexp "[ \t]+") s in + match buf with + | "struct" :: _ + -> Typ.Name.C.from_qual_name + | "class" :: _ + -> Typ.Name.Cpp.from_qual_name Typ.NoTemplate + | "union" :: _ + -> Typ.Name.C.union_from_qual_name + | _ + -> Typ.Name.C.from_qual_name ) + | _ + -> assert false let get_class_template_name = function - | Clang_ast_t.ClassTemplateDecl (_, name_info, _ ) -> CAst_utils.get_qualified_name name_info - | _ -> assert false + | Clang_ast_t.ClassTemplateDecl (_, name_info, _) + -> CAst_utils.get_qualified_name name_info + | _ + -> assert false let get_superclass_decls decl = let open Clang_ast_t in match decl with | CXXRecordDecl (_, _, _, _, _, _, _, cxx_rec_info) - | ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, cxx_rec_info, _) -> - (* there is no concept of virtual inheritance in the backend right now *) + | ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, cxx_rec_info, _) + -> (* there is no concept of virtual inheritance in the backend right now *) let base_ptr = cxx_rec_info.Clang_ast_t.xrdi_bases @ cxx_rec_info.Clang_ast_t.xrdi_vbases in - let get_decl_or_fail typ_ptr = match CAst_utils.get_decl_from_typ_ptr typ_ptr with - | Some decl -> decl - | None -> assert false in + let get_decl_or_fail typ_ptr = + match CAst_utils.get_decl_from_typ_ptr typ_ptr with + | Some decl + -> decl + | None + -> assert false + in List.map ~f:get_decl_or_fail base_ptr - | _ -> [] + | _ + -> [] let translate_as_type_ptr_matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["infer_traits::TranslateAsType"] @@ -54,23 +66,32 @@ let translate_as_type_ptr_matcher = let get_translate_as_friend_decl decl_list = let is_translate_as_friend_name (_, name_info) = let qual_name = CAst_utils.get_qualified_name name_info in - QualifiedCppName.Match.match_qualifiers translate_as_type_ptr_matcher qual_name in - let get_friend_decl_opt (decl : Clang_ast_t.decl) = match decl with - | FriendDecl (_, `Type type_ptr) -> CAst_utils.get_decl_from_typ_ptr type_ptr - | _ -> None in + QualifiedCppName.Match.match_qualifiers translate_as_type_ptr_matcher qual_name + in + let get_friend_decl_opt (decl: Clang_ast_t.decl) = + match decl with + | FriendDecl (_, `Type type_ptr) + -> CAst_utils.get_decl_from_typ_ptr type_ptr + | _ + -> None + in let is_translate_as_friend_decl decl = match get_friend_decl_opt decl with - | Some decl -> - let named_decl_tuple_opt = Clang_ast_proj.get_named_decl_tuple decl in + | Some decl + -> let named_decl_tuple_opt = Clang_ast_proj.get_named_decl_tuple decl in Option.value_map ~f:is_translate_as_friend_name ~default:false named_decl_tuple_opt - | None -> false in + | None + -> false + in match get_friend_decl_opt (List.find_exn ~f:is_translate_as_friend_decl decl_list) with | Some - (Clang_ast_t.ClassTemplateSpecializationDecl - (_, _, _, _, _, _, _, _, {tsi_specialization_args=[`Type t_ptr]})) -> - Some t_ptr - | _ -> None - | exception Not_found -> None + Clang_ast_t.ClassTemplateSpecializationDecl + (_, _, _, _, _, _, _, _, {tsi_specialization_args= [(`Type t_ptr)]}) + -> Some t_ptr + | _ + -> None + | exception Not_found + -> None let get_record_definition decl = let open Clang_ast_t in @@ -79,35 +100,46 @@ let get_record_definition decl = (_, _, _, _, _, _, {rdi_is_complete_definition; rdi_definition_ptr}, _, _) | CXXRecordDecl (_, _, _, _, _, _, {rdi_is_complete_definition; rdi_definition_ptr}, _) | RecordDecl (_, _, _, _, _, _, {rdi_is_complete_definition; rdi_definition_ptr}) - when not rdi_is_complete_definition && rdi_definition_ptr <> 0 -> - CAst_utils.get_decl rdi_definition_ptr |> Option.value ~default:decl - | _ -> decl + when not rdi_is_complete_definition && rdi_definition_ptr <> 0 + -> CAst_utils.get_decl rdi_definition_ptr |> Option.value ~default:decl + | _ + -> decl let rec get_struct_fields tenv decl = let open Clang_ast_t in - let decl_list = match decl with + let decl_list = + match decl with | ClassTemplateSpecializationDecl (_, _, _, _, decl_list, _, _, _, _) | CXXRecordDecl (_, _, _, _, decl_list, _, _, _) - | RecordDecl (_, _, _, _, decl_list, _, _) -> decl_list - | _ -> [] in + | RecordDecl (_, _, _, _, decl_list, _, _) + -> decl_list + | _ + -> [] + in let class_tname = get_record_typename ~tenv decl in - let do_one_decl decl = match decl with - | FieldDecl (_, {ni_name}, qt, _) -> - let id = CGeneral_utils.mk_class_field_name class_tname ni_name in + let do_one_decl decl = + match decl with + | FieldDecl (_, {ni_name}, qt, _) + -> let id = CGeneral_utils.mk_class_field_name class_tname ni_name in let typ = qual_type_to_sil_type tenv qt in - let annotation_items = [] in (* For the moment we don't use them*) + let annotation_items = [] in + (* For the moment we don't use them*) [(id, typ, annotation_items)] - | _ -> [] in + | _ + -> [] + in let base_decls = get_superclass_decls decl in let base_class_fields = List.map ~f:(get_struct_fields tenv) base_decls in - List.concat (base_class_fields @ (List.map ~f:do_one_decl decl_list)) + List.concat (base_class_fields @ List.map ~f:do_one_decl decl_list) (* For a record declaration it returns/constructs the type *) and get_record_declaration_type tenv decl = let definition_decl = get_record_definition decl in match get_record_custom_type tenv definition_decl with - | Some t -> t.Typ.desc - | None -> get_record_struct_type tenv definition_decl + | Some t + -> t.Typ.desc + | None + -> get_record_struct_type tenv definition_decl and get_record_custom_type tenv definition_decl = let result = get_record_friend_decl_type tenv definition_decl in @@ -118,58 +150,70 @@ and get_record_friend_decl_type tenv definition_decl = let open Clang_ast_t in match definition_decl with | ClassTemplateSpecializationDecl (_, _, _, _, decl_list, _, _, _, _) - | CXXRecordDecl (_, _, _, _, decl_list, _, _, _) -> - Option.map ~f:(qual_type_to_sil_type tenv) (get_translate_as_friend_decl decl_list) - | _ -> None + | CXXRecordDecl (_, _, _, _, decl_list, _, _, _) + -> Option.map ~f:(qual_type_to_sil_type tenv) (get_translate_as_friend_decl decl_list) + | _ + -> None -and get_record_as_typevar (definition_decl : Clang_ast_t.decl) = +and get_record_as_typevar (definition_decl: Clang_ast_t.decl) = let open Clang_ast_t in match definition_decl with - | CXXRecordDecl (decl_info, name_info, _, _, _, _, _, _) -> - let is_infer_typevar = function - | AnnotateAttr {ai_parameters=[_; name; _]} - when String.equal name "__infer_type_var" -> true - | _ -> false in + | CXXRecordDecl (decl_info, name_info, _, _, _, _, _, _) + -> let is_infer_typevar = function + | AnnotateAttr {ai_parameters= [_; name; _]} when String.equal name "__infer_type_var" + -> true + | _ + -> false + in if List.exists ~f:is_infer_typevar decl_info.di_attributes then let tname = CAst_utils.get_qualified_name name_info |> QualifiedCppName.to_qual_string in Some (Typ.mk (TVar tname)) - else - None - | _ -> None + else None + | _ + -> None (* We need to take the name out of the type as the struct can be anonymous If tenv is not passed, then template instantiaion information may be incorrect, as it defaults to Typ.NoTemplate *) and get_record_typename ?tenv decl = let open Clang_ast_t in - match decl, tenv with - | RecordDecl (_, name_info, opt_type, _, _, _, _), _ -> - CAst_utils.get_qualified_name name_info |> create_c_record_typename opt_type - | ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, _, spec_info), Some tenv -> - let tname = match CAst_utils.get_decl spec_info.tsi_template_decl with - | Some dec -> get_class_template_name dec - | None -> assert false in - let args_in_sil = List.map spec_info.tsi_specialization_args ~f:(function - | `Type qual_type -> Some (qual_type_to_sil_type tenv qual_type) - | _ -> None) in + match (decl, tenv) with + | RecordDecl (_, name_info, opt_type, _, _, _, _), _ + -> CAst_utils.get_qualified_name name_info |> create_c_record_typename opt_type + | ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, _, spec_info), Some tenv + -> let tname = + match CAst_utils.get_decl spec_info.tsi_template_decl with + | Some dec + -> get_class_template_name dec + | None + -> assert false + in + let args_in_sil = + List.map spec_info.tsi_specialization_args ~f:(function + | `Type qual_type + -> Some (qual_type_to_sil_type tenv qual_type) + | _ + -> None ) + in Typ.Name.Cpp.from_qual_name (Typ.Template args_in_sil) tname | CXXRecordDecl (_, name_info, _, _, _, _, _, _), _ - | ClassTemplateSpecializationDecl (_, name_info, _, _, _, _, _, _, _), _ -> - (* we use Typ.CppClass for C++ because we expect Typ.CppClass from *) + | ClassTemplateSpecializationDecl (_, name_info, _, _, _, _, _, _, _), _ + -> (* we use Typ.CppClass for C++ because we expect Typ.CppClass from *) (* types that have methods. And in C++ struct/class/union can have methods *) Typ.Name.Cpp.from_qual_name Typ.NoTemplate (CAst_utils.get_qualified_name name_info) - | ObjCInterfaceDecl (_, name_info, _, _, _), _ | ObjCImplementationDecl (_, name_info, _, _, _), _ - | ObjCProtocolDecl (_, name_info, _, _, _), _ -> - CAst_utils.get_qualified_name name_info |> Typ.Name.Objc.from_qual_name - | ObjCCategoryDecl (_, _, _, _, {odi_class_interface=Some {dr_name}}), _ - | ObjCCategoryImplDecl (_, _, _, _, {ocidi_class_interface=Some {dr_name}}), _ -> ( - match dr_name with - | Some name_info -> - CAst_utils.get_qualified_name name_info |> Typ.Name.Objc.from_qual_name - | None -> assert false) - | _ -> assert false + | ObjCProtocolDecl (_, name_info, _, _, _), _ + -> CAst_utils.get_qualified_name name_info |> Typ.Name.Objc.from_qual_name + | ObjCCategoryDecl (_, _, _, _, {odi_class_interface= Some {dr_name}}), _ + | ObjCCategoryImplDecl (_, _, _, _, {ocidi_class_interface= Some {dr_name}}), _ -> ( + match dr_name with + | Some name_info + -> CAst_utils.get_qualified_name name_info |> Typ.Name.Objc.from_qual_name + | None + -> assert false ) + | _ + -> assert false (** fetches list of superclasses for C++ classes *) and get_superclass_list_cpp tenv decl = @@ -182,52 +226,64 @@ and get_record_struct_type tenv definition_decl : Typ.desc = match definition_decl with | ClassTemplateSpecializationDecl (_, _, _, type_ptr, _, _, record_decl_info, _, _) | CXXRecordDecl (_, _, _, type_ptr, _, _, record_decl_info, _) - | RecordDecl (_, _, _, type_ptr, _, _, record_decl_info) -> + | RecordDecl (_, _, _, type_ptr, _, _, record_decl_info) + -> ( let sil_typename = get_record_typename ~tenv definition_decl in let sil_desc = Typ.Tstruct sil_typename in - (match Tenv.lookup tenv sil_typename with - | Some _ -> sil_desc (* just reuse what is already in tenv *) - | None -> - let is_complete_definition = record_decl_info.Clang_ast_t.rdi_is_complete_definition in - let extra_fields = - if CTrans_models.is_objc_memory_model_controlled (Typ.Name.name sil_typename) then - [Typ.Struct.objc_ref_counter_field] - else [] in - let annots = - if Typ.Name.Cpp.is_class sil_typename then Annot.Class.cpp - else Annot.Item.empty (* No annotations for structs *) in - if is_complete_definition then ( - CAst_utils.update_sil_types_map type_ptr sil_desc; - let non_statics = get_struct_fields tenv definition_decl in - let fields = CGeneral_utils.append_no_duplicates_fields non_statics extra_fields in - let statics = [] in (* Note: We treat static field same as global variables *) - let methods = [] in (* C++ methods are not put into tenv (info isn't used) *) - let supers = get_superclass_list_cpp tenv definition_decl in - Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots - sil_typename |> ignore; - CAst_utils.update_sil_types_map type_ptr sil_desc; - sil_desc - ) else ( - (* There is no definition for that struct in whole translation unit. + match Tenv.lookup tenv sil_typename with + | Some _ + -> sil_desc (* just reuse what is already in tenv *) + | None + -> let is_complete_definition = record_decl_info.Clang_ast_t.rdi_is_complete_definition in + let extra_fields = + if CTrans_models.is_objc_memory_model_controlled (Typ.Name.name sil_typename) then + [Typ.Struct.objc_ref_counter_field] + else [] + in + let annots = + if Typ.Name.Cpp.is_class sil_typename then Annot.Class.cpp else Annot.Item.empty + (* No annotations for structs *) + in + if is_complete_definition then ( + CAst_utils.update_sil_types_map type_ptr sil_desc ; + let non_statics = get_struct_fields tenv definition_decl in + let fields = CGeneral_utils.append_no_duplicates_fields non_statics extra_fields in + let statics = [] in + (* Note: We treat static field same as global variables *) + let methods = [] in + (* C++ methods are not put into tenv (info isn't used) *) + let supers = get_superclass_list_cpp tenv definition_decl in + Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots sil_typename |> ignore ; + CAst_utils.update_sil_types_map type_ptr sil_desc ; + sil_desc ) + else ( + (* There is no definition for that struct in whole translation unit. Put empty struct into tenv to prevent backend problems *) - ignore (Tenv.mk_struct tenv ~fields:extra_fields sil_typename); - CAst_utils.update_sil_types_map type_ptr sil_desc; - sil_desc)) - | _ -> assert false + ignore (Tenv.mk_struct tenv ~fields:extra_fields sil_typename) ; + CAst_utils.update_sil_types_map type_ptr sil_desc ; + sil_desc ) ) + | _ + -> assert false and add_types_from_decl_to_tenv tenv decl = let open Clang_ast_t in match decl with - | ClassTemplateSpecializationDecl _ | CXXRecordDecl _ | RecordDecl _ -> - get_record_declaration_type tenv decl - | ObjCInterfaceDecl _ -> ObjcInterface_decl.interface_declaration qual_type_to_sil_type tenv decl - | ObjCImplementationDecl _ -> - ObjcInterface_decl.interface_impl_declaration qual_type_to_sil_type tenv decl - | ObjCProtocolDecl _ -> ObjcProtocol_decl.protocol_decl qual_type_to_sil_type tenv decl - | ObjCCategoryDecl _ -> ObjcCategory_decl.category_decl qual_type_to_sil_type tenv decl - | ObjCCategoryImplDecl _ -> ObjcCategory_decl.category_impl_decl qual_type_to_sil_type tenv decl - | EnumDecl _ -> CEnum_decl.enum_decl decl - | _ -> assert false + | ClassTemplateSpecializationDecl _ | CXXRecordDecl _ | RecordDecl _ + -> get_record_declaration_type tenv decl + | ObjCInterfaceDecl _ + -> ObjcInterface_decl.interface_declaration qual_type_to_sil_type tenv decl + | ObjCImplementationDecl _ + -> ObjcInterface_decl.interface_impl_declaration qual_type_to_sil_type tenv decl + | ObjCProtocolDecl _ + -> ObjcProtocol_decl.protocol_decl qual_type_to_sil_type tenv decl + | ObjCCategoryDecl _ + -> ObjcCategory_decl.category_decl qual_type_to_sil_type tenv decl + | ObjCCategoryImplDecl _ + -> ObjcCategory_decl.category_impl_decl qual_type_to_sil_type tenv decl + | EnumDecl _ + -> CEnum_decl.enum_decl decl + | _ + -> assert false and qual_type_to_sil_type tenv qual_type = CType_to_sil_type.qual_type_to_sil_type add_types_from_decl_to_tenv tenv qual_type @@ -238,12 +294,17 @@ let get_type_from_expr_info ei tenv = let class_from_pointer_type tenv qual_type = match (qual_type_to_sil_type tenv qual_type).Typ.desc with - | Tptr({desc=Tstruct typename}, _) -> typename - | _ -> assert false + | Tptr ({desc= Tstruct typename}, _) + -> typename + | _ + -> assert false let get_class_type_np tenv expr_info obj_c_message_expr_info = let qt = match obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind with - | `Class qt -> qt - | _ -> expr_info.Clang_ast_t.ei_qual_type in + | `Class qt + -> qt + | _ + -> expr_info.Clang_ast_t.ei_qual_type + in qual_type_to_sil_type tenv qt diff --git a/infer/src/clang/CType_decl.mli b/infer/src/clang/CType_decl.mli index e375e638e..751dd20d8 100644 --- a/infer/src/clang/CType_decl.mli +++ b/infer/src/clang/CType_decl.mli @@ -17,13 +17,14 @@ val add_types_from_decl_to_tenv : Tenv.t -> Clang_ast_t.decl -> Typ.desc (* Adds the predefined types objc_class which is a struct, *) (* and Class, which is a pointer to objc_class. *) + val add_predefined_types : Tenv.t -> unit val qual_type_to_sil_type : Tenv.t -> Clang_ast_t.qual_type -> Typ.t val class_from_pointer_type : Tenv.t -> Clang_ast_t.qual_type -> Typ.Name.t -val get_class_type_np : Tenv.t -> Clang_ast_t.expr_info -> - Clang_ast_t.obj_c_message_expr_info -> Typ.t +val get_class_type_np : + Tenv.t -> Clang_ast_t.expr_info -> Clang_ast_t.obj_c_message_expr_info -> Typ.t val get_type_from_expr_info : Clang_ast_t.expr_info -> Tenv.t -> Typ.t diff --git a/infer/src/clang/Capture.ml b/infer/src/clang/Capture.ml new file mode 100644 index 000000000..56ed53999 --- /dev/null +++ b/infer/src/clang/Capture.ml @@ -0,0 +1,183 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module CLOpt = CommandLineOption +module L = Logging + +(** enable debug mode (to get more data saved to disk for future inspections) *) +let debug_mode = Config.debug_mode || Config.frontend_stats || Config.frontend_debug + +let buffer_len = 262143 + +let catch_biniou_buffer_errors f x = + try[@warning "-52"] f x + with + | Invalid_argument + (* suppress warning: allow this one case because we're just reraising the error with another + error message so it doesn't really matter if this eventually fails *) + "Bi_inbuf.refill_from_channel" + -> + L.external_error "WARNING: biniou buffer too short, skipping the file@\n" ; + assert false + +(* This function reads the json file in fname, validates it, and encoded in the AST data structure + defined in Clang_ast_t. *) +let validate_decl_from_file fname = + catch_biniou_buffer_errors (Ag_util.Biniou.from_file ~len:buffer_len Clang_ast_b.read_decl) fname + +let validate_decl_from_channel chan = + catch_biniou_buffer_errors (Ag_util.Biniou.from_channel ~len:buffer_len Clang_ast_b.read_decl) + chan + +let register_perf_stats_report source_file = + let stats_dir = Filename.concat Config.results_dir Config.frontend_stats_dir_name in + let abbrev_source_file = DB.source_file_encoding source_file in + let stats_file = Config.perf_stats_prefix ^ "_" ^ abbrev_source_file ^ ".json" in + Unix.mkdir_p stats_dir ; + PerfStats.register_report_at_exit (Filename.concat stats_dir stats_file) + +let init_global_state_for_capture_and_linters source_file = + L.(debug Capture Medium) "Processing %s" (Filename.basename (SourceFile.to_abs_path source_file)) ; + register_perf_stats_report source_file ; + Config.curr_language := Config.Clang ; + DB.Results_dir.init source_file ; + CFrontend_config.reset_global_state () + +let run_clang_frontend ast_source = + let init_time = Unix.gettimeofday () in + let print_elapsed () = + let elapsed = Unix.gettimeofday () -. init_time in + L.(debug Capture Quiet) "Elapsed: %07.3f seconds.@\n" elapsed + in + let ast_decl = + match ast_source with + | `File path + -> validate_decl_from_file path + | `Pipe chan + -> validate_decl_from_channel chan + in + let trans_unit_ctx = + match ast_decl with + | Clang_ast_t.TranslationUnitDecl (_, _, _, info) + -> Config.arc_mode := info.Clang_ast_t.tudi_arc_enabled ; + let source_file = SourceFile.from_abs_path info.Clang_ast_t.tudi_input_path in + init_global_state_for_capture_and_linters source_file ; + let lang = + match info.Clang_ast_t.tudi_input_kind with + | `IK_C + -> CFrontend_config.C + | `IK_CXX + -> CFrontend_config.CPP + | `IK_ObjC + -> CFrontend_config.ObjC + | `IK_ObjCXX + -> CFrontend_config.ObjCPP + | _ + -> assert false + in + {CFrontend_config.source_file= source_file; lang} + | _ + -> assert false + in + let pp_ast_filename fmt ast_source = + match ast_source with + | `File path + -> Format.fprintf fmt "%s" path + | `Pipe _ + -> Format.fprintf fmt "stdin of %a" SourceFile.pp trans_unit_ctx.CFrontend_config.source_file + in + ClangPointers.populate_all_tables ast_decl ; + L.(debug Capture Quiet) "Clang frontend action is %s@\n" Config.clang_frontend_action_string ; + L.(debug Capture Medium) + "Start %s of AST from %a@\n" Config.clang_frontend_action_string pp_ast_filename ast_source ; + if Config.clang_frontend_do_lint then + CFrontend_checkers_main.do_frontend_checks trans_unit_ctx ast_decl ; + if Config.clang_frontend_do_capture then CFrontend.do_source_file trans_unit_ctx ast_decl ; + L.(debug Capture Medium) "End translation AST file %a... OK!@\n" pp_ast_filename ast_source ; + print_elapsed () + +let run_and_validate_clang_frontend ast_source = + try run_clang_frontend ast_source + with exc -> if not Config.failures_allowed then raise exc + +let run_clang clang_command read = + let exit_with_error exit_code = + L.external_error "Error: the following clang command did not run successfully:@\n %s@\n" + clang_command ; + exit exit_code + in + (* NOTE: exceptions will propagate through without exiting here *) + match Utils.with_process_in clang_command read with + | res, Ok () + -> res + | _, Error `Exit_non_zero n + -> (* exit with the same error code as clang in case of compilation failure *) + exit_with_error n + | _ + -> exit_with_error 1 + +let run_plugin_and_frontend source_path frontend clang_args = + let clang_command = ClangCommand.command_to_run (ClangCommand.with_plugin_args clang_args) in + ( if debug_mode then + (* -cc1 clang commands always set -o explicitly *) + let basename = source_path ^ ".ast" in + (* Emit the clang command with the extra args piped to infer-as-clang *) + let frontend_script_fname = Printf.sprintf "%s.sh" basename in + let debug_script_out = Out_channel.create frontend_script_fname in + let debug_script_fmt = Format.formatter_of_out_channel debug_script_out in + let biniou_fname = Printf.sprintf "%s.biniou" basename in + Format.fprintf debug_script_fmt "%s \\@\n > %s@\n" clang_command biniou_fname ; + Format.fprintf debug_script_fmt + "bdump -x -d \"%s/clang_ast.dict\" -w '!!DUMMY!!' %s \\@\n > %s.bdump" Config.etc_dir + biniou_fname basename ; + Out_channel.close debug_script_out ) ; + run_clang clang_command frontend + +let cc1_capture clang_cmd = + let source_path = + let root = Unix.getcwd () in + let orig_argv = ClangCommand.get_orig_argv clang_cmd in + (* the source file is always the last argument of the original -cc1 clang command *) + Utils.filename_to_absolute ~root orig_argv.(Array.length orig_argv - 1) + in + L.(debug Capture Quiet) "@\n*** Beginning capture of file %s ***@\n" source_path ; + if Config.equal_analyzer Config.analyzer Config.CompileOnly + || not Config.skip_analysis_in_path_skips_compilation + && CLocation.is_file_blacklisted source_path + then ( + L.(debug Capture Quiet) "@\n Skip the analysis of source file %s@\n@\n" source_path ; + (* We still need to run clang, but we don't have to attach the plugin. *) + run_clang (ClangCommand.command_to_run clang_cmd) Utils.consume_in ) + else if Config.skip_analysis_in_path_skips_compilation + && CLocation.is_file_blacklisted source_path + then ( + L.(debug Capture Quiet) "@\n Skip compilation and analysis of source file %s@\n@\n" source_path ; + () ) + else ( + ( match Config.clang_biniou_file with + | Some fname + -> run_and_validate_clang_frontend (`File fname) + | None + -> run_plugin_and_frontend source_path + (fun chan_in -> run_and_validate_clang_frontend (`Pipe chan_in)) + clang_cmd ) ; + () ) + +let capture clang_cmd = + if ClangCommand.can_attach_ast_exporter clang_cmd then + (* this command compiles some code; replace the invocation of clang with our own clang and + plugin *) + cc1_capture clang_cmd + else + (* Non-compilation (eg, linking) command. Run the command as-is. It will not get captured + further since `clang -### ...` will only output commands that invoke binaries using their + absolute paths. *) + let command_to_run = ClangCommand.command_to_run clang_cmd in + L.(debug Capture Quiet) "Running non-cc command without capture: %s@\n" command_to_run ; + run_clang command_to_run Utils.consume_in diff --git a/infer/src/clang/Capture.rei b/infer/src/clang/Capture.mli similarity index 84% rename from infer/src/clang/Capture.rei rename to infer/src/clang/Capture.mli index ea6051d7b..c4c5f18b2 100644 --- a/infer/src/clang/Capture.rei +++ b/infer/src/clang/Capture.mli @@ -1,11 +1,12 @@ -/* +(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) -let capture: ClangCommand.t => unit; +open! IStd + +val capture : ClangCommand.t -> unit diff --git a/infer/src/clang/Capture.re b/infer/src/clang/Capture.re deleted file mode 100644 index 60db1e3c5..000000000 --- a/infer/src/clang/Capture.re +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module CLOpt = CommandLineOption; - -module L = Logging; - - -/** enable debug mode (to get more data saved to disk for future inspections) */ -let debug_mode = Config.debug_mode || Config.frontend_stats || Config.frontend_debug; - -let buffer_len = 262143; - -let catch_biniou_buffer_errors f x => - ( - try (f x) { - /* suppress warning: allow this one case because we're just reraising the error with another - error message so it doesn't really matter if this eventually fails */ - | Invalid_argument "Bi_inbuf.refill_from_channel" => - L.external_error "WARNING: biniou buffer too short, skipping the file@\n"; - assert false - } - ) - [@warning "-52"]; - -/* This function reads the json file in fname, validates it, and encoded in the AST data structure - defined in Clang_ast_t. */ -let validate_decl_from_file fname => - catch_biniou_buffer_errors - (Ag_util.Biniou.from_file len::buffer_len Clang_ast_b.read_decl) fname; - -let validate_decl_from_channel chan => - catch_biniou_buffer_errors - (Ag_util.Biniou.from_channel len::buffer_len Clang_ast_b.read_decl) chan; - -let register_perf_stats_report source_file => { - let stats_dir = Filename.concat Config.results_dir Config.frontend_stats_dir_name; - let abbrev_source_file = DB.source_file_encoding source_file; - let stats_file = Config.perf_stats_prefix ^ "_" ^ abbrev_source_file ^ ".json"; - Unix.mkdir_p stats_dir; - PerfStats.register_report_at_exit (Filename.concat stats_dir stats_file) -}; - -let init_global_state_for_capture_and_linters source_file => { - L.(debug Capture Medium) - "Processing %s" (Filename.basename (SourceFile.to_abs_path source_file)); - register_perf_stats_report source_file; - Config.curr_language := Config.Clang; - DB.Results_dir.init source_file; - CFrontend_config.reset_global_state () -}; - -let run_clang_frontend ast_source => { - let init_time = Unix.gettimeofday (); - let print_elapsed () => { - let elapsed = Unix.gettimeofday () -. init_time; - L.(debug Capture Quiet) "Elapsed: %07.3f seconds.@\n" elapsed - }; - let ast_decl = - switch ast_source { - | `File path => validate_decl_from_file path - | `Pipe chan => validate_decl_from_channel chan - }; - let trans_unit_ctx = - switch ast_decl { - | Clang_ast_t.TranslationUnitDecl (_, _, _, info) => - Config.arc_mode := info.Clang_ast_t.tudi_arc_enabled; - let source_file = SourceFile.from_abs_path info.Clang_ast_t.tudi_input_path; - init_global_state_for_capture_and_linters source_file; - let lang = - switch info.Clang_ast_t.tudi_input_kind { - | `IK_C => CFrontend_config.C - | `IK_CXX => CFrontend_config.CPP - | `IK_ObjC => CFrontend_config.ObjC - | `IK_ObjCXX => CFrontend_config.ObjCPP - | _ => assert false - }; - {CFrontend_config.source_file: source_file, lang} - | _ => assert false - }; - let pp_ast_filename fmt ast_source => - switch ast_source { - | `File path => Format.fprintf fmt "%s" path - | `Pipe _ => - Format.fprintf fmt "stdin of %a" SourceFile.pp trans_unit_ctx.CFrontend_config.source_file - }; - ClangPointers.populate_all_tables ast_decl; - L.(debug Capture Quiet) "Clang frontend action is %s@\n" Config.clang_frontend_action_string; - L.(debug Capture Medium) - "Start %s of AST from %a@\n" Config.clang_frontend_action_string pp_ast_filename ast_source; - if Config.clang_frontend_do_lint { - CFrontend_checkers_main.do_frontend_checks trans_unit_ctx ast_decl - }; - if Config.clang_frontend_do_capture { - CFrontend.do_source_file trans_unit_ctx ast_decl - }; - L.(debug Capture Medium) "End translation AST file %a... OK!@\n" pp_ast_filename ast_source; - print_elapsed () -}; - -let run_and_validate_clang_frontend ast_source => - try (run_clang_frontend ast_source) { - | exc => - if (not Config.failures_allowed) { - raise exc - } - }; - -let run_clang clang_command read => { - let exit_with_error exit_code => { - L.external_error - "Error: the following clang command did not run successfully:@\n %s@\n" clang_command; - exit exit_code - }; - /* NOTE: exceptions will propagate through without exiting here */ - switch (Utils.with_process_in clang_command read) { - | (res, Ok ()) => res - | (_, Error (`Exit_non_zero n)) => - /* exit with the same error code as clang in case of compilation failure */ - exit_with_error n - | _ => exit_with_error 1 - } -}; - -let run_plugin_and_frontend source_path frontend clang_args => { - let clang_command = ClangCommand.command_to_run (ClangCommand.with_plugin_args clang_args); - if debug_mode { - /* -cc1 clang commands always set -o explicitly */ - let basename = source_path ^ ".ast"; - /* Emit the clang command with the extra args piped to infer-as-clang */ - let frontend_script_fname = Printf.sprintf "%s.sh" basename; - let debug_script_out = Out_channel.create frontend_script_fname; - let debug_script_fmt = Format.formatter_of_out_channel debug_script_out; - let biniou_fname = Printf.sprintf "%s.biniou" basename; - Format.fprintf debug_script_fmt "%s \\@\n > %s@\n" clang_command biniou_fname; - Format.fprintf - debug_script_fmt - "bdump -x -d \"%s/clang_ast.dict\" -w '!!DUMMY!!' %s \\@\n > %s.bdump" - Config.etc_dir - biniou_fname - basename; - Out_channel.close debug_script_out - }; - run_clang clang_command frontend -}; - -let cc1_capture clang_cmd => { - let source_path = { - let root = Unix.getcwd (); - let orig_argv = ClangCommand.get_orig_argv clang_cmd; - /* the source file is always the last argument of the original -cc1 clang command */ - Utils.filename_to_absolute ::root orig_argv.(Array.length orig_argv - 1) - }; - L.(debug Capture Quiet) "@\n*** Beginning capture of file %s ***@\n" source_path; - if ( - Config.equal_analyzer Config.analyzer Config.CompileOnly || - not Config.skip_analysis_in_path_skips_compilation && CLocation.is_file_blacklisted source_path - ) { - L.(debug Capture Quiet) "@\n Skip the analysis of source file %s@\n@\n" source_path; - /* We still need to run clang, but we don't have to attach the plugin. */ - run_clang (ClangCommand.command_to_run clang_cmd) Utils.consume_in - } else if ( - Config.skip_analysis_in_path_skips_compilation && CLocation.is_file_blacklisted source_path - ) { - L.(debug Capture Quiet) - "@\n Skip compilation and analysis of source file %s@\n@\n" source_path; - () - } else { - switch Config.clang_biniou_file { - | Some fname => run_and_validate_clang_frontend (`File fname) - | None => - run_plugin_and_frontend - source_path (fun chan_in => run_and_validate_clang_frontend (`Pipe chan_in)) clang_cmd - }; - () - } -}; - -let capture clang_cmd => - if (ClangCommand.can_attach_ast_exporter clang_cmd) { - /* this command compiles some code; replace the invocation of clang with our own clang and - plugin */ - cc1_capture clang_cmd - } else { - /* Non-compilation (eg, linking) command. Run the command as-is. It will not get captured - further since `clang -### ...` will only output commands that invoke binaries using their - absolute paths. */ - let command_to_run = ClangCommand.command_to_run clang_cmd; - L.(debug Capture Quiet) "Running non-cc command without capture: %s@\n" command_to_run; - run_clang command_to_run Utils.consume_in - }; diff --git a/infer/src/clang/CiOSVersionNumbers.ml b/infer/src/clang/CiOSVersionNumbers.ml index f80883f83..726fdc94e 100644 --- a/infer/src/clang/CiOSVersionNumbers.ml +++ b/infer/src/clang/CiOSVersionNumbers.ml @@ -13,62 +13,69 @@ open! IStd To be found in CoreFoundation/CFBase.h *) type machine_readable_version = float + type human_readable_version = string + type t = machine_readable_version * human_readable_version let version_numbers : t list = - [(478.23, "2.0"); - (478.26, "2.1"); - (478.29, "2.2"); - (478.47, "3.0"); - (478.52, "3.1"); - (478.61, "3.2"); - (550.32, "4.0"); - (550.38, "4.1"); - (550.52, "4.3"); - (675.00, "5.0"); - (690.10, "5.1"); - (793.00, "6.1"); - (847.20, "7.0"); - (847.24, "7.1"); - (1140.1, "8.0"); - (1141.14, "8.1"); - (1142.16, "8.2"); - (1144.17, "8.3"); - (1145.15, "8.4"); - (1240.1, "9.0"); - (1241.11, "9.1"); - (1242.13, "9.3"); - (1280.38, "9.4"); - (1348.0, "10.0"); - (1348.22, "10.2");] + [ (478.23, "2.0") + ; (478.26, "2.1") + ; (478.29, "2.2") + ; (478.47, "3.0") + ; (478.52, "3.1") + ; (478.61, "3.2") + ; (550.32, "4.0") + ; (550.38, "4.1") + ; (550.52, "4.3") + ; (675.00, "5.0") + ; (690.10, "5.1") + ; (793.00, "6.1") + ; (847.20, "7.0") + ; (847.24, "7.1") + ; (1140.1, "8.0") + ; (1141.14, "8.1") + ; (1142.16, "8.2") + ; (1144.17, "8.3") + ; (1145.15, "8.4") + ; (1240.1, "9.0") + ; (1241.11, "9.1") + ; (1242.13, "9.3") + ; (1280.38, "9.4") + ; (1348.0, "10.0") + ; (1348.22, "10.2") ] let sort_versions versions = let compare (version_float1, _) (version_float2, _) = - Float.compare version_float1 version_float2 in + Float.compare version_float1 version_float2 + in List.sort ~cmp:compare versions let version_of number_s : human_readable_version option = let epsilon = 0.001 in let rec version_of_aux version_numbers number = match version_numbers with - | (version_n, version_s) :: (next_version_n, next_version_s) :: rest -> - if (number -. version_n) < epsilon && (number -. version_n) > -. epsilon then Some version_s - else if (number >= (version_n +. epsilon) && number <= (next_version_n -. epsilon)) - then Some next_version_s + | (version_n, version_s) :: (next_version_n, next_version_s) :: rest + -> if number -. version_n < epsilon && number -. version_n > ~-.epsilon then Some version_s + else if number >= version_n +. epsilon && number <= next_version_n -. epsilon then + Some next_version_s else version_of_aux ((next_version_n, next_version_s) :: rest) number - | [version_n, version_s] -> - if number >= version_n then Some version_s - else None - | [] -> None in + | [(version_n, version_s)] + -> if number >= version_n then Some version_s else None + | [] + -> None + in let number_opt = try Some (float_of_string number_s) - with Failure _ -> None in + with Failure _ -> None + in match number_opt with - | None -> None - | Some number -> version_of_aux (sort_versions version_numbers) number + | None + -> None + | Some number + -> version_of_aux (sort_versions version_numbers) number let pp_diff_of_version_opt fmt (expected, actual) = let option_to_string opt = Option.value ~default:"" opt in - Format.fprintf fmt - "Expected: [%s] Found: [%s]" (option_to_string expected) (option_to_string actual) + Format.fprintf fmt "Expected: [%s] Found: [%s]" (option_to_string expected) + (option_to_string actual) diff --git a/infer/src/clang/CiOSVersionNumbers.mli b/infer/src/clang/CiOSVersionNumbers.mli index bd7bab2c6..796580feb 100644 --- a/infer/src/clang/CiOSVersionNumbers.mli +++ b/infer/src/clang/CiOSVersionNumbers.mli @@ -10,10 +10,12 @@ open! IStd type machine_readable_version = float + type human_readable_version = string + type t = machine_readable_version * human_readable_version val version_of : string -> human_readable_version option -val pp_diff_of_version_opt : Format.formatter -> - (human_readable_version option * human_readable_version option) -> unit +val pp_diff_of_version_opt : + Format.formatter -> human_readable_version option * human_readable_version option -> unit diff --git a/infer/src/clang/ClangCommand.ml b/infer/src/clang/ClangCommand.ml new file mode 100644 index 000000000..c6c6043b6 --- /dev/null +++ b/infer/src/clang/ClangCommand.ml @@ -0,0 +1,171 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +open! IStd +module L = Logging + +type t = {exec: string; argv: string list; orig_argv: string list; quoting_style: ClangQuotes.style} + +let fcp_dir = + Config.bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name + ^/ "facebook-clang-plugins" + +(** path of the plugin to load in clang *) +let plugin_path = fcp_dir ^/ "libtooling" ^/ "build" ^/ "FacebookClangPlugin.dylib" + +(** name of the plugin to use *) +let plugin_name = "BiniouASTExporter" + +(** whether to amend include search path with C++ model headers *) +let infer_cxx_models = Config.cxx_infer_headers + +let value_of_argv_option argv opt_name = + List.fold + ~f:(fun (prev_arg, result) arg -> + let result' = + if Option.is_some result then result + else if String.equal opt_name prev_arg then Some arg + else None + in + (arg, result')) + ~init:("", None) argv + |> snd + +let value_of_option {orig_argv} = value_of_argv_option orig_argv + +let has_flag {orig_argv} flag = List.exists ~f:(String.equal flag) orig_argv + +let can_attach_ast_exporter cmd = + let is_supported_language cmd = + match value_of_option cmd "-x" with + | None + -> L.external_warning "malformed -cc1 command has no \"-x\" flag!" ; false + | Some lang when String.is_prefix ~prefix:"assembler" lang + -> false + | Some _ + -> true + in + (* -Eonly is -cc1 flag that gets produced by 'clang -M -### ...' *) + let is_preprocessor_only cmd = has_flag cmd "-E" || has_flag cmd "-Eonly" in + has_flag cmd "-cc1" && is_supported_language cmd && not (is_preprocessor_only cmd) + +let argv_cons a b = a :: b + +let argv_do_if cond action x = if cond then action x else x + +let file_arg_cmd_sanitizer cmd = + let file = ClangQuotes.mk_arg_file "clang_command_" cmd.quoting_style cmd.argv in + {cmd with argv= [Format.sprintf "@%s" file]} + +let include_override_regex = Option.map ~f:Str.regexp Config.clang_include_to_override_regex + +(* Work around various path or library issues occurring when one tries to substitute Apple's version + of clang with a different version. Also mitigate version discrepancies in clang's + fatal warnings. *) +let clang_cc1_cmd_sanitizer cmd = + (* command line options not supported by the opensource compiler or the plugins *) + let flags_blacklist = ["-fembed-bitcode-marker"; "-fno-canonical-system-headers"] in + let mllvm_flags_blacklist = ["-profile-guided-section-prefix"] in + let replace_option_arg option arg = + if String.equal option "-arch" && String.equal arg "armv7k" then "armv7" + else if (* replace armv7k arch with armv7 *) + String.is_suffix arg ~suffix:"dep.tmp" then ( + (* compilation-database Buck integration produces path to `dep.tmp` file that doesn't exist. Create it *) + Unix.mkdir_p (Filename.dirname arg) ; + arg ) + else if String.equal option "-dependency-file" + && Option.is_some Config.buck_compilation_database + (* In compilation database mode, dependency files are not assumed to exist *) + then "/dev/null" + else if String.equal option "-isystem" then + match include_override_regex with + | Some regexp when Str.string_match regexp arg 0 + -> fcp_dir ^/ "clang" ^/ "install" ^/ "lib" ^/ "clang" ^/ "4.0.0" ^/ "include" + | _ + -> arg + else arg + in + let args_defines = if Config.bufferoverrun then ["-D__INFER_BUFFEROVERRUN"] else [] in + let post_args_rev = + [] |> List.rev_append ["-include"; (Config.lib_dir ^/ "clang_wrappers" ^/ "global_defines.h")] + |> List.rev_append args_defines + |> (* Never error on warnings. Clang is often more strict than Apple's version. These arguments + are appended at the end to override previous opposite settings. How it's done: suppress + all the warnings, since there are no warnings, compiler can't elevate them to error + level. *) + argv_cons "-Wno-everything" + in + let rec filter_unsupported_args_and_swap_includes (prev, res_rev) = function + | [] + -> (* return non-reversed list *) + List.rev_append res_rev (List.rev post_args_rev) + | flag :: tl when List.mem ~equal:String.equal flags_blacklist flag + -> filter_unsupported_args_and_swap_includes (flag, res_rev) tl + | flag1 :: flag2 :: tl + when String.equal "-mllvm" flag1 + && List.exists ~f:(fun prefix -> String.is_prefix ~prefix flag2) mllvm_flags_blacklist + -> filter_unsupported_args_and_swap_includes (flag2, res_rev) tl + | arg :: tl + -> let res_rev' = replace_option_arg prev arg :: res_rev in + filter_unsupported_args_and_swap_includes (arg, res_rev') tl + in + let clang_arguments = filter_unsupported_args_and_swap_includes ("", []) cmd.argv in + file_arg_cmd_sanitizer {cmd with argv= clang_arguments} + +let mk quoting_style ~prog ~args = {exec= prog; orig_argv= args; argv= args; quoting_style} + +let command_to_run cmd = + let mk_cmd normalizer = + let {exec; argv; quoting_style} = normalizer cmd in + Printf.sprintf "'%s' %s" exec + (List.map ~f:(ClangQuotes.quote quoting_style) argv |> String.concat ~sep:" ") + in + if can_attach_ast_exporter cmd then mk_cmd clang_cc1_cmd_sanitizer + else if String.is_prefix ~prefix:"clang" (Filename.basename cmd.exec) then + (* `clang` supports argument files and the commands can be longer than the maximum length of the + command line, so put arguments in a file *) + mk_cmd file_arg_cmd_sanitizer + else (* other commands such as `ld` do not support argument files *) + mk_cmd (fun x -> x) + +let with_exec exec args = {args with exec} + +let with_plugin_args args = + let args_before_rev = + [] + |> (* -cc1 has to be the first argument or clang will think it runs in driver mode *) + argv_cons "-cc1" + |> (* It's important to place this option before other -isystem options. *) + argv_do_if infer_cxx_models (List.rev_append ["-isystem"; Config.cpp_extra_include_dir]) + |> List.rev_append + [ "-load" + ; plugin_path + ; (* (t7400979) this is a workaround to avoid that clang crashes when the -fmodules flag and the + YojsonASTExporter plugin are used. Since the -plugin argument disables the generation of .o + files, we invoke apple clang again to generate the expected artifacts. This will keep + xcodebuild plus all the sub-steps happy. *) + (if has_flag args "-fmodules" then "-plugin" else "-add-plugin") + ; plugin_name + ; ("-plugin-arg-" ^ plugin_name) + ; "-" + ; ("-plugin-arg-" ^ plugin_name) + ; "PREPEND_CURRENT_DIR=1" ] + in + (* add -O0 option to avoid compiler obfuscation of AST *) + let args_after_rev = + [] |> argv_cons "-O0" |> argv_do_if Config.fcp_syntax_only (argv_cons "-fsyntax-only") + in + {args with argv= List.rev_append args_before_rev (args.argv @ List.rev args_after_rev)} + +let prepend_arg arg clang_args = {clang_args with argv= arg :: clang_args.argv} + +let prepend_args args clang_args = {clang_args with argv= args @ clang_args.argv} + +let append_args args clang_args = {clang_args with argv= clang_args.argv @ args} + +let get_orig_argv {exec; orig_argv} = Array.of_list (exec :: orig_argv) diff --git a/infer/src/clang/ClangCommand.mli b/infer/src/clang/ClangCommand.mli new file mode 100644 index 000000000..65c3cefb2 --- /dev/null +++ b/infer/src/clang/ClangCommand.mli @@ -0,0 +1,53 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +type t + +(** [mk qs prog args] finds the type of command depending on its arguments [args]. The quoting style + of the arguments have to be provided, so that the command may be run later on. Beware that this + doesn't look inside argument files. This can be used to create a "clang -### ..." command on + which to call [command_to_run], but other functions from the module will not work as expected + unless the command has been normalized by "clang -### ...". *) + +val mk : ClangQuotes.style -> prog:string -> args:string list -> t + +(** Make a command into a string ready to be passed to a shell to be executed. Fine to call with + clang driver commands. *) + +val command_to_run : t -> string + +(** Whether the command has this flag set in its arguments. Must be called on normalized commands. *) + +val has_flag : t -> string -> bool + +(** The value passed to an option in the arguments of a command. Must be called on normalized commands. *) + +val value_of_option : t -> string -> string option + +(** Whether the command is suitable for attaching the AST exporter. Must be called on normalized commands. *) + +val can_attach_ast_exporter : t -> bool + +(** Add the arguments needed to attach the facebook-clang-plugins plugin. Must be called on normalized commands. *) + +val with_plugin_args : t -> t + +val prepend_arg : string -> t -> t + +val prepend_args : string list -> t -> t + +val append_args : string list -> t -> t + +val get_orig_argv : t -> string array + +(** update the executable to be run *) + +val with_exec : string -> t -> t diff --git a/infer/src/clang/ClangCommand.re b/infer/src/clang/ClangCommand.re deleted file mode 100644 index e591101f3..000000000 --- a/infer/src/clang/ClangCommand.re +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -module L = Logging; - -type t = { - exec: string, - argv: list string, - orig_argv: list string, - quoting_style: ClangQuotes.style -}; - -let fcp_dir = - Config.bin_dir ^\/ - Filename.parent_dir_name ^\/ Filename.parent_dir_name ^\/ "facebook-clang-plugins"; - - -/** path of the plugin to load in clang */ -let plugin_path = fcp_dir ^\/ "libtooling" ^\/ "build" ^\/ "FacebookClangPlugin.dylib"; - - -/** name of the plugin to use */ -let plugin_name = "BiniouASTExporter"; - - -/** whether to amend include search path with C++ model headers */ -let infer_cxx_models = Config.cxx_infer_headers; - -let value_of_argv_option argv opt_name => - List.fold - f::( - fun (prev_arg, result) arg => { - let result' = - if (Option.is_some result) { - result - } else if (String.equal opt_name prev_arg) { - Some arg - } else { - None - }; - (arg, result') - } - ) - init::("", None) - argv |> snd; - -let value_of_option {orig_argv} => value_of_argv_option orig_argv; - -let has_flag {orig_argv} flag => List.exists f::(String.equal flag) orig_argv; - -let can_attach_ast_exporter cmd => { - let is_supported_language cmd => - switch (value_of_option cmd "-x") { - | None => - L.external_warning "malformed -cc1 command has no \"-x\" flag!"; - false - | Some lang when String.is_prefix prefix::"assembler" lang => false - | Some _ => true - }; - /* -Eonly is -cc1 flag that gets produced by 'clang -M -### ...' */ - let is_preprocessor_only cmd => has_flag cmd "-E" || has_flag cmd "-Eonly"; - has_flag cmd "-cc1" && is_supported_language cmd && not (is_preprocessor_only cmd) -}; - -let argv_cons a b => [a, ...b]; - -let argv_do_if cond action x => - if cond { - action x - } else { - x - }; - -let file_arg_cmd_sanitizer cmd => { - let file = ClangQuotes.mk_arg_file "clang_command_" cmd.quoting_style cmd.argv; - {...cmd, argv: [Format.sprintf "@%s" file]} -}; - -let include_override_regex = Option.map f::Str.regexp Config.clang_include_to_override_regex; - -/* Work around various path or library issues occurring when one tries to substitute Apple's version - of clang with a different version. Also mitigate version discrepancies in clang's - fatal warnings. */ -let clang_cc1_cmd_sanitizer cmd => { - /* command line options not supported by the opensource compiler or the plugins */ - let flags_blacklist = ["-fembed-bitcode-marker", "-fno-canonical-system-headers"]; - let mllvm_flags_blacklist = ["-profile-guided-section-prefix"]; - let replace_option_arg option arg => - if (String.equal option "-arch" && String.equal arg "armv7k") { - "armv7" - /* replace armv7k arch with armv7 */ - } else if ( - String.is_suffix arg suffix::"dep.tmp" - ) { - /* compilation-database Buck integration produces path to `dep.tmp` file that doesn't exist. Create it */ - Unix.mkdir_p (Filename.dirname arg); - arg - } else if ( - String.equal option "-dependency-file" && Option.is_some Config.buck_compilation_database - /* In compilation database mode, dependency files are not assumed to exist */ - ) { - "/dev/null" - } else if ( - String.equal option "-isystem" - ) { - switch include_override_regex { - | Some regexp when Str.string_match regexp arg 0 => - fcp_dir ^\/ "clang" ^\/ "install" ^\/ "lib" ^\/ "clang" ^\/ "4.0.0" ^\/ "include" - | _ => arg - } - } else { - arg - }; - let args_defines = - if Config.bufferoverrun { - ["-D__INFER_BUFFEROVERRUN"] - } else { - [] - }; - let post_args_rev = - [] |> List.rev_append ["-include", Config.lib_dir ^\/ "clang_wrappers" ^\/ "global_defines.h"] |> - List.rev_append args_defines |> - /* Never error on warnings. Clang is often more strict than Apple's version. These arguments - are appended at the end to override previous opposite settings. How it's done: suppress - all the warnings, since there are no warnings, compiler can't elevate them to error - level. */ - argv_cons "-Wno-everything"; - let rec filter_unsupported_args_and_swap_includes (prev, res_rev) => - fun - | [] => - /* return non-reversed list */ - List.rev_append res_rev (List.rev post_args_rev) - | [flag, ...tl] when List.mem equal::String.equal flags_blacklist flag => - filter_unsupported_args_and_swap_includes (flag, res_rev) tl - | [flag1, flag2, ...tl] - when - String.equal "-mllvm" flag1 && - List.exists f::(fun prefix => String.is_prefix ::prefix flag2) mllvm_flags_blacklist => - filter_unsupported_args_and_swap_includes (flag2, res_rev) tl - | [arg, ...tl] => { - let res_rev' = [replace_option_arg prev arg, ...res_rev]; - filter_unsupported_args_and_swap_includes (arg, res_rev') tl - }; - let clang_arguments = filter_unsupported_args_and_swap_includes ("", []) cmd.argv; - file_arg_cmd_sanitizer {...cmd, argv: clang_arguments} -}; - -let mk quoting_style ::prog ::args => {exec: prog, orig_argv: args, argv: args, quoting_style}; - -let command_to_run cmd => { - let mk_cmd normalizer => { - let {exec, argv, quoting_style} = normalizer cmd; - Printf.sprintf - "'%s' %s" exec (List.map f::(ClangQuotes.quote quoting_style) argv |> String.concat sep::" ") - }; - if (can_attach_ast_exporter cmd) { - mk_cmd clang_cc1_cmd_sanitizer - } else if ( - String.is_prefix prefix::"clang" (Filename.basename cmd.exec) - ) { - /* `clang` supports argument files and the commands can be longer than the maximum length of the - command line, so put arguments in a file */ - mk_cmd file_arg_cmd_sanitizer - } else { - /* other commands such as `ld` do not support argument files */ - mk_cmd (fun x => x) - } -}; - -let with_exec exec args => {...args, exec}; - -let with_plugin_args args => { - let args_before_rev = - [] |> - /* -cc1 has to be the first argument or clang will think it runs in driver mode */ - argv_cons "-cc1" |> - /* It's important to place this option before other -isystem options. */ - argv_do_if infer_cxx_models (List.rev_append ["-isystem", Config.cpp_extra_include_dir]) |> - List.rev_append [ - "-load", - plugin_path, - /* (t7400979) this is a workaround to avoid that clang crashes when the -fmodules flag and the - YojsonASTExporter plugin are used. Since the -plugin argument disables the generation of .o - files, we invoke apple clang again to generate the expected artifacts. This will keep - xcodebuild plus all the sub-steps happy. */ - if (has_flag args "-fmodules") { - "-plugin" - } else { - "-add-plugin" - }, - plugin_name, - "-plugin-arg-" ^ plugin_name, - "-", - "-plugin-arg-" ^ plugin_name, - "PREPEND_CURRENT_DIR=1" - ]; - /* add -O0 option to avoid compiler obfuscation of AST */ - let args_after_rev = - [] |> argv_cons "-O0" |> argv_do_if Config.fcp_syntax_only (argv_cons "-fsyntax-only"); - {...args, argv: List.rev_append args_before_rev (args.argv @ List.rev args_after_rev)} -}; - -let prepend_arg arg clang_args => {...clang_args, argv: [arg, ...clang_args.argv]}; - -let prepend_args args clang_args => {...clang_args, argv: args @ clang_args.argv}; - -let append_args args clang_args => {...clang_args, argv: clang_args.argv @ args}; - -let get_orig_argv {exec, orig_argv} => Array.of_list [exec, ...orig_argv]; diff --git a/infer/src/clang/ClangCommand.rei b/infer/src/clang/ClangCommand.rei deleted file mode 100644 index f98d377a8..000000000 --- a/infer/src/clang/ClangCommand.rei +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -type t; - - -/** [mk qs prog args] finds the type of command depending on its arguments [args]. The quoting style - of the arguments have to be provided, so that the command may be run later on. Beware that this - doesn't look inside argument files. This can be used to create a "clang -### ..." command on - which to call [command_to_run], but other functions from the module will not work as expected - unless the command has been normalized by "clang -### ...". */ -let mk: ClangQuotes.style => prog::string => args::list string => t; - - -/** Make a command into a string ready to be passed to a shell to be executed. Fine to call with - clang driver commands. */ -let command_to_run: t => string; - - -/** Whether the command has this flag set in its arguments. Must be called on normalized commands. */ -let has_flag: t => string => bool; - - -/** The value passed to an option in the arguments of a command. Must be called on normalized commands. */ -let value_of_option: t => string => option string; - - -/** Whether the command is suitable for attaching the AST exporter. Must be called on normalized commands. */ -let can_attach_ast_exporter: t => bool; - - -/** Add the arguments needed to attach the facebook-clang-plugins plugin. Must be called on normalized commands. */ -let with_plugin_args: t => t; - -let prepend_arg: string => t => t; - -let prepend_args: list string => t => t; - -let append_args: list string => t => t; - -let get_orig_argv: t => array string; - - -/** update the executable to be run */ -let with_exec: string => t => t; diff --git a/infer/src/clang/ClangPointers.ml b/infer/src/clang/ClangPointers.ml index 6282d3932..b3a0c2f10 100644 --- a/infer/src/clang/ClangPointers.ml +++ b/infer/src/clang/ClangPointers.ml @@ -11,46 +11,46 @@ open! IStd type t = Clang_ast_t.pointer -module Map = Map.Make(Int) +module Map = Map.Make (Int) let ivar_to_property_table = Int.Table.create ~size:256 () + let pointer_decl_table = Int.Table.create ~size:256 () + let pointer_stmt_table = Int.Table.create ~size:256 () + let pointer_type_table = Int.Table.create ~size:256 () let empty_v = Clang_ast_visit.empty_visitor + (* This function is not thread-safe *) -let visit_ast - ?(visit_decl=empty_v) ?(visit_stmt=empty_v) ?(visit_type=empty_v) ?(visit_src_loc=empty_v) - top_decl = - Clang_ast_visit.decl_visitor := visit_decl; - Clang_ast_visit.stmt_visitor := visit_stmt; - Clang_ast_visit.type_visitor := visit_type; - Clang_ast_visit.source_location_visitor := visit_src_loc; +let visit_ast ?(visit_decl= empty_v) ?(visit_stmt= empty_v) ?(visit_type= empty_v) + ?(visit_src_loc= empty_v) top_decl = + Clang_ast_visit.decl_visitor := visit_decl ; + Clang_ast_visit.stmt_visitor := visit_stmt ; + Clang_ast_visit.type_visitor := visit_type ; + Clang_ast_visit.source_location_visitor := visit_src_loc ; match Clang_ast_v.validate_decl [] top_decl (* visit *) with - | None -> - () - | Some error -> - failwithf "visiting the clang AST failed with error %s" + | None + -> () + | Some error + -> failwithf "visiting the clang AST failed with error %s" (Ag_util.Validation.string_of_error error) let get_ptr_from_node node = match node with - | `DeclNode decl -> - let decl_info = Clang_ast_proj.get_decl_tuple decl in + | `DeclNode decl + -> let decl_info = Clang_ast_proj.get_decl_tuple decl in decl_info.Clang_ast_t.di_pointer - | `StmtNode stmt -> - let stmt_info,_ = Clang_ast_proj.get_stmt_tuple stmt in + | `StmtNode stmt + -> let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in stmt_info.Clang_ast_t.si_pointer - | `TypeNode c_type -> - let type_info = Clang_ast_proj.get_type_tuple c_type in + | `TypeNode c_type + -> let type_info = Clang_ast_proj.get_type_tuple c_type in type_info.Clang_ast_t.ti_pointer let get_val_from_node node = - match node with - | `DeclNode decl -> decl - | `StmtNode stmt -> stmt - | `TypeNode c_type -> c_type + match node with `DeclNode decl -> decl | `StmtNode stmt -> stmt | `TypeNode c_type -> c_type let add_node_to_cache node cache = let key = get_ptr_from_node node in @@ -58,30 +58,26 @@ let add_node_to_cache node cache = Int.Table.set cache ~key ~data let process_decl _ decl = - add_node_to_cache (`DeclNode decl) pointer_decl_table; + add_node_to_cache (`DeclNode decl) pointer_decl_table ; match decl with - | Clang_ast_t.ObjCPropertyDecl (_, _, {opdi_ivar_decl=Some decl_ref}) -> - let ivar_pointer = decl_ref.Clang_ast_t.dr_decl_pointer in + | Clang_ast_t.ObjCPropertyDecl (_, _, {opdi_ivar_decl= Some decl_ref}) + -> let ivar_pointer = decl_ref.Clang_ast_t.dr_decl_pointer in Int.Table.set ivar_to_property_table ~key:ivar_pointer ~data:decl - | _ -> () + | _ + -> () -let add_stmt_to_cache _ stmt = - add_node_to_cache (`StmtNode stmt) pointer_stmt_table +let add_stmt_to_cache _ stmt = add_node_to_cache (`StmtNode stmt) pointer_stmt_table -let add_type_to_cache _ c_type = - add_node_to_cache (`TypeNode c_type) pointer_type_table +let add_type_to_cache _ c_type = add_node_to_cache (`TypeNode c_type) pointer_type_table -let previous_sloc = { Clang_ast_t.sl_file = None; sl_line = None; sl_column = None } +let previous_sloc = {Clang_ast_t.sl_file= None; sl_line= None; sl_column= None} -let get_sloc current previous = - match current with - | None -> previous - | Some _ -> current +let get_sloc current previous = match current with None -> previous | Some _ -> current let mutate_sloc sloc file line column = let open Clang_ast_t in - sloc.sl_file <- file; - sloc.sl_line <- line; + sloc.sl_file <- file ; + sloc.sl_line <- line ; sloc.sl_column <- column let reset_sloc sloc = mutate_sloc sloc None None None @@ -91,22 +87,18 @@ let complete_source_location _ source_loc = let file = get_sloc source_loc.sl_file previous_sloc.sl_file in let line = get_sloc source_loc.sl_line previous_sloc.sl_line in let column = get_sloc source_loc.sl_column previous_sloc.sl_column in - mutate_sloc source_loc file line column; - mutate_sloc previous_sloc file line column + mutate_sloc source_loc file line column ; mutate_sloc previous_sloc file line column let reset_cache () = - Int.Table.clear pointer_decl_table; - Int.Table.clear pointer_stmt_table; - Int.Table.clear pointer_type_table; - Int.Table.clear ivar_to_property_table; + Int.Table.clear pointer_decl_table ; + Int.Table.clear pointer_stmt_table ; + Int.Table.clear pointer_type_table ; + Int.Table.clear ivar_to_property_table ; reset_sloc previous_sloc (* This function is not thread-safe *) let populate_all_tables top_decl = - reset_cache (); + reset_cache () ; (* populate caches *) - visit_ast - ~visit_decl:process_decl - ~visit_stmt:add_stmt_to_cache - ~visit_type:add_type_to_cache + visit_ast ~visit_decl:process_decl ~visit_stmt:add_stmt_to_cache ~visit_type:add_type_to_cache ~visit_src_loc:complete_source_location top_decl diff --git a/infer/src/clang/ClangPointers.mli b/infer/src/clang/ClangPointers.mli index 70c261c4f..65d19ac27 100644 --- a/infer/src/clang/ClangPointers.mli +++ b/infer/src/clang/ClangPointers.mli @@ -6,25 +6,26 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd (** pointers produced by the AST exporter to represent sharing in the AST *) type t = Clang_ast_t.pointer -module Map : module type of Map.Make(Int) +module Map : module type of Map.Make (Int) -(** maps ivar decl pointer to its decl record *) val ivar_to_property_table : Clang_ast_t.decl Int.Table.t +(** maps ivar decl pointer to its decl record *) -(** maps decl pointer to its decl record *) val pointer_decl_table : Clang_ast_t.decl Int.Table.t +(** maps decl pointer to its decl record *) -(** maps stmt pointer to its stmt record *) val pointer_stmt_table : Clang_ast_t.stmt Int.Table.t +(** maps stmt pointer to its stmt record *) -(** map pointer to its type *) val pointer_type_table : Clang_ast_t.c_type Int.Table.t +(** map pointer to its type *) +val populate_all_tables : Clang_ast_t.decl -> unit (** discover what pointers should point to in the tables above; should be run once for the current toplevel decl *) -val populate_all_tables : Clang_ast_t.decl -> unit diff --git a/infer/src/clang/ClangWrapper.ml b/infer/src/clang/ClangWrapper.ml new file mode 100644 index 000000000..af191f028 --- /dev/null +++ b/infer/src/clang/ClangWrapper.ml @@ -0,0 +1,150 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +(** Given a clang command, normalize it via `clang -###` if needed to get a clear view of what work + is being done and which source files are being compiled, if any, then replace compilation + commands by our own clang with our plugin attached for each source file. *) +open! IStd +module L = Logging + +type action_item = Command of ClangCommand.t | ClangError of string | ClangWarning of string + +let clang_ignore_regex = Option.map ~f:Str.regexp Config.clang_ignore_regex + +let check_for_existing_file args = + if Option.is_some clang_ignore_regex && Option.is_none Config.buck_compilation_database then + let arg_files, args_list = List.partition_tf ~f:(String.is_prefix ~prefix:"@") args in + let read_arg_files args_list arg_file_at = + let file = String.slice arg_file_at 1 (String.length arg_file_at) in + let args_list_file = In_channel.read_lines file in + List.append args_list args_list_file + in + let all_args_ = List.fold_left ~f:read_arg_files ~init:args_list arg_files in + let all_args = List.map ~f:String.strip all_args_ in + let rec check_for_existing_file_arg args = + match args with + | [] + -> () + | option :: rest + -> if String.equal option "-c" then + match + (* infer-capture-all flavour of buck produces path to generated file that doesn't exist. + Create empty file empty file and pass that to clang. This is to enable compilation to continue *) + (clang_ignore_regex, List.hd rest) + with + | Some regexp, Some arg + -> if Str.string_match regexp arg 0 && Sys.file_exists arg <> `Yes then ( + Unix.mkdir_p (Filename.dirname arg) ; + let file = Unix.openfile ~mode:[Unix.O_CREAT; Unix.O_RDONLY] arg in + Unix.close file ) + | _ + -> () + else check_for_existing_file_arg rest + in + check_for_existing_file_arg all_args + +(** Given a list of arguments for clang [args], return a list of new commands to run according to + the results of `clang -### [args]`. *) +let normalize ~prog ~args : action_item list = + let cmd = ClangCommand.mk ClangQuotes.SingleQuotes ~prog ~args in + let clang_hashhashhash = + Printf.sprintf "%s 2>&1" + ( ClangCommand.prepend_arg "-###" cmd + |> (* c++ modules are not supported, so let clang know in case it was passed "-fmodules". + Unfortunately we cannot know accurately if "-fmodules" was passed because we don't go + into argument files at this point ("clang -### ..." will do that for us), so we also pass + "-Qunused-arguments" to silence the potential warning that "-fno-cxx-modules" was + ignored. Moreover, "-fno-cxx-modules" is only accepted by the clang driver so we have to + pass it now. + + Using clang instead of gcc may trigger warnings about unsupported optimization flags; + passing -Wno-ignored-optimization-argument prevents that. *) + ClangCommand.append_args + ["-fno-cxx-modules"; "-Qunused-arguments"; "-Wno-ignored-optimization-argument"] + |> ClangCommand.command_to_run ) + in + L.(debug Capture Medium) "clang -### invocation: %s@\n" clang_hashhashhash ; + let normalized_commands = ref [] in + let one_line line = + if String.is_prefix ~prefix:" \"" line then + Command + ( match + (* massage line to remove edge-cases for splitting *) + "\"" ^ line ^ " \"" |> (* split by whitespace *) + Str.split (Str.regexp_string "\" \"") + with + | prog :: args + -> ClangCommand.mk ClangQuotes.EscapedDoubleQuotes ~prog ~args + | [] + -> failwith "ClangWrapper: argv cannot be empty" ) + else if Str.string_match (Str.regexp "clang[^ :]*: warning: ") line 0 then ClangWarning line + else ClangError line + in + let commands_or_errors = + (* commands generated by `clang -### ...` start with ' "/absolute/path/to/binary"' *) + Str.regexp " \"/\\|clang[^ :]*: \\(error\\|warning\\): " + in + let consume_input i = + try + while true do + let line = In_channel.input_line_exn i in + (* keep only commands and errors *) + if Str.string_match commands_or_errors line 0 then normalized_commands + := one_line line :: !normalized_commands + done + with End_of_file -> () + in + (* collect stdout and stderr output together (in reverse order) *) + Utils.with_process_in clang_hashhashhash consume_input |> ignore ; + normalized_commands := List.rev !normalized_commands ; + !normalized_commands + +let exec_action_item = function + | ClangError error + -> (* An error in the output of `clang -### ...`. Outputs the error and fail. This is because + `clang -###` pretty much never fails, but warns of failures on stderr instead. *) + failwithf + "Failed to execute compilation command. Output:@\n%s@\n*** Infer needs a working compilation command to run." + error + | ClangWarning warning + -> L.external_warning "%s@\n" warning + | Command clang_cmd + -> Capture.capture clang_cmd + +let exe ~prog ~args = + let xx_suffix = match String.is_suffix ~suffix:"++" prog with true -> "++" | false -> "" in + (* use clang in facebook-clang-plugins *) + let clang_xx = CFrontend_config.clang_bin xx_suffix in + check_for_existing_file args ; + let commands = normalize ~prog:clang_xx ~args in + (* xcodebuild projects may require the object files to be generated by the Apple compiler, eg to + generate precompiled headers compatible with Apple's clang. *) + let prog, should_run_original_command = + match Config.fcp_apple_clang with + | Some bin + -> let bin_xx = bin ^ xx_suffix in + L.(debug Capture Medium) "Will run Apple clang %s" bin_xx ; (bin_xx, true) + | None + -> (clang_xx, false) + in + List.iter ~f:exec_action_item commands ; + if List.is_empty commands || should_run_original_command then ( + if List.is_empty commands then + (* No command to execute after -###, let's execute the original command + instead. + + In particular, this can happen when + - there are only assembly commands to execute, which we skip, or + - the user tries to run `infer -- clang -c file_that_does_not_exist.c`. In this case, this + will fail with the appropriate error message from clang instead of silently analyzing 0 + files. *) + L.(debug Capture Quiet) + "WARNING: `clang -### ` returned an empty set of commands to run and no error. Will run the original command directly:@\n %s@\n" + (String.concat ~sep:" " @@ prog :: args) ; + Process.create_process_and_wait ~prog ~args ) diff --git a/infer/src/clang/ClangWrapper.rei b/infer/src/clang/ClangWrapper.mli similarity index 81% rename from infer/src/clang/ClangWrapper.rei rename to infer/src/clang/ClangWrapper.mli index c49ee7276..564221fb9 100644 --- a/infer/src/clang/ClangWrapper.rei +++ b/infer/src/clang/ClangWrapper.mli @@ -1,11 +1,12 @@ -/* +(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; + *) -let exe: prog::string => args::list string => unit; +open! IStd + +val exe : prog:string -> args:string list -> unit diff --git a/infer/src/clang/ClangWrapper.re b/infer/src/clang/ClangWrapper.re deleted file mode 100644 index e25d3fe2c..000000000 --- a/infer/src/clang/ClangWrapper.re +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/** Given a clang command, normalize it via `clang -###` if needed to get a clear view of what work - is being done and which source files are being compiled, if any, then replace compilation - commands by our own clang with our plugin attached for each source file. */ -open! IStd; - -module L = Logging; - -type action_item = - | Command ClangCommand.t - | ClangError string - | ClangWarning string; - -let clang_ignore_regex = Option.map f::Str.regexp Config.clang_ignore_regex; - -let check_for_existing_file args => - if (Option.is_some clang_ignore_regex && Option.is_none Config.buck_compilation_database) { - let (arg_files, args_list) = List.partition_tf f::(String.is_prefix prefix::"@") args; - let read_arg_files args_list arg_file_at => { - let file = String.slice arg_file_at 1 (String.length arg_file_at); - let args_list_file = In_channel.read_lines file; - List.append args_list args_list_file - }; - let all_args_ = List.fold_left f::read_arg_files init::args_list arg_files; - let all_args = List.map f::String.strip all_args_; - let rec check_for_existing_file_arg args => - switch args { - | [] => () - | [option, ...rest] => - if (String.equal option "-c") { - /* infer-capture-all flavour of buck produces path to generated file that doesn't exist. - Create empty file empty file and pass that to clang. This is to enable compilation to continue */ - switch (clang_ignore_regex, List.hd rest) { - | (Some regexp, Some arg) => - if (Str.string_match regexp arg 0 && Sys.file_exists arg != `Yes) { - Unix.mkdir_p (Filename.dirname arg); - let file = Unix.openfile mode::[Unix.O_CREAT, Unix.O_RDONLY] arg; - Unix.close file - } - | _ => () - } - } else { - check_for_existing_file_arg rest - } - }; - check_for_existing_file_arg all_args - }; - - -/** Given a list of arguments for clang [args], return a list of new commands to run according to - the results of `clang -### [args]`. */ -let normalize ::prog ::args :list action_item => { - let cmd = ClangCommand.mk ClangQuotes.SingleQuotes ::prog ::args; - let clang_hashhashhash = - Printf.sprintf - "%s 2>&1" - ( - ClangCommand.prepend_arg "-###" cmd |> - /* c++ modules are not supported, so let clang know in case it was passed "-fmodules". - Unfortunately we cannot know accurately if "-fmodules" was passed because we don't go - into argument files at this point ("clang -### ..." will do that for us), so we also pass - "-Qunused-arguments" to silence the potential warning that "-fno-cxx-modules" was - ignored. Moreover, "-fno-cxx-modules" is only accepted by the clang driver so we have to - pass it now. - - Using clang instead of gcc may trigger warnings about unsupported optimization flags; - passing -Wno-ignored-optimization-argument prevents that. */ - ClangCommand.append_args [ - "-fno-cxx-modules", - "-Qunused-arguments", - "-Wno-ignored-optimization-argument" - ] |> ClangCommand.command_to_run - ); - L.(debug Capture Medium) "clang -### invocation: %s@\n" clang_hashhashhash; - let normalized_commands = ref []; - let one_line line => - if (String.is_prefix prefix::" \"" line) { - Command ( - switch ( - /* massage line to remove edge-cases for splitting */ - "\"" ^ line ^ " \"" |> - /* split by whitespace */ - Str.split (Str.regexp_string "\" \"") - ) { - | [prog, ...args] => ClangCommand.mk ClangQuotes.EscapedDoubleQuotes ::prog ::args - | [] => failwith "ClangWrapper: argv cannot be empty" - } - ) - } else if ( - Str.string_match (Str.regexp "clang[^ :]*: warning: ") line 0 - ) { - ClangWarning line - } else { - ClangError line - }; - let commands_or_errors = - /* commands generated by `clang -### ...` start with ' "/absolute/path/to/binary"' */ - Str.regexp " \"/\\|clang[^ :]*: \\(error\\|warning\\): "; - let consume_input i => - try ( - while true { - let line = In_channel.input_line_exn i; - /* keep only commands and errors */ - if (Str.string_match commands_or_errors line 0) { - normalized_commands := [one_line line, ...!normalized_commands] - } - } - ) { - | End_of_file => () - }; - /* collect stdout and stderr output together (in reverse order) */ - Utils.with_process_in clang_hashhashhash consume_input |> ignore; - normalized_commands := List.rev !normalized_commands; - !normalized_commands -}; - -let exec_action_item = - fun - | ClangError error => - /* An error in the output of `clang -### ...`. Outputs the error and fail. This is because - `clang -###` pretty much never fails, but warns of failures on stderr instead. */ - failwithf - "Failed to execute compilation command. Output:@\n%s@\n*** Infer needs a working compilation command to run." - error - | ClangWarning warning => L.external_warning "%s@\n" warning - | Command clang_cmd => Capture.capture clang_cmd; - -let exe ::prog ::args => { - let xx_suffix = String.is_suffix suffix::"++" prog ? "++" : ""; - /* use clang in facebook-clang-plugins */ - let clang_xx = CFrontend_config.clang_bin xx_suffix; - check_for_existing_file args; - let commands = normalize prog::clang_xx ::args; - /* xcodebuild projects may require the object files to be generated by the Apple compiler, eg to - generate precompiled headers compatible with Apple's clang. */ - let (prog, should_run_original_command) = - switch Config.fcp_apple_clang { - | Some bin => - let bin_xx = bin ^ xx_suffix; - L.(debug Capture Medium) "Will run Apple clang %s" bin_xx; - (bin_xx, true) - | None => (clang_xx, false) - }; - List.iter f::exec_action_item commands; - if (List.is_empty commands || should_run_original_command) { - if (List.is_empty commands) { - /* No command to execute after -###, let's execute the original command - instead. - - In particular, this can happen when - - there are only assembly commands to execute, which we skip, or - - the user tries to run `infer -- clang -c file_that_does_not_exist.c`. In this case, this - will fail with the appropriate error message from clang instead of silently analyzing 0 - files. */ - L.(debug Capture Quiet) - "WARNING: `clang -### ` returned an empty set of commands to run and no error. Will run the original command directly:@\n %s@\n" - (String.concat sep::" " @@ [prog, ...args]) - }; - Process.create_process_and_wait ::prog ::args - } -}; diff --git a/infer/src/clang/ComponentKit.ml b/infer/src/clang/ComponentKit.ml index 48ce4400c..93773761a 100644 --- a/infer/src/clang/ComponentKit.ml +++ b/infer/src/clang/ComponentKit.ml @@ -9,33 +9,30 @@ open! IStd open! PVariant - module MF = MarkupFormatter let get_source_range an = match an with - | Ctl_parser_types.Decl decl -> - let decl_info = Clang_ast_proj.get_decl_tuple decl in + | Ctl_parser_types.Decl decl + -> let decl_info = Clang_ast_proj.get_decl_tuple decl in decl_info.Clang_ast_t.di_source_range - | Ctl_parser_types.Stmt stmt -> - let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in + | Ctl_parser_types.Stmt stmt + -> let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in stmt_info.Clang_ast_t.si_source_range let is_in_main_file translation_unit_context an = let file_opt = (fst (get_source_range an)).Clang_ast_t.sl_file in match file_opt with - | None -> - false - | Some source_file -> - SourceFile.equal (SourceFile.from_abs_path source_file) + | None + -> false + | Some source_file + -> SourceFile.equal (SourceFile.from_abs_path source_file) translation_unit_context.CFrontend_config.source_file let is_ck_context (context: CLintersContext.context) an = - context.is_ck_translation_unit - && is_in_main_file context.translation_unit_context an + context.is_ck_translation_unit && is_in_main_file context.translation_unit_context an && CGeneral_utils.is_objc_extension context.translation_unit_context - (** Recursively go up the inheritance hierarchy of a given ObjCInterfaceDecl. (Returns false on decls other than that one.) *) let is_component_or_controller_if decl = @@ -46,11 +43,12 @@ let is_component_or_controller_if decl = CKComponentController, false otherwise *) let rec is_component_or_controller_descendant_impl decl = match decl with - | Clang_ast_t.ObjCImplementationDecl _ -> - is_component_or_controller_if (CAst_utils.get_super_if (Some decl)) - | Clang_ast_t.LinkageSpecDecl (_, decl_list, _) -> - contains_ck_impl decl_list - | _ -> false + | Clang_ast_t.ObjCImplementationDecl _ + -> is_component_or_controller_if (CAst_utils.get_super_if (Some decl)) + | Clang_ast_t.LinkageSpecDecl (_, decl_list, _) + -> contains_ck_impl decl_list + | _ + -> false (** Returns true if the passed-in list of decls contains an ObjCImplementationDecl of a descendant of CKComponent or @@ -88,57 +86,63 @@ let mutable_local_vars_advice context an = let rec get_referenced_type (qual_type: Clang_ast_t.qual_type) : Clang_ast_t.decl option = let typ_opt = CAst_utils.get_desugared_type qual_type.qt_type_ptr in match (typ_opt : Clang_ast_t.c_type option) with - | Some ObjCInterfaceType (_, decl_ptr) - | Some RecordType (_, decl_ptr) -> CAst_utils.get_decl decl_ptr + | Some ObjCInterfaceType (_, decl_ptr) | Some RecordType (_, decl_ptr) + -> CAst_utils.get_decl decl_ptr | Some PointerType (_, inner_qual_type) | Some ObjCObjectPointerType (_, inner_qual_type) - | Some LValueReferenceType (_, inner_qual_type) -> get_referenced_type inner_qual_type - | _ -> None in - + | Some LValueReferenceType (_, inner_qual_type) + -> get_referenced_type inner_qual_type + | _ + -> None + in let is_of_whitelisted_type qual_type = - let cpp_whitelist = [ - "CKComponentScope"; - "FBTrackingNodeScope"; - "FBTrackingCodeScope"; - "CKComponentContext"; - "CKComponentKey" - ] in + let cpp_whitelist = + [ "CKComponentScope" + ; "FBTrackingNodeScope" + ; "FBTrackingCodeScope" + ; "CKComponentContext" + ; "CKComponentKey" ] + in let objc_whitelist = ["NSError"] in match get_referenced_type qual_type with - | Some CXXRecordDecl (_, ndi, _, _, _, _, _, _) -> - List.mem ~equal:String.equal cpp_whitelist ndi.ni_name - | Some ObjCInterfaceDecl (_, ndi, _, _, _) -> - List.mem ~equal:String.equal objc_whitelist ndi.ni_name - | _ -> false in - + | Some CXXRecordDecl (_, ndi, _, _, _, _, _, _) + -> List.mem ~equal:String.equal cpp_whitelist ndi.ni_name + | Some ObjCInterfaceDecl (_, ndi, _, _, _) + -> List.mem ~equal:String.equal objc_whitelist ndi.ni_name + | _ + -> false + in match an with - | Ctl_parser_types.Decl (Clang_ast_t.VarDecl(decl_info, named_decl_info, qual_type, _) as decl)-> - let is_const_ref = match CAst_utils.get_type qual_type.qt_type_ptr with - | Some LValueReferenceType (_, {Clang_ast_t.qt_is_const}) -> - qt_is_const - | _ -> false in + | Ctl_parser_types.Decl (Clang_ast_t.VarDecl (decl_info, named_decl_info, qual_type, _) as decl) + -> let is_const_ref = + match CAst_utils.get_type qual_type.qt_type_ptr with + | Some LValueReferenceType (_, {Clang_ast_t.qt_is_const}) + -> qt_is_const + | _ + -> false + in let is_const = qual_type.qt_is_const || is_const_ref in - let condition = is_ck_context context an - && (not (CAst_utils.is_syntactically_global_var decl)) - && (not is_const) - && not (is_of_whitelisted_type qual_type) - && not decl_info.di_is_implicit in + let condition = + is_ck_context context an && not (CAst_utils.is_syntactically_global_var decl) + && not is_const && not (is_of_whitelisted_type qual_type) && not decl_info.di_is_implicit + in if condition then - Some { - CIssue.id = "MUTABLE_LOCAL_VARIABLE_IN_COMPONENT_FILE"; - name = None; - severity = Exceptions.Kadvice; - mode = CIssue.On; - description = - "Local variable " ^ MF.monospaced_to_string named_decl_info.ni_name ^ - " should be const to avoid reassignment"; - suggestion = Some "Add a const (after the asterisk for pointer types)."; - doc_url = None; - loc = CFrontend_checkers.location_from_dinfo context decl_info - } + Some + { CIssue.id= "MUTABLE_LOCAL_VARIABLE_IN_COMPONENT_FILE" + ; name= None + ; severity= Exceptions.Kadvice + ; mode= CIssue.On + ; description= + "Local variable " ^ MF.monospaced_to_string named_decl_info.ni_name + ^ " should be const to avoid reassignment" + ; suggestion= Some "Add a const (after the asterisk for pointer types)." + ; doc_url= None + ; loc= CFrontend_checkers.location_from_dinfo context decl_info } else None - | _ -> None (* Should only be called with a VarDecl *) + | _ + -> None +(* Should only be called with a VarDecl *) (** Catches functions that should be composite components. http://componentkit.org/docs/break-out-composites.html @@ -146,30 +150,30 @@ let mutable_local_vars_advice context an = Any static function that returns a subclass of CKComponent will be flagged. *) let component_factory_function_advice context an = let is_component_if decl = - CAst_utils.is_objc_if_descendant decl [CFrontend_config.ckcomponent_cl] in - + CAst_utils.is_objc_if_descendant decl [CFrontend_config.ckcomponent_cl] + in match an with - | Ctl_parser_types.Decl (Clang_ast_t.FunctionDecl (decl_info, _, (qual_type: Clang_ast_t.qual_type), _)) -> - let objc_interface = - CAst_utils.qual_type_to_objc_interface qual_type in - let condition = - is_ck_context context an && is_component_if objc_interface in + | Ctl_parser_types.Decl + Clang_ast_t.FunctionDecl (decl_info, _, (qual_type: Clang_ast_t.qual_type), _) + -> let objc_interface = CAst_utils.qual_type_to_objc_interface qual_type in + let condition = is_ck_context context an && is_component_if objc_interface in if condition then - Some { - CIssue.id = "COMPONENT_FACTORY_FUNCTION"; - name = None; - severity = Exceptions.Kadvice; - mode = CIssue.Off; - description = "Break out composite components"; - suggestion = Some ( - "Prefer subclassing CKCompositeComponent to static helper functions \ - that return a CKComponent subclass." - ); - doc_url = None; - loc = CFrontend_checkers.location_from_dinfo context decl_info - } + Some + { CIssue.id= "COMPONENT_FACTORY_FUNCTION" + ; name= None + ; severity= Exceptions.Kadvice + ; mode= CIssue.Off + ; description= "Break out composite components" + ; suggestion= + Some + "Prefer subclassing CKCompositeComponent to static helper functions that return a CKComponent subclass." + ; doc_url= None + ; loc= CFrontend_checkers.location_from_dinfo context decl_info } else None - | _ -> None (* Should only be called with FunctionDecl *) + | _ + -> None + +(* Should only be called with FunctionDecl *) (** Components should not inherit from each other. They should instead inherit from CKComponent, CKCompositeComponent, or @@ -177,56 +181,58 @@ let component_factory_function_advice context an = let component_with_unconventional_superclass_advice context an = let check_interface if_decl = match if_decl with - | Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, _) -> - if is_component_or_controller_if (Some if_decl) then - let superclass_name = match CAst_utils.get_super_if (Some if_decl) with - | Some Clang_ast_t.ObjCInterfaceDecl (_, named_decl_info, _, _, _) -> - Some named_decl_info.ni_name - | _ -> None in + | Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, _) + -> if is_component_or_controller_if (Some if_decl) then + let superclass_name = + match CAst_utils.get_super_if (Some if_decl) with + | Some Clang_ast_t.ObjCInterfaceDecl (_, named_decl_info, _, _, _) + -> Some named_decl_info.ni_name + | _ + -> None + in let has_conventional_superclass = let open CFrontend_config in match superclass_name with - | Some name when List.mem ~equal:String.equal - [ - ckcomponent_cl; - ckcomponentcontroller_cl; - "CKCompositeComponent"; - "CKStatefulViewComponent"; - "CKStatefulViewComponentController"; - "NTNativeTemplateComponent" - ] - name -> true - | _ -> false in + | Some name + when List.mem ~equal:String.equal + [ ckcomponent_cl + ; ckcomponentcontroller_cl + ; "CKCompositeComponent" + ; "CKStatefulViewComponent" + ; "CKStatefulViewComponentController" + ; "NTNativeTemplateComponent" ] name + -> true + | _ + -> false + in let condition = - is_component_or_controller_if (Some if_decl) - && not has_conventional_superclass in + is_component_or_controller_if (Some if_decl) && not has_conventional_superclass + in if condition then - Some { - CIssue.id = "COMPONENT_WITH_UNCONVENTIONAL_SUPERCLASS"; - name = None; - severity = Exceptions.Kadvice; - mode = CIssue.On; - description = "Never Subclass Components"; - suggestion = Some ( - "Instead, create a new subclass of CKCompositeComponent." - ); - doc_url = None; - loc = CFrontend_checkers.location_from_decl context if_decl - } - else - None - else - None - | _ -> assert false in + Some + { CIssue.id= "COMPONENT_WITH_UNCONVENTIONAL_SUPERCLASS" + ; name= None + ; severity= Exceptions.Kadvice + ; mode= CIssue.On + ; description= "Never Subclass Components" + ; suggestion= Some "Instead, create a new subclass of CKCompositeComponent." + ; doc_url= None + ; loc= CFrontend_checkers.location_from_decl context if_decl } + else None + else None + | _ + -> assert false + in match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCImplementationDecl (_, _, _, _, impl_decl_info)) -> - let if_decl_opt = - CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface in + | Ctl_parser_types.Decl Clang_ast_t.ObjCImplementationDecl (_, _, _, _, impl_decl_info) + -> let if_decl_opt = + CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface + in if Option.is_some if_decl_opt && is_ck_context context an then check_interface (Option.value_exn if_decl_opt) - else - None - | _ -> None + else None + | _ + -> None (** Components should only have one factory method. @@ -242,42 +248,50 @@ let component_with_unconventional_superclass_advice context an = Given n factory methods, the rule should emit n-1 issues. Each issue's location should point to the method declaration. *) let component_with_multiple_factory_methods_advice context an = - let is_unavailable_attr attr = match attr with - | Clang_ast_t.UnavailableAttr _ -> true - | _ -> false in + let is_unavailable_attr attr = + match attr with Clang_ast_t.UnavailableAttr _ -> true | _ -> false + in let is_available_factory_method if_decl (decl: Clang_ast_t.decl) = - let attrs = match decl with - | ObjCMethodDecl (decl_info, _, _) -> decl_info.Clang_ast_t.di_attributes - | _ -> assert false in - let unavailable_attrs = (List.filter ~f:is_unavailable_attr attrs) in + let attrs = + match decl with + | ObjCMethodDecl (decl_info, _, _) + -> decl_info.Clang_ast_t.di_attributes + | _ + -> assert false + in + let unavailable_attrs = List.filter ~f:is_unavailable_attr attrs in let is_available = Int.equal (List.length unavailable_attrs) 0 in - (CAst_utils.is_objc_factory_method if_decl decl) && is_available in - + CAst_utils.is_objc_factory_method if_decl decl && is_available + in let check_interface if_decl = match if_decl with - | Clang_ast_t.ObjCInterfaceDecl (_, _, decls, _, _) -> - let factory_methods = List.filter ~f:(is_available_factory_method if_decl) decls in - List.map ~f:(fun meth_decl -> { - CIssue.id = "COMPONENT_WITH_MULTIPLE_FACTORY_METHODS"; - name = None; - severity = Exceptions.Kadvice; - mode = CIssue.On; - description = "Avoid Overrides"; - suggestion = - Some "Instead, always expose all parameters in a single \ - designated initializer and document which are optional."; - doc_url = None; - loc = CFrontend_checkers.location_from_decl context meth_decl - }) (List.drop factory_methods 1) - | _ -> assert false in + | Clang_ast_t.ObjCInterfaceDecl (_, _, decls, _, _) + -> let factory_methods = List.filter ~f:(is_available_factory_method if_decl) decls in + List.map + ~f:(fun meth_decl -> + { CIssue.id= "COMPONENT_WITH_MULTIPLE_FACTORY_METHODS" + ; name= None + ; severity= Exceptions.Kadvice + ; mode= CIssue.On + ; description= "Avoid Overrides" + ; suggestion= + Some + "Instead, always expose all parameters in a single designated initializer and document which are optional." + ; doc_url= None + ; loc= CFrontend_checkers.location_from_decl context meth_decl }) + (List.drop factory_methods 1) + | _ + -> assert false + in match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCImplementationDecl (_, _, _, _, impl_decl_info)) -> + | Ctl_parser_types.Decl Clang_ast_t.ObjCImplementationDecl (_, _, _, _, impl_decl_info) + -> ( let if_decl_opt = - CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface in - (match if_decl_opt with - | Some d when is_ck_context context an -> check_interface d - | _ -> []) - | _ -> [] + CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface + in + match if_decl_opt with Some d when is_ck_context context an -> check_interface d | _ -> [] ) + | _ + -> [] let in_ck_class (context: CLintersContext.context) = Option.value_map ~f:is_component_or_controller_descendant_impl ~default:false @@ -293,49 +307,50 @@ let in_ck_class (context: CLintersContext.context) = relies on other threads (dispatch_sync). Other side-effects, like reading of global variables, is not checked by this analyzer, although still an infraction of the rule. *) -let rec _component_initializer_with_side_effects_advice - (context: CLintersContext.context) call_stmt = +let rec _component_initializer_with_side_effects_advice (context: CLintersContext.context) + call_stmt = let condition = - in_ck_class context - && context.in_objc_static_factory_method - && (match context.current_objc_impl with - | Some d -> is_in_main_file context.translation_unit_context (Ctl_parser_types.Decl d) - | None -> false) in + in_ck_class context && context.in_objc_static_factory_method + && + match context.current_objc_impl with + | Some d + -> is_in_main_file context.translation_unit_context (Ctl_parser_types.Decl d) + | None + -> false + in if condition then match call_stmt with - | Clang_ast_t.ImplicitCastExpr (_, stmt :: _, _, _) -> - _component_initializer_with_side_effects_advice context stmt - | Clang_ast_t.DeclRefExpr (_, _, _, decl_ref_expr_info) -> - let refs = [decl_ref_expr_info.drti_decl_ref; - decl_ref_expr_info.drti_found_decl_ref] in - (match List.find_map ~f:CAst_utils.name_of_decl_ref_opt refs with - | Some "dispatch_after" - | Some "dispatch_async" - | Some "dispatch_sync" -> - Some { - CIssue.id = "COMPONENT_INITIALIZER_WITH_SIDE_EFFECTS"; - name = None; - severity = Exceptions.Kadvice; - mode = CIssue.On; - description = "No Side-effects"; - suggestion = Some "Your +new method should not modify any \ - global variables or global state."; - doc_url = None; - loc = CFrontend_checkers.location_from_stmt context call_stmt - } - | _ -> - None) - | _-> - None - else - None - -let component_initializer_with_side_effects_advice - (context: CLintersContext.context) an = + | Clang_ast_t.ImplicitCastExpr (_, stmt :: _, _, _) + -> _component_initializer_with_side_effects_advice context stmt + | Clang_ast_t.DeclRefExpr (_, _, _, decl_ref_expr_info) + -> ( + let refs = [decl_ref_expr_info.drti_decl_ref; decl_ref_expr_info.drti_found_decl_ref] in + match List.find_map ~f:CAst_utils.name_of_decl_ref_opt refs with + | Some "dispatch_after" | Some "dispatch_async" | Some "dispatch_sync" + -> Some + { CIssue.id= "COMPONENT_INITIALIZER_WITH_SIDE_EFFECTS" + ; name= None + ; severity= Exceptions.Kadvice + ; mode= CIssue.On + ; description= "No Side-effects" + ; suggestion= + Some "Your +new method should not modify any global variables or global state." + ; doc_url= None + ; loc= CFrontend_checkers.location_from_stmt context call_stmt } + | _ + -> None ) + | _ + -> None + else None + +let component_initializer_with_side_effects_advice (context: CLintersContext.context) an = match an with - | Ctl_parser_types.Stmt (CallExpr (_, called_func_stmt :: _, _)) -> - _component_initializer_with_side_effects_advice context called_func_stmt - | _ -> None (* only to be called in CallExpr *) + | Ctl_parser_types.Stmt CallExpr (_, called_func_stmt :: _, _) + -> _component_initializer_with_side_effects_advice context called_func_stmt + | _ + -> None + +(* only to be called in CallExpr *) (** Returns one issue per line of code, with the column set to 0. @@ -344,26 +359,22 @@ let component_initializer_with_side_effects_advice let component_file_line_count_info (context: CLintersContext.context) dec = let condition = Config.compute_analytics && context.is_ck_translation_unit in match dec with - | Ctl_parser_types.Decl Clang_ast_t.TranslationUnitDecl _ when condition -> - let source_file = - context.translation_unit_context.CFrontend_config.source_file in + | Ctl_parser_types.Decl Clang_ast_t.TranslationUnitDecl _ when condition + -> let source_file = context.translation_unit_context.CFrontend_config.source_file in let line_count = SourceFile.line_count source_file in - List.map ~f:(fun i -> { - CIssue.id = "COMPONENT_FILE_LINE_COUNT"; - name = None; - severity = Exceptions.Kinfo; - mode = CIssue.Off; - description = "Line count analytics"; - suggestion = None; - doc_url = None; - loc = { - Location.line = i; - Location.col = 0; - Location.file = source_file - } - } - ) (List.range 1 line_count ~start:`inclusive ~stop:`inclusive) - | _ -> [] + List.map + ~f:(fun i -> + { CIssue.id= "COMPONENT_FILE_LINE_COUNT" + ; name= None + ; severity= Exceptions.Kinfo + ; mode= CIssue.Off + ; description= "Line count analytics" + ; suggestion= None + ; doc_url= None + ; loc= {Location.line= i; Location.col= 0; Location.file= source_file} }) + (List.range 1 line_count ~start:`inclusive ~stop:`inclusive) + | _ + -> [] (** Computes a component file's cyclomatic complexity. @@ -371,7 +382,8 @@ let component_file_line_count_info (context: CLintersContext.context) dec = https://github.com/oclint/oclint/blob/5889b5ec168185513ba69ce83821ea1cc8e63fbe /oclint-metrics/lib/CyclomaticComplexityMetric.cpp *) let component_file_cyclomatic_complexity_info (context: CLintersContext.context) an = - let is_cyclo_stmt stmt = match stmt with + let is_cyclo_stmt stmt = + match stmt with | Clang_ast_t.IfStmt _ | Clang_ast_t.ForStmt _ | Clang_ast_t.ObjCForCollectionStmt _ @@ -381,29 +393,34 @@ let component_file_cyclomatic_complexity_info (context: CLintersContext.context) | Clang_ast_t.CaseStmt _ | Clang_ast_t.ObjCAtCatchStmt _ | Clang_ast_t.CXXCatchStmt _ - | Clang_ast_t.ConditionalOperator _ -> true - | Clang_ast_t.BinaryOperator (_, _, _, boi) -> - List.mem ~equal:(=) [`LAnd; `LOr] boi.Clang_ast_t.boi_kind - | _ -> false in - let cyclo_loc_opt an = match an with - | Ctl_parser_types.Stmt stmt when (Config.compute_analytics - && is_cyclo_stmt stmt - && is_ck_context context an) -> - Some (CFrontend_checkers.location_from_stmt context stmt) + | Clang_ast_t.ConditionalOperator _ + -> true + | Clang_ast_t.BinaryOperator (_, _, _, boi) + -> List.mem ~equal:( = ) [`LAnd; `LOr] boi.Clang_ast_t.boi_kind + | _ + -> false + in + let cyclo_loc_opt an = + match an with + | Ctl_parser_types.Stmt stmt + when Config.compute_analytics && is_cyclo_stmt stmt && is_ck_context context an + -> Some (CFrontend_checkers.location_from_stmt context stmt) | Ctl_parser_types.Decl (Clang_ast_t.TranslationUnitDecl _ as d) - when Config.compute_analytics && context.is_ck_translation_unit -> - Some (CFrontend_checkers.location_from_decl context d) - | _ -> None in + when Config.compute_analytics && context.is_ck_translation_unit + -> Some (CFrontend_checkers.location_from_decl context d) + | _ + -> None + in match cyclo_loc_opt an with - | Some loc -> - Some { - CIssue.id = "COMPONENT_FILE_CYCLOMATIC_COMPLEXITY"; - name = None; - severity = Exceptions.Kinfo; - mode = CIssue.Off; - description = "Cyclomatic Complexity Incremental Marker"; - suggestion = None; - doc_url = None; - loc = loc - } - | _ -> None + | Some loc + -> Some + { CIssue.id= "COMPONENT_FILE_CYCLOMATIC_COMPLEXITY" + ; name= None + ; severity= Exceptions.Kinfo + ; mode= CIssue.Off + ; description= "Cyclomatic Complexity Incremental Marker" + ; suggestion= None + ; doc_url= None + ; loc } + | _ + -> None diff --git a/infer/src/clang/ComponentKit.mli b/infer/src/clang/ComponentKit.mli index 6d5d37b5e..450b3918a 100644 --- a/infer/src/clang/ComponentKit.mli +++ b/infer/src/clang/ComponentKit.mli @@ -9,12 +9,12 @@ open! IStd +val contains_ck_impl : Clang_ast_t.decl list -> bool (** Returns true if the passed-in list of decls contains an ObjCImplementationDecl of a descendant of CKComponent or CKComponentController. Does not recurse into hierarchy. *) -val contains_ck_impl : Clang_ast_t.decl list -> bool val mutable_local_vars_advice : CLintersContext.context -> Ctl_parser_types.ast_node -> CIssue.issue_desc option diff --git a/infer/src/clang/ast_expressions.ml b/infer/src/clang/ast_expressions.ml index 3ada54197..cca7d8f38 100644 --- a/infer/src/clang/ast_expressions.ml +++ b/infer/src/clang/ast_expressions.ml @@ -10,74 +10,61 @@ (** This module creates extra ast constructs that are needed for the translation *) open! IStd - module L = Logging let dummy_source_range () = - let dummy_source_loc = { - Clang_ast_t.sl_file = None; - sl_line = None; - sl_column = None; - } in + let dummy_source_loc = {Clang_ast_t.sl_file= None; sl_line= None; sl_column= None} in (dummy_source_loc, dummy_source_loc) -let dummy_stmt_info () = { - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer (); - si_source_range = dummy_source_range (); -} +let dummy_stmt_info () = + {Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer (); si_source_range= dummy_source_range ()} (* given a stmt_info return the same stmt_info with a fresh pointer *) let fresh_stmt_info stmt_info = - { stmt_info with Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer () } + {stmt_info with Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer ()} let fresh_decl_info decl_info = - { decl_info with Clang_ast_t.di_pointer = CAst_utils.get_fresh_pointer () } - -let empty_decl_info = { - Clang_ast_t.di_pointer = CAst_utils.get_invalid_pointer (); - di_parent_pointer = None; - di_previous_decl = `None; - di_source_range = dummy_source_range (); - di_owning_module = None; - di_is_hidden = false; - di_is_implicit = false; - di_is_used = true; - di_is_this_declaration_referenced = true; - di_is_invalid_decl = false; - di_attributes = []; - di_full_comment = None; - di_access = `None; -} - -let empty_var_decl_info = { - Clang_ast_t.vdi_storage_class = None; - vdi_tls_kind =`Tls_none; - vdi_is_global = false; - vdi_is_static_local = false; - vdi_is_module_private = false; - vdi_is_nrvo_variable = false; - vdi_is_const_expr = false; - vdi_init_expr = None; - vdi_parm_index_in_function = None; -} - -let stmt_info_with_fresh_pointer stmt_info = { - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer (); - si_source_range = stmt_info.Clang_ast_t.si_source_range; -} - -let create_qual_type ?(quals=Typ.mk_type_quals ()) qt_type_ptr = - { Clang_ast_t.qt_type_ptr; - qt_is_const=Typ.is_const quals; - qt_is_volatile=Typ.is_volatile quals; - qt_is_restrict=Typ.is_restrict quals; - } + {decl_info with Clang_ast_t.di_pointer= CAst_utils.get_fresh_pointer ()} + +let empty_decl_info = + { Clang_ast_t.di_pointer= CAst_utils.get_invalid_pointer () + ; di_parent_pointer= None + ; di_previous_decl= `None + ; di_source_range= dummy_source_range () + ; di_owning_module= None + ; di_is_hidden= false + ; di_is_implicit= false + ; di_is_used= true + ; di_is_this_declaration_referenced= true + ; di_is_invalid_decl= false + ; di_attributes= [] + ; di_full_comment= None + ; di_access= `None } + +let empty_var_decl_info = + { Clang_ast_t.vdi_storage_class= None + ; vdi_tls_kind= `Tls_none + ; vdi_is_global= false + ; vdi_is_static_local= false + ; vdi_is_module_private= false + ; vdi_is_nrvo_variable= false + ; vdi_is_const_expr= false + ; vdi_init_expr= None + ; vdi_parm_index_in_function= None } + +let stmt_info_with_fresh_pointer stmt_info = + { Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer () + ; si_source_range= stmt_info.Clang_ast_t.si_source_range } + +let create_qual_type ?(quals= Typ.mk_type_quals ()) qt_type_ptr = + { Clang_ast_t.qt_type_ptr= qt_type_ptr + ; qt_is_const= Typ.is_const quals + ; qt_is_volatile= Typ.is_volatile quals + ; qt_is_restrict= Typ.is_restrict quals } let builtin_to_qual_type kind = create_qual_type (Clang_ast_extend.Builtin kind) - -let create_pointer_qual_type ?quals typ = - create_qual_type ?quals (Clang_ast_extend.PointerOf typ) +let create_pointer_qual_type ?quals typ = create_qual_type ?quals (Clang_ast_extend.PointerOf typ) let create_reference_qual_type ?quals typ = create_qual_type ?quals (Clang_ast_extend.ReferenceOf typ) @@ -94,6 +81,7 @@ let create_void_star_type = create_pointer_qual_type create_void_type let create_id_type = create_pointer_qual_type (builtin_to_qual_type `ObjCId) let create_char_type = builtin_to_qual_type `Char_S + let create_char_star_type ?quals () = create_pointer_qual_type ?quals create_char_type let create_BOOL_type = builtin_to_qual_type `SChar @@ -112,48 +100,34 @@ let make_objc_class_qual_type class_name = let create_integer_literal n = let stmt_info = dummy_stmt_info () in - let expr_info = { - Clang_ast_t.ei_qual_type = create_int_type; - ei_value_kind = `RValue; - ei_object_kind = `Ordinary; - } in - let integer_literal_info = { - Clang_ast_t.ili_is_signed = true; - ili_bitwidth = 32; - ili_value = n; - } in + let expr_info = + {Clang_ast_t.ei_qual_type= create_int_type; ei_value_kind= `RValue; ei_object_kind= `Ordinary} + in + let integer_literal_info = {Clang_ast_t.ili_is_signed= true; ili_bitwidth= 32; ili_value= n} in Clang_ast_t.IntegerLiteral (stmt_info, [], expr_info, integer_literal_info) let create_cstyle_cast_expr stmt_info stmts qt = - let expr_info = { - Clang_ast_t.ei_qual_type = create_void_star_type; - ei_value_kind = `RValue; - ei_object_kind = `Ordinary; - } in - let cast_expr = { - Clang_ast_t.cei_cast_kind = `NullToPointer; - cei_base_path = []; - } in + let expr_info = + { Clang_ast_t.ei_qual_type= create_void_star_type + ; ei_value_kind= `RValue + ; ei_object_kind= `Ordinary } + in + let cast_expr = {Clang_ast_t.cei_cast_kind= `NullToPointer; cei_base_path= []} in Clang_ast_t.CStyleCastExpr (stmt_info, stmts, expr_info, cast_expr, qt) let create_parent_expr stmt_info stmts = - let expr_info = { - Clang_ast_t.ei_qual_type = create_void_star_type; - ei_value_kind = `RValue; - ei_object_kind = `Ordinary; - } in + let expr_info = + { Clang_ast_t.ei_qual_type= create_void_star_type + ; ei_value_kind= `RValue + ; ei_object_kind= `Ordinary } + in Clang_ast_t.ParenExpr (stmt_info, stmts, expr_info) let create_implicit_cast_expr stmt_info stmts typ cast_kind = - let expr_info = { - Clang_ast_t.ei_qual_type = typ; - ei_value_kind = `RValue; - ei_object_kind = `Ordinary; - } in - let cast_expr_info = { - Clang_ast_t.cei_cast_kind = cast_kind; - cei_base_path = []; - } in + let expr_info = + {Clang_ast_t.ei_qual_type= typ; ei_value_kind= `RValue; ei_object_kind= `Ordinary} + in + let cast_expr_info = {Clang_ast_t.cei_cast_kind= cast_kind; cei_base_path= []} in Clang_ast_t.ImplicitCastExpr (stmt_info, stmts, expr_info, cast_expr_info) let create_nil stmt_info = @@ -165,147 +139,142 @@ let create_nil stmt_info = let dummy_stmt () = let pointer = CAst_utils.get_fresh_pointer () in let source_range = dummy_source_range () in - Clang_ast_t.NullStmt({ Clang_ast_t.si_pointer = pointer; si_source_range = source_range } ,[]) + Clang_ast_t.NullStmt ({Clang_ast_t.si_pointer= pointer; si_source_range= source_range}, []) -let make_stmt_info di = { - Clang_ast_t.si_pointer = di.Clang_ast_t.di_pointer; - si_source_range = di.Clang_ast_t.di_source_range; -} +let make_stmt_info di = + { Clang_ast_t.si_pointer= di.Clang_ast_t.di_pointer + ; si_source_range= di.Clang_ast_t.di_source_range } -let make_expr_info qt vk objc_kind = { - Clang_ast_t.ei_qual_type = qt; - ei_value_kind = vk; - ei_object_kind = objc_kind; -} +let make_expr_info qt vk objc_kind = + {Clang_ast_t.ei_qual_type= qt; ei_value_kind= vk; ei_object_kind= objc_kind} -let make_expr_info_with_objc_kind qt objc_kind = - make_expr_info qt `LValue objc_kind +let make_expr_info_with_objc_kind qt objc_kind = make_expr_info qt `LValue objc_kind let make_decl_ref_exp stmt_info expr_info drei = - let stmt_info = { - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer (); - si_source_range = stmt_info.Clang_ast_t.si_source_range - } in - Clang_ast_t.DeclRefExpr(stmt_info, [], expr_info, drei) - -let make_obj_c_message_expr_info_instance sel = { - Clang_ast_t.omei_selector = sel; - omei_receiver_kind = `Instance; - omei_is_definition_found = false; - omei_decl_pointer = None; (* TODO look into it *) -} - -let make_obj_c_message_expr_info_class selector tname pointer = { - Clang_ast_t.omei_selector = selector; - omei_receiver_kind = `Class (create_class_qual_type tname); - omei_is_definition_found = false; - omei_decl_pointer = pointer -} - -let make_decl_ref k decl_ptr name is_hidden qt_opt = { - Clang_ast_t.dr_kind = k; - dr_decl_pointer = decl_ptr; - dr_name = Some name; - dr_is_hidden = is_hidden ; - dr_qual_type = qt_opt -} + let stmt_info = + { Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer () + ; si_source_range= stmt_info.Clang_ast_t.si_source_range } + in + Clang_ast_t.DeclRefExpr (stmt_info, [], expr_info, drei) + +let make_obj_c_message_expr_info_instance sel = + { Clang_ast_t.omei_selector= sel + ; omei_receiver_kind= `Instance + ; omei_is_definition_found= false + ; omei_decl_pointer= None (* TODO look into it *) } + +let make_obj_c_message_expr_info_class selector tname pointer = + { Clang_ast_t.omei_selector= selector + ; omei_receiver_kind= `Class (create_class_qual_type tname) + ; omei_is_definition_found= false + ; omei_decl_pointer= pointer } + +let make_decl_ref k decl_ptr name is_hidden qt_opt = + { Clang_ast_t.dr_kind= k + ; dr_decl_pointer= decl_ptr + ; dr_name= Some name + ; dr_is_hidden= is_hidden + ; dr_qual_type= qt_opt } let make_decl_ref_qt k decl_ptr name is_hidden qt = make_decl_ref k decl_ptr name is_hidden (Some qt) -let make_decl_ref_no_qt k decl_ptr name is_hidden = - make_decl_ref k decl_ptr name is_hidden None +let make_decl_ref_no_qt k decl_ptr name is_hidden = make_decl_ref k decl_ptr name is_hidden None let make_decl_ref_invalid k name is_hidden qt = make_decl_ref k (CAst_utils.get_invalid_pointer ()) name is_hidden (Some qt) -let make_decl_ref_expr_info decl_ref = { - Clang_ast_t.drti_decl_ref = Some decl_ref; - drti_found_decl_ref = None; -} +let make_decl_ref_expr_info decl_ref = + {Clang_ast_t.drti_decl_ref= Some decl_ref; drti_found_decl_ref= None} -let make_expr_info qt = { - Clang_ast_t.ei_qual_type = qt; - ei_value_kind = `LValue; - ei_object_kind = `ObjCProperty -} +let make_expr_info qt = + {Clang_ast_t.ei_qual_type= qt; ei_value_kind= `LValue; ei_object_kind= `ObjCProperty} -let make_general_expr_info qt vk ok = { - Clang_ast_t.ei_qual_type = qt; - ei_value_kind = vk; - ei_object_kind = ok -} +let make_general_expr_info qt vk ok = + {Clang_ast_t.ei_qual_type= qt; ei_value_kind= vk; ei_object_kind= ok} let make_ObjCBoolLiteralExpr stmt_info value = let ei = make_expr_info create_BOOL_type in - Clang_ast_t.ObjCBoolLiteralExpr((fresh_stmt_info stmt_info),[], ei, value) + Clang_ast_t.ObjCBoolLiteralExpr (fresh_stmt_info stmt_info, [], ei, value) let make_message_expr param_qt selector decl_ref_exp stmt_info add_cast = let stmt_info = stmt_info_with_fresh_pointer stmt_info in let parameters = if add_cast then - let cast_expr = create_implicit_cast_expr stmt_info [decl_ref_exp] param_qt `LValueToRValue in + let cast_expr = + create_implicit_cast_expr stmt_info [decl_ref_exp] param_qt `LValueToRValue + in [cast_expr] - else [decl_ref_exp] in + else [decl_ref_exp] + in let obj_c_message_expr_info = make_obj_c_message_expr_info_instance selector in let expr_info = make_expr_info_with_objc_kind param_qt `ObjCProperty in Clang_ast_t.ObjCMessageExpr (stmt_info, parameters, expr_info, obj_c_message_expr_info) let make_binary_stmt stmt1 stmt2 stmt_info expr_info boi = let stmt_info = stmt_info_with_fresh_pointer stmt_info in - Clang_ast_t.BinaryOperator(stmt_info, [stmt1; stmt2], expr_info, boi) + Clang_ast_t.BinaryOperator (stmt_info, [stmt1; stmt2], expr_info, boi) let make_next_object_exp stmt_info item items = let var_decl_ref, var_type = match item with - | Clang_ast_t.DeclStmt (_, _, [Clang_ast_t.VarDecl(di, name_info, var_qual_type, _)]) -> - let decl_ptr = di.Clang_ast_t.di_pointer in + | Clang_ast_t.DeclStmt (_, _, [(Clang_ast_t.VarDecl (di, name_info, var_qual_type, _))]) + -> let decl_ptr = di.Clang_ast_t.di_pointer in let decl_ref = make_decl_ref_qt `Var decl_ptr name_info false var_qual_type in - let stmt_info_var = { - Clang_ast_t.si_pointer = di.Clang_ast_t.di_pointer; - si_source_range = di.Clang_ast_t.di_source_range - } in + let stmt_info_var = + { Clang_ast_t.si_pointer= di.Clang_ast_t.di_pointer + ; si_source_range= di.Clang_ast_t.di_source_range } + in let expr_info = make_expr_info_with_objc_kind var_qual_type `ObjCProperty in let decl_ref_expr_info = make_decl_ref_expr_info decl_ref in - Clang_ast_t.DeclRefExpr (stmt_info_var, [], expr_info, decl_ref_expr_info), - var_qual_type - | _ -> assert false in - let message_call = make_message_expr create_id_type - CFrontend_config.next_object items stmt_info false in - let boi = { Clang_ast_t.boi_kind = `Assign } in + (Clang_ast_t.DeclRefExpr (stmt_info_var, [], expr_info, decl_ref_expr_info), var_qual_type) + | _ + -> assert false + in + let message_call = + make_message_expr create_id_type CFrontend_config.next_object items stmt_info false + in + let boi = {Clang_ast_t.boi_kind= `Assign} in let expr_info = make_expr_info_with_objc_kind var_type `ObjCProperty in let assignment = make_binary_stmt var_decl_ref message_call stmt_info expr_info boi in - let boi' = { Clang_ast_t.boi_kind = `NE } in + let boi' = {Clang_ast_t.boi_kind= `NE} in let cast = create_implicit_cast_expr stmt_info [var_decl_ref] var_type `LValueToRValue in let nil_exp = create_nil stmt_info in let loop_cond = make_binary_stmt cast nil_exp stmt_info expr_info boi' in - assignment, loop_cond + (assignment, loop_cond) (* 1. dispatch_once(v,block_def) is transformed as: block_def() *) (* 2. dispatch_once(v,block_var) is transformed as n$1 = *&block_var; n$2=n$1() *) let translate_dispatch_function stmt_info stmt_list n = let open Clang_ast_t in match stmt_list with - | _:: args_stmts -> - let expr_info_call = make_general_expr_info create_void_star_type `XValue `Ordinary in - let arg_stmt = try List.nth_exn args_stmts n with Failure _ -> assert false in + | _ :: args_stmts + -> let expr_info_call = make_general_expr_info create_void_star_type `XValue `Ordinary in + let arg_stmt = + try List.nth_exn args_stmts n + with Failure _ -> assert false + in CallExpr (stmt_info, [arg_stmt], expr_info_call) - | _ -> assert false + | _ + -> assert false (* Create declaration statement: qt vname = iexp *) let make_DeclStmt stmt_info di qt vname old_vdi iexp = - let init_expr_opt, init_expr_l = match iexp with - | Some iexp' -> - let ie = create_implicit_cast_expr stmt_info [iexp'] qt `IntegralCast in - Some ie, [ie] - | None -> None, [] in - let var_decl_info = { old_vdi with Clang_ast_t.vdi_init_expr = init_expr_opt } in + let init_expr_opt, init_expr_l = + match iexp with + | Some iexp' + -> let ie = create_implicit_cast_expr stmt_info [iexp'] qt `IntegralCast in + (Some ie, [ie]) + | None + -> (None, []) + in + let var_decl_info = {old_vdi with Clang_ast_t.vdi_init_expr= init_expr_opt} in let di = fresh_decl_info di in let var_decl = Clang_ast_t.VarDecl (di, vname, qt, var_decl_info) in Clang_ast_t.DeclStmt (stmt_info, init_expr_l, [var_decl]) let build_OpaqueValueExpr si source_expr ei = - let opaque_value_expr_info = { Clang_ast_t.ovei_source_expr = Some source_expr } in + let opaque_value_expr_info = {Clang_ast_t.ovei_source_expr= Some source_expr} in Clang_ast_t.OpaqueValueExpr (si, [], ei, opaque_value_expr_info) let pseudo_object_qt = make_objc_class_qual_type CFrontend_config.pseudo_object_type @@ -313,36 +282,41 @@ let pseudo_object_qt = make_objc_class_qual_type CFrontend_config.pseudo_object_ (* Create expression PseudoObjectExpr for 'o.m' *) let build_PseudoObjectExpr qt_m o_cast_decl_ref_exp mname = match o_cast_decl_ref_exp with - | Clang_ast_t.ImplicitCastExpr (si, _, ei, _) -> - let ove = build_OpaqueValueExpr si o_cast_decl_ref_exp ei in + | Clang_ast_t.ImplicitCastExpr (si, _, ei, _) + -> let ove = build_OpaqueValueExpr si o_cast_decl_ref_exp ei in let ei_opre = make_expr_info pseudo_object_qt in let count_name = CAst_utils.make_name_decl CFrontend_config.count in let pointer = si.Clang_ast_t.si_pointer in - let obj_c_property_ref_expr_info = { - Clang_ast_t.oprei_kind = - `PropertyRef (make_decl_ref_no_qt `ObjCProperty pointer count_name false); - oprei_is_super_receiver = false; - oprei_is_messaging_getter = true; - oprei_is_messaging_setter = false; - } in - let opre = Clang_ast_t.ObjCPropertyRefExpr (si, [ove], ei_opre, obj_c_property_ref_expr_info) in + let obj_c_property_ref_expr_info = + { Clang_ast_t.oprei_kind= + `PropertyRef (make_decl_ref_no_qt `ObjCProperty pointer count_name false) + ; oprei_is_super_receiver= false + ; oprei_is_messaging_getter= true + ; oprei_is_messaging_setter= false } + in + let opre = + Clang_ast_t.ObjCPropertyRefExpr (si, [ove], ei_opre, obj_c_property_ref_expr_info) + in let ome = make_message_expr qt_m mname o_cast_decl_ref_exp si false in let poe_ei = make_general_expr_info qt_m `LValue `Ordinary in Clang_ast_t.PseudoObjectExpr (si, [opre; ove; ome], poe_ei) - | _ -> assert false + | _ + -> assert false let create_call stmt_info decl_pointer function_name qt parameters = - let expr_info_call = { - Clang_ast_t.ei_qual_type = create_void_star_type; - ei_value_kind = `XValue; - ei_object_kind = `Ordinary - } in + let expr_info_call = + { Clang_ast_t.ei_qual_type= create_void_star_type + ; ei_value_kind= `XValue + ; ei_object_kind= `Ordinary } + in let expr_info_dre = make_expr_info_with_objc_kind qt `Ordinary in let decl_ref = make_decl_ref_qt `Function decl_pointer function_name false qt in let decl_ref_info = make_decl_ref_expr_info decl_ref in let decl_ref_exp = Clang_ast_t.DeclRefExpr (stmt_info, [], expr_info_dre, decl_ref_info) in - let cast = create_implicit_cast_expr (fresh_stmt_info stmt_info) [decl_ref_exp] qt `FunctionToPointerDecay in - Clang_ast_t.CallExpr (stmt_info, cast:: parameters, expr_info_call) + let cast = + create_implicit_cast_expr (fresh_stmt_info stmt_info) [decl_ref_exp] qt `FunctionToPointerDecay + in + Clang_ast_t.CallExpr (stmt_info, cast :: parameters, expr_info_call) (* For a of type NSArray* Translate [a enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL * stop) { @@ -368,179 +342,220 @@ let create_call stmt_info decl_pointer function_name qt parameters = free(stop); *) let translate_block_enumerate block_name stmt_info stmt_list ei = - let rec get_name_pointers lp = match lp with - | [] -> [] - | Clang_ast_t.ParmVarDecl (di, name, qt, _) :: lp' -> - (name.Clang_ast_t.ni_name, di.Clang_ast_t.di_pointer, qt):: get_name_pointers lp' - | _ -> assert false in - + | [] + -> [] + | (Clang_ast_t.ParmVarDecl (di, name, qt, _)) :: lp' + -> (name.Clang_ast_t.ni_name, di.Clang_ast_t.di_pointer, qt) :: get_name_pointers lp' + | _ + -> assert false + in let build_idx_decl pidx = match pidx with - | Clang_ast_t.ParmVarDecl (di_idx, name_idx, qt_idx, vdi) -> - let zero = create_integer_literal "0" in + | Clang_ast_t.ParmVarDecl (di_idx, name_idx, qt_idx, vdi) + -> let zero = create_integer_literal "0" in (* qt_idx idx = 0; *) - let idx_decl_stmt = make_DeclStmt (fresh_stmt_info stmt_info) di_idx qt_idx - name_idx vdi (Some zero) in + let idx_decl_stmt = + make_DeclStmt (fresh_stmt_info stmt_info) di_idx qt_idx name_idx vdi (Some zero) + in let idx_ei = make_expr_info qt_idx in let pointer = di_idx.Clang_ast_t.di_pointer in let idx_decl_ref = make_decl_ref_qt `Var pointer name_idx false qt_idx in let idx_drei = make_decl_ref_expr_info idx_decl_ref in let idx_decl_ref_exp = make_decl_ref_exp stmt_info idx_ei idx_drei in - let idx_cast = create_implicit_cast_expr (fresh_stmt_info stmt_info) [idx_decl_ref_exp] - qt_idx `LValueToRValue in - idx_decl_stmt, idx_decl_ref_exp, idx_cast, qt_idx - | _ -> assert false in - + let idx_cast = + create_implicit_cast_expr (fresh_stmt_info stmt_info) [idx_decl_ref_exp] qt_idx + `LValueToRValue + in + (idx_decl_stmt, idx_decl_ref_exp, idx_cast, qt_idx) + | _ + -> assert false + in let cast_expr decl_ref qt = let ei = make_expr_info qt in let drei = make_decl_ref_expr_info decl_ref in let decl_ref_exp = make_decl_ref_exp (fresh_stmt_info stmt_info) ei drei in - create_implicit_cast_expr (fresh_stmt_info stmt_info) [decl_ref_exp] qt `LValueToRValue in - + create_implicit_cast_expr (fresh_stmt_info stmt_info) [decl_ref_exp] qt `LValueToRValue + in (* build statement BOOL *stop = malloc(sizeof(BOOL)); *) let build_stop pstop = match pstop with - | Clang_ast_t.ParmVarDecl (di, name, qt, vdi) -> - let qt_fun = create_void_unsigned_long_type in + | Clang_ast_t.ParmVarDecl (di, name, qt, vdi) + -> let qt_fun = create_void_unsigned_long_type in let type_opt = Some create_BOOL_type in - let parameter = Clang_ast_t.UnaryExprOrTypeTraitExpr - ((fresh_stmt_info stmt_info), [], - make_general_expr_info create_unsigned_long_type `RValue `Ordinary, - {Clang_ast_t.uttei_kind = `SizeOfWithSize 1; - Clang_ast_t.uttei_qual_type = type_opt}) in + let parameter = + Clang_ast_t.UnaryExprOrTypeTraitExpr + ( fresh_stmt_info stmt_info + , [] + , make_general_expr_info create_unsigned_long_type `RValue `Ordinary + , {Clang_ast_t.uttei_kind= `SizeOfWithSize 1; Clang_ast_t.uttei_qual_type= type_opt} ) + in let pointer = di.Clang_ast_t.di_pointer in let stmt_info = fresh_stmt_info stmt_info in let malloc_name = CAst_utils.make_name_decl CFrontend_config.malloc in let malloc = create_call stmt_info pointer malloc_name qt_fun [parameter] in - let init_exp = create_implicit_cast_expr (fresh_stmt_info stmt_info) [malloc] qt `BitCast in + let init_exp = + create_implicit_cast_expr (fresh_stmt_info stmt_info) [malloc] qt `BitCast + in make_DeclStmt (fresh_stmt_info stmt_info) di qt name vdi (Some init_exp) - | _ -> assert false in - + | _ + -> assert false + in (* BOOL *stop =NO; *) let stop_equal_no pstop = match pstop with - | Clang_ast_t.ParmVarDecl (di, name, qt, _) -> - let decl_ref = make_decl_ref_qt `Var di.Clang_ast_t.di_pointer name false qt in + | Clang_ast_t.ParmVarDecl (di, name, qt, _) + -> let decl_ref = make_decl_ref_qt `Var di.Clang_ast_t.di_pointer name false qt in let cast = cast_expr decl_ref qt in - let postfix_deref = { Clang_ast_t.uoi_kind = `Deref; uoi_is_postfix = true } in - let lhs = Clang_ast_t.UnaryOperator (fresh_stmt_info stmt_info, [cast], ei, postfix_deref) in + let postfix_deref = {Clang_ast_t.uoi_kind= `Deref; uoi_is_postfix= true} in + let lhs = + Clang_ast_t.UnaryOperator (fresh_stmt_info stmt_info, [cast], ei, postfix_deref) + in let bool_NO = make_ObjCBoolLiteralExpr stmt_info 0 in - let assign = { Clang_ast_t.boi_kind = `Assign } in + let assign = {Clang_ast_t.boi_kind= `Assign} in Clang_ast_t.BinaryOperator (fresh_stmt_info stmt_info, [lhs; bool_NO], ei, assign) - | _ -> assert false in - + | _ + -> assert false + in (* build statement free(stop); *) let free_stop pstop = match pstop with - | Clang_ast_t.ParmVarDecl (di, name, qt, _) -> - let qt_fun = create_void_void_type in + | Clang_ast_t.ParmVarDecl (di, name, qt, _) + -> let qt_fun = create_void_void_type in let decl_ref = make_decl_ref_qt `Var di.Clang_ast_t.di_pointer name false qt in let cast = cast_expr decl_ref qt in let free_name = CAst_utils.make_name_decl CFrontend_config.free in let parameter = - create_implicit_cast_expr (fresh_stmt_info stmt_info) [cast] create_void_star_type `BitCast in + create_implicit_cast_expr (fresh_stmt_info stmt_info) [cast] create_void_star_type + `BitCast + in let pointer = di.Clang_ast_t.di_pointer in create_call (fresh_stmt_info stmt_info) pointer free_name qt_fun [parameter] - | _ -> assert false in - + | _ + -> assert false + in (* idx ei - | _ -> assert false in - + match cast with Clang_ast_t.ImplicitCastExpr (_, _, ei, _) -> ei | _ -> assert false + in (* id object = objects[idx]; *) let build_object_DeclStmt pobj decl_ref_expr_array decl_ref_expr_idx = let open Clang_ast_t in match pobj with - | ParmVarDecl(di_obj, name_obj, qt_obj, _) -> - let poe_ei = make_general_expr_info qt_obj `RValue `Ordinary in + | ParmVarDecl (di_obj, name_obj, qt_obj, _) + -> let poe_ei = make_general_expr_info qt_obj `RValue `Ordinary in let ei_array = get_ei_from_cast decl_ref_expr_array in - let ove_array = build_OpaqueValueExpr (fresh_stmt_info stmt_info) decl_ref_expr_array ei_array in + let ove_array = + build_OpaqueValueExpr (fresh_stmt_info stmt_info) decl_ref_expr_array ei_array + in let ei_idx = get_ei_from_cast decl_ref_expr_idx in let ove_idx = build_OpaqueValueExpr (fresh_stmt_info stmt_info) decl_ref_expr_idx ei_idx in - let objc_sre = ObjCSubscriptRefExpr (fresh_stmt_info stmt_info, [ove_array; ove_idx], - make_expr_info pseudo_object_qt, - { osrei_kind =`ArraySubscript; osrei_getter = None; osrei_setter = None; }) in - let obj_c_message_expr_info = make_obj_c_message_expr_info_instance CFrontend_config.object_at_indexed_subscript_m in - let ome = ObjCMessageExpr (fresh_stmt_info stmt_info, [ove_array; ove_idx], poe_ei, obj_c_message_expr_info) in - let pseudo_obj_expr = PseudoObjectExpr (fresh_stmt_info stmt_info, [objc_sre; ove_array; ove_idx; ome], poe_ei) in - let vdi = { empty_var_decl_info with vdi_init_expr = Some (pseudo_obj_expr) } in + let objc_sre = + ObjCSubscriptRefExpr + ( fresh_stmt_info stmt_info + , [ove_array; ove_idx] + , make_expr_info pseudo_object_qt + , {osrei_kind= `ArraySubscript; osrei_getter= None; osrei_setter= None} ) + in + let obj_c_message_expr_info = + make_obj_c_message_expr_info_instance CFrontend_config.object_at_indexed_subscript_m + in + let ome = + ObjCMessageExpr + (fresh_stmt_info stmt_info, [ove_array; ove_idx], poe_ei, obj_c_message_expr_info) + in + let pseudo_obj_expr = + PseudoObjectExpr (fresh_stmt_info stmt_info, [objc_sre; ove_array; ove_idx; ome], poe_ei) + in + let vdi = {empty_var_decl_info with vdi_init_expr= Some pseudo_obj_expr} in let var_decl = VarDecl (di_obj, name_obj, qt_obj, vdi) in DeclStmt (fresh_stmt_info stmt_info, [pseudo_obj_expr], [var_decl]) - | _ -> assert false in - + | _ + -> assert false + in (* NSArray *objects = a *) let objects_array_DeclStmt init = - let di = { empty_decl_info with Clang_ast_t.di_pointer = CAst_utils.get_fresh_pointer () } in + let di = {empty_decl_info with Clang_ast_t.di_pointer= CAst_utils.get_fresh_pointer ()} in let qt = create_pointer_qual_type (make_objc_class_qual_type CFrontend_config.nsarray_cl) in (* init should be ImplicitCastExpr of array a *) - let vdi = { empty_var_decl_info with Clang_ast_t.vdi_init_expr = Some (init) } in + let vdi = {empty_var_decl_info with Clang_ast_t.vdi_init_expr= Some init} in let objects_name = CAst_utils.make_name_decl CFrontend_config.objects in let var_decl = Clang_ast_t.VarDecl (di, objects_name, qt, vdi) in - Clang_ast_t.DeclStmt (fresh_stmt_info stmt_info, [init], [var_decl]), [(CFrontend_config.objects, di.Clang_ast_t.di_pointer, qt)] in - + ( Clang_ast_t.DeclStmt (fresh_stmt_info stmt_info, [init], [var_decl]) + , [(CFrontend_config.objects, di.Clang_ast_t.di_pointer, qt)] ) + in let make_object_cast_decl_ref_expr objects = match objects with - | Clang_ast_t.DeclStmt (si, _, [Clang_ast_t.VarDecl (_, name, qt, _)]) -> - let decl_ref = make_decl_ref_qt `Var si.Clang_ast_t.si_pointer name false qt in + | Clang_ast_t.DeclStmt (si, _, [(Clang_ast_t.VarDecl (_, name, qt, _))]) + -> let decl_ref = make_decl_ref_qt `Var si.Clang_ast_t.si_pointer name false qt in cast_expr decl_ref qt - | _ -> assert false in - + | _ + -> assert false + in let build_cast_decl_ref_expr_from_parm p = match p with - | Clang_ast_t.ParmVarDecl (di, name, qt, _) -> - let decl_ref = make_decl_ref_qt `Var di.Clang_ast_t.di_pointer name false qt in + | Clang_ast_t.ParmVarDecl (di, name, qt, _) + -> let decl_ref = make_decl_ref_qt `Var di.Clang_ast_t.di_pointer name false qt in cast_expr decl_ref qt - | _ -> assert false in - + | _ + -> assert false + in let qual_block_name = CAst_utils.make_name_decl block_name in - let make_block_decl be = match be with - | Clang_ast_t.BlockExpr (bsi, _, bei, _) -> - let di = { empty_decl_info with di_pointer = CAst_utils.get_fresh_pointer () } in - let vdi = { empty_var_decl_info with Clang_ast_t.vdi_init_expr = Some (be) } in + | Clang_ast_t.BlockExpr (bsi, _, bei, _) + -> let di = {empty_decl_info with di_pointer= CAst_utils.get_fresh_pointer ()} in + let vdi = {empty_var_decl_info with Clang_ast_t.vdi_init_expr= Some be} in let qt = bei.Clang_ast_t.ei_qual_type in let var_decl = Clang_ast_t.VarDecl (di, qual_block_name, qt, vdi) in - Clang_ast_t.DeclStmt (bsi, [be], [var_decl]), [(block_name, di.Clang_ast_t.di_pointer, qt)] - | _ -> assert false in - + ( Clang_ast_t.DeclStmt (bsi, [be], [var_decl]) + , [(block_name, di.Clang_ast_t.di_pointer, qt)] ) + | _ + -> assert false + in let make_block_call block_qt object_cast idx_cast stop_cast = let decl_ref = make_decl_ref_invalid `Var qual_block_name false block_qt in let fun_cast = cast_expr decl_ref block_qt in let ei_call = make_expr_info create_void_star_type in - Clang_ast_t.CallExpr (fresh_stmt_info stmt_info, [fun_cast; object_cast; idx_cast; stop_cast], ei_call) in - + Clang_ast_t.CallExpr + (fresh_stmt_info stmt_info, [fun_cast; object_cast; idx_cast; stop_cast], ei_call) + in (* build statement "if (stop) break;" *) let build_if_stop stop_cast = let bool_qt = create_BOOL_type in let ei = make_expr_info bool_qt in - let unary_op = Clang_ast_t.UnaryOperator (fresh_stmt_info stmt_info, [stop_cast], ei, { Clang_ast_t.uoi_kind = `Deref; uoi_is_postfix = true }) in - let cond = create_implicit_cast_expr (fresh_stmt_info stmt_info) [unary_op] bool_qt `LValueToRValue in + let unary_op = + Clang_ast_t.UnaryOperator + ( fresh_stmt_info stmt_info + , [stop_cast] + , ei + , {Clang_ast_t.uoi_kind= `Deref; uoi_is_postfix= true} ) + in + let cond = + create_implicit_cast_expr (fresh_stmt_info stmt_info) [unary_op] bool_qt `LValueToRValue + in let break_stmt = Clang_ast_t.BreakStmt (fresh_stmt_info stmt_info, []) in Clang_ast_t.IfStmt - (fresh_stmt_info stmt_info, [dummy_stmt(); dummy_stmt (); cond; break_stmt; dummy_stmt ()]) in - + (fresh_stmt_info stmt_info, [dummy_stmt (); dummy_stmt (); cond; break_stmt; dummy_stmt ()]) + in let translate params array_cast_decl_ref_exp block_decl block_qt = match params with - | [pobj; pidx; pstop] -> - let objects_decl, op = objects_array_DeclStmt array_cast_decl_ref_exp in + | [pobj; pidx; pstop] + -> let objects_decl, op = objects_array_DeclStmt array_cast_decl_ref_exp in let decl_stop = build_stop pstop in let assign_stop = stop_equal_no pstop in let objects = make_object_cast_decl_ref_expr objects_decl in @@ -553,27 +568,42 @@ let translate_block_enumerate block_name stmt_info stmt_list ei = let call_block = make_block_call block_qt object_cast idx_cast stop_cast in let if_stop = build_if_stop stop_cast in let free_stop = free_stop pstop in - [ objects_decl; block_decl; decl_stop; assign_stop; - Clang_ast_t.ForStmt (stmt_info, [idx_decl_stmt; dummy_stmt (); guard; incr; - Clang_ast_t.CompoundStmt(stmt_info, [obj_assignment; call_block; if_stop])]); free_stop], op - | _ -> assert false in + ( [ objects_decl + ; block_decl + ; decl_stop + ; assign_stop + ; Clang_ast_t.ForStmt + ( stmt_info + , [ idx_decl_stmt + ; dummy_stmt () + ; guard + ; incr + ; Clang_ast_t.CompoundStmt (stmt_info, [obj_assignment; call_block; if_stop]) ] ) + ; free_stop ] + , op ) + | _ + -> assert false + in let open Clang_ast_t in match stmt_list with - | [s; BlockExpr (_, _, bei, BlockDecl (_, bdi)) as be] -> - let block_decl, bv = make_block_decl be in + | [s; (BlockExpr (_, _, bei, BlockDecl (_, bdi)) as be)] + -> let block_decl, bv = make_block_decl be in let vars_to_register = get_name_pointers bdi.bdi_parameters in let translated_stmt, op = translate bdi.bdi_parameters s block_decl bei.ei_qual_type in - CompoundStmt (stmt_info, translated_stmt), vars_to_register @ op @ bv - | _ -> (* When it is not the method we expect with only one parameter, we don't translate *) - L.(debug Capture Verbose) "WARNING: Block Enumeration called at %s not translated." - (Clang_ast_j.string_of_stmt_info stmt_info); - CompoundStmt (stmt_info, stmt_list), [] + (CompoundStmt (stmt_info, translated_stmt), vars_to_register @ op @ bv) + | _ + -> (* When it is not the method we expect with only one parameter, we don't translate *) + L.(debug Capture Verbose) + "WARNING: Block Enumeration called at %s not translated." + (Clang_ast_j.string_of_stmt_info stmt_info) ; + (CompoundStmt (stmt_info, stmt_list), []) (* We translate an expression with a conditional*) (* x <=> x?1:0 *) let trans_with_conditional stmt_info expr_info stmt_list = let stmt_list_cond = stmt_list @ [create_integer_literal "1"] @ [create_integer_literal "0"] in Clang_ast_t.ConditionalOperator (stmt_info, stmt_list_cond, expr_info) + (* We translate the logical negation of an expression with a conditional*) (* !x <=> x?0:1 *) let trans_negation_with_conditional stmt_info expr_info stmt_list = diff --git a/infer/src/clang/ast_expressions.mli b/infer/src/clang/ast_expressions.mli index e1a90fba7..7d9794ddf 100644 --- a/infer/src/clang/ast_expressions.mli +++ b/infer/src/clang/ast_expressions.mli @@ -8,7 +8,6 @@ *) open! IStd - open Clang_ast_t (** This module creates extra ast constructs that are needed for the translation *) @@ -41,8 +40,8 @@ val make_stmt_info : decl_info -> stmt_info val make_decl_ref_expr_info : decl_ref -> decl_ref_expr_info -val make_next_object_exp : stmt_info -> stmt -> Clang_ast_t.stmt -> - Clang_ast_t.stmt * Clang_ast_t.stmt +val make_next_object_exp : + stmt_info -> stmt -> Clang_ast_t.stmt -> Clang_ast_t.stmt * Clang_ast_t.stmt val create_nil : stmt_info -> stmt @@ -50,19 +49,23 @@ val create_implicit_cast_expr : stmt_info -> stmt list -> qual_type -> cast_kind val make_binary_stmt : stmt -> stmt -> stmt_info -> expr_info -> binary_operator_info -> stmt -val make_obj_c_message_expr_info_class : string -> Typ.Name.t -> pointer option -> - obj_c_message_expr_info +val make_obj_c_message_expr_info_class : + string -> Typ.Name.t -> pointer option -> obj_c_message_expr_info val make_obj_c_message_expr_info_instance : string -> obj_c_message_expr_info val translate_dispatch_function : stmt_info -> stmt list -> int -> stmt -val translate_block_enumerate : string -> stmt_info -> stmt list -> expr_info -> - stmt * (string * Clang_ast_t.pointer * qual_type) list +val translate_block_enumerate : + string -> stmt_info -> stmt list -> expr_info + -> stmt * (string * Clang_ast_t.pointer * qual_type) list (* We translate an expression with a conditional*) (* x <=> x?1:0 *) + val trans_with_conditional : stmt_info -> expr_info -> stmt list -> stmt + (* We translate the logical negation of an expression with a conditional*) (* !x <=> x?0:1 *) + val trans_negation_with_conditional : stmt_info -> expr_info -> stmt list -> stmt diff --git a/infer/src/clang/cArithmetic_trans.ml b/infer/src/clang/cArithmetic_trans.ml index 619c6133a..eafb6669a 100644 --- a/infer/src/clang/cArithmetic_trans.ml +++ b/infer/src/clang/cArithmetic_trans.ml @@ -10,7 +10,6 @@ (** Utility module for translating unary and binary operations and compound assignments *) open! IStd - module L = Logging (* Returns the translation of assignment when ARC mode is enabled in Obj-C *) @@ -27,31 +26,32 @@ let assignment_arc_mode e1 typ e2 loc rhs_owning_method is_e1_decl = let autorelease_pname = BuiltinDecl.__set_autorelease_attribute in let mk_call procname e t = let bi_retain = Exp.Const (Const.Cfun procname) in - Sil.Call (None, bi_retain, [(e, t)], loc, CallFlags.default) in + Sil.Call (None, bi_retain, [(e, t)], loc, CallFlags.default) + in match typ.Typ.desc with - | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && not is_e1_decl -> - (* for __strong e1 = e2 the semantics is*) + | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && not is_e1_decl + -> (* for __strong e1 = e2 the semantics is*) (* retain(e2); tmp=e1; e1=e2; release(tmp); *) let retain = mk_call retain_pname e2 typ in let id = Ident.create_fresh Ident.knormal in let tmp_assign = Sil.Load (id, e1, typ, loc) in let release = mk_call release_pname (Exp.Var id) typ in - (e1,[retain; tmp_assign; assign; release]) - | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && is_e1_decl -> - (* for A __strong *e1 = e2 the semantics is*) + (e1, [retain; tmp_assign; assign; release]) + | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && is_e1_decl + -> (* for A __strong *e1 = e2 the semantics is*) (* retain(e2); e1=e2; *) let retain = mk_call retain_pname e2 typ in - (e1,[retain; assign]) - | Typ.Tptr (_, Typ.Pk_objc_weak) - | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) -> - (e1, [assign]) - | Typ.Tptr (_, Typ.Pk_objc_autoreleasing) -> - (* for __autoreleasing e1 = e2 the semantics is*) + (e1, [retain; assign]) + | Typ.Tptr (_, Typ.Pk_objc_weak) | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) + -> (e1, [assign]) + | Typ.Tptr (_, Typ.Pk_objc_autoreleasing) + -> (* for __autoreleasing e1 = e2 the semantics is*) (* retain(e2); autorelease(e2); e1=e2; *) let retain = mk_call retain_pname e2 typ in let autorelease = mk_call autorelease_pname e2 typ in (e1, [retain; autorelease; assign]) - | _ -> (e1, [assign]) + | _ + -> (e1, [assign]) (* Returns a pair ([binary_expression], instructions) for binary operator representing a *) (* CompoundAssignment. "binary_expression" is returned when we are calculating an expression*) @@ -60,167 +60,230 @@ let assignment_arc_mode e1 typ e2 loc rhs_owning_method is_e1_decl = let compound_assignment_binary_operation_instruction boi e1 typ e2 loc = let id = Ident.create_fresh Ident.knormal in let instr1 = Sil.Load (id, e1, typ, loc) in - let e_res, instr_op = match boi.Clang_ast_t.boi_kind with - | `AddAssign -> - let e1_plus_e2 = Exp.BinOp(Binop.PlusA, Exp.Var id, e2) in + let e_res, instr_op = + match boi.Clang_ast_t.boi_kind with + | `AddAssign + -> let e1_plus_e2 = Exp.BinOp (Binop.PlusA, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_plus_e2, loc)]) - | `SubAssign -> - let e1_sub_e2 = Exp.BinOp(Binop.MinusA, Exp.Var id, e2) in + | `SubAssign + -> let e1_sub_e2 = Exp.BinOp (Binop.MinusA, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_sub_e2, loc)]) - | `MulAssign -> - let e1_mul_e2 = Exp.BinOp(Binop.Mult, Exp.Var id, e2) in + | `MulAssign + -> let e1_mul_e2 = Exp.BinOp (Binop.Mult, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_mul_e2, loc)]) - | `DivAssign -> - let e1_div_e2 = Exp.BinOp(Binop.Div, Exp.Var id, e2) in + | `DivAssign + -> let e1_div_e2 = Exp.BinOp (Binop.Div, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_div_e2, loc)]) - | `ShlAssign -> - let e1_shl_e2 = Exp.BinOp(Binop.Shiftlt, Exp.Var id, e2) in + | `ShlAssign + -> let e1_shl_e2 = Exp.BinOp (Binop.Shiftlt, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_shl_e2, loc)]) - | `ShrAssign -> - let e1_shr_e2 = Exp.BinOp(Binop.Shiftrt, Exp.Var id, e2) in + | `ShrAssign + -> let e1_shr_e2 = Exp.BinOp (Binop.Shiftrt, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_shr_e2, loc)]) - | `RemAssign -> - let e1_mod_e2 = Exp.BinOp(Binop.Mod, Exp.Var id, e2) in + | `RemAssign + -> let e1_mod_e2 = Exp.BinOp (Binop.Mod, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_mod_e2, loc)]) - | `AndAssign -> - let e1_and_e2 = Exp.BinOp(Binop.BAnd, Exp.Var id, e2) in + | `AndAssign + -> let e1_and_e2 = Exp.BinOp (Binop.BAnd, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_and_e2, loc)]) - | `OrAssign -> - let e1_or_e2 = Exp.BinOp(Binop.BOr, Exp.Var id, e2) in + | `OrAssign + -> let e1_or_e2 = Exp.BinOp (Binop.BOr, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_or_e2, loc)]) - | `XorAssign -> - let e1_xor_e2 = Exp.BinOp(Binop.BXor, Exp.Var id, e2) in + | `XorAssign + -> let e1_xor_e2 = Exp.BinOp (Binop.BXor, Exp.Var id, e2) in (e1, [Sil.Store (e1, typ, e1_xor_e2, loc)]) - | _ -> assert false in - (e_res, instr1:: instr_op) + | _ + -> assert false + in + (e_res, instr1 :: instr_op) (* Returns a pair ([binary_expression], instructions). "binary_expression" *) (* is returned when we are calculating an expression "instructions" is not *) (* empty when the binary operator is actually a statement like an *) (* assignment. *) let binary_operation_instruction boi e1 typ e2 loc rhs_owning_method = - let binop_exp op = Exp.BinOp(op, e1, e2) in + let binop_exp op = Exp.BinOp (op, e1, e2) in match boi.Clang_ast_t.boi_kind with - | `Add -> (binop_exp (Binop.PlusA), []) - | `Mul -> (binop_exp (Binop.Mult), []) - | `Div -> (binop_exp (Binop.Div), []) - | `Rem -> (binop_exp (Binop.Mod), []) - | `Sub -> (binop_exp (Binop.MinusA), []) - | `Shl -> (binop_exp (Binop.Shiftlt), []) - | `Shr -> (binop_exp(Binop.Shiftrt), []) - | `Or -> (binop_exp (Binop.BOr), []) - | `And -> (binop_exp (Binop.BAnd), []) - | `Xor -> (binop_exp (Binop.BXor), []) - | `LT -> (binop_exp (Binop.Lt), []) - | `GT -> (binop_exp (Binop.Gt), []) - | `LE -> (binop_exp (Binop.Le), []) - | `GE -> (binop_exp (Binop.Ge), []) - | `NE -> (binop_exp (Binop.Ne), []) - | `EQ -> (binop_exp (Binop.Eq), []) - | `LAnd -> (binop_exp (Binop.LAnd), []) - | `LOr -> (binop_exp (Binop.LOr), []) - | `Assign -> - if !Config.arc_mode && ObjcInterface_decl.is_pointer_to_objc_class typ then + | `Add + -> (binop_exp Binop.PlusA, []) + | `Mul + -> (binop_exp Binop.Mult, []) + | `Div + -> (binop_exp Binop.Div, []) + | `Rem + -> (binop_exp Binop.Mod, []) + | `Sub + -> (binop_exp Binop.MinusA, []) + | `Shl + -> (binop_exp Binop.Shiftlt, []) + | `Shr + -> (binop_exp Binop.Shiftrt, []) + | `Or + -> (binop_exp Binop.BOr, []) + | `And + -> (binop_exp Binop.BAnd, []) + | `Xor + -> (binop_exp Binop.BXor, []) + | `LT + -> (binop_exp Binop.Lt, []) + | `GT + -> (binop_exp Binop.Gt, []) + | `LE + -> (binop_exp Binop.Le, []) + | `GE + -> (binop_exp Binop.Ge, []) + | `NE + -> (binop_exp Binop.Ne, []) + | `EQ + -> (binop_exp Binop.Eq, []) + | `LAnd + -> (binop_exp Binop.LAnd, []) + | `LOr + -> (binop_exp Binop.LOr, []) + | `Assign + -> if !Config.arc_mode && ObjcInterface_decl.is_pointer_to_objc_class typ then assignment_arc_mode e1 typ e2 loc rhs_owning_method false - else - (e1, [Sil.Store (e1, typ, e2, loc)]) - | `Comma -> (e2, []) (* C99 6.5.17-2 *) - | `MulAssign | `DivAssign | `RemAssign | `AddAssign | `SubAssign - | `ShlAssign | `ShrAssign | `AndAssign | `XorAssign | `OrAssign -> - compound_assignment_binary_operation_instruction boi e1 typ e2 loc + else (e1, [Sil.Store (e1, typ, e2, loc)]) + | `Comma + -> (e2, []) (* C99 6.5.17-2 *) + | `MulAssign + | `DivAssign + | `RemAssign + | `AddAssign + | `SubAssign + | `ShlAssign + | `ShrAssign + | `AndAssign + | `XorAssign + | `OrAssign + -> compound_assignment_binary_operation_instruction boi e1 typ e2 loc (* We should not get here. *) (* These should be treated by compound_assignment_binary_operation_instruction*) - | bok -> - L.(debug Capture Medium) + | bok + -> L.(debug Capture Medium) "@\nWARNING: Missing translation for Binary Operator Kind %s. Construct ignored...@\n" - (Clang_ast_j.string_of_binary_operator_kind bok); + (Clang_ast_j.string_of_binary_operator_kind bok) ; (Exp.minus_one, []) let unary_operation_instruction translation_unit_context uoi e typ loc = - let un_exp op = - Exp.UnOp(op, e, Some typ) in + let un_exp op = Exp.UnOp (op, e, Some typ) in match uoi.Clang_ast_t.uoi_kind with - | `PostInc -> - let id = Ident.create_fresh Ident.knormal in + | `PostInc + -> let id = Ident.create_fresh Ident.knormal in let instr1 = Sil.Load (id, e, typ, loc) in - let e_plus_1 = Exp.BinOp(Binop.PlusA, Exp.Var id, Exp.Const(Const.Cint (IntLit.one))) in - (Exp.Var id, instr1::[Sil.Store (e, typ, e_plus_1, loc)]) - | `PreInc -> - let id = Ident.create_fresh Ident.knormal in + let e_plus_1 = Exp.BinOp (Binop.PlusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in + (Exp.Var id, [instr1; Sil.Store (e, typ, e_plus_1, loc)]) + | `PreInc + -> let id = Ident.create_fresh Ident.knormal in let instr1 = Sil.Load (id, e, typ, loc) in - let e_plus_1 = Exp.BinOp(Binop.PlusA, Exp.Var id, Exp.Const(Const.Cint (IntLit.one))) in - let exp = if CGeneral_utils.is_cpp_translation translation_unit_context then - e - else - e_plus_1 in - (exp, instr1::[Sil.Store (e, typ, e_plus_1, loc)]) - | `PostDec -> - let id = Ident.create_fresh Ident.knormal in + let e_plus_1 = Exp.BinOp (Binop.PlusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in + let exp = + if CGeneral_utils.is_cpp_translation translation_unit_context then e else e_plus_1 + in + (exp, [instr1; Sil.Store (e, typ, e_plus_1, loc)]) + | `PostDec + -> let id = Ident.create_fresh Ident.knormal in let instr1 = Sil.Load (id, e, typ, loc) in - let e_minus_1 = Exp.BinOp(Binop.MinusA, Exp.Var id, Exp.Const(Const.Cint (IntLit.one))) in - (Exp.Var id, instr1::[Sil.Store (e, typ, e_minus_1, loc)]) - | `PreDec -> - let id = Ident.create_fresh Ident.knormal in + let e_minus_1 = Exp.BinOp (Binop.MinusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in + (Exp.Var id, [instr1; Sil.Store (e, typ, e_minus_1, loc)]) + | `PreDec + -> let id = Ident.create_fresh Ident.knormal in let instr1 = Sil.Load (id, e, typ, loc) in - let e_minus_1 = Exp.BinOp(Binop.MinusA, Exp.Var id, Exp.Const(Const.Cint (IntLit.one))) in - let exp = if CGeneral_utils.is_cpp_translation translation_unit_context then - e - else - e_minus_1 in - (exp, instr1::[Sil.Store (e, typ, e_minus_1, loc)]) - | `Not -> (un_exp (Unop.BNot), []) - | `Minus -> (un_exp (Unop.Neg), []) - | `Plus -> (e, []) - | `LNot -> (un_exp (Unop.LNot), []) - | `Deref -> - (* Actual dereferencing is handled by implicit cast from rvalue to lvalue *) + let e_minus_1 = Exp.BinOp (Binop.MinusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in + let exp = + if CGeneral_utils.is_cpp_translation translation_unit_context then e else e_minus_1 + in + (exp, [instr1; Sil.Store (e, typ, e_minus_1, loc)]) + | `Not + -> (un_exp Unop.BNot, []) + | `Minus + -> (un_exp Unop.Neg, []) + | `Plus + -> (e, []) + | `LNot + -> (un_exp Unop.LNot, []) + | `Deref + -> (* Actual dereferencing is handled by implicit cast from rvalue to lvalue *) (e, []) - | `AddrOf -> (e, []) - | `Real | `Imag | `Extension | `Coawait -> - let uok = Clang_ast_j.string_of_unary_operator_kind (uoi.Clang_ast_t.uoi_kind) in + | `AddrOf + -> (e, []) + | `Real | `Imag | `Extension | `Coawait + -> let uok = Clang_ast_j.string_of_unary_operator_kind uoi.Clang_ast_t.uoi_kind in L.(debug Capture Medium) - "@\nWARNING: Missing translation for Unary Operator Kind %s. \ - The construct has been ignored...@\n" - uok; + "@\nWARNING: Missing translation for Unary Operator Kind %s. The construct has been ignored...@\n" + uok ; (e, []) let bin_op_to_string boi = match boi.Clang_ast_t.boi_kind with - | `PtrMemD -> "PtrMemD" - | `PtrMemI -> "PtrMemI" - | `Mul -> "Mul" - | `Div -> "Div" - | `Rem -> "Rem" - | `Add -> "Add" - | `Sub -> "Sub" - | `Shl -> "Shl" - | `Shr -> "Shr" - | `LT -> "LT" - | `GT -> "GT" - | `LE -> "LE" - | `GE -> "GE" - | `EQ -> "EQ" - | `NE -> "NE" - | `And -> "And" - | `Xor -> "Xor" - | `Or -> "Or" - | `LAnd -> "LAnd" - | `LOr -> "LOr" - | `Assign -> "Assign" - | `MulAssign -> "MulAssign" - | `DivAssign -> "DivAssign" - | `RemAssign -> "RemAssing" - | `AddAssign -> "AddAssign" - | `SubAssign -> "SubAssign" - | `ShlAssign -> "ShlAssign" - | `ShrAssign -> "ShrAssign" - | `AndAssign -> "AndAssign" - | `XorAssign -> "XorAssign" - | `OrAssign -> "OrAssign" - | `Comma -> "Comma" + | `PtrMemD + -> "PtrMemD" + | `PtrMemI + -> "PtrMemI" + | `Mul + -> "Mul" + | `Div + -> "Div" + | `Rem + -> "Rem" + | `Add + -> "Add" + | `Sub + -> "Sub" + | `Shl + -> "Shl" + | `Shr + -> "Shr" + | `LT + -> "LT" + | `GT + -> "GT" + | `LE + -> "LE" + | `GE + -> "GE" + | `EQ + -> "EQ" + | `NE + -> "NE" + | `And + -> "And" + | `Xor + -> "Xor" + | `Or + -> "Or" + | `LAnd + -> "LAnd" + | `LOr + -> "LOr" + | `Assign + -> "Assign" + | `MulAssign + -> "MulAssign" + | `DivAssign + -> "DivAssign" + | `RemAssign + -> "RemAssing" + | `AddAssign + -> "AddAssign" + | `SubAssign + -> "SubAssign" + | `ShlAssign + -> "ShlAssign" + | `ShrAssign + -> "ShrAssign" + | `AndAssign + -> "AndAssign" + | `XorAssign + -> "XorAssign" + | `OrAssign + -> "OrAssign" + | `Comma + -> "Comma" let sil_const_plus_one const = match const with - | Exp.Const (Const.Cint n) -> - Exp.Const (Const.Cint (IntLit.add n IntLit.one)) - | _ -> Exp.BinOp (Binop.PlusA, const, Exp.Const (Const.Cint (IntLit.one))) + | Exp.Const Const.Cint n + -> Exp.Const (Const.Cint (IntLit.add n IntLit.one)) + | _ + -> Exp.BinOp (Binop.PlusA, const, Exp.Const (Const.Cint IntLit.one)) diff --git a/infer/src/clang/cArithmetic_trans.mli b/infer/src/clang/cArithmetic_trans.mli index a94aaa824..51ad13109 100644 --- a/infer/src/clang/cArithmetic_trans.mli +++ b/infer/src/clang/cArithmetic_trans.mli @@ -14,11 +14,12 @@ open! IStd val bin_op_to_string : Clang_ast_t.binary_operator_info -> string val binary_operation_instruction : - Clang_ast_t.binary_operator_info -> Exp.t -> Typ.t -> Exp.t -> - Location.t -> bool -> Exp.t * Sil.instr list + Clang_ast_t.binary_operator_info -> Exp.t -> Typ.t -> Exp.t -> Location.t -> bool + -> Exp.t * Sil.instr list -val unary_operation_instruction : CFrontend_config.translation_unit_context -> - Clang_ast_t.unary_operator_info -> Exp.t -> Typ.t -> Location.t -> Exp.t * Sil.instr list +val unary_operation_instruction : + CFrontend_config.translation_unit_context -> Clang_ast_t.unary_operator_info -> Exp.t -> Typ.t + -> Location.t -> Exp.t * Sil.instr list val assignment_arc_mode : Exp.t -> Typ.t -> Exp.t -> Location.t -> bool -> bool -> Exp.t * Sil.instr list diff --git a/infer/src/clang/cAst_utils.ml b/infer/src/clang/cAst_utils.ml index 1662deb8b..ff1fc8100 100644 --- a/infer/src/clang/cAst_utils.ml +++ b/infer/src/clang/cAst_utils.ml @@ -18,89 +18,86 @@ module F = Format type qual_type_to_sil_type = Tenv.t -> Clang_ast_t.qual_type -> Typ.t let sanitize_name = Str.global_replace (Str.regexp "[/ ]") "_" + let get_qual_name qual_name_list = List.map ~f:sanitize_name qual_name_list |> QualifiedCppName.of_rev_list -let get_qualified_name name_info = - get_qual_name name_info.Clang_ast_t.ni_qual_name +let get_qualified_name name_info = get_qual_name name_info.Clang_ast_t.ni_qual_name let get_unqualified_name name_info = - let name = match name_info.Clang_ast_t.ni_qual_name with - | name :: _ -> name - | [] -> name_info.Clang_ast_t.ni_name in + let name = + match name_info.Clang_ast_t.ni_qual_name with + | name :: _ + -> name + | [] + -> name_info.Clang_ast_t.ni_name + in sanitize_name name let get_class_name_from_member member_name_info = match member_name_info.Clang_ast_t.ni_qual_name with - | _ :: class_qual_list -> get_qual_name class_qual_list - | [] -> assert false + | _ :: class_qual_list + -> get_qual_name class_qual_list + | [] + -> assert false -let make_name_decl name = { - Clang_ast_t.ni_name = name; - ni_qual_name = [name]; -} +let make_name_decl name = {Clang_ast_t.ni_name= name; ni_qual_name= [name]} -let make_qual_name_decl class_name_quals name = { - Clang_ast_t.ni_name = name; - ni_qual_name = name :: class_name_quals; -} +let make_qual_name_decl class_name_quals name = + {Clang_ast_t.ni_name= name; ni_qual_name= name :: class_name_quals} let pointer_counter = ref 0 let get_fresh_pointer () = - pointer_counter := !pointer_counter + 1; - let internal_pointer = -(!pointer_counter) in + pointer_counter := !pointer_counter + 1 ; + let internal_pointer = - !pointer_counter in internal_pointer -let get_invalid_pointer () = - CFrontend_config.invalid_pointer +let get_invalid_pointer () = CFrontend_config.invalid_pointer let type_from_unary_expr_or_type_trait_expr_info info = - match info.Clang_ast_t.uttei_qual_type with - | Some tp -> Some tp - | None -> None + match info.Clang_ast_t.uttei_qual_type with Some tp -> Some tp | None -> None -let get_decl decl_ptr = - Int.Table.find ClangPointers.pointer_decl_table decl_ptr +let get_decl decl_ptr = Int.Table.find ClangPointers.pointer_decl_table decl_ptr let get_decl_opt decl_ptr_opt = - match decl_ptr_opt with - | Some decl_ptr -> get_decl decl_ptr - | None -> None + match decl_ptr_opt with Some decl_ptr -> get_decl decl_ptr | None -> None let get_stmt stmt_ptr = let stmt = Int.Table.find ClangPointers.pointer_stmt_table stmt_ptr in - if Option.is_none stmt then L.internal_error "stmt with pointer %d not found@\n" stmt_ptr; + if Option.is_none stmt then L.internal_error "stmt with pointer %d not found@\n" stmt_ptr ; stmt let get_stmt_opt stmt_ptr_opt = - match stmt_ptr_opt with - | Some stmt_ptr -> get_stmt stmt_ptr - | None -> None + match stmt_ptr_opt with Some stmt_ptr -> get_stmt stmt_ptr | None -> None let get_decl_opt_with_decl_ref decl_ref_opt = match decl_ref_opt with - | Some decl_ref -> get_decl decl_ref.Clang_ast_t.dr_decl_pointer - | None -> None + | Some decl_ref + -> get_decl decl_ref.Clang_ast_t.dr_decl_pointer + | None + -> None -let get_property_of_ivar decl_ptr = - Int.Table.find ClangPointers.ivar_to_property_table decl_ptr +let get_property_of_ivar decl_ptr = Int.Table.find ClangPointers.ivar_to_property_table decl_ptr let update_sil_types_map type_ptr sil_type = - CFrontend_config.sil_types_map := - Clang_ast_extend.TypePointerMap.add type_ptr sil_type !CFrontend_config.sil_types_map + CFrontend_config.sil_types_map + := Clang_ast_extend.TypePointerMap.add type_ptr sil_type !CFrontend_config.sil_types_map let update_enum_map enum_constant_pointer sil_exp = - let (predecessor_pointer_opt, _) = - ClangPointers.Map.find_exn !CFrontend_config.enum_map enum_constant_pointer in + let predecessor_pointer_opt, _ = + ClangPointers.Map.find_exn !CFrontend_config.enum_map enum_constant_pointer + in let enum_map_value = (predecessor_pointer_opt, Some sil_exp) in - CFrontend_config.enum_map := - ClangPointers.Map.add !CFrontend_config.enum_map ~key:enum_constant_pointer ~data:enum_map_value + CFrontend_config.enum_map + := ClangPointers.Map.add !CFrontend_config.enum_map ~key:enum_constant_pointer + ~data:enum_map_value let add_enum_constant enum_constant_pointer predecessor_pointer_opt = let enum_map_value = (predecessor_pointer_opt, None) in - CFrontend_config.enum_map := - ClangPointers.Map.add !CFrontend_config.enum_map ~key:enum_constant_pointer ~data:enum_map_value + CFrontend_config.enum_map + := ClangPointers.Map.add !CFrontend_config.enum_map ~key:enum_constant_pointer + ~data:enum_map_value let get_enum_constant_exp enum_constant_pointer = ClangPointers.Map.find_exn !CFrontend_config.enum_map enum_constant_pointer @@ -108,85 +105,93 @@ let get_enum_constant_exp enum_constant_pointer = let get_type type_ptr = (* There is chance for success only if type_ptr is in fact clang pointer *) match type_ptr with - | Clang_ast_types.TypePtr.Ptr raw_ptr -> - let typ = Int.Table.find ClangPointers.pointer_type_table raw_ptr in - if Option.is_none typ then L.internal_error "type with pointer %d not found@\n" raw_ptr; + | Clang_ast_types.TypePtr.Ptr raw_ptr + -> let typ = Int.Table.find ClangPointers.pointer_type_table raw_ptr in + if Option.is_none typ then L.internal_error "type with pointer %d not found@\n" raw_ptr ; typ - | _ -> - (* otherwise, function fails *) + | _ + -> (* otherwise, function fails *) let type_str = Clang_ast_extend.type_ptr_to_string type_ptr in - L.(debug Capture Medium) "type %s is not clang pointer@\n" type_str; - None + L.(debug Capture Medium) "type %s is not clang pointer@\n" type_str ; None let get_desugared_type type_ptr = let typ_opt = get_type type_ptr in match typ_opt with - | Some typ -> + | Some typ + -> ( let type_info = Clang_ast_proj.get_type_tuple typ in - (match type_info.Clang_ast_t.ti_desugared_type with - | Some ptr -> get_type ptr - | _ -> typ_opt) - | _ -> typ_opt + match type_info.Clang_ast_t.ti_desugared_type with Some ptr -> get_type ptr | _ -> typ_opt ) + | _ + -> typ_opt let get_decl_from_typ_ptr typ_ptr = let typ_opt = get_desugared_type typ_ptr in let typ = match typ_opt with Some t -> t | _ -> assert false in match typ with - | Clang_ast_t.RecordType (_, decl_ptr) - | Clang_ast_t.ObjCInterfaceType (_, decl_ptr) -> get_decl decl_ptr - | _ -> None + | Clang_ast_t.RecordType (_, decl_ptr) | Clang_ast_t.ObjCInterfaceType (_, decl_ptr) + -> get_decl decl_ptr + | _ + -> None let sil_annot_of_type {Clang_ast_t.qt_type_ptr} = let default_visibility = true in let mk_annot annot_name_opt = match annot_name_opt with - | Some annot_name -> - [{ Annot.class_name = annot_name; parameters = []; }, default_visibility] - | None -> Annot.Item.empty in + | Some annot_name + -> [({Annot.class_name= annot_name; parameters= []}, default_visibility)] + | None + -> Annot.Item.empty + in let annot_name_opt = match get_type qt_type_ptr with - | Some AttributedType (_, attr_info) -> - if attr_info.ati_attr_kind = `Nullable then Some Annotations.nullable + | Some AttributedType (_, attr_info) + -> if attr_info.ati_attr_kind = `Nullable then Some Annotations.nullable else if attr_info.ati_attr_kind = `Nonnull then Some Annotations.nonnull - (* other annotations go here *) + (* other annotations go here *) else None - | _ -> None in + | _ + -> None + in mk_annot annot_name_opt let name_of_typedef_type_info {Clang_ast_t.tti_decl_ptr} = match get_decl tti_decl_ptr with - | Some TypedefDecl (_, name_decl_info, _, _, _) -> - get_qualified_name name_decl_info - | _ -> QualifiedCppName.empty + | Some TypedefDecl (_, name_decl_info, _, _, _) + -> get_qualified_name name_decl_info + | _ + -> QualifiedCppName.empty let name_opt_of_typedef_qual_type qual_type = match get_type qual_type.Clang_ast_t.qt_type_ptr with - | Some Clang_ast_t.TypedefType (_, typedef_type_info) -> - Some (name_of_typedef_type_info typedef_type_info) - | _ -> None + | Some Clang_ast_t.TypedefType (_, typedef_type_info) + -> Some (name_of_typedef_type_info typedef_type_info) + | _ + -> None let string_of_qual_type {Clang_ast_t.qt_type_ptr; qt_is_const} = Printf.sprintf "%s%s" (if qt_is_const then "is_const " else "") (Clang_ast_extend.type_ptr_to_string qt_type_ptr) -let qual_type_of_decl_ptr decl_ptr = { - (* This function needs to be in this module - CAst_utils can't depend on +let qual_type_of_decl_ptr decl_ptr = + { (* This function needs to be in this module - CAst_utils can't depend on Ast_expressions *) - Clang_ast_t.qt_type_ptr=Clang_ast_extend.DeclPtr decl_ptr; - qt_is_const=false; - qt_is_volatile=false; - qt_is_restrict=false; -} + Clang_ast_t.qt_type_ptr= Clang_ast_extend.DeclPtr decl_ptr + ; qt_is_const= false + ; qt_is_volatile= false + ; qt_is_restrict= false } let add_type_from_decl_ref qual_type_to_sil_type tenv dr = let qual_type = qual_type_of_decl_ptr dr.Clang_ast_t.dr_decl_pointer in ignore (qual_type_to_sil_type tenv qual_type) let add_type_from_decl_ref_opt qual_type_to_sil_type tenv decl_ref_opt fail_if_not_found = - match decl_ref_opt with (* translate interface first if found *) - | Some dr -> add_type_from_decl_ref qual_type_to_sil_type tenv dr - | _ -> if fail_if_not_found then assert false else () + match decl_ref_opt with + (* translate interface first if found *) + | Some dr + -> add_type_from_decl_ref qual_type_to_sil_type tenv dr + | _ + -> if fail_if_not_found then assert false else () let add_type_from_decl_ref_list qual_type_to_sil_type tenv decl_ref_list = List.iter ~f:(add_type_from_decl_ref qual_type_to_sil_type tenv) decl_ref_list @@ -194,25 +199,30 @@ let add_type_from_decl_ref_list qual_type_to_sil_type tenv decl_ref_list = let get_function_decl_with_body decl_ptr = let open Clang_ast_t in let decl_opt = get_decl decl_ptr in - let decl_ptr' = match decl_opt with - | Some (FunctionDecl (_, _, _, fdecl_info)) - | Some (CXXMethodDecl (_, _, _, fdecl_info, _)) - | Some (CXXConstructorDecl (_, _, _, fdecl_info, _)) - | Some (CXXConversionDecl (_, _, _, fdecl_info, _)) - | Some (CXXDestructorDecl (_, _, _, fdecl_info, _)) -> - fdecl_info.Clang_ast_t.fdi_decl_ptr_with_body - | _ -> Some decl_ptr in + let decl_ptr' = + match decl_opt with + | Some FunctionDecl (_, _, _, fdecl_info) + | Some CXXMethodDecl (_, _, _, fdecl_info, _) + | Some CXXConstructorDecl (_, _, _, fdecl_info, _) + | Some CXXConversionDecl (_, _, _, fdecl_info, _) + | Some CXXDestructorDecl (_, _, _, fdecl_info, _) + -> fdecl_info.Clang_ast_t.fdi_decl_ptr_with_body + | _ + -> Some decl_ptr + in if [%compare.equal : int option] decl_ptr' (Some decl_ptr) then decl_opt else get_decl_opt decl_ptr' let get_info_from_decl_ref decl_ref = let name_info = match decl_ref.Clang_ast_t.dr_name with Some ni -> ni | _ -> assert false in let decl_ptr = decl_ref.Clang_ast_t.dr_decl_pointer in - let qual_type = match decl_ref.Clang_ast_t.dr_qual_type with Some tp -> tp | _ -> assert false in - name_info, decl_ptr, qual_type + let qual_type = + match decl_ref.Clang_ast_t.dr_qual_type with Some tp -> tp | _ -> assert false + in + (name_info, decl_ptr, qual_type) (* st |= EF (atomic_pred param) *) -let rec exists_eventually_st atomic_pred param st = +let rec exists_eventually_st atomic_pred param st = if atomic_pred param st then true else let _, st_list = Clang_ast_proj.get_stmt_tuple st in @@ -220,29 +230,29 @@ let rec exists_eventually_st atomic_pred param st = let is_syntactically_global_var decl = match decl with - | Clang_ast_t.VarDecl (_, _ ,_, vdi) -> - vdi.vdi_is_global && not vdi.vdi_is_static_local - | _ -> false + | Clang_ast_t.VarDecl (_, _, _, vdi) + -> vdi.vdi_is_global && not vdi.vdi_is_static_local + | _ + -> false let is_const_expr_var decl = - match decl with - | Clang_ast_t.VarDecl (_, _ ,_, vdi) -> vdi.vdi_is_const_expr - | _ -> false + match decl with Clang_ast_t.VarDecl (_, _, _, vdi) -> vdi.vdi_is_const_expr | _ -> false let full_name_of_decl_opt decl_opt = match decl_opt with - | Some decl -> - (match Clang_ast_proj.get_named_decl_tuple decl with - | Some (_, name_info) -> get_qualified_name name_info - | None -> QualifiedCppName.empty) - | None -> QualifiedCppName.empty + | Some decl -> ( + match Clang_ast_proj.get_named_decl_tuple decl with + | Some (_, name_info) + -> get_qualified_name name_info + | None + -> QualifiedCppName.empty ) + | None + -> QualifiedCppName.empty (* Generates a unique number for each variant of a type. *) let get_tag ast_item = let item_rep = Obj.repr ast_item in - if Obj.is_block item_rep then - Obj.tag item_rep - else -(Obj.obj item_rep) + if Obj.is_block item_rep then Obj.tag item_rep else -Obj.obj item_rep (* Generates a key for a statement based on its sub-statements and the statement tag. *) let rec generate_key_stmt stmt = @@ -251,165 +261,168 @@ let rec generate_key_stmt stmt = let tags = List.map ~f:generate_key_stmt stmts in let buffer = Buffer.create 16 in let tags = tag_str :: tags in - List.iter ~f:(fun tag -> Buffer.add_string buffer tag) tags; + List.iter ~f:(fun tag -> Buffer.add_string buffer tag) tags ; Buffer.contents buffer (* Generates a key for a declaration based on its name and the declaration tag. *) let generate_key_decl decl = let buffer = Buffer.create 16 in let name = full_name_of_decl_opt (Some decl) in - Buffer.add_string buffer (string_of_int (get_tag decl)); - Buffer.add_string buffer (QualifiedCppName.to_qual_string name); + Buffer.add_string buffer (string_of_int (get_tag decl)) ; + Buffer.add_string buffer (QualifiedCppName.to_qual_string name) ; Buffer.contents buffer let rec get_super_if decl = match decl with - | Some Clang_ast_t.ObjCImplementationDecl(_, _, _, _, impl_decl_info) -> - (* Try getting the super ref through the impl info, and fall back to + | Some Clang_ast_t.ObjCImplementationDecl (_, _, _, _, impl_decl_info) + -> (* Try getting the super ref through the impl info, and fall back to getting the if decl first and getting the super ref through it. *) let super_ref = get_decl_opt_with_decl_ref impl_decl_info.oidi_super in - if Option.is_some super_ref then - super_ref - else - get_super_if (get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface) - | Some Clang_ast_t.ObjCInterfaceDecl(_, _, _, _, interface_decl_info) -> - get_decl_opt_with_decl_ref interface_decl_info.otdi_super - | _ -> None + if Option.is_some super_ref then super_ref + else get_super_if (get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface) + | Some Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, interface_decl_info) + -> get_decl_opt_with_decl_ref interface_decl_info.otdi_super + | _ + -> None let get_super_impl impl_decl_info = let objc_interface_decl_current = - get_decl_opt_with_decl_ref - impl_decl_info.Clang_ast_t.oidi_class_interface in + get_decl_opt_with_decl_ref impl_decl_info.Clang_ast_t.oidi_class_interface + in let objc_interface_decl_super = get_super_if objc_interface_decl_current in let objc_implementation_decl_super = match objc_interface_decl_super with - | Some ObjCInterfaceDecl(_, _, _, _, interface_decl_info) -> - get_decl_opt_with_decl_ref - interface_decl_info.otdi_implementation - | _ -> None in + | Some ObjCInterfaceDecl (_, _, _, _, interface_decl_info) + -> get_decl_opt_with_decl_ref interface_decl_info.otdi_implementation + | _ + -> None + in match objc_implementation_decl_super with - | Some ObjCImplementationDecl(_, _, decl_list, _, impl_decl_info) -> - Some (decl_list, impl_decl_info) - | _ -> None + | Some ObjCImplementationDecl (_, _, decl_list, _, impl_decl_info) + -> Some (decl_list, impl_decl_info) + | _ + -> None let get_super_ObjCImplementationDecl impl_decl_info = let objc_interface_decl_current = - get_decl_opt_with_decl_ref - impl_decl_info.Clang_ast_t.oidi_class_interface in + get_decl_opt_with_decl_ref impl_decl_info.Clang_ast_t.oidi_class_interface + in let objc_interface_decl_super = get_super_if objc_interface_decl_current in let objc_implementation_decl_super = match objc_interface_decl_super with - | Some ObjCInterfaceDecl(_, _, _, _, interface_decl_info) -> - get_decl_opt_with_decl_ref - interface_decl_info.otdi_implementation - | _ -> None in + | Some ObjCInterfaceDecl (_, _, _, _, interface_decl_info) + -> get_decl_opt_with_decl_ref interface_decl_info.otdi_implementation + | _ + -> None + in objc_implementation_decl_super let get_impl_decl_info dec = - match dec with - | Clang_ast_t.ObjCImplementationDecl (_, _, _, _, idi) -> Some idi - | _ -> None + match dec with Clang_ast_t.ObjCImplementationDecl (_, _, _, _, idi) -> Some idi | _ -> None -let default_blacklist = - let open CFrontend_config in - [nsobject_cl; nsproxy_cl] +let default_blacklist = CFrontend_config.([nsobject_cl; nsproxy_cl]) -let rec is_objc_if_descendant ?(blacklist = default_blacklist) if_decl ancestors = +let rec is_objc_if_descendant ?(blacklist= default_blacklist) if_decl ancestors = (* List of ancestors to check for and list of classes to short-circuit to false can't intersect *) if not String.Set.(is_empty (inter (of_list blacklist) (of_list ancestors))) then failwith "Blacklist and ancestors must be mutually exclusive." else match if_decl with - | Some Clang_ast_t.ObjCInterfaceDecl (_, ndi, _, _, _) -> - let in_list some_list = - List.mem ~equal:String.equal some_list ndi.Clang_ast_t.ni_name in + | Some Clang_ast_t.ObjCInterfaceDecl (_, ndi, _, _, _) + -> let in_list some_list = List.mem ~equal:String.equal some_list ndi.Clang_ast_t.ni_name in not (in_list blacklist) - && (in_list ancestors - || is_objc_if_descendant ~blacklist:blacklist (get_super_if if_decl) ancestors) - | _ -> false + && (in_list ancestors || is_objc_if_descendant ~blacklist (get_super_if if_decl) ancestors) + | _ + -> false let rec qual_type_to_objc_interface qual_type = - let typ_opt = get_desugared_type (qual_type.Clang_ast_t.qt_type_ptr) in + let typ_opt = get_desugared_type qual_type.Clang_ast_t.qt_type_ptr in ctype_to_objc_interface typ_opt + and ctype_to_objc_interface typ_opt = match (typ_opt : Clang_ast_t.c_type option) with - | Some ObjCInterfaceType (_, decl_ptr) -> get_decl decl_ptr - | Some ObjCObjectPointerType (_, (inner_qual_type: Clang_ast_t.qual_type)) -> - qual_type_to_objc_interface inner_qual_type + | Some ObjCInterfaceType (_, decl_ptr) + -> get_decl decl_ptr + | Some ObjCObjectPointerType (_, (inner_qual_type: Clang_ast_t.qual_type)) + -> qual_type_to_objc_interface inner_qual_type | Some FunctionProtoType (_, function_type_info, _) - | Some FunctionNoProtoType (_, function_type_info) -> - qual_type_to_objc_interface function_type_info.Clang_ast_t.fti_return_type - | _ -> None + | Some FunctionNoProtoType (_, function_type_info) + -> qual_type_to_objc_interface function_type_info.Clang_ast_t.fti_return_type + | _ + -> None -let qual_type_is_typedef_named qual_type (type_name: string): bool = +let qual_type_is_typedef_named qual_type (type_name: string) : bool = let is_decl_name_match decl_opt = - let tuple_opt = match decl_opt with - | Some decl -> Clang_ast_proj.get_named_decl_tuple decl - | _ -> None in - match tuple_opt with - | Some (_, ni) -> - String.equal type_name ni.ni_name - | _ -> false in + let tuple_opt = + match decl_opt with Some decl -> Clang_ast_proj.get_named_decl_tuple decl | _ -> None + in + match tuple_opt with Some (_, ni) -> String.equal type_name ni.ni_name | _ -> false + in match get_type qual_type.Clang_ast_t.qt_type_ptr with - | Some TypedefType (_, tti) -> - let decl_opt = get_decl tti.tti_decl_ptr in + | Some TypedefType (_, tti) + -> let decl_opt = get_decl tti.tti_decl_ptr in is_decl_name_match decl_opt - | _ -> false + | _ + -> false let if_decl_to_di_pointer_opt if_decl = match if_decl with - | Clang_ast_t.ObjCInterfaceDecl (if_decl_info, _, _, _, _) -> - Some if_decl_info.di_pointer - | _ -> None + | Clang_ast_t.ObjCInterfaceDecl (if_decl_info, _, _, _, _) + -> Some if_decl_info.di_pointer + | _ + -> None let is_instance_type qual_type = match name_opt_of_typedef_qual_type qual_type with - | Some name -> String.equal (QualifiedCppName.to_qual_string name) "instancetype" - | None -> false + | Some name + -> String.equal (QualifiedCppName.to_qual_string name) "instancetype" + | None + -> false let return_type_matches_class_type rtp type_decl_pointer = - if is_instance_type rtp then - true + if is_instance_type rtp then true else let return_type_decl_opt = qual_type_to_objc_interface rtp in let return_type_decl_pointer_opt = - Option.map ~f:if_decl_to_di_pointer_opt return_type_decl_opt in + Option.map ~f:if_decl_to_di_pointer_opt return_type_decl_opt + in [%compare.equal : int option option] (Some type_decl_pointer) return_type_decl_pointer_opt let is_objc_factory_method if_decl meth_decl = let if_type_decl_pointer = if_decl_to_di_pointer_opt if_decl in match meth_decl with - | Clang_ast_t.ObjCMethodDecl (_, _, omdi) -> - (not omdi.omdi_is_instance_method) && - (return_type_matches_class_type omdi.omdi_result_type if_type_decl_pointer) - | _ -> false + | Clang_ast_t.ObjCMethodDecl (_, _, omdi) + -> not omdi.omdi_is_instance_method + && return_type_matches_class_type omdi.omdi_result_type if_type_decl_pointer + | _ + -> false let name_of_decl_ref_opt (decl_ref_opt: Clang_ast_t.decl_ref option) = match decl_ref_opt with - | Some decl_ref -> - (match decl_ref.dr_name with - | Some named_decl_info -> Some named_decl_info.ni_name - | _ -> None) - | _ -> None + | Some decl_ref -> ( + match decl_ref.dr_name with Some named_decl_info -> Some named_decl_info.ni_name | _ -> None ) + | _ + -> None let type_of_decl decl = let open Clang_ast_t in match decl with - | ObjCMethodDecl (_, _, obj_c_method_decl_info) -> - Some obj_c_method_decl_info.omdi_result_type.qt_type_ptr - | ObjCPropertyDecl (_, _, obj_c_property_decl_info) -> - Some obj_c_property_decl_info.opdi_qual_type.qt_type_ptr + | ObjCMethodDecl (_, _, obj_c_method_decl_info) + -> Some obj_c_method_decl_info.omdi_result_type.qt_type_ptr + | ObjCPropertyDecl (_, _, obj_c_property_decl_info) + -> Some obj_c_property_decl_info.opdi_qual_type.qt_type_ptr | EnumDecl (_, _, _, type_ptr, _, _, _) | RecordDecl (_, _, _, type_ptr, _, _, _) - | CXXRecordDecl(_, _, _, type_ptr, _, _, _, _) - | ClassTemplateSpecializationDecl(_, _, _, type_ptr, _, _, _, _, _) - | ClassTemplatePartialSpecializationDecl(_, _, _, type_ptr, _, _, _, _, _) + | CXXRecordDecl (_, _, _, type_ptr, _, _, _, _) + | ClassTemplateSpecializationDecl (_, _, _, type_ptr, _, _, _, _, _) + | ClassTemplatePartialSpecializationDecl (_, _, _, type_ptr, _, _, _, _, _) | TemplateTypeParmDecl (_, _, _, type_ptr) | ObjCTypeParamDecl (_, _, _, type_ptr) | TypeAliasDecl (_, _, _, type_ptr) | TypedefDecl (_, _, _, type_ptr, _) - | UnresolvedUsingTypenameDecl (_, _, _, type_ptr) -> Some type_ptr + | UnresolvedUsingTypenameDecl (_, _, _, type_ptr) + -> Some type_ptr | BindingDecl (_, _, qual_type) | FieldDecl (_, _, qual_type, _) | ObjCAtDefsFieldDecl (_, _, qual_type, _) @@ -431,6 +444,7 @@ let type_of_decl decl = | EnumConstantDecl (_, _, qual_type, _) | IndirectFieldDecl (_, _, qual_type, _) | OMPDeclareReductionDecl (_, _, qual_type) - | UnresolvedUsingValueDecl (_, _, qual_type) -> - Some qual_type.qt_type_ptr - | _ -> None + | UnresolvedUsingValueDecl (_, _, qual_type) + -> Some qual_type.qt_type_ptr + | _ + -> None diff --git a/infer/src/clang/cAst_utils.mli b/infer/src/clang/cAst_utils.mli index afb07def8..1f41a25e0 100644 --- a/infer/src/clang/cAst_utils.mli +++ b/infer/src/clang/cAst_utils.mli @@ -38,30 +38,30 @@ val add_enum_constant : Clang_ast_t.pointer -> Clang_ast_t.pointer option -> uni val get_enum_constant_exp : Clang_ast_t.pointer -> Clang_ast_t.pointer option * Exp.t option -(** returns sanitized, fully qualified name given name info *) val get_qualified_name : Clang_ast_t.named_decl_info -> QualifiedCppName.t +(** returns sanitized, fully qualified name given name info *) -(** returns sanitized unqualified name given name info *) val get_unqualified_name : Clang_ast_t.named_decl_info -> string +(** returns sanitized unqualified name given name info *) -(** returns qualified class name given member name info *) val get_class_name_from_member : Clang_ast_t.named_decl_info -> QualifiedCppName.t +(** returns qualified class name given member name info *) -(** looks up clang pointer to type and returns c_type. It requires type_ptr to be `TPtr. *) val get_type : Clang_ast_t.type_ptr -> Clang_ast_t.c_type option +(** looks up clang pointer to type and returns c_type. It requires type_ptr to be `TPtr. *) +val get_desugared_type : Clang_ast_t.type_ptr -> Clang_ast_t.c_type option (** looks up clang pointer to type and resolves any sugar around it. See get_type for more info and restrictions *) -val get_desugared_type : Clang_ast_t.type_ptr -> Clang_ast_t.c_type option +val get_decl_from_typ_ptr : Clang_ast_t.type_ptr -> Clang_ast_t.decl option (** returns declaration of the type for certain types (RecordType, ObjCInterfaceType and None for others *) -val get_decl_from_typ_ptr : Clang_ast_t.type_ptr -> Clang_ast_t.decl option val name_of_typedef_type_info : Clang_ast_t.typedef_type_info -> QualifiedCppName.t -(** returns name of typedef if qual_type points to Typedef, None otherwise *) val name_opt_of_typedef_qual_type : Clang_ast_t.qual_type -> QualifiedCppName.t option +(** returns name of typedef if qual_type points to Typedef, None otherwise *) val string_of_qual_type : Clang_ast_t.qual_type -> string @@ -73,51 +73,51 @@ type qual_type_to_sil_type = Tenv.t -> Clang_ast_t.qual_type -> Typ.t val qual_type_of_decl_ptr : Clang_ast_t.pointer -> Clang_ast_t.qual_type -val add_type_from_decl_ref_opt : qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl_ref option -> - bool -> unit +val add_type_from_decl_ref_opt : + qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl_ref option -> bool -> unit -val add_type_from_decl_ref_list : qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl_ref list -> - unit +val add_type_from_decl_ref_list : + qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl_ref list -> unit val get_function_decl_with_body : Clang_ast_t.pointer -> Clang_ast_t.decl option -val get_info_from_decl_ref : Clang_ast_t.decl_ref -> - Clang_ast_t.named_decl_info * Clang_ast_t.pointer * Clang_ast_t.qual_type +val get_info_from_decl_ref : + Clang_ast_t.decl_ref -> Clang_ast_t.named_decl_info * Clang_ast_t.pointer * Clang_ast_t.qual_type val exists_eventually_st : ('a -> Clang_ast_t.stmt -> bool) -> 'a -> Clang_ast_t.stmt -> bool -(** true if a declaration is a global variable *) val is_syntactically_global_var : Clang_ast_t.decl -> bool +(** true if a declaration is a global variable *) -(** true if a declaration is a constexpr variable *) val is_const_expr_var : Clang_ast_t.decl -> bool +(** true if a declaration is a constexpr variable *) val full_name_of_decl_opt : Clang_ast_t.decl option -> QualifiedCppName.t -(** Generates a key for a statement based on its sub-statements and the statement tag. *) val generate_key_stmt : Clang_ast_t.stmt -> string +(** Generates a key for a statement based on its sub-statements and the statement tag. *) -(** Generates a key for a declaration based on its name and the declaration tag. *) val generate_key_decl : Clang_ast_t.decl -> string +(** Generates a key for a declaration based on its name and the declaration tag. *) +val get_super_if : Clang_ast_t.decl option -> Clang_ast_t.decl option (** Given an objc impl or interface decl, returns the objc interface decl of the superclass, if any. *) -val get_super_if : Clang_ast_t.decl option -> Clang_ast_t.decl option val get_impl_decl_info : Clang_ast_t.decl -> Clang_ast_t.obj_c_implementation_decl_info option +val get_super_impl : + Clang_ast_t.obj_c_implementation_decl_info + -> (Clang_ast_t.decl list * Clang_ast_t.obj_c_implementation_decl_info) option (** Given an objc impl decl info, return the super class's list of decls and its objc impl decl info. *) -val get_super_impl : - Clang_ast_t.obj_c_implementation_decl_info -> - (Clang_ast_t.decl list * - Clang_ast_t.obj_c_implementation_decl_info) - option -(** Given an objc impl decl info, return its super class implementation decl *) val get_super_ObjCImplementationDecl : Clang_ast_t.obj_c_implementation_decl_info -> Clang_ast_t.decl option +(** Given an objc impl decl info, return its super class implementation decl *) +val is_objc_if_descendant : + ?blacklist:string list -> Clang_ast_t.decl option -> string list -> bool (** Recursively go up the inheritance hierarchy of a given ObjCInterfaceDecl. Returns true if the passed in decl is an objc interface decl that's an eventual descendant of one of the classes passed in. @@ -126,15 +126,13 @@ val get_super_ObjCImplementationDecl : common base classes. The list of classes to short-circuit on can be overridden via specifying the named `blacklist` argument. *) -val is_objc_if_descendant : - ?blacklist:string list -> Clang_ast_t.decl option -> string list -> bool val qual_type_to_objc_interface : Clang_ast_t.qual_type -> Clang_ast_t.decl option val qual_type_is_typedef_named : Clang_ast_t.qual_type -> string -> bool -(** A class method that returns an instance of the class is a factory method. *) val is_objc_factory_method : Clang_ast_t.decl -> Clang_ast_t.decl -> bool +(** A class method that returns an instance of the class is a factory method. *) val name_of_decl_ref_opt : Clang_ast_t.decl_ref option -> string option diff --git a/infer/src/clang/cContext.ml b/infer/src/clang/cContext.ml index 84e3744ad..1741dac0d 100644 --- a/infer/src/clang/cContext.ml +++ b/infer/src/clang/cContext.ml @@ -11,44 +11,49 @@ open! IStd module Hashtbl = Caml.Hashtbl (** Contains current class and current method to be translated as well as local variables, *) + (** and the cg, cfg, and tenv corresponding to the current file. *) module L = Logging -type pointer (* = Clang_ast_t.pointer *) = int [@@deriving compare] +type pointer = int [@@deriving compare] -type curr_class = - | ContextClsDeclPtr of pointer - | ContextNoCls -[@@deriving compare] +(* = Clang_ast_t.pointer *) + +type curr_class = ContextClsDeclPtr of pointer | ContextNoCls [@@deriving compare] let equal_curr_class = [%compare.equal : curr_class] type str_node_map = (string, Procdesc.Node.t) Hashtbl.t type t = - { - translation_unit_context : CFrontend_config.translation_unit_context; - tenv : Tenv.t; - cg : Cg.t; - cfg : Cfg.cfg; - procdesc : Procdesc.t; - is_objc_method : bool; - curr_class: curr_class; - return_param_typ : Typ.t option; - outer_context : t option; (** in case of objc blocks, the context of the method containing the + { translation_unit_context: CFrontend_config.translation_unit_context + ; tenv: Tenv.t + ; cg: Cg.t + ; cfg: Cfg.cfg + ; procdesc: Procdesc.t + ; is_objc_method: bool + ; curr_class: curr_class + ; return_param_typ: Typ.t option + ; outer_context: t option + (** in case of objc blocks, the context of the method containing the block *) - mutable blocks_static_vars : ((Pvar.t * Typ.t) list) Typ.Procname.Map.t; - label_map : str_node_map; - } + ; mutable blocks_static_vars: (Pvar.t * Typ.t) list Typ.Procname.Map.t + ; label_map: str_node_map } let create_context translation_unit_context tenv cg cfg procdesc curr_class return_param_typ is_objc_method outer_context = - { translation_unit_context; tenv; cg; cfg; procdesc; curr_class; return_param_typ; - is_objc_method; outer_context; - blocks_static_vars = Typ.Procname.Map.empty; - label_map = Hashtbl.create 17; - } + { translation_unit_context + ; tenv + ; cg + ; cfg + ; procdesc + ; curr_class + ; return_param_typ + ; is_objc_method + ; outer_context + ; blocks_static_vars= Typ.Procname.Map.empty + ; label_map= Hashtbl.create 17 } let get_cfg context = context.cfg @@ -60,70 +65,82 @@ let get_procdesc context = context.procdesc let rec is_objc_method context = match context.outer_context with - | Some outer_context -> is_objc_method outer_context - | None -> context.is_objc_method + | Some outer_context + -> is_objc_method outer_context + | None + -> context.is_objc_method let rec is_objc_instance context = match context.outer_context with - | Some outer_context -> is_objc_instance outer_context - | None -> - let attrs = Procdesc.get_attributes context.procdesc in + | Some outer_context + -> is_objc_instance outer_context + | None + -> let attrs = Procdesc.get_attributes context.procdesc in attrs.ProcAttributes.is_objc_instance_method let rec get_curr_class context = - match context.curr_class, context.outer_context with - | ContextNoCls, Some outer_context -> - get_curr_class outer_context - | _ -> context.curr_class + match (context.curr_class, context.outer_context) with + | ContextNoCls, Some outer_context + -> get_curr_class outer_context + | _ + -> context.curr_class let get_curr_class_decl_ptr curr_class = - match curr_class with - | ContextClsDeclPtr ptr -> ptr - | _ -> assert false + match curr_class with ContextClsDeclPtr ptr -> ptr | _ -> assert false let get_curr_class_ptr curr_class = let decl_ptr = get_curr_class_decl_ptr curr_class in let get_ptr_from_decl_ref = function - | Some dr -> dr.Clang_ast_t.dr_decl_pointer - | None -> assert false in + | Some dr + -> dr.Clang_ast_t.dr_decl_pointer + | None + -> assert false + in (* Resolve categories to their class names *) match CAst_utils.get_decl decl_ptr with - | Some ObjCCategoryDecl (_, _, _, _, ocdi) -> - get_ptr_from_decl_ref ocdi.odi_class_interface - | Some ObjCCategoryImplDecl (_, _, _, _, ocidi) -> - get_ptr_from_decl_ref ocidi.ocidi_class_interface - | _ -> decl_ptr + | Some ObjCCategoryDecl (_, _, _, _, ocdi) + -> get_ptr_from_decl_ref ocdi.odi_class_interface + | Some ObjCCategoryImplDecl (_, _, _, _, ocidi) + -> get_ptr_from_decl_ref ocidi.ocidi_class_interface + | _ + -> decl_ptr let get_curr_class_typename context = let tenv = context.tenv in let curr_class = get_curr_class context in match get_curr_class_ptr curr_class |> CAst_utils.get_decl with - | Some decl -> CType_decl.get_record_typename ~tenv decl - | None -> assert false + | Some decl + -> CType_decl.get_record_typename ~tenv decl + | None + -> assert false let curr_class_to_string curr_class = match curr_class with - | ContextClsDeclPtr ptr -> ("decl_ptr: " ^ string_of_int ptr) - | ContextNoCls -> "no class" + | ContextClsDeclPtr ptr + -> "decl_ptr: " ^ string_of_int ptr + | ContextNoCls + -> "no class" let add_block_static_var context block_name static_var_typ = - match context.outer_context, static_var_typ with - | Some outer_context, (static_var, _) when Pvar.is_global static_var -> - (let new_static_vars, duplicate = - try - let static_vars = Typ.Procname.Map.find block_name outer_context.blocks_static_vars in - if List.mem ~equal:( - fun (var1, _) (var2, _) -> Pvar.equal var1 var2 - ) static_vars static_var_typ then - static_vars, true - else - static_var_typ :: static_vars, false - with Not_found -> [static_var_typ], false in - if not duplicate then - let blocks_static_vars = - Typ.Procname.Map.add block_name new_static_vars outer_context.blocks_static_vars in - outer_context.blocks_static_vars <- blocks_static_vars) - | _ -> () + match (context.outer_context, static_var_typ) with + | Some outer_context, (static_var, _) when Pvar.is_global static_var + -> let new_static_vars, duplicate = + try + let static_vars = Typ.Procname.Map.find block_name outer_context.blocks_static_vars in + if List.mem + ~equal:(fun (var1, _) (var2, _) -> Pvar.equal var1 var2) + static_vars static_var_typ + then (static_vars, true) + else (static_var_typ :: static_vars, false) + with Not_found -> ([static_var_typ], false) + in + if not duplicate then + let blocks_static_vars = + Typ.Procname.Map.add block_name new_static_vars outer_context.blocks_static_vars + in + outer_context.blocks_static_vars <- blocks_static_vars + | _ + -> () let static_vars_for_block context block_name = try Typ.Procname.Map.find block_name context.blocks_static_vars @@ -131,5 +148,7 @@ let static_vars_for_block context block_name = let rec get_outer_procname context = match context.outer_context with - | Some outer_context -> get_outer_procname outer_context - | None -> Procdesc.get_proc_name context.procdesc + | Some outer_context + -> get_outer_procname outer_context + | None + -> Procdesc.get_proc_name context.procdesc diff --git a/infer/src/clang/cContext.mli b/infer/src/clang/cContext.mli index e2ce51751..1587545de 100644 --- a/infer/src/clang/cContext.mli +++ b/infer/src/clang/cContext.mli @@ -10,32 +10,29 @@ open! IStd (** Contains current class and current method to be translated as well as local variables, *) + (** and the cg, cfg, and tenv corresponding to the current file. *) -type curr_class = - | ContextClsDeclPtr of int - | ContextNoCls -[@@deriving compare] +type curr_class = ContextClsDeclPtr of int | ContextNoCls [@@deriving compare] val equal_curr_class : curr_class -> curr_class -> bool type str_node_map = (string, Procdesc.Node.t) Caml.Hashtbl.t type t = - { - translation_unit_context : CFrontend_config.translation_unit_context; - tenv : Tenv.t; - cg : Cg.t; - cfg : Cfg.cfg; - procdesc : Procdesc.t; - is_objc_method : bool; - curr_class: curr_class; - return_param_typ : Typ.t option; - outer_context : t option; (** in case of objc blocks, the context of the method containing the + { translation_unit_context: CFrontend_config.translation_unit_context + ; tenv: Tenv.t + ; cg: Cg.t + ; cfg: Cfg.cfg + ; procdesc: Procdesc.t + ; is_objc_method: bool + ; curr_class: curr_class + ; return_param_typ: Typ.t option + ; outer_context: t option + (** in case of objc blocks, the context of the method containing the block *) - mutable blocks_static_vars : ((Pvar.t * Typ.t) list) Typ.Procname.Map.t; - label_map : str_node_map; - } + ; mutable blocks_static_vars: (Pvar.t * Typ.t) list Typ.Procname.Map.t + ; label_map: str_node_map } val get_procdesc : t -> Procdesc.t @@ -55,10 +52,11 @@ val is_objc_method : t -> bool val get_tenv : t -> Tenv.t -val create_context : CFrontend_config.translation_unit_context -> Tenv.t -> Cg.t -> Cfg.cfg -> - Procdesc.t -> curr_class -> Typ.t option -> bool -> t option -> t +val create_context : + CFrontend_config.translation_unit_context -> Tenv.t -> Cg.t -> Cfg.cfg -> Procdesc.t + -> curr_class -> Typ.t option -> bool -> t option -> t -val add_block_static_var : t -> Typ.Procname.t -> (Pvar.t * Typ.t) -> unit +val add_block_static_var : t -> Typ.Procname.t -> Pvar.t * Typ.t -> unit val static_vars_for_block : t -> Typ.Procname.t -> (Pvar.t * Typ.t) list diff --git a/infer/src/clang/cEnum_decl.ml b/infer/src/clang/cEnum_decl.ml index 41e287539..662fefbea 100644 --- a/infer/src/clang/cEnum_decl.ml +++ b/infer/src/clang/cEnum_decl.ml @@ -10,6 +10,7 @@ open! IStd (** Translate an enumeration declaration by adding it to the tenv and *) + (** translating the code and adding it to a fake procdesc *) (*Check if the constant is in the map, in which case that means that all the *) @@ -17,35 +18,37 @@ open! IStd (* to the map. *) let add_enum_constant_to_map_if_needed decl_pointer pred_decl_opt = try - ignore (CAst_utils.get_enum_constant_exp decl_pointer); + ignore (CAst_utils.get_enum_constant_exp decl_pointer) ; true - with Not_found -> - CAst_utils.add_enum_constant decl_pointer pred_decl_opt; - false + with Not_found -> CAst_utils.add_enum_constant decl_pointer pred_decl_opt ; false (* Add the constants of this enum to the map if they are not in the map yet *) let enum_decl decl = let open Clang_ast_t in let get_constant_decl_ptr decl = match decl with - | EnumConstantDecl (decl_info, _, _, _) -> decl_info.di_pointer - | _ -> assert false in + | EnumConstantDecl (decl_info, _, _, _) + -> decl_info.di_pointer + | _ + -> assert false + in let rec add_enum_constants_to_map decl_list = match decl_list with - | decl :: pred_decl :: rest -> - let decl_pointer = get_constant_decl_ptr decl in + | decl :: pred_decl :: rest + -> let decl_pointer = get_constant_decl_ptr decl in let pred_decl_pointer = get_constant_decl_ptr pred_decl in if not (add_enum_constant_to_map_if_needed decl_pointer (Some pred_decl_pointer)) then - add_enum_constants_to_map (pred_decl::rest) - | [decl] -> - let decl_pointer = get_constant_decl_ptr decl in + add_enum_constants_to_map (pred_decl :: rest) + | [decl] + -> let decl_pointer = get_constant_decl_ptr decl in ignore (add_enum_constant_to_map_if_needed decl_pointer None) - | _ -> () in + | _ + -> () + in match decl with - | EnumDecl (_, _, _, type_ptr, decl_list, _, _) -> - add_enum_constants_to_map (List.rev decl_list); + | EnumDecl (_, _, _, type_ptr, decl_list, _, _) + -> add_enum_constants_to_map (List.rev decl_list) ; let sil_desc = Typ.Tint Typ.IInt in - CAst_utils.update_sil_types_map type_ptr sil_desc; - sil_desc - - | _ -> assert false + CAst_utils.update_sil_types_map type_ptr sil_desc ; sil_desc + | _ + -> assert false diff --git a/infer/src/clang/cEnum_decl.mli b/infer/src/clang/cEnum_decl.mli index 0dca50a22..1dbbeea64 100644 --- a/infer/src/clang/cEnum_decl.mli +++ b/infer/src/clang/cEnum_decl.mli @@ -10,6 +10,7 @@ open! IStd (** Translate an enumeration declaration by adding it to the tenv and *) + (** translating the code and adding it to a fake procdesc *) val enum_decl : Clang_ast_t.decl -> Typ.desc diff --git a/infer/src/clang/cField_decl.ml b/infer/src/clang/cField_decl.ml index ae2daa145..276532e4b 100644 --- a/infer/src/clang/cField_decl.ml +++ b/infer/src/clang/cField_decl.ml @@ -16,81 +16,95 @@ module L = Logging type field_type = Typ.Fieldname.t * Typ.t * (Annot.t * bool) list let rec get_fields_super_classes tenv super_class = - L.(debug Capture Verbose) " ... Getting fields of superclass '%s'@\n" - (Typ.Name.to_string super_class); + L.(debug Capture Verbose) + " ... Getting fields of superclass '%s'@\n" (Typ.Name.to_string super_class) ; match Tenv.lookup tenv super_class with - | None -> [] - | Some { fields; supers = super_class :: _ } -> - let sc_fields = get_fields_super_classes tenv super_class in + | None + -> [] + | Some {fields; supers= super_class :: _} + -> let sc_fields = get_fields_super_classes tenv super_class in CGeneral_utils.append_no_duplicates_fields fields sc_fields - | Some { fields } -> fields + | Some {fields} + -> fields let fields_superclass tenv interface_decl_info = match interface_decl_info.Clang_ast_t.otdi_super with - | Some dr -> - (match dr.Clang_ast_t.dr_name with - | Some sc -> - let classname = Typ.Name.Objc.from_qual_name (CAst_utils.get_qualified_name sc) in - get_fields_super_classes tenv classname - | _ -> []) - | _ -> [] + | Some dr -> ( + match dr.Clang_ast_t.dr_name with + | Some sc + -> let classname = Typ.Name.Objc.from_qual_name (CAst_utils.get_qualified_name sc) in + get_fields_super_classes tenv classname + | _ + -> [] ) + | _ + -> [] let build_sil_field qual_type_to_sil_type tenv class_tname field_name qual_type prop_attributes = let prop_atts = List.map ~f:Clang_ast_j.string_of_property_attribute prop_attributes in let annotation_from_type t = match t.Typ.desc with - | Typ.Tptr (_, Typ.Pk_objc_weak) -> [Config.weak] - | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) -> [Config.unsafe_unret] - | _ -> [] in + | Typ.Tptr (_, Typ.Pk_objc_weak) + -> [Config.weak] + | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained) + -> [Config.unsafe_unret] + | _ + -> [] + in let fname = CGeneral_utils.mk_class_field_name class_tname field_name.Clang_ast_t.ni_name in let typ = qual_type_to_sil_type tenv qual_type in - let item_annotations = match prop_atts with - | [] -> - ({ Annot.class_name = Config.ivar_attributes; parameters = annotation_from_type typ }, - true) - | _ -> - ({ Annot.class_name = Config.property_attributes; parameters = prop_atts }, - true) in - let item_annotations = item_annotations :: (CAst_utils.sil_annot_of_type qual_type) in - fname, typ, item_annotations + let item_annotations = + match prop_atts with + | [] + -> ({Annot.class_name= Config.ivar_attributes; parameters= annotation_from_type typ}, true) + | _ + -> ({Annot.class_name= Config.property_attributes; parameters= prop_atts}, true) + in + let item_annotations = item_annotations :: CAst_utils.sil_annot_of_type qual_type in + (fname, typ, item_annotations) (* Given a list of declarations in an interface returns a list of fields *) let rec get_fields qual_type_to_sil_type tenv class_tname decl_list = let open Clang_ast_t in - let add_field name_info (qt : qual_type) attributes decl_list' = + let add_field name_info (qt: qual_type) attributes decl_list' = let fields = get_fields qual_type_to_sil_type tenv class_tname decl_list' in - let field_tuple = build_sil_field qual_type_to_sil_type tenv class_tname - name_info qt attributes in - CGeneral_utils.append_no_duplicates_fields [field_tuple] fields in + let field_tuple = + build_sil_field qual_type_to_sil_type tenv class_tname name_info qt attributes + in + CGeneral_utils.append_no_duplicates_fields [field_tuple] fields + in match decl_list with - | [] -> [] - | ObjCPropertyDecl (_, _, obj_c_property_decl_info) :: decl_list' -> - (let ivar_decl_ref = obj_c_property_decl_info.Clang_ast_t.opdi_ivar_decl in - match CAst_utils.get_decl_opt_with_decl_ref ivar_decl_ref with - | Some (ObjCIvarDecl (_, name_info, qual_type, _, _)) -> - let attributes = obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes in - add_field name_info qual_type attributes decl_list' - | _ -> get_fields qual_type_to_sil_type tenv class_tname decl_list') - | ObjCIvarDecl (_, name_info, qual_type, _, _) :: decl_list' -> - add_field name_info qual_type [] decl_list' - | _ :: decl_list' -> - get_fields qual_type_to_sil_type tenv class_tname decl_list' + | [] + -> [] + | (ObjCPropertyDecl (_, _, obj_c_property_decl_info)) :: decl_list' + -> ( + let ivar_decl_ref = obj_c_property_decl_info.Clang_ast_t.opdi_ivar_decl in + match CAst_utils.get_decl_opt_with_decl_ref ivar_decl_ref with + | Some ObjCIvarDecl (_, name_info, qual_type, _, _) + -> let attributes = obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes in + add_field name_info qual_type attributes decl_list' + | _ + -> get_fields qual_type_to_sil_type tenv class_tname decl_list' ) + | (ObjCIvarDecl (_, name_info, qual_type, _, _)) :: decl_list' + -> add_field name_info qual_type [] decl_list' + | _ :: decl_list' + -> get_fields qual_type_to_sil_type tenv class_tname decl_list' (* Add potential extra fields defined only in the implementation of the class *) (* to the info given in the interface. Update the tenv accordingly. *) let add_missing_fields tenv class_name missing_fields = let class_tn_name = Typ.Name.Objc.from_qual_name class_name in match Tenv.lookup tenv class_tn_name with - | Some ({ fields } as struct_typ) -> - let new_fields = CGeneral_utils.append_no_duplicates_fields fields missing_fields in - ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:new_fields ~statics:[] class_tn_name); - L.(debug Capture Verbose) " Updating info for class '%a' in tenv@\n" - QualifiedCppName.pp class_name - | _ -> () + | Some ({fields} as struct_typ) + -> let new_fields = CGeneral_utils.append_no_duplicates_fields fields missing_fields in + ignore (Tenv.mk_struct tenv ~default:struct_typ ~fields:new_fields ~statics:[] class_tn_name) ; + L.(debug Capture Verbose) + " Updating info for class '%a' in tenv@\n" QualifiedCppName.pp class_name + | _ + -> () let modelled_fields_in_classes = - [("NSData", "_bytes", Typ.mk (Tptr (Typ.mk Tvoid, Typ.Pk_pointer))); - ("NSArray", "elementData", Typ.mk (Tint Typ.IInt))] + [ ("NSData", "_bytes", Typ.mk (Tptr (Typ.mk Tvoid, Typ.Pk_pointer))) + ; ("NSArray", "elementData", Typ.mk (Tint Typ.IInt)) ] let modelled_field class_name_info = let modelled_field_in_class res (class_name, field_name, typ) = @@ -98,5 +112,6 @@ let modelled_field class_name_info = let class_tname = Typ.Name.Objc.from_string class_name in let name = Typ.Fieldname.Clang.from_class_name class_tname field_name in (name, typ, Annot.Item.empty) :: res - else res in + else res + in List.fold ~f:modelled_field_in_class ~init:[] modelled_fields_in_classes diff --git a/infer/src/clang/cField_decl.mli b/infer/src/clang/cField_decl.mli index 4bfefc4b8..e921e1d8b 100644 --- a/infer/src/clang/cField_decl.mli +++ b/infer/src/clang/cField_decl.mli @@ -13,8 +13,9 @@ open! IStd type field_type = Typ.Fieldname.t * Typ.t * (Annot.t * bool) list -val get_fields : CAst_utils.qual_type_to_sil_type -> Tenv.t -> Typ.Name.t -> - Clang_ast_t.decl list -> field_type list +val get_fields : + CAst_utils.qual_type_to_sil_type -> Tenv.t -> Typ.Name.t -> Clang_ast_t.decl list + -> field_type list val fields_superclass : Tenv.t -> Clang_ast_t.obj_c_interface_decl_info -> field_type list diff --git a/infer/src/clang/cFrontend.ml b/infer/src/clang/cFrontend.ml index 4ed15a054..caba198f4 100644 --- a/infer/src/clang/cFrontend.ml +++ b/infer/src/clang/cFrontend.ml @@ -8,45 +8,45 @@ *) open! IStd - - module L = Logging -module rec CTransImpl : CModule_type.CTranslation = - CTrans.CTrans_funct(CFrontend_declImpl) -and CFrontend_declImpl : CModule_type.CFrontend = - CFrontend_decl.CFrontend_decl_funct(CTransImpl) +module rec CTransImpl : CModule_type.CTranslation = CTrans.CTrans_funct (CFrontend_declImpl) + +and CFrontend_declImpl : CModule_type.CFrontend = CFrontend_decl.CFrontend_decl_funct (CTransImpl) (* Translates a file by translating the ast into a cfg. *) let compute_icfg trans_unit_ctx tenv ast = match ast with - | Clang_ast_t.TranslationUnitDecl(_, decl_list, _, _) -> - CFrontend_config.global_translation_unit_decls := decl_list; - L.(debug Capture Verbose) "@\n Start creating icfg@\n"; + | Clang_ast_t.TranslationUnitDecl (_, decl_list, _, _) + -> CFrontend_config.global_translation_unit_decls := decl_list ; + L.(debug Capture Verbose) "@\n Start creating icfg@\n" ; let cg = Cg.create trans_unit_ctx.CFrontend_config.source_file in let cfg = Cfg.create_cfg () in List.iter ~f:(CFrontend_declImpl.translate_one_declaration trans_unit_ctx tenv cg cfg `DeclTraversal) - decl_list; - L.(debug Capture Verbose) "@\n Finished creating icfg@\n"; + decl_list ; + L.(debug Capture Verbose) "@\n Finished creating icfg@\n" ; (cg, cfg) - | _ -> assert false (* NOTE: Assumes that an AST alsways starts with a TranslationUnitDecl *) + | _ + -> assert false + +(* NOTE: Assumes that an AST alsways starts with a TranslationUnitDecl *) let init_global_state_capture () = - Ident.NameGenerator.reset (); - CFrontend_config.global_translation_unit_decls := []; + Ident.NameGenerator.reset () ; + CFrontend_config.global_translation_unit_decls := [] ; CProcname.reset_block_counter () let do_source_file translation_unit_context ast = let tenv = Tenv.create () in - CType_decl.add_predefined_types tenv; - init_global_state_capture (); + CType_decl.add_predefined_types tenv ; + init_global_state_capture () ; let source_file = translation_unit_context.CFrontend_config.source_file in - L.(debug Capture Verbose) "@\n Start building call/cfg graph for '%a'....@\n" - SourceFile.pp source_file; + L.(debug Capture Verbose) + "@\n Start building call/cfg graph for '%a'....@\n" SourceFile.pp source_file ; let call_graph, cfg = compute_icfg translation_unit_context tenv ast in - L.(debug Capture Verbose) "@\n End building call/cfg graph for '%a'.@\n" - SourceFile.pp source_file; + L.(debug Capture Verbose) + "@\n End building call/cfg graph for '%a'.@\n" SourceFile.pp source_file ; (* This part below is a boilerplate in every frontends. *) (* This could be moved in the cfg_infer module *) let source_dir = DB.source_dir_from_source_file source_file in @@ -55,19 +55,15 @@ let do_source_file translation_unit_context ast = changes here, it should be changed there as well*) let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in - NullabilityPreanalysis.analysis cfg tenv; - Cg.store_to_file cg_file call_graph; - Cfg.store_cfg_to_file ~source_file cfg_file cfg; - CGeneral_utils.sort_fields_tenv tenv; - Tenv.store_to_file tenv_file tenv; - if Config.stats_mode then Cfg.check_cfg_connectedness cfg; - if Config.stats_mode - || Config.debug_mode - || Config.testing_mode - || Config.frontend_tests - || Option.is_some Config.icfg_dotty_outfile then - (Dotty.print_icfg_dotty source_file cfg; - Cg.save_call_graph_dotty source_file call_graph); - L.(debug Capture Verbose) "%a" Cfg.pp_proc_signatures cfg; + NullabilityPreanalysis.analysis cfg tenv ; + Cg.store_to_file cg_file call_graph ; + Cfg.store_cfg_to_file ~source_file cfg_file cfg ; + CGeneral_utils.sort_fields_tenv tenv ; + Tenv.store_to_file tenv_file tenv ; + if Config.stats_mode then Cfg.check_cfg_connectedness cfg ; + if Config.stats_mode || Config.debug_mode || Config.testing_mode || Config.frontend_tests + || Option.is_some Config.icfg_dotty_outfile + then ( Dotty.print_icfg_dotty source_file cfg ; Cg.save_call_graph_dotty source_file call_graph ) ; + L.(debug Capture Verbose) "%a" Cfg.pp_proc_signatures cfg ; (* NOTE: nothing should be written to source_dir after this *) DB.mark_file_updated (DB.source_dir_to_string source_dir) diff --git a/infer/src/clang/cFrontend.mli b/infer/src/clang/cFrontend.mli index dbc0c908c..ea97f0dff 100644 --- a/infer/src/clang/cFrontend.mli +++ b/infer/src/clang/cFrontend.mli @@ -9,8 +9,8 @@ open! IStd +val do_source_file : CFrontend_config.translation_unit_context -> Clang_ast_t.decl -> unit (** Translate one file into a cfg. Create a tenv, cg and cfg file for a source file given its ast in json format. Translate the json file into a cfg by adding all the type and class declarations to the tenv, adding all the functions and methods declarations as procdescs to the cfg, and adding the control flow graph of all the code of those functions and methods to the cfg. *) -val do_source_file : CFrontend_config.translation_unit_context -> Clang_ast_t.decl -> unit diff --git a/infer/src/clang/cFrontend_checkers.ml b/infer/src/clang/cFrontend_checkers.ml index dec7a112a..e87fe7ae9 100644 --- a/infer/src/clang/cFrontend_checkers.ml +++ b/infer/src/clang/cFrontend_checkers.ml @@ -26,58 +26,72 @@ let location_from_decl lctx dec = let location_from_an lcxt an = match an with - | Ctl_parser_types.Stmt st -> location_from_stmt lcxt st - | Ctl_parser_types.Decl d -> location_from_decl lcxt d + | Ctl_parser_types.Stmt st + -> location_from_stmt lcxt st + | Ctl_parser_types.Decl d + -> location_from_decl lcxt d let tag_name_of_node an = match an with - | Ctl_parser_types.Stmt stmt -> Clang_ast_proj.get_stmt_kind_string stmt - | Ctl_parser_types.Decl decl -> Clang_ast_proj.get_decl_kind_string decl + | Ctl_parser_types.Stmt stmt + -> Clang_ast_proj.get_stmt_kind_string stmt + | Ctl_parser_types.Decl decl + -> Clang_ast_proj.get_decl_kind_string decl let decl_ref_or_selector_name an = - match CTL.next_state_via_transition an (CTL.PointerToDecl) with - | [Ctl_parser_types.Decl ObjCMethodDecl _ as decl_an] -> - "The selector " ^ (Ctl_parser_types.ast_node_name decl_an) - | [Ctl_parser_types.Decl _ as decl_an] -> - "The reference " ^ (Ctl_parser_types.ast_node_name decl_an) - | _ -> failwith("decl_ref_or_selector_name must be called with a DeclRefExpr \ - or an ObjCMessageExpr, but got " ^ (tag_name_of_node an)) + match CTL.next_state_via_transition an CTL.PointerToDecl with + | [(Ctl_parser_types.Decl ObjCMethodDecl _ as decl_an)] + -> "The selector " ^ Ctl_parser_types.ast_node_name decl_an + | [(Ctl_parser_types.Decl _ as decl_an)] + -> "The reference " ^ Ctl_parser_types.ast_node_name decl_an + | _ + -> failwith + ( "decl_ref_or_selector_name must be called with a DeclRefExpr or an ObjCMessageExpr, but got " + ^ tag_name_of_node an ) let iphoneos_target_sdk_version _ = - match Config.iphoneos_target_sdk_version with - | Some f -> f - | None -> "0" + match Config.iphoneos_target_sdk_version with Some f -> f | None -> "0" let available_ios_sdk an = let open Ctl_parser_types in - match CTL.next_state_via_transition an (CTL.PointerToDecl) with - | [Decl decl] -> - (match CPredicates.get_available_attr_ios_sdk (Decl decl) with - | Some version -> version - | None -> "") - | _ -> failwith("available_ios_sdk must be called with a DeclRefExpr \ - or an ObjCMessageExpr, but got " ^ (tag_name_of_node an)) + match CTL.next_state_via_transition an CTL.PointerToDecl with + | [(Decl decl)] -> ( + match CPredicates.get_available_attr_ios_sdk (Decl decl) with + | Some version + -> version + | None + -> "" ) + | _ + -> failwith + ( "available_ios_sdk must be called with a DeclRefExpr or an ObjCMessageExpr, but got " + ^ tag_name_of_node an ) let ivar_name an = let open Clang_ast_t in match an with - | Ctl_parser_types.Stmt (ObjCIvarRefExpr (_, _, _, rei)) -> + | Ctl_parser_types.Stmt ObjCIvarRefExpr (_, _, _, rei) + -> ( let dr_ref = rei.ovrei_decl_ref in let ivar_pointer = dr_ref.dr_decl_pointer in - (match CAst_utils.get_decl ivar_pointer with - | Some (ObjCIvarDecl (_, named_decl_info, _, _, _)) -> - named_decl_info.Clang_ast_t.ni_name - | _ -> "") - | _ -> "" + match CAst_utils.get_decl ivar_pointer with + | Some ObjCIvarDecl (_, named_decl_info, _, _, _) + -> named_decl_info.Clang_ast_t.ni_name + | _ + -> "" ) + | _ + -> "" let cxx_ref_captured_in_block an = let open Ctl_parser_types in let open Clang_ast_t in - let capt_refs = match an with - | Decl _ -> CPredicates.captured_variables_cxx_ref an - | Stmt (BlockExpr(_, _, _, d)) -> - CPredicates.captured_variables_cxx_ref (Decl d) - | _ -> [] in - let var_desc vars var_named_decl_info = - vars ^ "'" ^ var_named_decl_info.ni_name ^ "'" in + let capt_refs = + match an with + | Decl _ + -> CPredicates.captured_variables_cxx_ref an + | Stmt BlockExpr (_, _, _, d) + -> CPredicates.captured_variables_cxx_ref (Decl d) + | _ + -> [] + in + let var_desc vars var_named_decl_info = vars ^ "'" ^ var_named_decl_info.ni_name ^ "'" in List.fold ~f:var_desc ~init:"" capt_refs diff --git a/infer/src/clang/cFrontend_checkers.mli b/infer/src/clang/cFrontend_checkers.mli index 7e53a19fa..677fef3e6 100644 --- a/infer/src/clang/cFrontend_checkers.mli +++ b/infer/src/clang/cFrontend_checkers.mli @@ -9,17 +9,13 @@ open! IStd -val location_from_stmt : - CLintersContext.context -> Clang_ast_t.stmt -> Location.t +val location_from_stmt : CLintersContext.context -> Clang_ast_t.stmt -> Location.t -val location_from_dinfo : - CLintersContext.context -> Clang_ast_t.decl_info -> Location.t +val location_from_dinfo : CLintersContext.context -> Clang_ast_t.decl_info -> Location.t -val location_from_an : - CLintersContext.context -> Ctl_parser_types.ast_node -> Location.t +val location_from_an : CLintersContext.context -> Ctl_parser_types.ast_node -> Location.t -val location_from_decl : - CLintersContext.context -> Clang_ast_t.decl -> Location.t +val location_from_decl : CLintersContext.context -> Clang_ast_t.decl -> Location.t val ivar_name : Ctl_parser_types.ast_node -> string diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index d5a0fc2f3..93678522f 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -8,28 +8,25 @@ *) open! IStd - open Lexing open Ctl_lexer - module L = Logging let parse_al_file fname channel : CTL.al_file option = let pos_str lexbuf = let pos = lexbuf.lex_curr_p in - pos.pos_fname ^ ":" ^ (string_of_int pos.pos_lnum) ^ ":" ^ - (string_of_int (pos.pos_cnum - pos.pos_bol + 1)) in + pos.pos_fname ^ ":" ^ string_of_int pos.pos_lnum ^ ":" + ^ string_of_int (pos.pos_cnum - pos.pos_bol + 1) + in let parse_with_error lexbuf = try Some (Ctl_parser.al_file token lexbuf) with - | Ctl_parser_types.ALParsingException s -> - raise (Ctl_parser_types.ALParsingException - (s ^ " at " ^ (pos_str lexbuf))) - | SyntaxError _ - | Ctl_parser.Error -> - raise (Ctl_parser_types.ALParsingException - ("SYNTAX ERROR at " ^ (pos_str lexbuf))) in + | Ctl_parser_types.ALParsingException s + -> raise (Ctl_parser_types.ALParsingException (s ^ " at " ^ pos_str lexbuf)) + | SyntaxError _ | Ctl_parser.Error + -> raise (Ctl_parser_types.ALParsingException ("SYNTAX ERROR at " ^ pos_str lexbuf)) + in let lexbuf = Lexing.from_channel channel in - lexbuf.lex_curr_p <- { lexbuf.lex_curr_p with pos_fname = fname }; + lexbuf.lex_curr_p <- {lexbuf.lex_curr_p with pos_fname= fname} ; parse_with_error lexbuf let already_imported_files = ref [] @@ -37,238 +34,265 @@ let already_imported_files = ref [] let rec parse_import_file import_file channel = if List.mem ~equal:String.equal !already_imported_files import_file then failwith ("Cyclic imports: file '" ^ import_file ^ "' was already imported.") - else ( + else match parse_al_file import_file channel with - | Some { - import_files = imports; - global_macros = curr_file_macros; - global_paths = curr_file_paths; - checkers = _ - } -> - already_imported_files := import_file :: !already_imported_files; + | Some + { import_files= imports + ; global_macros= curr_file_macros + ; global_paths= curr_file_paths + ; checkers= _ } + -> already_imported_files := import_file :: !already_imported_files ; collect_all_macros_and_paths imports curr_file_macros curr_file_paths - | None -> L.(debug Linters Medium) "No macros or paths found.@\n";[], []) + | None + -> L.(debug Linters Medium) "No macros or paths found.@\n" ; ([], []) and collect_all_macros_and_paths imports curr_file_macros curr_file_paths = - L.(debug Linters Medium) "#### Start parsing import macros #####@\n"; + L.(debug Linters Medium) "#### Start parsing import macros #####@\n" ; let import_macros, import_paths = parse_imports imports in - L.(debug Linters Medium) "#### Add global macros to import macros #####@\n"; + L.(debug Linters Medium) "#### Add global macros to import macros #####@\n" ; let macros = List.append import_macros curr_file_macros in let paths = List.append import_paths curr_file_paths in - macros, paths + (macros, paths) (* Parse import files with macro definitions, and it returns a list of LET clauses *) and parse_imports imports_files = let parse_one_import_file fimport (macros, paths) = - L.(debug Linters Medium) " Loading import macros from file %s@\n" fimport; + L.(debug Linters Medium) " Loading import macros from file %s@\n" fimport ; let in_channel = In_channel.create fimport in let parsed_macros, parsed_paths = parse_import_file fimport in_channel in - In_channel.close in_channel; + In_channel.close in_channel ; let macros = List.append parsed_macros macros in let paths = List.append parsed_paths paths in - macros, paths in + (macros, paths) + in List.fold_right ~f:parse_one_import_file ~init:([], []) imports_files let parse_ctl_file linters_def_file channel : CFrontend_errors.linter list = match parse_al_file linters_def_file channel with - | Some { - import_files = imports; - global_macros = curr_file_macros; - global_paths = curr_file_paths; - checkers = parsed_checkers - } -> - already_imported_files := [linters_def_file]; + | Some + { import_files= imports + ; global_macros= curr_file_macros + ; global_paths= curr_file_paths + ; checkers= parsed_checkers } + -> already_imported_files := [linters_def_file] ; let macros, paths = collect_all_macros_and_paths imports curr_file_macros curr_file_paths in let macros_map = CFrontend_errors.build_macros_map macros in let paths_map = CFrontend_errors.build_paths_map paths in - L.(debug Linters Medium) "#### Start Expanding checkers #####@\n"; + L.(debug Linters Medium) "#### Start Expanding checkers #####@\n" ; let exp_checkers = CFrontend_errors.expand_checkers macros_map paths_map parsed_checkers in - L.(debug Linters Medium) "#### Checkers Expanded #####@\n"; - if Config.debug_mode then List.iter ~f:CTL.print_checker exp_checkers; + L.(debug Linters Medium) "#### Checkers Expanded #####@\n" ; + if Config.debug_mode then List.iter ~f:CTL.print_checker exp_checkers ; CFrontend_errors.create_parsed_linters linters_def_file exp_checkers - | None -> L.(debug Linters Medium) "No linters found.@\n";[] + | None + -> L.(debug Linters Medium) "No linters found.@\n" ; [] (* Parse the files with linters definitions, and it returns a list of linters *) let parse_ctl_files linters_def_files : CFrontend_errors.linter list = let collect_parsed_linters linters_def_file linters = - L.(debug Linters Medium) "Loading linters rules from %s@\n" linters_def_file; + L.(debug Linters Medium) "Loading linters rules from %s@\n" linters_def_file ; let in_channel = In_channel.create linters_def_file in let parsed_linters = parse_ctl_file linters_def_file in_channel in - In_channel.close in_channel; - List.append parsed_linters linters in + In_channel.close in_channel ; List.append parsed_linters linters + in List.fold_right ~f:collect_parsed_linters ~init:[] linters_def_files let rec get_responds_to_selector stmt = let open Clang_ast_t in let responToSelectorMethods = ["respondsToSelector:"; "instancesRespondToSelector:"] in match stmt with - | ObjCMessageExpr (_, [_; ObjCSelectorExpr (_, _, _, method_name)], _, mdi) - | ObjCMessageExpr (_, [ObjCSelectorExpr (_, _, _, method_name)], _, mdi) - when List.mem ~equal:String.equal responToSelectorMethods mdi.Clang_ast_t.omei_selector -> - [method_name] - | BinaryOperator (_, [stmt1;stmt2], _, bo_info) - when PVariant.(=) bo_info.Clang_ast_t.boi_kind `LAnd -> - List.append (get_responds_to_selector stmt1) (get_responds_to_selector stmt2) + | ObjCMessageExpr (_, [_; (ObjCSelectorExpr (_, _, _, method_name))], _, mdi) + | ObjCMessageExpr (_, [(ObjCSelectorExpr (_, _, _, method_name))], _, mdi) + when List.mem ~equal:String.equal responToSelectorMethods mdi.Clang_ast_t.omei_selector + -> [method_name] + | BinaryOperator (_, [stmt1; stmt2], _, bo_info) + when PVariant.( = ) bo_info.Clang_ast_t.boi_kind `LAnd + -> List.append (get_responds_to_selector stmt1) (get_responds_to_selector stmt2) | ImplicitCastExpr (_, [stmt], _, _) | ParenExpr (_, [stmt], _) - | ExprWithCleanups(_, [stmt], _, _) -> - get_responds_to_selector stmt - | _ -> [] + | ExprWithCleanups (_, [stmt], _, _) + -> get_responds_to_selector stmt + | _ + -> [] let rec is_core_foundation_version_number stmt = let open Clang_ast_t in match stmt with - | DeclRefExpr (_, _, _, decl_ref_info) -> - (match decl_ref_info.drti_decl_ref with - | Some decl_ref_info -> - let name_info, _, _ = CAst_utils.get_info_from_decl_ref decl_ref_info in - String.equal name_info.ni_name "kCFCoreFoundationVersionNumber" - | None -> false) - | ImplicitCastExpr (_, [stmt], _, _) -> - is_core_foundation_version_number stmt - | _ -> false + | DeclRefExpr (_, _, _, decl_ref_info) -> ( + match decl_ref_info.drti_decl_ref with + | Some decl_ref_info + -> let name_info, _, _ = CAst_utils.get_info_from_decl_ref decl_ref_info in + String.equal name_info.ni_name "kCFCoreFoundationVersionNumber" + | None + -> false ) + | ImplicitCastExpr (_, [stmt], _, _) + -> is_core_foundation_version_number stmt + | _ + -> false let rec current_os_version_constant stmt = let open Clang_ast_t in match stmt with - | FloatingLiteral (_, _, _, number) -> - CiOSVersionNumbers.version_of number - | IntegerLiteral (_, _, _, info) -> - CiOSVersionNumbers.version_of info.ili_value - | ImplicitCastExpr (_, [stmt], _, _) -> - current_os_version_constant stmt - | _ -> None + | FloatingLiteral (_, _, _, number) + -> CiOSVersionNumbers.version_of number + | IntegerLiteral (_, _, _, info) + -> CiOSVersionNumbers.version_of info.ili_value + | ImplicitCastExpr (_, [stmt], _, _) + -> current_os_version_constant stmt + | _ + -> None let rec get_current_os_version stmt = let open Clang_ast_t in match stmt with - | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when - PVariant.(=) bo_info.Clang_ast_t.boi_kind `GE && - is_core_foundation_version_number stmt1 -> - Option.to_list (current_os_version_constant stmt2) - | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when - PVariant.(=) bo_info.Clang_ast_t.boi_kind `LE && - is_core_foundation_version_number stmt2 -> - Option.to_list (current_os_version_constant stmt1) - | BinaryOperator (_, [stmt1;stmt2], _, bo_info) when - PVariant.(=) bo_info.Clang_ast_t.boi_kind `LAnd -> - List.append (get_current_os_version stmt1) (get_current_os_version stmt2) + | BinaryOperator (_, [stmt1; stmt2], _, bo_info) + when PVariant.( = ) bo_info.Clang_ast_t.boi_kind `GE && is_core_foundation_version_number stmt1 + -> Option.to_list (current_os_version_constant stmt2) + | BinaryOperator (_, [stmt1; stmt2], _, bo_info) + when PVariant.( = ) bo_info.Clang_ast_t.boi_kind `LE && is_core_foundation_version_number stmt2 + -> Option.to_list (current_os_version_constant stmt1) + | BinaryOperator (_, [stmt1; stmt2], _, bo_info) + when PVariant.( = ) bo_info.Clang_ast_t.boi_kind `LAnd + -> List.append (get_current_os_version stmt1) (get_current_os_version stmt2) | ImplicitCastExpr (_, [stmt], _, _) | ParenExpr (_, [stmt], _) - | ExprWithCleanups(_, [stmt], _, _) -> - get_current_os_version stmt - | _ -> [] + | ExprWithCleanups (_, [stmt], _, _) + -> get_current_os_version stmt + | _ + -> [] -let compute_if_context (context:CLintersContext.context) stmt = +let compute_if_context (context: CLintersContext.context) stmt = let selector = get_responds_to_selector stmt in let os_version = get_current_os_version stmt in let within_responds_to_selector_block, ios_version_guard = match context.if_context with - | Some if_context -> - let within_responds_to_selector_block = - List.append selector if_context.within_responds_to_selector_block in - let ios_version_guard = - List.append os_version if_context.ios_version_guard in - within_responds_to_selector_block, ios_version_guard - | None -> selector, os_version in + | Some if_context + -> let within_responds_to_selector_block = + List.append selector if_context.within_responds_to_selector_block + in + let ios_version_guard = List.append os_version if_context.ios_version_guard in + (within_responds_to_selector_block, ios_version_guard) + | None + -> (selector, os_version) + in Some ({within_responds_to_selector_block; ios_version_guard} : CLintersContext.if_context) let is_factory_method (context: CLintersContext.context) decl = let interface_decl_opt = - (match context.current_objc_impl with - | Some ObjCImplementationDecl (_, _, _, _, impl_decl_info) -> - CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface - | _ -> None) in - (match interface_decl_opt with - | Some interface_decl -> CAst_utils.is_objc_factory_method interface_decl decl - | _ -> false) + match context.current_objc_impl with + | Some ObjCImplementationDecl (_, _, _, _, impl_decl_info) + -> CAst_utils.get_decl_opt_with_decl_ref impl_decl_info.oidi_class_interface + | _ + -> None + in + match interface_decl_opt with + | Some interface_decl + -> CAst_utils.is_objc_factory_method interface_decl decl + | _ + -> false -let rec do_frontend_checks_stmt (context:CLintersContext.context) stmt = +let rec do_frontend_checks_stmt (context: CLintersContext.context) stmt = let open Clang_ast_t in let do_all_checks_on_stmts context stmt = - (match stmt with - | DeclStmt (_, _, decl_list) -> - List.iter ~f:(do_frontend_checks_decl context) decl_list - | BlockExpr (_, _, _, decl) -> - List.iter ~f:(do_frontend_checks_decl context) [decl] - | _ -> ()); - do_frontend_checks_stmt context stmt in - CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Stmt stmt); + ( match stmt with + | DeclStmt (_, _, decl_list) + -> List.iter ~f:(do_frontend_checks_decl context) decl_list + | BlockExpr (_, _, _, decl) + -> List.iter ~f:(do_frontend_checks_decl context) [decl] + | _ + -> () ) ; + do_frontend_checks_stmt context stmt + in + CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Stmt stmt) ; match stmt with - | ObjCAtSynchronizedStmt (_, stmt_list) -> - let stmt_context = { context with CLintersContext.in_synchronized_block = true } in + | ObjCAtSynchronizedStmt (_, stmt_list) + -> let stmt_context = {context with CLintersContext.in_synchronized_block= true} in List.iter ~f:(do_all_checks_on_stmts stmt_context) stmt_list - | IfStmt (_, [stmt1; stmt2; cond_stmt; inside_if_stmt; inside_else_stmt]) -> - (* here we analyze the children of the if stmt with the standard context, + | IfStmt (_, [stmt1; stmt2; cond_stmt; inside_if_stmt; inside_else_stmt]) + -> (* here we analyze the children of the if stmt with the standard context, except for inside_if_stmt... *) - List.iter ~f:(do_all_checks_on_stmts context) [stmt1; stmt2; cond_stmt; inside_else_stmt]; + List.iter ~f:(do_all_checks_on_stmts context) [stmt1; stmt2; cond_stmt; inside_else_stmt] ; let inside_if_stmt_context = - {context with CLintersContext.if_context = compute_if_context context cond_stmt } in + {context with CLintersContext.if_context= compute_if_context context cond_stmt} + in (* ...and here we analyze the stmt inside the if with the context extended with the condition of the if *) do_all_checks_on_stmts inside_if_stmt_context inside_if_stmt - | OpaqueValueExpr (_, lstmt, _, opaque_value_expr_info) -> - let stmts = (match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with - | Some stmt -> lstmt @ [stmt] - | _ -> lstmt) + | OpaqueValueExpr (_, lstmt, _, opaque_value_expr_info) + -> let stmts = + match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with + | Some stmt + -> lstmt @ [stmt] + | _ + -> lstmt in List.iter ~f:(do_all_checks_on_stmts context) stmts (* given that this has not been translated, looking up for variables *) (* inside leads to inconsistencies *) - | ObjCAtCatchStmt _ -> - () - | _ -> - let stmts = snd (Clang_ast_proj.get_stmt_tuple stmt) in + | ObjCAtCatchStmt _ + -> () + | _ + -> let stmts = snd (Clang_ast_proj.get_stmt_tuple stmt) in List.iter ~f:(do_all_checks_on_stmts context) stmts and do_frontend_checks_decl (context: CLintersContext.context) decl = let open Clang_ast_t in - CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Decl decl); + CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Decl decl) ; match decl with - | FunctionDecl(_, _, _, fdi) + | FunctionDecl (_, _, _, fdi) | CXXMethodDecl (_, _, _, fdi, _) | CXXConstructorDecl (_, _, _, fdi, _) | CXXConversionDecl (_, _, _, fdi, _) - | CXXDestructorDecl (_, _, _, fdi, _) -> - let context' = {context with CLintersContext.current_method = Some decl } in - (match fdi.Clang_ast_t.fdi_body with - | Some stmt -> do_frontend_checks_stmt context' stmt - | None -> ()) - | ObjCMethodDecl (_, _, mdi) -> - let context' = {context with - CLintersContext.current_method = Some decl; - CLintersContext.in_objc_static_factory_method = - is_factory_method context decl} in - (match mdi.Clang_ast_t.omdi_body with - | Some stmt -> do_frontend_checks_stmt context' stmt - | None -> ()) - | BlockDecl (_, block_decl_info) -> - let context' = {context with CLintersContext.current_method = Some decl } in - (match block_decl_info.Clang_ast_t.bdi_body with - | Some stmt -> do_frontend_checks_stmt context' stmt - | None -> ()) - | ObjCImplementationDecl (_, _, decls, _, _) -> - let context' = {context with current_objc_impl = Some decl} in + | CXXDestructorDecl (_, _, _, fdi, _) + -> ( + let context' = {context with CLintersContext.current_method= Some decl} in + match fdi.Clang_ast_t.fdi_body with + | Some stmt + -> do_frontend_checks_stmt context' stmt + | None + -> () ) + | ObjCMethodDecl (_, _, mdi) + -> ( + let context' = + { context with + CLintersContext.current_method= Some decl + ; CLintersContext.in_objc_static_factory_method= is_factory_method context decl } + in + match mdi.Clang_ast_t.omdi_body with + | Some stmt + -> do_frontend_checks_stmt context' stmt + | None + -> () ) + | BlockDecl (_, block_decl_info) + -> ( + let context' = {context with CLintersContext.current_method= Some decl} in + match block_decl_info.Clang_ast_t.bdi_body with + | Some stmt + -> do_frontend_checks_stmt context' stmt + | None + -> () ) + | ObjCImplementationDecl (_, _, decls, _, _) + -> let context' = {context with current_objc_impl= Some decl} in List.iter ~f:(do_frontend_checks_decl context') decls - | _ -> match Clang_ast_proj.get_decl_context_tuple decl with - | Some (decls, _) -> - List.iter ~f:(do_frontend_checks_decl context) decls - | None -> () + | _ -> + match Clang_ast_proj.get_decl_context_tuple decl with + | Some (decls, _) + -> List.iter ~f:(do_frontend_checks_decl context) decls + | None + -> () let context_with_ck_set context decl_list = - let is_ck = context.CLintersContext.is_ck_translation_unit - || ComponentKit.contains_ck_impl decl_list in - if is_ck then - { context with CLintersContext.is_ck_translation_unit = true } - else - context + let is_ck = + context.CLintersContext.is_ck_translation_unit || ComponentKit.contains_ck_impl decl_list + in + if is_ck then {context with CLintersContext.is_ck_translation_unit= true} else context let store_issues source_file = let abbrev_source_file = DB.source_file_encoding source_file in let lint_issues_dir = Config.results_dir ^/ Config.lint_issues_dir_name in - Utils.create_dir lint_issues_dir; + Utils.create_dir lint_issues_dir ; let lint_issues_file = - DB.filename_from_string (Filename.concat lint_issues_dir (abbrev_source_file ^ ".issue")) in + DB.filename_from_string (Filename.concat lint_issues_dir (abbrev_source_file ^ ".issue")) + in LintIssues.store_issues lint_issues_file !LintIssues.errLogMap let find_linters_files () = @@ -276,49 +300,55 @@ let find_linters_files () = let aux base_path files rel_path = let full_path = Filename.concat base_path rel_path in match (Unix.stat full_path).Unix.st_kind with - | Unix.S_REG when String.is_suffix ~suffix:".al" full_path -> full_path :: files - | Unix.S_DIR -> find_aux files full_path - | _ -> files in - Sys.fold_dir ~init ~f:(aux dir_path) dir_path in + | Unix.S_REG when String.is_suffix ~suffix:".al" full_path + -> full_path :: files + | Unix.S_DIR + -> find_aux files full_path + | _ + -> files + in + Sys.fold_dir ~init ~f:(aux dir_path) dir_path + in List.concat (List.map ~f:(fun folder -> find_aux [] folder) Config.linters_def_folder) let linters_files = List.dedup ~compare:String.compare (find_linters_files () @ Config.linters_def_file) let do_frontend_checks (trans_unit_ctx: CFrontend_config.translation_unit_context) ast = - L.(debug Capture Quiet) "Loading the following linters files: %a@\n" - (Pp.comma_seq Format.pp_print_string) linters_files; - CTL.create_ctl_evaluation_tracker trans_unit_ctx.source_file; + L.(debug Capture Quiet) + "Loading the following linters files: %a@\n" (Pp.comma_seq Format.pp_print_string) + linters_files ; + CTL.create_ctl_evaluation_tracker trans_unit_ctx.source_file ; try let parsed_linters = parse_ctl_files linters_files in let filtered_parsed_linters = - CFrontend_errors.filter_parsed_linters parsed_linters trans_unit_ctx.source_file in - CFrontend_errors.parsed_linters := filtered_parsed_linters; + CFrontend_errors.filter_parsed_linters parsed_linters trans_unit_ctx.source_file + in + CFrontend_errors.parsed_linters := filtered_parsed_linters ; let source_file = trans_unit_ctx.CFrontend_config.source_file in - L.(debug Linters Medium) "Start linting file %a with rules: @\n%a@\n" - SourceFile.pp source_file - CFrontend_errors.pp_linters filtered_parsed_linters; + L.(debug Linters Medium) + "Start linting file %a with rules: @\n%a@\n" SourceFile.pp source_file + CFrontend_errors.pp_linters filtered_parsed_linters ; if Config.print_active_checkers then - L.progress "Linting file %a, active linters: @\n%a@\n" - SourceFile.pp source_file - CFrontend_errors.pp_linters filtered_parsed_linters; + L.progress "Linting file %a, active linters: @\n%a@\n" SourceFile.pp source_file + CFrontend_errors.pp_linters filtered_parsed_linters ; match ast with - | Clang_ast_t.TranslationUnitDecl(_, decl_list, _, _) -> - let context = - context_with_ck_set (CLintersContext.empty trans_unit_ctx) decl_list in + | Clang_ast_t.TranslationUnitDecl (_, decl_list, _, _) + -> let context = context_with_ck_set (CLintersContext.empty trans_unit_ctx) decl_list in let is_decl_allowed decl = let decl_info = Clang_ast_proj.get_decl_tuple decl in - CLocation.should_do_frontend_check trans_unit_ctx decl_info.Clang_ast_t.di_source_range in + CLocation.should_do_frontend_check trans_unit_ctx decl_info.Clang_ast_t.di_source_range + in let allowed_decls = List.filter ~f:is_decl_allowed decl_list in (* We analyze the top level and then all the allowed declarations *) - CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Decl ast); - List.iter ~f:(do_frontend_checks_decl context) allowed_decls; - if (LintIssues.exists_issues ()) then - store_issues source_file; - L.(debug Linters Medium) "End linting file %a@\n" SourceFile.pp source_file; - CTL.save_dotty_when_in_debug_mode trans_unit_ctx.CFrontend_config.source_file; - | _ -> assert false (* NOTE: Assumes that an AST always starts with a TranslationUnitDecl *) - with - | Assert_failure (file, line, column) as exn -> - L.internal_error "Fatal error: exception Assert_failure(%s, %d, %d)@\n%!" file line column; - raise exn + CFrontend_errors.invoke_set_of_checkers_on_node context (Ctl_parser_types.Decl ast) ; + List.iter ~f:(do_frontend_checks_decl context) allowed_decls ; + if LintIssues.exists_issues () then store_issues source_file ; + L.(debug Linters Medium) "End linting file %a@\n" SourceFile.pp source_file ; + CTL.save_dotty_when_in_debug_mode trans_unit_ctx.CFrontend_config.source_file + | _ + -> assert false + (* NOTE: Assumes that an AST always starts with a TranslationUnitDecl *) + with Assert_failure (file, line, column) as exn -> + L.internal_error "Fatal error: exception Assert_failure(%s, %d, %d)@\n%!" file line column ; + raise exn diff --git a/infer/src/clang/cFrontend_config.ml b/infer/src/clang/cFrontend_config.ml index 55fd98157..7af6b043d 100644 --- a/infer/src/clang/cFrontend_config.ml +++ b/infer/src/clang/cFrontend_config.ml @@ -15,91 +15,151 @@ type clang_lang = C | CPP | ObjC | ObjCPP [@@deriving compare] let equal_clang_lang = [%compare.equal : clang_lang] -type translation_unit_context = { - lang : clang_lang; - source_file : SourceFile.t -} +type translation_unit_context = {lang: clang_lang; source_file: SourceFile.t} (** Constants *) let alloc = "alloc" + let array_with_objects_count_m = "arrayWithObjects:count:" + let assert_fail = "__assert_fail" + let assert_rtn = "__assert_rtn" + let atomic_att = "<\"Atomic\">" + let autorelease = "autorelease" + let block = "block" + let builtin_expect = "__builtin_expect" + let builtin_memset_chk = "__builtin___memset_chk" + let builtin_object_size = "__builtin_object_size" -let cf_alloc ="__cf_alloc" + +let cf_alloc = "__cf_alloc" + let cf_autorelease = "CFAutorelease" + let cf_bridging_release = "CFBridgingRelease" + let cf_bridging_retain = "CFBridgingRetain" -let cf_non_null_alloc ="__cf_non_null_alloc" + +let cf_non_null_alloc = "__cf_non_null_alloc" + let ckcomponent_cl = "CKComponent" + let ckcomponentcontroller_cl = "CKComponentController" (** script to run our own clang *) let clang_bin xx = - Config.bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name ^/ - "facebook-clang-plugins" ^/ - "clang" ^/ "install" ^/ "bin" ^/ "clang" ^ xx + Config.bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name + ^/ "facebook-clang-plugins" ^/ "clang" ^/ "install" ^/ "bin" ^/ "clang" ^ xx + let class_method = "class" + let class_type = "Class" + let copy = "copy" + let count = "count" + let drain = "drain" -let emtpy_name_category ="EMPTY_NAME_CATEGORY_FOR_" + +let emtpy_name_category = "EMPTY_NAME_CATEGORY_FOR_" + let enumerateObjectsUsingBlock = "enumerateObjectsUsingBlock:" + let fbAssertWithSignalAndLogFunctionHelper = "FBAssertWithSignalAndLogFunctionHelper" + let free = "free" + let google_LogMessageFatal = "google::LogMessageFatal_LogMessageFatal" + let google_MakeCheckOpString = "google::MakeCheckOpString" + let handleFailureInFunction = "handleFailureInFunction:file:lineNumber:description:" + let handleFailureInMethod = "handleFailureInMethod:object:file:lineNumber:description:" + let id_cl = "id" + let infer = "infer" + let infer_skip_fun = "__infer_skip_function" + let infer_skip_gcc_asm_stmt = "__infer_skip_gcc_asm_stmt" + let init = "init" + let invalid_pointer = 0 + let is_kind_of_class = "isKindOfClass:" + let malloc = "malloc" + let mutableCopy = "mutableCopy" + let new_str = "new" + let next_object = "nextObject" + let ns_make_collectable = "NSMakeCollectable" + let nsarray_cl = "NSArray" + let nsautorelease_pool_cl = "NSAutoreleasePool" + let nsproxy_cl = "NSProxy" + let nsobject_cl = "NSObject" + let nsstring_cl = "NSString" + let objc_class = "objc_class" + let objc_object = "objc_object" + let object_at_indexed_subscript_m = "objectAtIndexedSubscript:" + let objects = "objects" + let pseudo_object_type = "" + let release = "release" + let retain = "retain" + let return_param = "__return_param" + let self = "self" + let static = "static" + let string_with_utf8_m = "stringWithUTF8String:" + let this = "this" + let void = "void" + let replace_with_deref_first_arg_attr = "__infer_replace_with_deref_first_arg" + let modeled_function_attributes = [replace_with_deref_first_arg_attr] (** Global state *) let enum_map = ref ClangPointers.Map.empty + let global_translation_unit_decls : Clang_ast_t.decl list ref = ref [] + let log_out = ref Format.std_formatter + let sil_types_map = ref Clang_ast_extend.TypePointerMap.empty let reset_global_state () = - enum_map := ClangPointers.Map.empty; - global_translation_unit_decls := []; - log_out := Format.std_formatter; - sil_types_map := Clang_ast_extend.TypePointerMap.empty; + enum_map := ClangPointers.Map.empty ; + global_translation_unit_decls := [] ; + log_out := Format.std_formatter ; + sil_types_map := Clang_ast_extend.TypePointerMap.empty diff --git a/infer/src/clang/cFrontend_config.mli b/infer/src/clang/cFrontend_config.mli index 5882a4a82..63aefea10 100644 --- a/infer/src/clang/cFrontend_config.mli +++ b/infer/src/clang/cFrontend_config.mli @@ -15,90 +15,148 @@ type clang_lang = C | CPP | ObjC | ObjCPP [@@deriving compare] val equal_clang_lang : clang_lang -> clang_lang -> bool -type translation_unit_context = { - lang : clang_lang; - source_file : SourceFile.t -} +type translation_unit_context = {lang: clang_lang; source_file: SourceFile.t} (** Constants *) val alloc : string + val array_with_objects_count_m : string + val assert_fail : string + val assert_rtn : string + val atomic_att : string + val autorelease : string + val block : string + val builtin_expect : string + val builtin_memset_chk : string + val builtin_object_size : string + val cf_alloc : string + val cf_autorelease : string + val cf_bridging_release : string + val cf_bridging_retain : string + val cf_non_null_alloc : string + val ckcomponent_cl : string + val ckcomponentcontroller_cl : string -(** Script to run our own clang. The argument is expected to be either "" or "++". *) val clang_bin : string -> string +(** Script to run our own clang. The argument is expected to be either "" or "++". *) val class_method : string + val class_type : string + val copy : string + val count : string + val drain : string + val emtpy_name_category : string + val enumerateObjectsUsingBlock : string + val fbAssertWithSignalAndLogFunctionHelper : string + val free : string + val google_LogMessageFatal : string + val google_MakeCheckOpString : string + val handleFailureInFunction : string + val handleFailureInMethod : string + val id_cl : string + val infer : string + val infer_skip_fun : string + val infer_skip_gcc_asm_stmt : string + val init : string + val invalid_pointer : int + val is_kind_of_class : string + val malloc : string + val mutableCopy : string + val new_str : string + val next_object : string + val ns_make_collectable : string + val nsarray_cl : string + val nsautorelease_pool_cl : string + val nsproxy_cl : string + val nsobject_cl : string + val nsstring_cl : string + val objc_class : string + val objc_object : string + val object_at_indexed_subscript_m : string + val objects : string + val pseudo_object_type : string + val release : string + val retain : string + val return_param : string + val self : string + val static : string + val string_with_utf8_m : string + val this : string + val void : string + val replace_with_deref_first_arg_attr : string -val modeled_function_attributes : string list +val modeled_function_attributes : string list (** Global state *) -(** Map from enum constants pointers to their predecesor and their sil value *) val enum_map : (Clang_ast_t.pointer option * Exp.t option) ClangPointers.Map.t ref +(** Map from enum constants pointers to their predecesor and their sil value *) + val global_translation_unit_decls : Clang_ast_t.decl list ref + val log_out : Format.formatter ref +val sil_types_map : Typ.desc Clang_ast_extend.TypePointerMap.t ref (** Map from type pointers (clang pointers and types created later by frontend) to sil types Populated during frontend execution when new type is found *) -val sil_types_map : (Typ.desc Clang_ast_extend.TypePointerMap.t) ref val reset_global_state : unit -> unit diff --git a/infer/src/clang/cFrontend_decl.ml b/infer/src/clang/cFrontend_decl.ml index 42cf8c25e..e2a777490 100644 --- a/infer/src/clang/cFrontend_decl.ml +++ b/infer/src/clang/cFrontend_decl.ml @@ -13,120 +13,133 @@ open! IStd module L = Logging -module CFrontend_decl_funct(T: CModule_type.CTranslation) : CModule_type.CFrontend = -struct - let model_exists procname = - Specs.summary_exists_in_models procname && not Config.models_mode +module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFrontend = struct + let model_exists procname = Specs.summary_exists_in_models procname && not Config.models_mode (* Translates the method/function's body into nodes of the cfg. *) let add_method trans_unit_ctx tenv cg cfg class_decl_opt procname body has_return_param is_objc_method outer_context_opt extra_instrs = let handle_translation_failure () = - Cfg.remove_proc_desc cfg procname; + Cfg.remove_proc_desc cfg procname ; CMethod_trans.create_external_procdesc cfg procname is_objc_method None in L.(debug Capture Verbose) - "@\n@\n>>---------- ADDING METHOD: '%s' ---------<<@\n@." (Typ.Procname.to_string procname); + "@\n@\n>>---------- ADDING METHOD: '%s' ---------<<@\n@." (Typ.Procname.to_string procname) ; try - (match Cfg.find_proc_desc_from_name cfg procname with - | Some procdesc -> - if (Procdesc.is_defined procdesc && not (model_exists procname)) then - (let context = - CContext.create_context trans_unit_ctx tenv cg cfg procdesc class_decl_opt - has_return_param is_objc_method outer_context_opt in - let start_node = Procdesc.get_start_node procdesc in - let exit_node = Procdesc.get_exit_node procdesc in - L.(debug Capture Verbose) - "@\n@\n>>---------- Start translating body of function: '%s' ---------<<@\n@." - (Typ.Procname.to_string procname); - let meth_body_nodes = T.instructions_trans context body extra_instrs exit_node in - let proc_attributes = Procdesc.get_attributes procdesc in - Procdesc.Node.add_locals_ret_declaration - start_node proc_attributes (Procdesc.get_locals procdesc); - Procdesc.node_set_succs_exn procdesc start_node meth_body_nodes []; - Cg.add_defined_node (CContext.get_cg context) (Procdesc.get_proc_name procdesc)) - | None -> ()) + match Cfg.find_proc_desc_from_name cfg procname with + | Some procdesc + -> if Procdesc.is_defined procdesc && not (model_exists procname) then + let context = + CContext.create_context trans_unit_ctx tenv cg cfg procdesc class_decl_opt + has_return_param is_objc_method outer_context_opt + in + let start_node = Procdesc.get_start_node procdesc in + let exit_node = Procdesc.get_exit_node procdesc in + L.(debug Capture Verbose) + "@\n@\n>>---------- Start translating body of function: '%s' ---------<<@\n@." + (Typ.Procname.to_string procname) ; + let meth_body_nodes = T.instructions_trans context body extra_instrs exit_node in + let proc_attributes = Procdesc.get_attributes procdesc in + Procdesc.Node.add_locals_ret_declaration start_node proc_attributes + (Procdesc.get_locals procdesc) ; + Procdesc.node_set_succs_exn procdesc start_node meth_body_nodes [] ; + Cg.add_defined_node (CContext.get_cg context) (Procdesc.get_proc_name procdesc) + | None + -> () with - | Not_found -> () - | CTrans_utils.Self.SelfClassException _ -> - (* this shouldn't happen, because self or [a class] should always be arguments of + | Not_found + -> () + | CTrans_utils.Self.SelfClassException _ + -> (* this shouldn't happen, because self or [a class] should always be arguments of functions. This is to make sure I'm not wrong. *) assert false - | CTrans_utils.TemplatedCodeException _ -> - L.internal_error "Fatal error: frontend doesn't support translation of templated code@\n"; + | CTrans_utils.TemplatedCodeException _ + -> L.internal_error "Fatal error: frontend doesn't support translation of templated code@\n" ; handle_translation_failure () - | Assert_failure (file, line, column) when Config.failures_allowed -> - L.internal_error "Fatal error: exception Assert_failure(%s, %d, %d)@\n%!" file line column; + | Assert_failure (file, line, column) when Config.failures_allowed + -> L.internal_error "Fatal error: exception Assert_failure(%s, %d, %d)@\n%!" file line column ; handle_translation_failure () let function_decl trans_unit_ctx tenv cfg cg func_decl block_data_opt = let captured_vars, outer_context_opt = match block_data_opt with - | Some (outer_context, _, _, captured_vars) -> captured_vars, Some outer_context - | None -> [], None in + | Some (outer_context, _, _, captured_vars) + -> (captured_vars, Some outer_context) + | None + -> ([], None) + in let ms, body_opt, extra_instrs = - CMethod_trans.method_signature_of_decl trans_unit_ctx tenv func_decl block_data_opt in + CMethod_trans.method_signature_of_decl trans_unit_ctx tenv func_decl block_data_opt + in match body_opt with - | Some body -> - (* Only in the case the function declaration has a defined body we create a procdesc *) + | Some body + -> (* Only in the case the function declaration has a defined body we create a procdesc *) let procname = CMethod_signature.ms_get_name ms in let return_param_typ_opt = CMethod_signature.ms_get_return_param_typ ms in - if CMethod_trans.create_local_procdesc - trans_unit_ctx cfg tenv ms [body] captured_vars false then + if CMethod_trans.create_local_procdesc trans_unit_ctx cfg tenv ms [body] captured_vars + false + then add_method trans_unit_ctx tenv cg cfg CContext.ContextNoCls procname body return_param_typ_opt false outer_context_opt extra_instrs - | None -> () + | None + -> () - let process_method_decl ?(set_objc_accessor_attr=false) trans_unit_ctx tenv cg cfg - curr_class meth_decl ~is_objc = + let process_method_decl ?(set_objc_accessor_attr= false) trans_unit_ctx tenv cg cfg curr_class + meth_decl ~is_objc = let ms, body_opt, extra_instrs = - CMethod_trans.method_signature_of_decl trans_unit_ctx tenv meth_decl None in + CMethod_trans.method_signature_of_decl trans_unit_ctx tenv meth_decl None + in let is_instance = CMethod_signature.ms_is_instance ms in let is_objc_inst_method = is_instance && is_objc in match body_opt with - | Some body -> - let procname = CMethod_signature.ms_get_name ms in + | Some body + -> let procname = CMethod_signature.ms_get_name ms in let return_param_typ_opt = CMethod_signature.ms_get_return_param_typ ms in - if CMethod_trans.create_local_procdesc ~set_objc_accessor_attr - trans_unit_ctx cfg tenv ms [body] [] is_objc_inst_method then + if CMethod_trans.create_local_procdesc ~set_objc_accessor_attr trans_unit_ctx cfg tenv ms + [body] [] is_objc_inst_method + then add_method trans_unit_ctx tenv cg cfg curr_class procname body return_param_typ_opt is_objc None extra_instrs - | None -> - if set_objc_accessor_attr then - ignore (CMethod_trans.create_local_procdesc ~set_objc_accessor_attr trans_unit_ctx - cfg tenv ms [] [] is_objc_inst_method) + | None + -> if set_objc_accessor_attr then + ignore + (CMethod_trans.create_local_procdesc ~set_objc_accessor_attr trans_unit_ctx cfg tenv ms + [] [] is_objc_inst_method) let process_property_implementation trans_unit_ctx tenv cg cfg curr_class obj_c_property_impl_decl_info = let property_decl_opt = obj_c_property_impl_decl_info.Clang_ast_t.opidi_property_decl in match CAst_utils.get_decl_opt_with_decl_ref property_decl_opt with - | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) -> - let process_accessor pointer = - (match CAst_utils.get_decl_opt_with_decl_ref pointer with - | Some (ObjCMethodDecl _ as dec) -> - process_method_decl ~set_objc_accessor_attr:true trans_unit_ctx tenv cg cfg - curr_class dec ~is_objc:true - | _ -> ()) in - process_accessor obj_c_property_decl_info.Clang_ast_t.opdi_getter_method; + | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) + -> let process_accessor pointer = + match CAst_utils.get_decl_opt_with_decl_ref pointer with + | Some (ObjCMethodDecl _ as dec) + -> process_method_decl ~set_objc_accessor_attr:true trans_unit_ctx tenv cg cfg + curr_class dec ~is_objc:true + | _ + -> () + in + process_accessor obj_c_property_decl_info.Clang_ast_t.opdi_getter_method ; process_accessor obj_c_property_decl_info.Clang_ast_t.opdi_setter_method - | _ -> () + | _ + -> () let process_one_method_decl trans_unit_ctx tenv cg cfg curr_class dec = let open Clang_ast_t in match dec with - | CXXMethodDecl _ | CXXConstructorDecl _ | CXXConversionDecl _ | CXXDestructorDecl _ -> - process_method_decl trans_unit_ctx tenv cg cfg curr_class dec ~is_objc:false - | ObjCMethodDecl _ -> - process_method_decl trans_unit_ctx tenv cg cfg curr_class dec ~is_objc:true - | ObjCPropertyImplDecl (_, obj_c_property_impl_decl_info) -> - process_property_implementation trans_unit_ctx tenv cg cfg curr_class + | CXXMethodDecl _ | CXXConstructorDecl _ | CXXConversionDecl _ | CXXDestructorDecl _ + -> process_method_decl trans_unit_ctx tenv cg cfg curr_class dec ~is_objc:false + | ObjCMethodDecl _ + -> process_method_decl trans_unit_ctx tenv cg cfg curr_class dec ~is_objc:true + | ObjCPropertyImplDecl (_, obj_c_property_impl_decl_info) + -> process_property_implementation trans_unit_ctx tenv cg cfg curr_class obj_c_property_impl_decl_info - | EmptyDecl _ - | ObjCIvarDecl _ | ObjCPropertyDecl _ -> () - | _ -> - L.internal_error + | EmptyDecl _ | ObjCIvarDecl _ | ObjCPropertyDecl _ + -> () + | _ + -> L.internal_error "@\nWARNING: found Method Declaration '%s' skipped. NEED TO BE FIXED@\n@\n" - (Clang_ast_proj.get_decl_kind_string dec); + (Clang_ast_proj.get_decl_kind_string dec) ; () let process_methods trans_unit_ctx tenv cg cfg curr_class decl_list = @@ -136,143 +149,161 @@ struct whether method should be translated based on method and class whitelists *) let is_whitelisted_cpp_method = let method_matcher = - QualifiedCppName.Match.of_fuzzy_qual_names Config.whitelisted_cpp_methods in + QualifiedCppName.Match.of_fuzzy_qual_names Config.whitelisted_cpp_methods + in let class_matcher = - QualifiedCppName.Match.of_fuzzy_qual_names Config.whitelisted_cpp_classes in + QualifiedCppName.Match.of_fuzzy_qual_names Config.whitelisted_cpp_classes + in fun qual_name -> (* either the method is explictely whitelisted, or the whole class is whitelisted *) - QualifiedCppName.Match.match_qualifiers method_matcher qual_name || + QualifiedCppName.Match.match_qualifiers method_matcher qual_name + || match QualifiedCppName.extract_last qual_name with - | Some (_, class_qual_name) -> - (* make sure the class name is not empty; in particular, it cannot be a C function *) + | Some (_, class_qual_name) + -> (* make sure the class name is not empty; in particular, it cannot be a C function *) QualifiedCppName.Match.match_qualifiers class_matcher class_qual_name - | None -> - false + | None + -> false let should_translate_decl trans_unit_ctx dec decl_trans_context = let info = Clang_ast_proj.get_decl_tuple dec in let source_range = info.Clang_ast_t.di_source_range in - let translate_when_used = match dec with + let translate_when_used = + match dec with | Clang_ast_t.FunctionDecl (_, name_info, _, _) - | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) -> - is_whitelisted_cpp_method (CAst_utils.get_qualified_name name_info) - | _ -> false in + | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) + -> is_whitelisted_cpp_method (CAst_utils.get_qualified_name name_info) + | _ + -> false + in let translate_location = CLocation.should_translate_lib trans_unit_ctx source_range decl_trans_context - ~translate_when_used in - let never_translate_decl = match dec with + ~translate_when_used + in + let never_translate_decl = + match dec with | Clang_ast_t.FunctionDecl (_, name_info, _, _) - | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) -> - let fun_name = name_info.Clang_ast_t.ni_name in - Str.string_match (Str.regexp "__infer_skip__" ) fun_name 0 - | _ -> false in - (not never_translate_decl) && translate_location + | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) + -> let fun_name = name_info.Clang_ast_t.ni_name in + Str.string_match (Str.regexp "__infer_skip__") fun_name 0 + | _ + -> false + in + not never_translate_decl && translate_location (* Translate one global declaration *) let rec translate_one_declaration trans_unit_ctx tenv cg cfg decl_trans_context dec = let open Clang_ast_t in - (* each procedure has different scope: start names from id 0 *) - Ident.NameGenerator.reset (); + Ident.NameGenerator.reset () ; let translate = translate_one_declaration trans_unit_ctx tenv cg cfg decl_trans_context in - (if should_translate_decl trans_unit_ctx dec decl_trans_context then - let dec_ptr = (Clang_ast_proj.get_decl_tuple dec).di_pointer in - match dec with - | FunctionDecl(_, _, _, _) -> - function_decl trans_unit_ctx tenv cfg cg dec None - - | ObjCInterfaceDecl(_, _, decl_list, _, _) -> - let curr_class = CContext.ContextClsDeclPtr dec_ptr in - ignore - (ObjcInterface_decl.interface_declaration CType_decl.qual_type_to_sil_type tenv dec); - process_methods trans_unit_ctx tenv cg cfg curr_class decl_list - - | ObjCProtocolDecl(_, _, decl_list, _, _) -> - let curr_class = CContext.ContextClsDeclPtr dec_ptr in - ignore (ObjcProtocol_decl.protocol_decl CType_decl.qual_type_to_sil_type tenv dec); - process_methods trans_unit_ctx tenv cg cfg curr_class decl_list - - | ObjCCategoryDecl(_, _, decl_list, _, _) -> - let curr_class = CContext.ContextClsDeclPtr dec_ptr in - ignore (ObjcCategory_decl.category_decl CType_decl.qual_type_to_sil_type tenv dec); - process_methods trans_unit_ctx tenv cg cfg curr_class decl_list - - | ObjCCategoryImplDecl(_, _, decl_list, _, _) -> - let curr_class = CContext.ContextClsDeclPtr dec_ptr in - ignore (ObjcCategory_decl.category_impl_decl CType_decl.qual_type_to_sil_type tenv dec); - process_methods trans_unit_ctx tenv cg cfg curr_class decl_list; - - | ObjCImplementationDecl(decl_info, _, decl_list, _, _) -> - let curr_class = CContext.ContextClsDeclPtr dec_ptr in - let class_typename = CType_decl.get_record_typename ~tenv dec in - let qual_type_to_sil_type = CType_decl.qual_type_to_sil_type in - ignore (ObjcInterface_decl.interface_impl_declaration qual_type_to_sil_type tenv dec); - CMethod_trans.add_default_method_for_class trans_unit_ctx class_typename decl_info; - process_methods trans_unit_ctx tenv cg cfg curr_class decl_list; - - | CXXMethodDecl (decl_info, _, _, _, _) - | CXXConstructorDecl (decl_info, _, _, _, _) - | CXXConversionDecl (decl_info, _, _, _, _) - | CXXDestructorDecl (decl_info, _, _, _, _) -> - (* di_parent_pointer has pointer to lexical context such as class.*) - let parent_ptr = Option.value_exn decl_info.Clang_ast_t.di_parent_pointer in - let class_decl = CAst_utils.get_decl parent_ptr in - (match class_decl with - | Some (CXXRecordDecl _) - | Some (ClassTemplateSpecializationDecl _) when Config.cxx -> - let curr_class = CContext.ContextClsDeclPtr parent_ptr in + ( if should_translate_decl trans_unit_ctx dec decl_trans_context then + let dec_ptr = (Clang_ast_proj.get_decl_tuple dec).di_pointer in + match dec with + | FunctionDecl (_, _, _, _) + -> function_decl trans_unit_ctx tenv cfg cg dec None + | ObjCInterfaceDecl (_, _, decl_list, _, _) + -> let curr_class = CContext.ContextClsDeclPtr dec_ptr in + ignore + (ObjcInterface_decl.interface_declaration CType_decl.qual_type_to_sil_type tenv dec) ; + process_methods trans_unit_ctx tenv cg cfg curr_class decl_list + | ObjCProtocolDecl (_, _, decl_list, _, _) + -> let curr_class = CContext.ContextClsDeclPtr dec_ptr in + ignore (ObjcProtocol_decl.protocol_decl CType_decl.qual_type_to_sil_type tenv dec) ; + process_methods trans_unit_ctx tenv cg cfg curr_class decl_list + | ObjCCategoryDecl (_, _, decl_list, _, _) + -> let curr_class = CContext.ContextClsDeclPtr dec_ptr in + ignore (ObjcCategory_decl.category_decl CType_decl.qual_type_to_sil_type tenv dec) ; + process_methods trans_unit_ctx tenv cg cfg curr_class decl_list + | ObjCCategoryImplDecl (_, _, decl_list, _, _) + -> let curr_class = CContext.ContextClsDeclPtr dec_ptr in + ignore (ObjcCategory_decl.category_impl_decl CType_decl.qual_type_to_sil_type tenv dec) ; + process_methods trans_unit_ctx tenv cg cfg curr_class decl_list + | ObjCImplementationDecl (decl_info, _, decl_list, _, _) + -> let curr_class = CContext.ContextClsDeclPtr dec_ptr in + let class_typename = CType_decl.get_record_typename ~tenv dec in + let qual_type_to_sil_type = CType_decl.qual_type_to_sil_type in + ignore (ObjcInterface_decl.interface_impl_declaration qual_type_to_sil_type tenv dec) ; + CMethod_trans.add_default_method_for_class trans_unit_ctx class_typename decl_info ; + process_methods trans_unit_ctx tenv cg cfg curr_class decl_list + | CXXMethodDecl (decl_info, _, _, _, _) + | CXXConstructorDecl (decl_info, _, _, _, _) + | CXXConversionDecl (decl_info, _, _, _, _) + | CXXDestructorDecl (decl_info, _, _, _, _) + -> ( + (* di_parent_pointer has pointer to lexical context such as class.*) + let parent_ptr = Option.value_exn decl_info.Clang_ast_t.di_parent_pointer in + let class_decl = CAst_utils.get_decl parent_ptr in + match class_decl with + | (Some CXXRecordDecl _ | Some ClassTemplateSpecializationDecl _) when Config.cxx + -> let curr_class = CContext.ContextClsDeclPtr parent_ptr in process_methods trans_unit_ctx tenv cg cfg curr_class [dec] - | Some dec -> - L.(debug Capture Verbose) "Methods of %s skipped@\n" - (Clang_ast_proj.get_decl_kind_string dec) - | None -> ()) - | VarDecl (decl_info, named_decl_info, qt, ({ vdi_is_global; vdi_init_expr } as vdi)) - when vdi_is_global && Option.is_some vdi_init_expr -> - (* create a fake procedure that initializes the global variable so that the variable + | Some dec + -> L.(debug Capture Verbose) + "Methods of %s skipped@\n" (Clang_ast_proj.get_decl_kind_string dec) + | None + -> () ) + | VarDecl (decl_info, named_decl_info, qt, ({vdi_is_global; vdi_init_expr} as vdi)) + when vdi_is_global && Option.is_some vdi_init_expr + -> (* create a fake procedure that initializes the global variable so that the variable initializer can be analyzed by the backend (eg, the SIOF checker) *) - let procname = - (* create the corresponding global variable to get the right pname for its + let procname = + (* create the corresponding global variable to get the right pname for its initializer *) - let global = CGeneral_utils.mk_sil_global_var trans_unit_ctx named_decl_info vdi qt in - (* safe to Option.get because it's a global *) - Option.value_exn (Pvar.get_initializer_pname global) in - let ms = CMethod_signature.make_ms procname [] Ast_expressions.create_void_type - [] decl_info.Clang_ast_t.di_source_range false trans_unit_ctx.CFrontend_config.lang - None None None `None in - let stmt_info = { si_pointer = CAst_utils.get_fresh_pointer (); - si_source_range = decl_info.di_source_range } in - let body = Clang_ast_t.DeclStmt (stmt_info, [], [dec]) in - ignore (CMethod_trans.create_local_procdesc trans_unit_ctx cfg tenv ms [body] [] false); - add_method trans_unit_ctx tenv cg cfg CContext.ContextNoCls procname body None false - None [] - (* Note that C and C++ records are treated the same way + let global = + CGeneral_utils.mk_sil_global_var trans_unit_ctx named_decl_info vdi qt + in + (* safe to Option.get because it's a global *) + Option.value_exn (Pvar.get_initializer_pname global) + in + let ms = + CMethod_signature.make_ms procname [] Ast_expressions.create_void_type [] + decl_info.Clang_ast_t.di_source_range false trans_unit_ctx.CFrontend_config.lang + None None None `None + in + let stmt_info = + { si_pointer= CAst_utils.get_fresh_pointer () + ; si_source_range= decl_info.di_source_range } + in + let body = Clang_ast_t.DeclStmt (stmt_info, [], [dec]) in + ignore (CMethod_trans.create_local_procdesc trans_unit_ctx cfg tenv ms [body] [] false) ; + add_method trans_unit_ctx tenv cg cfg CContext.ContextNoCls procname body None false + None [] + (* Note that C and C++ records are treated the same way Skip translating implicit struct declarations, unless they have full definition (which happens with C++ lambdas) *) - | ClassTemplateSpecializationDecl (di, _, _, _, decl_list, _, rdi, _, _) - | CXXRecordDecl (di, _, _, _, decl_list, _, rdi, _) - | RecordDecl (di, _, _, _, decl_list, _, rdi) - when (not di.di_is_implicit) || rdi.rdi_is_complete_definition -> - let is_method_decl decl = match decl with - | CXXMethodDecl _ | CXXConstructorDecl _ | CXXConversionDecl _ - | CXXDestructorDecl _ | FunctionTemplateDecl _ -> - true - | _ -> false in - let method_decls, no_method_decls = List.partition_tf ~f:is_method_decl decl_list in - List.iter ~f:translate no_method_decls; - ignore (CType_decl.add_types_from_decl_to_tenv tenv dec); - List.iter ~f:translate method_decls - | _ -> ()); + | ClassTemplateSpecializationDecl (di, _, _, _, decl_list, _, rdi, _, _) + | CXXRecordDecl (di, _, _, _, decl_list, _, rdi, _) + | RecordDecl (di, _, _, _, decl_list, _, rdi) + when not di.di_is_implicit || rdi.rdi_is_complete_definition + -> let is_method_decl decl = + match decl with + | CXXMethodDecl _ + | CXXConstructorDecl _ + | CXXConversionDecl _ + | CXXDestructorDecl _ + | FunctionTemplateDecl _ + -> true + | _ + -> false + in + let method_decls, no_method_decls = List.partition_tf ~f:is_method_decl decl_list in + List.iter ~f:translate no_method_decls ; + ignore (CType_decl.add_types_from_decl_to_tenv tenv dec) ; + List.iter ~f:translate method_decls + | _ + -> () ) ; match dec with - | EnumDecl _ -> ignore (CEnum_decl.enum_decl dec) - | LinkageSpecDecl (_, decl_list, _) -> - L.(debug Capture Verbose) "ADDING: LinkageSpecDecl decl list@\n"; + | EnumDecl _ + -> ignore (CEnum_decl.enum_decl dec) + | LinkageSpecDecl (_, decl_list, _) + -> L.(debug Capture Verbose) "ADDING: LinkageSpecDecl decl list@\n" ; List.iter ~f:translate decl_list - | NamespaceDecl (_, _, decl_list, _, _) -> + | NamespaceDecl (_, _, decl_list, _, _) + -> List.iter ~f:translate decl_list + | ClassTemplateDecl (_, _, template_decl_info) | FunctionTemplateDecl (_, _, template_decl_info) + -> let decl_list = template_decl_info.Clang_ast_t.tdi_specializations in List.iter ~f:translate decl_list - | ClassTemplateDecl (_, _, template_decl_info) - | FunctionTemplateDecl (_, _, template_decl_info) -> - let decl_list = template_decl_info.Clang_ast_t.tdi_specializations in - List.iter ~f:translate decl_list - | _ -> () - + | _ + -> () end diff --git a/infer/src/clang/cFrontend_decl.mli b/infer/src/clang/cFrontend_decl.mli index dfa6bf5fd..3f006747c 100644 --- a/infer/src/clang/cFrontend_decl.mli +++ b/infer/src/clang/cFrontend_decl.mli @@ -11,4 +11,4 @@ open! IStd (** Translate declarations **) -module CFrontend_decl_funct(T: CModule_type.CTranslation) : CModule_type.CFrontend +module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFrontend diff --git a/infer/src/clang/cFrontend_errors.ml b/infer/src/clang/cFrontend_errors.ml index 7d8930653..a1cbb2660 100644 --- a/infer/src/clang/cFrontend_errors.ml +++ b/infer/src/clang/cFrontend_errors.ml @@ -8,32 +8,26 @@ *) open! IStd - module F = Format - module L = Logging module MF = MarkupFormatter -type linter = { - condition : CTL.t; - issue_desc : CIssue.issue_desc; - def_file : string option; - whitelist_paths : ALVar.t list; - blacklist_paths : ALVar.t list; -} +type linter = + { condition: CTL.t + ; issue_desc: CIssue.issue_desc + ; def_file: string option + ; whitelist_paths: ALVar.t list + ; blacklist_paths: ALVar.t list } (* If in linter developer mode and if current linter was passed, filter it out *) let filter_parsed_linters_developer parsed_linters = if List.length parsed_linters > 1 && Config.linters_developer_mode then match Config.linter with - | None -> - failwith "In linters developer mode you should debug only one linter at a time. \ - This is important for debugging the rule. Pass the flag \ - --linter to specify the linter you want to debug."; - | Some lint -> - List.filter ~f:( - fun (rule : linter) -> String.equal rule.issue_desc.id lint - ) parsed_linters + | None + -> failwith + "In linters developer mode you should debug only one linter at a time. This is important for debugging the rule. Pass the flag --linter to specify the linter you want to debug." + | Some lint + -> List.filter ~f:(fun (rule: linter) -> String.equal rule.issue_desc.id lint) parsed_linters else parsed_linters let filter_parsed_linters_by_path parsed_linters source_file = @@ -41,24 +35,26 @@ let filter_parsed_linters_by_path parsed_linters source_file = let should_lint paths = List.exists ~f:(fun path -> ALVar.compare_str_with_alexp (SourceFile.to_rel_path source_file) path) - paths in + paths + in let whitelist_ok = - List.is_empty linter.whitelist_paths || should_lint linter.whitelist_paths in + List.is_empty linter.whitelist_paths || should_lint linter.whitelist_paths + in let blacklist_ok = - List.is_empty linter.blacklist_paths || not (should_lint linter.blacklist_paths) in + List.is_empty linter.blacklist_paths || not (should_lint linter.blacklist_paths) + in whitelist_ok && blacklist_ok in List.filter ~f:filter_parsed_linter_by_path parsed_linters let filter_parsed_linters parsed_linters source_file = let linters = filter_parsed_linters_developer parsed_linters in - if (Config.debug_mode || not Config.filtering) then - linters (* do not filter by path if in debug or no filtering mode *) + if Config.debug_mode || not Config.filtering then linters + (* do not filter by path if in debug or no filtering mode *) else filter_parsed_linters_by_path linters source_file let pp_linters fmt linters = - let pp_linter fmt {issue_desc={id}} = - F.fprintf fmt "%s@\n" id in + let pp_linter fmt {issue_desc= {id}} = F.fprintf fmt "%s@\n" id in List.iter ~f:(pp_linter fmt) linters (* Map a formula id to a triple (visited, parameters, definition). @@ -67,31 +63,30 @@ let pp_linters fmt linters = type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t (* Map a path name to a list of paths. *) -type paths_map = (ALVar.t list) ALVar.VarMap.t +type paths_map = ALVar.t list ALVar.VarMap.t -let single_to_multi checker = - fun ctx an -> - let issue_desc_opt = checker ctx an in - Option.to_list issue_desc_opt +let single_to_multi checker ctx an = + let issue_desc_opt = checker ctx an in + Option.to_list issue_desc_opt (* List of checkers on decls *that return 0 or 1 issue* *) let decl_single_checkers_list = - [ComponentKit.component_with_unconventional_superclass_advice; - ComponentKit.mutable_local_vars_advice; - ComponentKit.component_factory_function_advice; - ComponentKit.component_file_cyclomatic_complexity_info;] + [ ComponentKit.component_with_unconventional_superclass_advice + ; ComponentKit.mutable_local_vars_advice + ; ComponentKit.component_factory_function_advice + ; ComponentKit.component_file_cyclomatic_complexity_info ] (* List of checkers on decls *) let decl_checkers_list = - ComponentKit.component_with_multiple_factory_methods_advice:: - (ComponentKit.component_file_line_count_info:: - (List.map ~f:single_to_multi decl_single_checkers_list)) + ComponentKit.component_with_multiple_factory_methods_advice + :: ComponentKit.component_file_line_count_info + :: List.map ~f:single_to_multi decl_single_checkers_list (* List of checkers on stmts *that return 0 or 1 issue* *) let stmt_single_checkers_list = - [ComponentKit.component_file_cyclomatic_complexity_info; - ComponentKit.component_initializer_with_side_effects_advice; - GraphQL.DeprecatedAPIUsage.checker;] + [ ComponentKit.component_file_cyclomatic_complexity_info + ; ComponentKit.component_initializer_with_side_effects_advice + ; GraphQL.DeprecatedAPIUsage.checker ] let stmt_checkers_list = List.map ~f:single_to_multi stmt_single_checkers_list @@ -101,20 +96,26 @@ let parsed_linters = ref [] let evaluate_place_holder ph an = match ph with - | "%ivar_name%" -> MF.monospaced_to_string (CFrontend_checkers.ivar_name an) - | "%decl_name%" -> MF.monospaced_to_string (Ctl_parser_types.ast_node_name an) - | "%cxx_ref_captured_in_block%" -> - MF.monospaced_to_string (CFrontend_checkers.cxx_ref_captured_in_block an) - | "%decl_ref_or_selector_name%" -> - MF.monospaced_to_string (CFrontend_checkers.decl_ref_or_selector_name an) - | "%iphoneos_target_sdk_version%" -> - MF.monospaced_to_string (CFrontend_checkers.iphoneos_target_sdk_version an) - | "%available_ios_sdk%" -> MF.monospaced_to_string (CFrontend_checkers.available_ios_sdk an) - | "%type%" -> MF.monospaced_to_string (Ctl_parser_types.ast_node_type an) - | "%child_type%" -> MF.monospaced_to_string (Ctl_parser_types.stmt_node_child_type an) - | "%name%" -> MF.monospaced_to_string (Ctl_parser_types.ast_node_name an) - | _ -> - L.internal_error "ERROR: helper function %s is unknown. Stop.@\n" ph; + | "%ivar_name%" + -> MF.monospaced_to_string (CFrontend_checkers.ivar_name an) + | "%decl_name%" + -> MF.monospaced_to_string (Ctl_parser_types.ast_node_name an) + | "%cxx_ref_captured_in_block%" + -> MF.monospaced_to_string (CFrontend_checkers.cxx_ref_captured_in_block an) + | "%decl_ref_or_selector_name%" + -> MF.monospaced_to_string (CFrontend_checkers.decl_ref_or_selector_name an) + | "%iphoneos_target_sdk_version%" + -> MF.monospaced_to_string (CFrontend_checkers.iphoneos_target_sdk_version an) + | "%available_ios_sdk%" + -> MF.monospaced_to_string (CFrontend_checkers.available_ios_sdk an) + | "%type%" + -> MF.monospaced_to_string (Ctl_parser_types.ast_node_type an) + | "%child_type%" + -> MF.monospaced_to_string (Ctl_parser_types.stmt_node_child_type an) + | "%name%" + -> MF.monospaced_to_string (Ctl_parser_types.ast_node_name an) + | _ + -> L.internal_error "ERROR: helper function %s is unknown. Stop.@\n" ph ; assert false (* given a message this function searches for a place-holder identifier, @@ -130,190 +131,227 @@ let rec expand_message_string message an = let _ = Str.search_forward re message 0 in let ms = Str.matched_string message in let res = evaluate_place_holder ms an in - L.(debug Linters Medium) "@\nMatched string '%s'@\n" ms; + L.(debug Linters Medium) "@\nMatched string '%s'@\n" ms ; let re_ms = Str.regexp_string ms in let message' = Str.replace_first re_ms res message in - L.(debug Linters Medium) "Replacing %s in message: @\n %s @\n" ms message; - L.(debug Linters Medium) "Resulting message: @\n %s @\n" message'; + L.(debug Linters Medium) "Replacing %s in message: @\n %s @\n" ms message ; + L.(debug Linters Medium) "Resulting message: @\n %s @\n" message' ; expand_message_string message' an with Not_found -> message -let remove_new_lines message = - String.substr_replace_all ~pattern:"\n" ~with_:" " message +let remove_new_lines message = String.substr_replace_all ~pattern:"\n" ~with_:" " message let string_to_err_kind = function - | "WARNING" -> Exceptions.Kwarning - | "ERROR" -> Exceptions.Kerror - | "INFO" -> Exceptions.Kinfo - | "ADVICE" -> Exceptions.Kadvice - | "LIKE" -> Exceptions.Klike - | s -> - L.internal_error "@\n[ERROR] Severity %s does not exist. Stop.@\n" s; + | "WARNING" + -> Exceptions.Kwarning + | "ERROR" + -> Exceptions.Kerror + | "INFO" + -> Exceptions.Kinfo + | "ADVICE" + -> Exceptions.Kadvice + | "LIKE" + -> Exceptions.Klike + | s + -> L.internal_error "@\n[ERROR] Severity %s does not exist. Stop.@\n" s ; assert false let string_to_issue_mode m = match m with - | "ON" -> CIssue.On - | "OFF" -> CIssue.Off - | s -> - L.internal_error "@\n[ERROR] Mode %s does not exist. Please specify ON/OFF@\n" s; + | "ON" + -> CIssue.On + | "OFF" + -> CIssue.Off + | s + -> L.internal_error "@\n[ERROR] Mode %s does not exist. Please specify ON/OFF@\n" s ; assert false (** Convert a parsed checker in list of linters *) let create_parsed_linters linters_def_file checkers : linter list = let open CIssue in let open CTL in - L.(debug Linters Medium) "@\nConverting checkers in (condition, issue) pairs@\n"; + L.(debug Linters Medium) "@\nConverting checkers in (condition, issue) pairs@\n" ; let do_one_checker checker : linter = - let dummy_issue = { - id = checker.id; - name = None; - description = ""; - suggestion = None; - loc = Location.dummy; - severity = Exceptions.Kwarning; - doc_url = None; - mode = CIssue.On; - } in + let dummy_issue = + { id= checker.id + ; name= None + ; description= "" + ; suggestion= None + ; loc= Location.dummy + ; severity= Exceptions.Kwarning + ; doc_url= None + ; mode= CIssue.On } + in let issue_desc, condition, whitelist_paths, blacklist_paths = let process_linter_definitions (issue, cond, wl_paths, bl_paths) description = match description with - | CSet (av, phi) when ALVar.is_report_when_keyword av -> - issue, phi, wl_paths, bl_paths - | CDesc (av, msg) when ALVar.is_message_keyword av -> - {issue with description = msg}, cond, wl_paths, bl_paths - | CDesc (av, sugg) when ALVar.is_suggestion_keyword av -> - {issue with suggestion = Some sugg}, cond, wl_paths, bl_paths - | CDesc (av, sev) when ALVar.is_severity_keyword av -> - {issue with severity = string_to_err_kind sev}, cond, wl_paths, bl_paths - | CDesc (av, m) when ALVar.is_mode_keyword av -> - {issue with mode = string_to_issue_mode m }, cond, wl_paths, bl_paths - | CDesc (av, doc) when ALVar.is_doc_url_keyword av -> - {issue with doc_url = Some doc }, cond, wl_paths, bl_paths - | CDesc (av, name) when ALVar.is_name_keyword av -> - {issue with name = Some name }, cond, wl_paths, bl_paths - | CPath (`WhitelistPath, paths) -> - issue, cond, paths, bl_paths - | CPath (`BlacklistPath, paths) -> - issue, cond, wl_paths, paths - | _ -> issue, cond, wl_paths, bl_paths in - List.fold - ~f:process_linter_definitions - ~init:(dummy_issue, CTL.False, [], []) - checker.definitions in - L.(debug Linters Medium) "@\nMaking condition and issue desc for checker '%s'@\n" checker.id; - L.(debug Linters Medium) "@\nCondition =@\n %a@\n" CTL.Debug.pp_formula condition; - L.(debug Linters Medium) "@\nIssue_desc = %a@\n" CIssue.pp_issue issue_desc; - {condition; issue_desc; def_file = Some linters_def_file; whitelist_paths; blacklist_paths;} in + | CSet (av, phi) when ALVar.is_report_when_keyword av + -> (issue, phi, wl_paths, bl_paths) + | CDesc (av, msg) when ALVar.is_message_keyword av + -> ({issue with description= msg}, cond, wl_paths, bl_paths) + | CDesc (av, sugg) when ALVar.is_suggestion_keyword av + -> ({issue with suggestion= Some sugg}, cond, wl_paths, bl_paths) + | CDesc (av, sev) when ALVar.is_severity_keyword av + -> ({issue with severity= string_to_err_kind sev}, cond, wl_paths, bl_paths) + | CDesc (av, m) when ALVar.is_mode_keyword av + -> ({issue with mode= string_to_issue_mode m}, cond, wl_paths, bl_paths) + | CDesc (av, doc) when ALVar.is_doc_url_keyword av + -> ({issue with doc_url= Some doc}, cond, wl_paths, bl_paths) + | CDesc (av, name) when ALVar.is_name_keyword av + -> ({issue with name= Some name}, cond, wl_paths, bl_paths) + | CPath (`WhitelistPath, paths) + -> (issue, cond, paths, bl_paths) + | CPath (`BlacklistPath, paths) + -> (issue, cond, wl_paths, paths) + | _ + -> (issue, cond, wl_paths, bl_paths) + in + List.fold ~f:process_linter_definitions ~init:(dummy_issue, CTL.False, [], []) + checker.definitions + in + L.(debug Linters Medium) "@\nMaking condition and issue desc for checker '%s'@\n" checker.id ; + L.(debug Linters Medium) "@\nCondition =@\n %a@\n" CTL.Debug.pp_formula condition ; + L.(debug Linters Medium) "@\nIssue_desc = %a@\n" CIssue.pp_issue issue_desc ; + {condition; issue_desc; def_file= Some linters_def_file; whitelist_paths; blacklist_paths} + in List.map ~f:do_one_checker checkers let rec apply_substitution f sub = - let sub_param p = try - snd (List.find_exn sub ~f:(fun (a,_) -> ALVar.equal p a)) - with Not_found -> p in - let sub_list_param ps = - List.map ps ~f:sub_param in + let sub_param p = + try snd (List.find_exn sub ~f:(fun (a, _) -> ALVar.equal p a)) + with Not_found -> p + in + let sub_list_param ps = List.map ps ~f:sub_param in let open CTL in match f with - | True - | False -> f - | Atomic (name, ps) -> - Atomic (name, sub_list_param ps) - | Not f1 -> - Not (apply_substitution f1 sub) - | And (f1, f2) -> - And (apply_substitution f1 sub, apply_substitution f2 sub) - | Or (f1, f2) -> - Or (apply_substitution f1 sub, apply_substitution f2 sub) - | Implies (f1, f2) -> - Implies (apply_substitution f1 sub, apply_substitution f2 sub) - | InNode (node_type_list, f1) -> - InNode (sub_list_param node_type_list, apply_substitution f1 sub) - | AU (trans, f1, f2) -> - AU (trans, apply_substitution f1 sub, apply_substitution f2 sub) - | EU (trans, f1, f2) -> - EU (trans, apply_substitution f1 sub, apply_substitution f2 sub) - | EF (trans, f1) -> - EF (trans, apply_substitution f1 sub) - | AF (trans, f1) -> - AF (trans, apply_substitution f1 sub) - | AG (trans, f1) -> - AG (trans, apply_substitution f1 sub) - | EX (trans, f1) -> - EX (trans, apply_substitution f1 sub) - | AX (trans, f1) -> - AX (trans, apply_substitution f1 sub) - | EH (cl, f1) -> - EH (sub_list_param cl, apply_substitution f1 sub) - | EG (trans, f1) -> - EG (trans, apply_substitution f1 sub) - | ET (ntl, sw, f1) -> - ET (sub_list_param ntl, sw, apply_substitution f1 sub) - | ETX (ntl, sw, f1) -> - ETX (sub_list_param ntl, sw, apply_substitution f1 sub) + | True | False + -> f + | Atomic (name, ps) + -> Atomic (name, sub_list_param ps) + | Not f1 + -> Not (apply_substitution f1 sub) + | And (f1, f2) + -> And (apply_substitution f1 sub, apply_substitution f2 sub) + | Or (f1, f2) + -> Or (apply_substitution f1 sub, apply_substitution f2 sub) + | Implies (f1, f2) + -> Implies (apply_substitution f1 sub, apply_substitution f2 sub) + | InNode (node_type_list, f1) + -> InNode (sub_list_param node_type_list, apply_substitution f1 sub) + | AU (trans, f1, f2) + -> AU (trans, apply_substitution f1 sub, apply_substitution f2 sub) + | EU (trans, f1, f2) + -> EU (trans, apply_substitution f1 sub, apply_substitution f2 sub) + | EF (trans, f1) + -> EF (trans, apply_substitution f1 sub) + | AF (trans, f1) + -> AF (trans, apply_substitution f1 sub) + | AG (trans, f1) + -> AG (trans, apply_substitution f1 sub) + | EX (trans, f1) + -> EX (trans, apply_substitution f1 sub) + | AX (trans, f1) + -> AX (trans, apply_substitution f1 sub) + | EH (cl, f1) + -> EH (sub_list_param cl, apply_substitution f1 sub) + | EG (trans, f1) + -> EG (trans, apply_substitution f1 sub) + | ET (ntl, sw, f1) + -> ET (sub_list_param ntl, sw, apply_substitution f1 sub) + | ETX (ntl, sw, f1) + -> ETX (sub_list_param ntl, sw, apply_substitution f1 sub) let expand_formula phi _map _error_msg = let fail_with_circular_macro_definition name error_msg = - failwithf "Macro '%s' has a circular definition.@\n Cycle:@\n%s" name error_msg in + failwithf "Macro '%s' has a circular definition.@\n Cycle:@\n%s" name error_msg + in let open CTL in let rec expand acc map error_msg = match acc with - | True - | False -> acc - | Atomic (ALVar.Formula_id (name) as av, actual_param) -> (* it may be a macro *) - (let error_msg' = - error_msg ^ " -Expanding formula identifier '" ^ name ^"'@\n" in - (try - match ALVar.FormulaIdMap.find av map with - | (true, _, _) -> - fail_with_circular_macro_definition name error_msg' - | (false, fparams, f1) -> (* in this case it should be a defined macro *) - (match List.zip fparams actual_param with - | Some sub -> - let f1_sub = apply_substitution f1 sub in - let map' = ALVar.FormulaIdMap.add av (true, fparams, f1) map in - expand f1_sub map' error_msg' - | None -> failwith ("Formula identifier '" ^ name ^ - "' is not called with the right number of parameters")) - with Not_found -> acc)) (* in this case it should be a predicate *) - | Not f1 -> Not (expand f1 map error_msg) - | And (f1, f2) -> And (expand f1 map error_msg, expand f2 map error_msg) - | Or (f1, f2) -> Or (expand f1 map error_msg, expand f2 map error_msg) - | Implies (f1, f2) -> Implies (expand f1 map error_msg, expand f2 map error_msg) - | InNode (node_type_list, f1) -> InNode (node_type_list, expand f1 map error_msg) - | AU (trans, f1, f2) -> AU (trans, expand f1 map error_msg, expand f2 map error_msg) - | EU (trans, f1, f2) -> EU (trans, expand f1 map error_msg, expand f2 map error_msg) - | EF (trans, f1) -> EF (trans, expand f1 map error_msg) - | AF (trans, f1) -> AF (trans, expand f1 map error_msg) - | AG (trans, f1) -> AG (trans, expand f1 map error_msg) - | EX (trans, f1) -> EX (trans, expand f1 map error_msg) - | AX (trans, f1) -> AX (trans, expand f1 map error_msg) - | EH (cl, f1) -> EH (cl, expand f1 map error_msg) - | EG (trans, f1) -> EG (trans, expand f1 map error_msg) - | ET (tl, sw, f1) -> ET (tl, sw, expand f1 map error_msg) - | ETX (tl, sw, f1) -> ETX (tl, sw, expand f1 map error_msg) in + | True | False + -> acc + | Atomic ((ALVar.Formula_id name as av), actual_param) + -> ( + (* it may be a macro *) + let error_msg' = error_msg ^ " -Expanding formula identifier '" ^ name ^ "'@\n" in + try + match ALVar.FormulaIdMap.find av map with + | true, _, _ + -> fail_with_circular_macro_definition name error_msg' + | false, fparams, f1 -> + (* in this case it should be a defined macro *) + match List.zip fparams actual_param with + | Some sub + -> let f1_sub = apply_substitution f1 sub in + let map' = ALVar.FormulaIdMap.add av (true, fparams, f1) map in + expand f1_sub map' error_msg' + | None + -> failwith + ( "Formula identifier '" ^ name + ^ "' is not called with the right number of parameters" ) + with Not_found -> acc + (* in this case it should be a predicate *) ) + | Not f1 + -> Not (expand f1 map error_msg) + | And (f1, f2) + -> And (expand f1 map error_msg, expand f2 map error_msg) + | Or (f1, f2) + -> Or (expand f1 map error_msg, expand f2 map error_msg) + | Implies (f1, f2) + -> Implies (expand f1 map error_msg, expand f2 map error_msg) + | InNode (node_type_list, f1) + -> InNode (node_type_list, expand f1 map error_msg) + | AU (trans, f1, f2) + -> AU (trans, expand f1 map error_msg, expand f2 map error_msg) + | EU (trans, f1, f2) + -> EU (trans, expand f1 map error_msg, expand f2 map error_msg) + | EF (trans, f1) + -> EF (trans, expand f1 map error_msg) + | AF (trans, f1) + -> AF (trans, expand f1 map error_msg) + | AG (trans, f1) + -> AG (trans, expand f1 map error_msg) + | EX (trans, f1) + -> EX (trans, expand f1 map error_msg) + | AX (trans, f1) + -> AX (trans, expand f1 map error_msg) + | EH (cl, f1) + -> EH (cl, expand f1 map error_msg) + | EG (trans, f1) + -> EG (trans, expand f1 map error_msg) + | ET (tl, sw, f1) + -> ET (tl, sw, expand f1 map error_msg) + | ETX (tl, sw, f1) + -> ETX (tl, sw, expand f1 map error_msg) + in expand phi _map _error_msg let rec expand_path paths path_map = match paths with - | [] -> [] - | ALVar.Var path_var :: rest -> - (try - let paths = ALVar.VarMap.find path_var path_map in - List.append paths (expand_path rest path_map) - with Not_found -> failwithf "Path variable %s not found. " path_var) - | path :: rest -> - path :: (expand_path rest path_map) - + | [] + -> [] + | (ALVar.Var path_var) :: rest -> ( + try + let paths = ALVar.VarMap.find path_var path_map in + List.append paths (expand_path rest path_map) + with Not_found -> failwithf "Path variable %s not found. " path_var ) + | path :: rest + -> path :: expand_path rest path_map let _build_macros_map macros init_map = - let macros_map = List.fold ~f:(fun map' data -> match data with - | CTL.CLet (key, params, formula) -> - if ALVar.FormulaIdMap.mem key map' then - failwith ("Macro '" ^ (ALVar.formula_id_to_string key) ^ - "' has more than one definition.") - else ALVar.FormulaIdMap.add key (false, params, formula) map' - | _ -> map') ~init:init_map macros in + let macros_map = + List.fold + ~f:(fun map' data -> + match data with + | CTL.CLet (key, params, formula) + -> if ALVar.FormulaIdMap.mem key map' then + failwith + ("Macro '" ^ ALVar.formula_id_to_string key ^ "' has more than one definition.") + else ALVar.FormulaIdMap.add key (false, params, formula) map' + | _ + -> map') + ~init:init_map macros + in macros_map let build_macros_map macros = @@ -322,55 +360,72 @@ let build_macros_map macros = let build_paths_map paths = let build_paths_map_aux paths init_map = - let paths_map = List.fold ~f:(fun map' data -> match data with - | (path_name, paths) -> + let paths_map = + List.fold + ~f:(fun map' data -> + match data + with path_name, paths -> if ALVar.VarMap.mem path_name map' then failwith ("Path '" ^ path_name ^ "' has more than one definition.") - else ALVar.VarMap.add path_name paths map') ~init:init_map paths in - paths_map in + else ALVar.VarMap.add path_name paths map') + ~init:init_map paths + in + paths_map + in build_paths_map_aux paths ALVar.VarMap.empty (* expands use of let defined formula id in checkers with their definition *) let expand_checkers macro_map path_map checkers = let open CTL in let expand_one_checker c = - L.(debug Linters Medium) " +Start expanding %s@\n" c.id; + L.(debug Linters Medium) " +Start expanding %s@\n" c.id ; let map = _build_macros_map c.definitions macro_map in - let exp_defs = List.fold ~f:(fun defs clause -> - match clause with - | CSet (report_when_const, phi) -> - L.(debug Linters Medium) " -Expanding report_when@\n"; - CSet (report_when_const, expand_formula phi map "") :: defs - | CPath (black_or_white_list, paths) -> - L.(debug Linters Medium) " -Expanding path@\n"; - CPath (black_or_white_list, expand_path paths path_map) :: defs - | cl -> cl :: defs) ~init:[] c.definitions in - { c with definitions = exp_defs} in + let exp_defs = + List.fold + ~f:(fun defs clause -> + match clause with + | CSet (report_when_const, phi) + -> L.(debug Linters Medium) " -Expanding report_when@\n" ; + CSet (report_when_const, expand_formula phi map "") :: defs + | CPath (black_or_white_list, paths) + -> L.(debug Linters Medium) " -Expanding path@\n" ; + CPath (black_or_white_list, expand_path paths path_map) :: defs + | cl + -> cl :: defs) + ~init:[] c.definitions + in + {c with definitions= exp_defs} + in List.map ~f:expand_one_checker checkers let get_err_log translation_unit_context method_decl_opt = - let procname = match method_decl_opt with - | Some method_decl -> CProcname.from_decl translation_unit_context method_decl - | None -> Typ.Procname.Linters_dummy_method in + let procname = + match method_decl_opt with + | Some method_decl + -> CProcname.from_decl translation_unit_context method_decl + | None + -> Typ.Procname.Linters_dummy_method + in LintIssues.get_err_log procname (* Add a frontend warning with a description desc at location loc to the errlog of a proc desc *) -let log_frontend_issue translation_unit_context method_decl_opt key (issue_desc : CIssue.issue_desc) +let log_frontend_issue translation_unit_context method_decl_opt key (issue_desc: CIssue.issue_desc) linters_def_file = let errlog = get_err_log translation_unit_context method_decl_opt in - let err_desc = Errdesc.explain_frontend_warning issue_desc.description - issue_desc.suggestion issue_desc.loc in - let exn = - Exceptions.Frontend_warning ((issue_desc.id, issue_desc.name), err_desc, __POS__) in - let trace = [ Errlog.make_trace_element 0 issue_desc.loc "" [] ] in + let err_desc = + Errdesc.explain_frontend_warning issue_desc.description issue_desc.suggestion issue_desc.loc + in + let exn = Exceptions.Frontend_warning ((issue_desc.id, issue_desc.name), err_desc, __POS__) in + let trace = [Errlog.make_trace_element 0 issue_desc.loc "" []] in let err_kind = issue_desc.severity in - let method_name = CAst_utils.full_name_of_decl_opt method_decl_opt - |> QualifiedCppName.to_qual_string in + let method_name = + CAst_utils.full_name_of_decl_opt method_decl_opt |> QualifiedCppName.to_qual_string + in let key = Hashtbl.hash (key ^ method_name) in Reporting.log_issue_from_errlog err_kind errlog exn ~loc:issue_desc.loc ~ltr:trace ~node_id:(0, key) ?linters_def_file ?doc_url:issue_desc.doc_url -let get_current_method context (an : Ctl_parser_types.ast_node) = +let get_current_method context (an: Ctl_parser_types.ast_node) = match an with | Decl (FunctionDecl _ as d) | Decl (CXXMethodDecl _ as d) @@ -378,48 +433,61 @@ let get_current_method context (an : Ctl_parser_types.ast_node) = | Decl (CXXConversionDecl _ as d) | Decl (CXXDestructorDecl _ as d) | Decl (ObjCMethodDecl _ as d) - | Decl (BlockDecl _ as d) -> Some d - | _ -> context.CLintersContext.current_method + | Decl (BlockDecl _ as d) + -> Some d + | _ + -> context.CLintersContext.current_method let fill_issue_desc_info_and_log context an key issue_desc linters_def_file loc = - let desc = remove_new_lines - (expand_message_string issue_desc.CIssue.description an) in - let issue_desc' = - {issue_desc with CIssue.description = desc; CIssue.loc = loc } in + let desc = remove_new_lines (expand_message_string issue_desc.CIssue.description an) in + let issue_desc' = {issue_desc with CIssue.description= desc; CIssue.loc= loc} in log_frontend_issue context.CLintersContext.translation_unit_context (get_current_method context an) key issue_desc' linters_def_file (* Calls the set of hard coded checkers (if any) *) -let invoke_set_of_hard_coded_checkers_an context (an : Ctl_parser_types.ast_node) = - let checkers, key = match an with - | Decl dec -> decl_checkers_list, CAst_utils.generate_key_decl dec - | Stmt st -> stmt_checkers_list, CAst_utils.generate_key_stmt st in - List.iter ~f:(fun checker -> +let invoke_set_of_hard_coded_checkers_an context (an: Ctl_parser_types.ast_node) = + let checkers, key = + match an with + | Decl dec + -> (decl_checkers_list, CAst_utils.generate_key_decl dec) + | Stmt st + -> (stmt_checkers_list, CAst_utils.generate_key_stmt st) + in + List.iter + ~f:(fun checker -> let issue_desc_list = checker context an in - List.iter ~f:(fun issue_desc -> + List.iter + ~f:(fun issue_desc -> if CIssue.should_run_check issue_desc.CIssue.mode then - fill_issue_desc_info_and_log context an key issue_desc None issue_desc.CIssue.loc - ) issue_desc_list - ) checkers + fill_issue_desc_info_and_log context an key issue_desc None issue_desc.CIssue.loc) + issue_desc_list) + checkers (* Calls the set of checkers parsed from files (if any) *) -let invoke_set_of_parsed_checkers_an parsed_linters context (an : Ctl_parser_types.ast_node) = - let key = match an with - | Decl dec -> CAst_utils.generate_key_decl dec - | Stmt st -> CAst_utils.generate_key_stmt st in - List.iter ~f:(fun (linter : linter) -> - if CIssue.should_run_check linter.issue_desc.CIssue.mode && - CTL.eval_formula linter.condition an context then +let invoke_set_of_parsed_checkers_an parsed_linters context (an: Ctl_parser_types.ast_node) = + let key = + match an with + | Decl dec + -> CAst_utils.generate_key_decl dec + | Stmt st + -> CAst_utils.generate_key_stmt st + in + List.iter + ~f:(fun (linter: linter) -> + if CIssue.should_run_check linter.issue_desc.CIssue.mode + && CTL.eval_formula linter.condition an context + then let loc = CFrontend_checkers.location_from_an context an in - fill_issue_desc_info_and_log context an key linter.issue_desc linter.def_file loc - ) parsed_linters + fill_issue_desc_info_and_log context an key linter.issue_desc linter.def_file loc) + parsed_linters (* We decouple the hardcoded checkers from the parsed ones *) let invoke_set_of_checkers_on_node context an = - (match an with - | Ctl_parser_types.Decl (Clang_ast_t.TranslationUnitDecl _) -> - (* Don't run parsed linters on TranslationUnitDecl node. + ( match an with + | Ctl_parser_types.Decl Clang_ast_t.TranslationUnitDecl _ + -> (* Don't run parsed linters on TranslationUnitDecl node. Because depending on the formula it may give an error at line -1 *) - () - | _ -> invoke_set_of_parsed_checkers_an !parsed_linters context an); + () + | _ + -> invoke_set_of_parsed_checkers_an !parsed_linters context an ) ; if Config.default_linters then invoke_set_of_hard_coded_checkers_an context an diff --git a/infer/src/clang/cFrontend_errors.mli b/infer/src/clang/cFrontend_errors.mli index f31fcf422..67a2d0f4f 100644 --- a/infer/src/clang/cFrontend_errors.mli +++ b/infer/src/clang/cFrontend_errors.mli @@ -9,13 +9,12 @@ open! IStd -type linter = { - condition : CTL.t; - issue_desc : CIssue.issue_desc; - def_file : string option; - whitelist_paths : ALVar.t list; - blacklist_paths : ALVar.t list; -} +type linter = + { condition: CTL.t + ; issue_desc: CIssue.issue_desc + ; def_file: string option + ; whitelist_paths: ALVar.t list + ; blacklist_paths: ALVar.t list } val filter_parsed_linters : linter list -> SourceFile.t -> linter list @@ -25,17 +24,20 @@ val pp_linters : Format.formatter -> linter list -> unit (visited, parameters, definition). Visited is used during the expansion phase to understand if the formula was already expanded and, if yes we have a cyclic definifion *) + type macros_map = (bool * ALVar.t list * CTL.t) ALVar.FormulaIdMap.t (* Map a path name to a list of paths. *) -type paths_map = (ALVar.t list) ALVar.VarMap.t + +type paths_map = ALVar.t list ALVar.VarMap.t (* List of checkers that will be filled after parsing them from a file *) + val parsed_linters : linter list ref (* Module for warnings detected at translation time by the frontend *) - (* Run frontend checkers on an AST node *) + val invoke_set_of_checkers_on_node : CLintersContext.context -> Ctl_parser_types.ast_node -> unit val build_macros_map : CTL.clause list -> macros_map diff --git a/infer/src/clang/cGeneral_utils.ml b/infer/src/clang/cGeneral_utils.ml index 302b88b12..7fa869f09 100644 --- a/infer/src/clang/cGeneral_utils.ml +++ b/infer/src/clang/cGeneral_utils.ml @@ -18,27 +18,25 @@ type var_info = Clang_ast_t.decl_info * Clang_ast_t.qual_type * Clang_ast_t.var_ let rec swap_elements_list l = match l with - | el1:: el2:: rest -> - el2:: el1:: (swap_elements_list rest) - | [] -> [] - | _ -> assert false + | el1 :: el2 :: rest + -> el2 :: el1 :: swap_elements_list rest + | [] + -> [] + | _ + -> assert false let rec string_from_list l = - match l with - | [] -> "" - | [item] -> item - | item:: l' -> item^" "^(string_from_list l') + match l with [] -> "" | [item] -> item | item :: l' -> item ^ " " ^ string_from_list l' let rec append_no_duplicates eq list1 list2 = match list2 with - | el:: rest2 -> - if (List.mem ~equal:eq list1 el) then - (append_no_duplicates eq list1 rest2) - else (append_no_duplicates eq list1 rest2)@[el] - | [] -> list1 + | el :: rest2 + -> if List.mem ~equal:eq list1 el then append_no_duplicates eq list1 rest2 + else append_no_duplicates eq list1 rest2 @ [el] + | [] + -> list1 -let append_no_duplicates_csu list1 list2 = - append_no_duplicates Typ.Name.equal list1 list2 +let append_no_duplicates_csu list1 list2 = append_no_duplicates Typ.Name.equal list1 list2 let append_no_duplicates_annotations list1 list2 = let eq (annot1, _) (annot2, _) = String.equal annot1.Annot.class_name annot2.Annot.class_name in @@ -46,132 +44,150 @@ let append_no_duplicates_annotations list1 list2 = let add_no_duplicates_fields field_tuple l = let rec replace_field field_tuple l found = - match field_tuple, l with - | (field, typ, annot), ((old_field, old_typ, old_annot) as old_field_tuple :: rest) -> - let ret_list, ret_found = replace_field field_tuple rest found in + match (field_tuple, l) with + | (field, typ, annot), (old_field, old_typ, old_annot as old_field_tuple) :: rest + -> let ret_list, ret_found = replace_field field_tuple rest found in if Typ.Fieldname.equal field old_field && Typ.equal typ old_typ then let annotations = append_no_duplicates_annotations annot old_annot in - (field, typ, annotations) :: ret_list, true - else old_field_tuple :: ret_list, ret_found - | _, [] -> [], found in + ((field, typ, annotations) :: ret_list, true) + else (old_field_tuple :: ret_list, ret_found) + | _, [] + -> ([], found) + in let new_list, found = replace_field field_tuple l false in - if found then new_list - else field_tuple :: l + if found then new_list else field_tuple :: l let rec append_no_duplicates_fields list1 list2 = match list1 with - | field_tuple :: rest -> - let updated_list2 = append_no_duplicates_fields rest list2 in + | field_tuple :: rest + -> let updated_list2 = append_no_duplicates_fields rest list2 in add_no_duplicates_fields field_tuple updated_list2 - | [] -> list2 + | [] + -> list2 let sort_fields fields = - let compare (name1, _, _) (name2, _, _) = - Typ.Fieldname.compare name1 name2 in + let compare (name1, _, _) (name2, _, _) = Typ.Fieldname.compare name1 name2 in List.sort ~cmp:compare fields - let sort_fields_tenv tenv = let sort_fields_struct name ({Typ.Struct.fields} as st) = - ignore (Tenv.mk_struct tenv ~default:st ~fields:(sort_fields fields) name) in + ignore (Tenv.mk_struct tenv ~default:st ~fields:(sort_fields fields) name) + in Tenv.iter sort_fields_struct tenv let rec collect_list_tuples l (a, a1, b, c, d) = match l with - | [] -> (a, a1, b, c, d) - | (a', a1', b', c', d'):: l' -> collect_list_tuples l' (a@a', a1@a1', b@b', c@c', d@d') + | [] + -> (a, a1, b, c, d) + | (a', a1', b', c', d') :: l' + -> collect_list_tuples l' (a @ a', a1 @ a1', b @ b', c @ c', d @ d') let is_static_var var_decl_info = match var_decl_info.Clang_ast_t.vdi_storage_class with - | Some sc -> String.equal sc CFrontend_config.static - | _ -> false + | Some sc + -> String.equal sc CFrontend_config.static + | _ + -> false let rec zip xs ys = - match xs, ys with - | [], _ - | _, [] -> [] - | x :: xs, y :: ys -> (x, y) :: zip xs ys + match (xs, ys) with [], _ | _, [] -> [] | x :: xs, y :: ys -> (x, y) :: zip xs ys let list_range i j = - let rec aux n acc = - if n < i then acc else aux (n -1) (n :: acc) - in aux j [] ;; + let rec aux n acc = if n < i then acc else aux (n - 1) (n :: acc) in + aux j [] -let replicate n el = List.map ~f:(fun _ -> el) (list_range 0 (n -1)) +let replicate n el = List.map ~f:(fun _ -> el) (list_range 0 (n - 1)) let mk_class_field_name class_tname field_name = Typ.Fieldname.Clang.from_class_name class_tname field_name let is_cpp_translation translation_unit_context = let lang = translation_unit_context.CFrontend_config.lang in - CFrontend_config.equal_clang_lang lang CFrontend_config.CPP || - CFrontend_config.equal_clang_lang lang CFrontend_config.ObjCPP + CFrontend_config.equal_clang_lang lang CFrontend_config.CPP + || CFrontend_config.equal_clang_lang lang CFrontend_config.ObjCPP let is_objc_extension translation_unit_context = let lang = translation_unit_context.CFrontend_config.lang in - CFrontend_config.equal_clang_lang lang CFrontend_config.ObjC || - CFrontend_config.equal_clang_lang lang CFrontend_config.ObjCPP + CFrontend_config.equal_clang_lang lang CFrontend_config.ObjC + || CFrontend_config.equal_clang_lang lang CFrontend_config.ObjCPP let get_var_name_mangled name_info var_decl_info = let clang_name = CAst_utils.get_qualified_name name_info |> QualifiedCppName.to_qual_string in let param_idx_opt = var_decl_info.Clang_ast_t.vdi_parm_index_in_function in let name_string = - match clang_name, param_idx_opt with - | "", Some index -> "__param_" ^ string_of_int index - | "", None -> assert false - | _ -> clang_name in - let mangled = match param_idx_opt with - | Some index -> Mangled.mangled name_string (string_of_int index) - | None -> Mangled.from_string name_string in - name_string, mangled - -let mk_sil_global_var {CFrontend_config.source_file} ?(mk_name=fun _ x -> x) - named_decl_info var_decl_info qt = + match (clang_name, param_idx_opt) with + | "", Some index + -> "__param_" ^ string_of_int index + | "", None + -> assert false + | _ + -> clang_name + in + let mangled = + match param_idx_opt with + | Some index + -> Mangled.mangled name_string (string_of_int index) + | None + -> Mangled.from_string name_string + in + (name_string, mangled) + +let mk_sil_global_var {CFrontend_config.source_file} ?(mk_name= fun _ x -> x) named_decl_info + var_decl_info qt = let name_string, simple_name = get_var_name_mangled named_decl_info var_decl_info in let translation_unit = - match (var_decl_info.Clang_ast_t.vdi_storage_class, - var_decl_info.Clang_ast_t.vdi_init_expr) with - | Some "extern", None -> - (* some compilers simply disregard "extern" when the global is given some initialisation + match + (var_decl_info.Clang_ast_t.vdi_storage_class, var_decl_info.Clang_ast_t.vdi_init_expr) + with + | Some "extern", None + -> (* some compilers simply disregard "extern" when the global is given some initialisation code, which is why we make sure that [vdi_init_expr] is None here... *) Pvar.TUExtern - | _ -> - Pvar.TUFile source_file in + | _ + -> Pvar.TUFile source_file + in let is_constexpr = var_decl_info.Clang_ast_t.vdi_is_const_expr in let is_pod = CAst_utils.get_desugared_type qt.Clang_ast_t.qt_type_ptr |> Option.bind ~f:(function - | Clang_ast_t.RecordType(_, decl_ptr) -> CAst_utils.get_decl decl_ptr - | _ -> None) + | Clang_ast_t.RecordType (_, decl_ptr) + -> CAst_utils.get_decl decl_ptr + | _ + -> None ) |> Option.value_map ~default:true ~f:(function - | Clang_ast_t.CXXRecordDecl(_, _, _, _, _, _, _, {xrdi_is_pod}) - | Clang_ast_t.ClassTemplateSpecializationDecl(_, _, _, _, _, _, _, {xrdi_is_pod}, _) -> - xrdi_is_pod - | _ -> true) in + | Clang_ast_t.CXXRecordDecl (_, _, _, _, _, _, _, {xrdi_is_pod}) + | Clang_ast_t.ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, {xrdi_is_pod}, _) + -> xrdi_is_pod + | _ + -> true ) + in Pvar.mk_global ~is_constexpr ~is_pod - ~is_static_local:(var_decl_info.Clang_ast_t.vdi_is_static_local) + ~is_static_local:var_decl_info.Clang_ast_t.vdi_is_static_local (mk_name name_string simple_name) translation_unit let mk_sil_var trans_unit_ctx named_decl_info decl_info_qual_type_opt procname outer_procname = match decl_info_qual_type_opt with - | Some (decl_info, qt, var_decl_info, should_be_mangled) -> - let name_string, simple_name = get_var_name_mangled named_decl_info var_decl_info in + | Some (decl_info, qt, var_decl_info, should_be_mangled) + -> let name_string, simple_name = get_var_name_mangled named_decl_info var_decl_info in if var_decl_info.Clang_ast_t.vdi_is_global then let mk_name = if var_decl_info.Clang_ast_t.vdi_is_static_local then - Some (fun name_string _ -> - Mangled.from_string ((Typ.Procname.to_string outer_procname) ^ "_" ^ name_string)) - else None in + Some + (fun name_string _ -> + Mangled.from_string (Typ.Procname.to_string outer_procname ^ "_" ^ name_string)) + else None + in mk_sil_global_var trans_unit_ctx ?mk_name named_decl_info var_decl_info qt else if not should_be_mangled then Pvar.mk simple_name procname else let start_location = fst decl_info.Clang_ast_t.di_source_range in let line_opt = start_location.Clang_ast_t.sl_line in - let line_str = match line_opt with | Some line -> string_of_int line | None -> "" in + let line_str = match line_opt with Some line -> string_of_int line | None -> "" in let mangled = Utils.string_crc_hex32 line_str in let mangled_name = Mangled.mangled name_string mangled in Pvar.mk mangled_name procname - | None -> - let name_string = CAst_utils.get_qualified_name named_decl_info - |> QualifiedCppName.to_qual_string in + | None + -> let name_string = + CAst_utils.get_qualified_name named_decl_info |> QualifiedCppName.to_qual_string + in Pvar.mk (Mangled.from_string name_string) procname diff --git a/infer/src/clang/cGeneral_utils.mli b/infer/src/clang/cGeneral_utils.mli index cf9b8e2f1..34c6ed6f6 100644 --- a/infer/src/clang/cGeneral_utils.mli +++ b/infer/src/clang/cGeneral_utils.mli @@ -11,52 +11,51 @@ open! IStd (** General utility functions such as functions on lists *) - type var_info = Clang_ast_t.decl_info * Clang_ast_t.qual_type * Clang_ast_t.var_decl_info * bool val string_from_list : string list -> string -val append_no_duplicates_fields : (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -> - (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -> - (Typ.Fieldname.t * Typ.t * Annot.Item.t) list +val append_no_duplicates_fields : + (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -> (Typ.Fieldname.t * Typ.t * Annot.Item.t) list + -> (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -val append_no_duplicates_csu : - Typ.Name.t list -> Typ.Name.t list -> Typ.Name.t list +val append_no_duplicates_csu : Typ.Name.t list -> Typ.Name.t list -> Typ.Name.t list val sort_fields : - (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -> - (Typ.Fieldname.t * Typ.t * Annot.Item.t) list + (Typ.Fieldname.t * Typ.t * Annot.Item.t) list -> (Typ.Fieldname.t * Typ.t * Annot.Item.t) list val sort_fields_tenv : Tenv.t -> unit -val collect_list_tuples : ('a list * 'b list * 'c list * 'd list * 'e list) list -> - 'a list * 'b list * 'c list * 'd list * 'e list -> - 'a list * 'b list * 'c list * 'd list * 'e list +val collect_list_tuples : + ('a list * 'b list * 'c list * 'd list * 'e list) list + -> 'a list * 'b list * 'c list * 'd list * 'e list + -> 'a list * 'b list * 'c list * 'd list * 'e list val swap_elements_list : 'a list -> 'a list val is_static_var : Clang_ast_t.var_decl_info -> bool -val zip: 'a list -> 'b list -> ('a * 'b) list +val zip : 'a list -> 'b list -> ('a * 'b) list -val list_range: int -> int -> int list +val list_range : int -> int -> int list -val replicate: int -> 'a -> 'a list +val replicate : int -> 'a -> 'a list val mk_class_field_name : Typ.Name.t -> string -> Typ.Fieldname.t -val get_var_name_mangled : Clang_ast_t.named_decl_info -> Clang_ast_t.var_decl_info -> - (string * Mangled.t) +val get_var_name_mangled : + Clang_ast_t.named_decl_info -> Clang_ast_t.var_decl_info -> string * Mangled.t -val mk_sil_global_var : CFrontend_config.translation_unit_context -> - ?mk_name:(string -> Mangled.t -> Mangled.t) -> - Clang_ast_t.named_decl_info -> Clang_ast_t.var_decl_info -> Clang_ast_t.qual_type -> Pvar.t +val mk_sil_global_var : + CFrontend_config.translation_unit_context -> ?mk_name:(string -> Mangled.t -> Mangled.t) + -> Clang_ast_t.named_decl_info -> Clang_ast_t.var_decl_info -> Clang_ast_t.qual_type -> Pvar.t -val mk_sil_var : CFrontend_config.translation_unit_context -> Clang_ast_t.named_decl_info -> - var_info option -> Typ.Procname.t -> Typ.Procname.t -> Pvar.t +val mk_sil_var : + CFrontend_config.translation_unit_context -> Clang_ast_t.named_decl_info -> var_info option + -> Typ.Procname.t -> Typ.Procname.t -> Pvar.t -(** true if the current language is C++ or ObjC++ *) val is_cpp_translation : CFrontend_config.translation_unit_context -> bool +(** true if the current language is C++ or ObjC++ *) -(** true if the current language is ObjC or ObjC++ *) val is_objc_extension : CFrontend_config.translation_unit_context -> bool +(** true if the current language is ObjC or ObjC++ *) diff --git a/infer/src/clang/cIssue.ml b/infer/src/clang/cIssue.ml index a0e246459..9c6c7d134 100644 --- a/infer/src/clang/cIssue.ml +++ b/infer/src/clang/cIssue.ml @@ -11,35 +11,38 @@ open! IStd type mode = On | Off -type issue_desc = { - id : string; (* issue id *) - description : string; (* Description in the error message *) - doc_url : string option; - mode : mode; - name : string option; (* issue name, if no name is given name will be a readable version of id, +type issue_desc = + { id: string + ; (* issue id *) + description: string + ; (* Description in the error message *) + doc_url: string option + ; mode: mode + ; name: string option + ; (* issue name, if no name is given name will be a readable version of id, by removing underscores and capitalizing first letters of words *) - loc : Location.t; (* location in the code *) - severity : Exceptions.err_kind; - suggestion : string option; (* an optional suggestion or correction *) -} + loc: Location.t + ; (* location in the code *) + severity: Exceptions.err_kind + ; suggestion: string option + (* an optional suggestion or correction *) } -let string_of_mode m = - match m with - | On -> "On" - | Off -> "Off" +let string_of_mode m = match m with On -> "On" | Off -> "Off" let pp_issue fmt issue = - Format.fprintf fmt "{@\n Id = %s@\n" (issue.id); - Format.fprintf fmt "{ Name = %s@\n" (Option.value ~default:"" issue.name); - Format.fprintf fmt " Severity = %s@\n" (Exceptions.err_kind_string issue.severity); - Format.fprintf fmt " Mode = %s@\n" (string_of_mode issue.mode); - Format.fprintf fmt " Description = %s@\n" issue.description; - Format.fprintf fmt " Suggestion = %s@\n" (Option.value ~default:"" issue.suggestion); - Format.fprintf fmt " Docs URL = %s@\n" (Option.value ~default:"" issue.doc_url); - Format.fprintf fmt " Loc = %s@\n" (Location.to_string issue.loc); - Format.fprintf fmt "}@\n" + Format.fprintf fmt "{@\n Id = %s@\n" issue.id ; + Format.fprintf fmt "{ Name = %s@\n" (Option.value ~default:"" issue.name) ; + Format.fprintf fmt " Severity = %s@\n" (Exceptions.err_kind_string issue.severity) ; + Format.fprintf fmt " Mode = %s@\n" (string_of_mode issue.mode) ; + Format.fprintf fmt " Description = %s@\n" issue.description ; + Format.fprintf fmt " Suggestion = %s@\n" (Option.value ~default:"" issue.suggestion) ; + Format.fprintf fmt " Docs URL = %s@\n" (Option.value ~default:"" issue.doc_url) ; + Format.fprintf fmt " Loc = %s@\n" (Location.to_string issue.loc) ; + Format.fprintf fmt "}@\n" let should_run_check mode = match mode with - | On -> true - | Off -> Config.debug_mode || Config.debug_exceptions || not Config.filtering + | On + -> true + | Off + -> Config.debug_mode || Config.debug_exceptions || not Config.filtering diff --git a/infer/src/clang/cIssue.mli b/infer/src/clang/cIssue.mli index 8ab74eaca..76e2d5242 100644 --- a/infer/src/clang/cIssue.mli +++ b/infer/src/clang/cIssue.mli @@ -11,17 +11,21 @@ open! IStd type mode = On | Off -type issue_desc = { - id : string; (* issue id *) - description : string; (* Description in the error message *) - doc_url : string option; - mode : mode; - name : string option; (* issue name, if no name is given name will be a readable version of id, +type issue_desc = + { id: string + ; (* issue id *) + description: string + ; (* Description in the error message *) + doc_url: string option + ; mode: mode + ; name: string option + ; (* issue name, if no name is given name will be a readable version of id, by removing underscores and capitalizing first letters of words *) - loc : Location.t; (* location in the code *) - severity : Exceptions.err_kind; - suggestion : string option; (* an optional suggestion or correction *) -} + loc: Location.t + ; (* location in the code *) + severity: Exceptions.err_kind + ; suggestion: string option + (* an optional suggestion or correction *) } val string_of_mode : mode -> string diff --git a/infer/src/clang/cLocation.ml b/infer/src/clang/cLocation.ml index eb1c10e13..fe6a34abe 100644 --- a/infer/src/clang/cLocation.ml +++ b/infer/src/clang/cLocation.ml @@ -17,7 +17,8 @@ let clang_to_sil_location trans_unit_ctx clang_loc = let col = Option.value ~default:(-1) clang_loc.Clang_ast_t.sl_column in let file = Option.value_map ~default:trans_unit_ctx.CFrontend_config.source_file - ~f:SourceFile.from_abs_path clang_loc.Clang_ast_t.sl_file in + ~f:SourceFile.from_abs_path clang_loc.Clang_ast_t.sl_file + in Location.{line; col; file} let source_file_in_project source_file = @@ -26,15 +27,17 @@ let source_file_in_project source_file = let file_should_be_skipped = List.exists ~f:(fun path -> String.is_prefix ~prefix:path rel_source_file) - Config.skip_translation_headers in - file_in_project && not (file_should_be_skipped) + Config.skip_translation_headers + in + file_in_project && not file_should_be_skipped let should_do_frontend_check trans_unit_ctx (loc_start, _) = match Option.map ~f:SourceFile.from_abs_path loc_start.Clang_ast_t.sl_file with - | Some source_file -> - SourceFile.equal source_file trans_unit_ctx.CFrontend_config.source_file || - (source_file_in_project source_file && not Config.testing_mode) - | None -> false + | Some source_file + -> SourceFile.equal source_file trans_unit_ctx.CFrontend_config.source_file + || source_file_in_project source_file && not Config.testing_mode + | None + -> false (** We translate by default the instructions in the current file. In C++ development, we also translate the headers that are part of the project. However, in testing mode, we don't want to @@ -43,30 +46,31 @@ let should_do_frontend_check trans_unit_ctx (loc_start, _) = let should_translate trans_unit_ctx (loc_start, loc_end) decl_trans_context ~translate_when_used = let map_file_of pred loc = match Option.map ~f:SourceFile.from_abs_path loc.Clang_ast_t.sl_file with - | Some f -> pred f - | None -> false + | Some f + -> pred f + | None + -> false in (* it's not necessary to compare inodes here because both files come from the same context - they are produced by the same invocation of ASTExporter which uses same logic to produce both files *) - let equal_current_source = SourceFile.equal trans_unit_ctx.CFrontend_config.source_file - in + let equal_current_source = SourceFile.equal trans_unit_ctx.CFrontend_config.source_file in let equal_header_of_current_source maybe_header = (* SourceFile.of_header will cache calls to filesystem *) let source_of_header_opt = SourceFile.of_header maybe_header in Option.value_map ~f:equal_current_source ~default:false source_of_header_opt in - let file_in_project = map_file_of source_file_in_project loc_end - || map_file_of source_file_in_project loc_start in + let file_in_project = + map_file_of source_file_in_project loc_end || map_file_of source_file_in_project loc_start + in let translate_on_demand = translate_when_used || file_in_project || Config.models_mode in - let file_in_models = map_file_of SourceFile.is_cpp_model loc_end - || map_file_of SourceFile.is_cpp_model loc_start in - map_file_of equal_current_source loc_end - || map_file_of equal_current_source loc_start - || file_in_models - || (Config.cxx && map_file_of equal_header_of_current_source loc_start) - || (Config.cxx && decl_trans_context = `Translation && translate_on_demand - && not Config.testing_mode) + let file_in_models = + map_file_of SourceFile.is_cpp_model loc_end || map_file_of SourceFile.is_cpp_model loc_start + in + map_file_of equal_current_source loc_end || map_file_of equal_current_source loc_start + || file_in_models || Config.cxx && map_file_of equal_header_of_current_source loc_start + || Config.cxx && decl_trans_context = `Translation && translate_on_demand + && not Config.testing_mode let should_translate_lib trans_unit_ctx source_range decl_trans_context ~translate_when_used = not Config.no_translate_libs @@ -75,9 +79,8 @@ let should_translate_lib trans_unit_ctx source_range decl_trans_context ~transla let is_file_blacklisted file = let paths = Config.skip_analysis_in_path in let is_file_blacklisted = - List.exists - ~f:(fun path -> Str.string_match (Str.regexp ("^.*/" ^ path)) file 0) - paths in + List.exists ~f:(fun path -> Str.string_match (Str.regexp ("^.*/" ^ path)) file 0) paths + in is_file_blacklisted let get_sil_location_from_range trans_unit_ctx source_range prefer_first = diff --git a/infer/src/clang/cLocation.mli b/infer/src/clang/cLocation.mli index ec4d9eb14..3c2030775 100644 --- a/infer/src/clang/cLocation.mli +++ b/infer/src/clang/cLocation.mli @@ -11,18 +11,19 @@ open! IStd (** Module for function to retrieve the location (file, line, etc) of instructions *) -val clang_to_sil_location : CFrontend_config.translation_unit_context -> - Clang_ast_t.source_location -> Location.t +val clang_to_sil_location : + CFrontend_config.translation_unit_context -> Clang_ast_t.source_location -> Location.t val get_sil_location : Clang_ast_t.stmt_info -> CContext.t -> Location.t -val should_translate_lib : CFrontend_config.translation_unit_context -> Clang_ast_t.source_range -> - CModule_type.decl_trans_context -> translate_when_used:bool -> bool +val should_translate_lib : + CFrontend_config.translation_unit_context -> Clang_ast_t.source_range + -> CModule_type.decl_trans_context -> translate_when_used:bool -> bool -val should_do_frontend_check : CFrontend_config.translation_unit_context -> - Clang_ast_t.source_range -> bool +val should_do_frontend_check : + CFrontend_config.translation_unit_context -> Clang_ast_t.source_range -> bool val is_file_blacklisted : string -> bool -val get_sil_location_from_range : CFrontend_config.translation_unit_context -> - Clang_ast_t.source_range -> bool -> Location.t +val get_sil_location_from_range : + CFrontend_config.translation_unit_context -> Clang_ast_t.source_range -> bool -> Location.t diff --git a/infer/src/clang/cMethod_signature.ml b/infer/src/clang/cMethod_signature.ml index 699e95574..22a06fcd4 100644 --- a/infer/src/clang/cMethod_signature.ml +++ b/infer/src/clang/cMethod_signature.ml @@ -10,108 +10,88 @@ open! IStd (** Define the signature of a method consisting of its name, its arguments, *) + (** return type, location and whether its an instance method. *) -type method_signature = { - mutable name : Typ.Procname.t; - access : Clang_ast_t.access_specifier; - args : (Mangled.t * Clang_ast_t.qual_type) list; - ret_type : Clang_ast_t.qual_type; - attributes : Clang_ast_t.attribute list; - loc : Clang_ast_t.source_range; - is_instance : bool; - is_cpp_virtual : bool; - is_cpp_nothrow : bool; - language : CFrontend_config.clang_lang; - pointer_to_parent : Clang_ast_t.pointer option; - pointer_to_property_opt : Clang_ast_t.pointer option; (* If set then method is a getter/setter *) - return_param_typ : Typ.t option; -} +type method_signature = + { mutable name: Typ.Procname.t + ; access: Clang_ast_t.access_specifier + ; args: (Mangled.t * Clang_ast_t.qual_type) list + ; ret_type: Clang_ast_t.qual_type + ; attributes: Clang_ast_t.attribute list + ; loc: Clang_ast_t.source_range + ; is_instance: bool + ; is_cpp_virtual: bool + ; is_cpp_nothrow: bool + ; language: CFrontend_config.clang_lang + ; pointer_to_parent: Clang_ast_t.pointer option + ; pointer_to_property_opt: Clang_ast_t.pointer option + ; (* If set then method is a getter/setter *) + return_param_typ: Typ.t option } -let ms_get_name { name } = - name +let ms_get_name {name} = name -let ms_set_name ms name = - ms.name <- name +let ms_set_name ms name = ms.name <- name -let ms_get_access { access } = - access +let ms_get_access {access} = access -let ms_get_args { args } = - args +let ms_get_args {args} = args -let ms_get_ret_type { ret_type } = - ret_type +let ms_get_ret_type {ret_type} = ret_type -let ms_get_attributes { attributes } = - attributes +let ms_get_attributes {attributes} = attributes -let ms_get_loc { loc } = - loc +let ms_get_loc {loc} = loc -let ms_is_instance { is_instance } = - is_instance +let ms_is_instance {is_instance} = is_instance -let ms_is_cpp_virtual { is_cpp_virtual } = - is_cpp_virtual +let ms_is_cpp_virtual {is_cpp_virtual} = is_cpp_virtual -let ms_is_cpp_nothrow { is_cpp_nothrow } = - is_cpp_nothrow +let ms_is_cpp_nothrow {is_cpp_nothrow} = is_cpp_nothrow -let ms_get_lang { language } = - language +let ms_get_lang {language} = language -let ms_get_pointer_to_parent { pointer_to_parent } = - pointer_to_parent +let ms_get_pointer_to_parent {pointer_to_parent} = pointer_to_parent -let ms_get_pointer_to_property_opt { pointer_to_property_opt } = - pointer_to_property_opt +let ms_get_pointer_to_property_opt {pointer_to_property_opt} = pointer_to_property_opt -let ms_get_return_param_typ { return_param_typ } = - return_param_typ +let ms_get_return_param_typ {return_param_typ} = return_param_typ (* A method is a getter if it has a link to a property and *) (* it has 1 argument (this includes self) *) -let ms_is_getter { pointer_to_property_opt; args } = - Option.is_some pointer_to_property_opt && - Int.equal (List.length args) 1 +let ms_is_getter {pointer_to_property_opt; args} = + Option.is_some pointer_to_property_opt && Int.equal (List.length args) 1 (* A method is a setter if it has a link to a property and *) (* it has 2 argument (this includes self) *) -let ms_is_setter { pointer_to_property_opt; args } = - Option.is_some pointer_to_property_opt && - Int.equal (List.length args) 2 - -let make_ms name args ret_type attributes loc is_instance ?is_cpp_virtual ?is_cpp_nothrow - language pointer_to_parent pointer_to_property_opt return_param_typ access = - let booloption_to_bool = function - | Some b -> b - | None -> false in +let ms_is_setter {pointer_to_property_opt; args} = + Option.is_some pointer_to_property_opt && Int.equal (List.length args) 2 + +let make_ms name args ret_type attributes loc is_instance ?is_cpp_virtual ?is_cpp_nothrow language + pointer_to_parent pointer_to_property_opt return_param_typ access = + let booloption_to_bool = function Some b -> b | None -> false in let is_cpp_virtual = booloption_to_bool is_cpp_virtual in let is_cpp_nothrow = booloption_to_bool is_cpp_nothrow in - { - name; - access; - args; - ret_type; - attributes; - loc; - is_instance; - is_cpp_virtual; - is_cpp_nothrow; - language; - pointer_to_parent; - pointer_to_property_opt; - return_param_typ; - } - -let replace_name_ms ms name = - { ms with name } + { name + ; access + ; args + ; ret_type + ; attributes + ; loc + ; is_instance + ; is_cpp_virtual + ; is_cpp_nothrow + ; language + ; pointer_to_parent + ; pointer_to_property_opt + ; return_param_typ } + +let replace_name_ms ms name = {ms with name} let ms_to_string ms = - "Method " ^ (Typ.Procname.to_string ms.name) ^ " " ^ - IList.to_string - (fun (s1, s2) -> (Mangled.to_string s1) ^ ", " ^ (CAst_utils.string_of_qual_type s2)) - ms.args - ^ "->" ^ (Clang_ast_extend.type_ptr_to_string ms.ret_type.Clang_ast_t.qt_type_ptr) ^ " " ^ - Clang_ast_j.string_of_source_range ms.loc + "Method " ^ Typ.Procname.to_string ms.name ^ " " + ^ IList.to_string + (fun (s1, s2) -> Mangled.to_string s1 ^ ", " ^ CAst_utils.string_of_qual_type s2) + ms.args + ^ "->" ^ Clang_ast_extend.type_ptr_to_string ms.ret_type.Clang_ast_t.qt_type_ptr ^ " " + ^ Clang_ast_j.string_of_source_range ms.loc diff --git a/infer/src/clang/cMethod_signature.mli b/infer/src/clang/cMethod_signature.mli index aed4c6e01..73bc23126 100644 --- a/infer/src/clang/cMethod_signature.mli +++ b/infer/src/clang/cMethod_signature.mli @@ -10,6 +10,7 @@ open! IStd (** Define the signature of a method consisting of its name, its arguments, *) + (** return type, location and whether its an instance method. *) type method_signature @@ -20,8 +21,7 @@ val ms_set_name : method_signature -> Typ.Procname.t -> unit val ms_get_access : method_signature -> Clang_ast_t.access_specifier -val ms_get_args : method_signature -> - (Mangled.t * Clang_ast_t.qual_type) list +val ms_get_args : method_signature -> (Mangled.t * Clang_ast_t.qual_type) list val ms_get_ret_type : method_signature -> Clang_ast_t.qual_type @@ -47,11 +47,11 @@ val ms_is_getter : method_signature -> bool val ms_is_setter : method_signature -> bool -val make_ms : Typ.Procname.t -> (Mangled.t * Clang_ast_t.qual_type) list -> Clang_ast_t.qual_type - -> Clang_ast_t.attribute list -> Clang_ast_t.source_range -> bool - -> ?is_cpp_virtual:bool -> ?is_cpp_nothrow:bool - -> CFrontend_config.clang_lang -> Clang_ast_t.pointer option -> Clang_ast_t.pointer option - -> Typ.t option -> Clang_ast_t.access_specifier -> method_signature +val make_ms : + Typ.Procname.t -> (Mangled.t * Clang_ast_t.qual_type) list -> Clang_ast_t.qual_type + -> Clang_ast_t.attribute list -> Clang_ast_t.source_range -> bool -> ?is_cpp_virtual:bool + -> ?is_cpp_nothrow:bool -> CFrontend_config.clang_lang -> Clang_ast_t.pointer option + -> Clang_ast_t.pointer option -> Typ.t option -> Clang_ast_t.access_specifier -> method_signature val replace_name_ms : method_signature -> Typ.Procname.t -> method_signature diff --git a/infer/src/clang/cMethod_trans.ml b/infer/src/clang/cMethod_trans.ml index 7dee218fb..8faeef768 100644 --- a/infer/src/clang/cMethod_trans.ml +++ b/infer/src/clang/cMethod_trans.ml @@ -20,93 +20,97 @@ exception Invalid_declaration means that it is an instance method and that the method to be called will be determined at runtime. If it is MCNoVirtual it means that it is an instance method but that the method to be called will be determined at compile time *) -type method_call_type = - | MCVirtual - | MCNoVirtual - | MCStatic [@@deriving compare] +type method_call_type = MCVirtual | MCNoVirtual | MCStatic [@@deriving compare] let equal_method_call_type = [%compare.equal : method_call_type] type function_method_decl_info = | Func_decl_info of Clang_ast_t.function_decl_info * Clang_ast_t.qual_type - | Cpp_Meth_decl_info of Clang_ast_t.function_decl_info * Clang_ast_t.cxx_method_decl_info * Clang_ast_t.pointer * Clang_ast_t.qual_type + | Cpp_Meth_decl_info of + Clang_ast_t.function_decl_info + * Clang_ast_t.cxx_method_decl_info + * Clang_ast_t.pointer + * Clang_ast_t.qual_type | ObjC_Meth_decl_info of Clang_ast_t.obj_c_method_decl_info * Clang_ast_t.pointer | Block_decl_info of Clang_ast_t.block_decl_info * Clang_ast_t.qual_type * CContext.t let is_instance_method function_method_decl_info = match function_method_decl_info with - | Func_decl_info _ | Block_decl_info _ -> false - | Cpp_Meth_decl_info (_, method_decl_info, _, _) -> - not method_decl_info.Clang_ast_t.xmdi_is_static - | ObjC_Meth_decl_info (method_decl_info, _) -> - method_decl_info.Clang_ast_t.omdi_is_instance_method + | Func_decl_info _ | Block_decl_info _ + -> false + | Cpp_Meth_decl_info (_, method_decl_info, _, _) + -> not method_decl_info.Clang_ast_t.xmdi_is_static + | ObjC_Meth_decl_info (method_decl_info, _) + -> method_decl_info.Clang_ast_t.omdi_is_instance_method let get_original_return_type function_method_decl_info = match function_method_decl_info with - | Func_decl_info (_, typ) - | Cpp_Meth_decl_info (_, _, _, typ) - | Block_decl_info (_, typ, _) -> CType.return_type_of_function_type typ - | ObjC_Meth_decl_info (method_decl_info, _) -> method_decl_info.Clang_ast_t.omdi_result_type + | Func_decl_info (_, typ) | Cpp_Meth_decl_info (_, _, _, typ) | Block_decl_info (_, typ, _) + -> CType.return_type_of_function_type typ + | ObjC_Meth_decl_info (method_decl_info, _) + -> method_decl_info.Clang_ast_t.omdi_result_type let get_class_param function_method_decl_info = - if (is_instance_method function_method_decl_info) then + if is_instance_method function_method_decl_info then match function_method_decl_info with - | Cpp_Meth_decl_info (_, _, class_decl_ptr, _) -> - let class_type = CAst_utils.qual_type_of_decl_ptr class_decl_ptr in + | Cpp_Meth_decl_info (_, _, class_decl_ptr, _) + -> let class_type = CAst_utils.qual_type_of_decl_ptr class_decl_ptr in [(Mangled.from_string CFrontend_config.this, class_type)] - | ObjC_Meth_decl_info (_, class_decl_ptr) -> - let class_type = CAst_utils.qual_type_of_decl_ptr class_decl_ptr in + | ObjC_Meth_decl_info (_, class_decl_ptr) + -> let class_type = CAst_utils.qual_type_of_decl_ptr class_decl_ptr in [(Mangled.from_string CFrontend_config.self, class_type)] - | _ -> [] + | _ + -> [] else [] - let should_add_return_param return_type ~is_objc_method = - match return_type.Typ.desc with - | Tstruct _ -> not is_objc_method - | _ -> false + match return_type.Typ.desc with Tstruct _ -> not is_objc_method | _ -> false let is_objc_method function_method_decl_info = - match function_method_decl_info with - | ObjC_Meth_decl_info _ -> true - | _ -> false + match function_method_decl_info with ObjC_Meth_decl_info _ -> true | _ -> false let get_return_param tenv function_method_decl_info = let is_objc_method = is_objc_method function_method_decl_info in let return_qual_type = get_original_return_type function_method_decl_info in let return_typ = CType_decl.qual_type_to_sil_type tenv return_qual_type in if should_add_return_param return_typ ~is_objc_method then - [(Mangled.from_string CFrontend_config.return_param, - Ast_expressions.create_pointer_qual_type return_qual_type)] - else - [] + [ ( Mangled.from_string CFrontend_config.return_param + , Ast_expressions.create_pointer_qual_type return_qual_type ) ] + else [] let get_param_decls function_method_decl_info = match function_method_decl_info with - | Func_decl_info (function_decl_info, _) - | Cpp_Meth_decl_info (function_decl_info, _, _, _) -> - function_decl_info.Clang_ast_t.fdi_parameters - | ObjC_Meth_decl_info (method_decl_info, _) -> method_decl_info.Clang_ast_t.omdi_parameters - | Block_decl_info (block_decl_info, _, _) -> block_decl_info.Clang_ast_t.bdi_parameters + | Func_decl_info (function_decl_info, _) | Cpp_Meth_decl_info (function_decl_info, _, _, _) + -> function_decl_info.Clang_ast_t.fdi_parameters + | ObjC_Meth_decl_info (method_decl_info, _) + -> method_decl_info.Clang_ast_t.omdi_parameters + | Block_decl_info (block_decl_info, _, _) + -> block_decl_info.Clang_ast_t.bdi_parameters let get_language trans_unit_ctx function_method_decl_info = match function_method_decl_info with - | Func_decl_info (_, _) -> trans_unit_ctx.CFrontend_config.lang - | Cpp_Meth_decl_info _ -> CFrontend_config.CPP - | ObjC_Meth_decl_info _ -> CFrontend_config.ObjC - | Block_decl_info _ -> CFrontend_config.ObjC + | Func_decl_info (_, _) + -> trans_unit_ctx.CFrontend_config.lang + | Cpp_Meth_decl_info _ + -> CFrontend_config.CPP + | ObjC_Meth_decl_info _ + -> CFrontend_config.ObjC + | Block_decl_info _ + -> CFrontend_config.ObjC let is_cpp_virtual function_method_decl_info = match function_method_decl_info with - | Cpp_Meth_decl_info (_, mdi, _, _) -> mdi.Clang_ast_t.xmdi_is_virtual - | _ -> false + | Cpp_Meth_decl_info (_, mdi, _, _) + -> mdi.Clang_ast_t.xmdi_is_virtual + | _ + -> false let is_cpp_nothrow function_method_decl_info = match function_method_decl_info with - | Func_decl_info (fdi, _) - | Cpp_Meth_decl_info (fdi, _, _, _) -> - fdi.Clang_ast_t.fdi_is_no_throw - | _ -> false + | Func_decl_info (fdi, _) | Cpp_Meth_decl_info (fdi, _, _, _) + -> fdi.Clang_ast_t.fdi_is_no_throw + | _ + -> false (** Returns parameters of a function/method. They will have following order: 1. self/this parameter (optional, only for methods) @@ -115,18 +119,23 @@ let is_cpp_nothrow function_method_decl_info = let get_parameters trans_unit_ctx tenv function_method_decl_info = let par_to_ms_par par = match par with - | Clang_ast_t.ParmVarDecl (_, name_info, qt, var_decl_info) -> - let _, mangled = CGeneral_utils.get_var_name_mangled name_info var_decl_info in + | Clang_ast_t.ParmVarDecl (_, name_info, qt, var_decl_info) + -> let _, mangled = CGeneral_utils.get_var_name_mangled name_info var_decl_info in let param_typ = CType_decl.qual_type_to_sil_type tenv qt in let new_qt = match param_typ.Typ.desc with - | Tstruct _ when CGeneral_utils.is_cpp_translation trans_unit_ctx -> - Ast_expressions.create_reference_qual_type qt - | _ -> qt in + | Tstruct _ when CGeneral_utils.is_cpp_translation trans_unit_ctx + -> Ast_expressions.create_reference_qual_type qt + | _ + -> qt + in (mangled, new_qt) - | _ -> assert false in + | _ + -> assert false + in let pars = List.map ~f:par_to_ms_par (get_param_decls function_method_decl_info) in - get_class_param function_method_decl_info @ pars @ get_return_param tenv function_method_decl_info + get_class_param function_method_decl_info @ pars + @ get_return_param tenv function_method_decl_info (** get return type of the function and optionally type of function's return parameter *) let get_return_val_and_param_types tenv function_method_decl_info = @@ -134,8 +143,8 @@ let get_return_val_and_param_types tenv function_method_decl_info = let return_typ = CType_decl.qual_type_to_sil_type tenv return_qual_type in let is_objc_method = is_objc_method function_method_decl_info in if should_add_return_param return_typ ~is_objc_method then - Ast_expressions.create_void_type, Some (CType.add_pointer_to_typ return_typ) - else return_qual_type, None + (Ast_expressions.create_void_type, Some (CType.add_pointer_to_typ return_typ)) + else (return_qual_type, None) let build_method_signature trans_unit_ctx tenv decl_info procname function_method_decl_info parent_pointer pointer_to_property_opt = @@ -148,9 +157,9 @@ let build_method_signature trans_unit_ctx tenv decl_info procname function_metho let is_cpp_virtual = is_cpp_virtual function_method_decl_info in let is_cpp_nothrow = is_cpp_nothrow function_method_decl_info in let access = decl_info.Clang_ast_t.di_access in - CMethod_signature.make_ms - procname parameters tp attributes source_range is_instance_method ~is_cpp_virtual - ~is_cpp_nothrow lang parent_pointer pointer_to_property_opt return_param_type_opt access + CMethod_signature.make_ms procname parameters tp attributes source_range is_instance_method + ~is_cpp_virtual ~is_cpp_nothrow lang parent_pointer pointer_to_property_opt + return_param_type_opt access let get_init_list_instrs method_decl_info = let create_custom_instr construct_instr = `CXXConstructorInit construct_instr in @@ -158,155 +167,190 @@ let get_init_list_instrs method_decl_info = let method_signature_of_decl trans_unit_ctx tenv meth_decl block_data_opt = let open Clang_ast_t in - match meth_decl, block_data_opt with - | FunctionDecl (decl_info, _, qt, fdi), _ -> - let func_decl = Func_decl_info (fdi, qt) in + match (meth_decl, block_data_opt) with + | FunctionDecl (decl_info, _, qt, fdi), _ + -> let func_decl = Func_decl_info (fdi, qt) in let procname = CProcname.from_decl trans_unit_ctx ~tenv meth_decl in let ms = build_method_signature trans_unit_ctx tenv decl_info procname func_decl None None in - ms, fdi.Clang_ast_t.fdi_body, [] + (ms, fdi.Clang_ast_t.fdi_body, []) | CXXMethodDecl (decl_info, _, qt, fdi, mdi), _ | CXXConstructorDecl (decl_info, _, qt, fdi, mdi), _ | CXXConversionDecl (decl_info, _, qt, fdi, mdi), _ - | CXXDestructorDecl (decl_info, _, qt, fdi, mdi), _ -> - let procname = CProcname.from_decl trans_unit_ctx ~tenv meth_decl in + | CXXDestructorDecl (decl_info, _, qt, fdi, mdi), _ + -> let procname = CProcname.from_decl trans_unit_ctx ~tenv meth_decl in let parent_ptr = Option.value_exn decl_info.di_parent_pointer in - let method_decl = Cpp_Meth_decl_info (fdi, mdi, parent_ptr, qt) in + let method_decl = Cpp_Meth_decl_info (fdi, mdi, parent_ptr, qt) in let parent_pointer = decl_info.Clang_ast_t.di_parent_pointer in - let ms = build_method_signature - trans_unit_ctx tenv decl_info procname method_decl parent_pointer None in - let init_list_instrs = get_init_list_instrs mdi in (* it will be empty for methods *) - ms, fdi.Clang_ast_t.fdi_body, init_list_instrs - | ObjCMethodDecl (decl_info, _, mdi), _ -> - let procname = CProcname.from_decl trans_unit_ctx ~tenv meth_decl in + let ms = + build_method_signature trans_unit_ctx tenv decl_info procname method_decl parent_pointer + None + in + let init_list_instrs = get_init_list_instrs mdi in + (* it will be empty for methods *) + (ms, fdi.Clang_ast_t.fdi_body, init_list_instrs) + | ObjCMethodDecl (decl_info, _, mdi), _ + -> let procname = CProcname.from_decl trans_unit_ctx ~tenv meth_decl in let parent_ptr = Option.value_exn decl_info.di_parent_pointer in let method_decl = ObjC_Meth_decl_info (mdi, parent_ptr) in let parent_pointer = decl_info.Clang_ast_t.di_parent_pointer in let pointer_to_property_opt = match mdi.Clang_ast_t.omdi_property_decl with - | Some decl_ref -> Some decl_ref.Clang_ast_t.dr_decl_pointer - | None -> None in - let ms = build_method_signature trans_unit_ctx tenv decl_info procname method_decl - parent_pointer pointer_to_property_opt in - ms, mdi.omdi_body, [] - | BlockDecl (decl_info, bdi), Some (outer_context, tp, procname, _) -> - let func_decl = Block_decl_info (bdi, tp, outer_context) in + | Some decl_ref + -> Some decl_ref.Clang_ast_t.dr_decl_pointer + | None + -> None + in + let ms = + build_method_signature trans_unit_ctx tenv decl_info procname method_decl parent_pointer + pointer_to_property_opt + in + (ms, mdi.omdi_body, []) + | BlockDecl (decl_info, bdi), Some (outer_context, tp, procname, _) + -> let func_decl = Block_decl_info (bdi, tp, outer_context) in let ms = build_method_signature trans_unit_ctx tenv decl_info procname func_decl None None in - ms, bdi.bdi_body, [] - | _ -> raise Invalid_declaration + (ms, bdi.bdi_body, []) + | _ + -> raise Invalid_declaration let method_signature_of_pointer trans_unit_ctx tenv pointer = try match CAst_utils.get_decl pointer with - | Some meth_decl -> - let ms, _, _ = method_signature_of_decl trans_unit_ctx tenv meth_decl None in + | Some meth_decl + -> let ms, _, _ = method_signature_of_decl trans_unit_ctx tenv meth_decl None in Some ms - | None -> None + | None + -> None with Invalid_declaration -> None let get_method_name_from_clang tenv ms_opt = match ms_opt with - | Some ms -> - (match CAst_utils.get_decl_opt (CMethod_signature.ms_get_pointer_to_parent ms) with - | Some decl -> - if ObjcProtocol_decl.is_protocol decl then None - else - (ignore (CType_decl.add_types_from_decl_to_tenv tenv decl); - match ObjcCategory_decl.get_base_class_name_from_category decl with - | Some class_typename -> - let procname = CMethod_signature.ms_get_name ms in - let new_procname = Typ.Procname.replace_class procname class_typename in - CMethod_signature.ms_set_name ms new_procname; - Some ms - | None -> Some ms) - | None -> Some ms) - | None -> None + | Some ms -> ( + match CAst_utils.get_decl_opt (CMethod_signature.ms_get_pointer_to_parent ms) with + | Some decl + -> if ObjcProtocol_decl.is_protocol decl then None + else ( + ignore (CType_decl.add_types_from_decl_to_tenv tenv decl) ; + match ObjcCategory_decl.get_base_class_name_from_category decl with + | Some class_typename + -> let procname = CMethod_signature.ms_get_name ms in + let new_procname = Typ.Procname.replace_class procname class_typename in + CMethod_signature.ms_set_name ms new_procname ; Some ms + | None + -> Some ms ) + | None + -> Some ms ) + | None + -> None let get_superclass_curr_class_objc context = let open Clang_ast_t in let super_of_decl_ref_opt decl_ref = - match decl_ref - |> Option.value_map ~f:(fun dr -> dr.dr_name) ~default:None - |> Option.map ~f:CAst_utils.get_qualified_name with - | Some name -> name - | None -> assert false + match + decl_ref |> Option.value_map ~f:(fun dr -> dr.dr_name) ~default:None + |> Option.map ~f:CAst_utils.get_qualified_name + with + | Some name + -> name + | None + -> assert false in - let retreive_super_name ptr = match CAst_utils.get_decl ptr with - | Some ObjCInterfaceDecl (_, _, _, _, otdi) -> super_of_decl_ref_opt otdi.otdi_super + let retreive_super_name ptr = + match CAst_utils.get_decl ptr with + | Some ObjCInterfaceDecl (_, _, _, _, otdi) + -> super_of_decl_ref_opt otdi.otdi_super | Some ObjCImplementationDecl (_, _, _, _, oi) -> ( - match oi.Clang_ast_t.oidi_class_interface - |> Option.map ~f:(fun dr -> dr.dr_decl_pointer) - |> Option.value_map ~f:CAst_utils.get_decl ~default:None with - | Some ObjCInterfaceDecl (_, _, _, _, otdi) -> super_of_decl_ref_opt otdi.otdi_super - | _ -> assert false - ) - | Some ObjCCategoryDecl (_, _, _, _, ocdi) -> - super_of_decl_ref_opt ocdi.odi_class_interface - | Some ObjCCategoryImplDecl (_, _, _, _, ocidi) -> - super_of_decl_ref_opt ocidi.ocidi_class_interface - | _ -> assert false in + match + oi.Clang_ast_t.oidi_class_interface |> Option.map ~f:(fun dr -> dr.dr_decl_pointer) + |> Option.value_map ~f:CAst_utils.get_decl ~default:None + with + | Some ObjCInterfaceDecl (_, _, _, _, otdi) + -> super_of_decl_ref_opt otdi.otdi_super + | _ + -> assert false ) + | Some ObjCCategoryDecl (_, _, _, _, ocdi) + -> super_of_decl_ref_opt ocdi.odi_class_interface + | Some ObjCCategoryImplDecl (_, _, _, _, ocidi) + -> super_of_decl_ref_opt ocidi.ocidi_class_interface + | _ + -> assert false + in match CContext.get_curr_class context with - | CContext.ContextClsDeclPtr ptr -> Typ.Name.Objc.from_qual_name (retreive_super_name ptr) - | CContext.ContextNoCls -> assert false + | CContext.ContextClsDeclPtr ptr + -> Typ.Name.Objc.from_qual_name (retreive_super_name ptr) + | CContext.ContextNoCls + -> assert false (* Gets the class name from a method signature found by clang, if search is successful *) let get_class_name_method_call_from_clang trans_unit_ctx tenv obj_c_message_expr_info = match obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer with - | Some pointer -> - (match method_signature_of_pointer trans_unit_ctx tenv pointer with - | Some ms -> - begin - match CMethod_signature.ms_get_name ms with - | Typ.Procname.ObjC_Cpp objc_cpp -> - Some (Typ.Procname.objc_cpp_get_class_type_name objc_cpp) - | _ -> - None - end - | None -> None) - | None -> None + | Some pointer -> ( + match method_signature_of_pointer trans_unit_ctx tenv pointer with + | Some ms -> ( + match CMethod_signature.ms_get_name ms with + | Typ.Procname.ObjC_Cpp objc_cpp + -> Some (Typ.Procname.objc_cpp_get_class_type_name objc_cpp) + | _ + -> None ) + | None + -> None ) + | None + -> None (* Get class name from a method call accorsing to the info given by the receiver kind *) let get_class_name_method_call_from_receiver_kind context obj_c_message_expr_info act_params = match obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind with - | `Class qt -> - let sil_type = CType_decl.qual_type_to_sil_type context.CContext.tenv qt in - (CType.objc_classname_of_type sil_type) - | `Instance -> - (match act_params with - | (_, {Typ.desc=Tptr(t, _)}):: _ - | (_, t):: _ -> CType.objc_classname_of_type t - | _ -> assert false) - | `SuperInstance ->get_superclass_curr_class_objc context - | `SuperClass -> get_superclass_curr_class_objc context + | `Class qt + -> let sil_type = CType_decl.qual_type_to_sil_type context.CContext.tenv qt in + CType.objc_classname_of_type sil_type + | `Instance -> ( + match act_params with + | (_, {Typ.desc= Tptr (t, _)}) :: _ | (_, t) :: _ + -> CType.objc_classname_of_type t + | _ + -> assert false ) + | `SuperInstance + -> get_superclass_curr_class_objc context + | `SuperClass + -> get_superclass_curr_class_objc context let get_objc_method_data obj_c_message_expr_info = let selector = obj_c_message_expr_info.Clang_ast_t.omei_selector in let pointer = obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer in match obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind with - | `Instance -> (selector, pointer, MCVirtual) - | `SuperInstance -> (selector, pointer, MCNoVirtual) - | `Class _ - | `SuperClass -> (selector, pointer, MCStatic) + | `Instance + -> (selector, pointer, MCVirtual) + | `SuperInstance + -> (selector, pointer, MCNoVirtual) + | `Class _ | `SuperClass + -> (selector, pointer, MCStatic) let get_formal_parameters tenv ms = let rec defined_parameters pl = match pl with - | [] -> [] - | (mangled, qual_type):: pl' -> - let should_add_pointer name ms = + | [] + -> [] + | (mangled, qual_type) :: pl' + -> let should_add_pointer name ms = let is_objc_self = - String.equal name CFrontend_config.self && - CFrontend_config.equal_clang_lang - (CMethod_signature.ms_get_lang ms) CFrontend_config.ObjC in + String.equal name CFrontend_config.self + && CFrontend_config.equal_clang_lang (CMethod_signature.ms_get_lang ms) + CFrontend_config.ObjC + in let is_cxx_this = - String.equal name CFrontend_config.this && - CFrontend_config.equal_clang_lang - (CMethod_signature.ms_get_lang ms) CFrontend_config.CPP in - (is_objc_self && CMethod_signature.ms_is_instance ms) || is_cxx_this in - let qt = if should_add_pointer (Mangled.to_string mangled) ms then - (Ast_expressions.create_pointer_qual_type qual_type) - else qual_type in + String.equal name CFrontend_config.this + && CFrontend_config.equal_clang_lang (CMethod_signature.ms_get_lang ms) + CFrontend_config.CPP + in + is_objc_self && CMethod_signature.ms_is_instance ms || is_cxx_this + in + let qt = + if should_add_pointer (Mangled.to_string mangled) ms then + Ast_expressions.create_pointer_qual_type qual_type + else qual_type + in let typ = CType_decl.qual_type_to_sil_type tenv qt in - (mangled, typ):: defined_parameters pl' in + (mangled, typ) :: defined_parameters pl' + in defined_parameters (CMethod_signature.ms_get_args ms) let get_return_type tenv ms = @@ -314,201 +358,226 @@ let get_return_type tenv ms = CType_decl.qual_type_to_sil_type tenv return_type let sil_func_attributes_of_attributes attrs = - let rec do_translation acc al = match al with - | [] -> List.rev acc - | Clang_ast_t.SentinelAttr attribute_info:: tl -> - let (sentinel, null_pos) = match attribute_info.Clang_ast_t.ai_parameters with - | a:: b::[] -> (int_of_string a, int_of_string b) - | _ -> assert false + let rec do_translation acc al = + match al with + | [] + -> List.rev acc + | (Clang_ast_t.SentinelAttr attribute_info) :: tl + -> let sentinel, null_pos = + match attribute_info.Clang_ast_t.ai_parameters with + | [a; b] + -> (int_of_string a, int_of_string b) + | _ + -> assert false in - do_translation (PredSymb.FA_sentinel(sentinel, null_pos):: acc) tl - | _:: tl -> do_translation acc tl in + do_translation (PredSymb.FA_sentinel (sentinel, null_pos) :: acc) tl + | _ :: tl + -> do_translation acc tl + in do_translation [] attrs let should_create_procdesc cfg procname defined set_objc_accessor_attr = match Cfg.find_proc_desc_from_name cfg procname with - | Some previous_procdesc -> - let is_defined_previous = Procdesc.is_defined previous_procdesc in - if (defined || set_objc_accessor_attr) && (not is_defined_previous) then - (Cfg.remove_proc_desc cfg (Procdesc.get_proc_name previous_procdesc); - true) + | Some previous_procdesc + -> let is_defined_previous = Procdesc.is_defined previous_procdesc in + if (defined || set_objc_accessor_attr) && not is_defined_previous then ( + Cfg.remove_proc_desc cfg (Procdesc.get_proc_name previous_procdesc) ; + true ) else false - | None -> true + | None + -> true let sil_method_annotation_of_args args method_type : Annot.Method.t = let args_types = List.map ~f:snd args in let param_annots = List.map ~f:CAst_utils.sil_annot_of_type args_types in let retval_annot = CAst_utils.sil_annot_of_type method_type in - retval_annot, param_annots + (retval_annot, param_annots) let is_pointer_to_const {Clang_ast_t.qt_type_ptr} = match CAst_utils.get_type qt_type_ptr with | Some PointerType (_, {Clang_ast_t.qt_is_const}) | Some ObjCObjectPointerType (_, {Clang_ast_t.qt_is_const}) | Some RValueReferenceType (_, {Clang_ast_t.qt_is_const}) - | Some LValueReferenceType (_, {Clang_ast_t.qt_is_const}) -> - qt_is_const - | _ -> - false + | Some LValueReferenceType (_, {Clang_ast_t.qt_is_const}) + -> qt_is_const + | _ + -> false (** Returns a list of the indices of expressions in [args] which point to const-typed values. Each index is offset by [shift]. *) let get_const_args_indices ~shift args = let i = ref shift in let rec aux result = function - | [] -> - List.rev result - | (_, qual_type)::tl -> - incr i; - if is_pointer_to_const qual_type then - aux (!i - 1::result) tl - else - aux result tl in + | [] + -> List.rev result + | (_, qual_type) :: tl + -> incr i ; + if is_pointer_to_const qual_type then aux (!i - 1 :: result) tl else aux result tl + in aux [] args let get_objc_property_accessor ms = let open Clang_ast_t in match CAst_utils.get_decl_opt (CMethod_signature.ms_get_pointer_to_property_opt ms) with - | Some (ObjCPropertyDecl (_, _, obj_c_property_decl_info)) -> + | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) + -> ( let ivar_decl_ref = obj_c_property_decl_info.Clang_ast_t.opdi_ivar_decl in - (match CAst_utils.get_decl_opt_with_decl_ref ivar_decl_ref with - | Some ObjCIvarDecl (_ , {ni_name}, _, _, _) -> - let class_tname = match CMethod_signature.ms_get_name ms with - | Typ.Procname.ObjC_Cpp objc_cpp -> - Typ.Procname.objc_cpp_get_class_type_name objc_cpp - | _ -> assert false - in - let field_name = CGeneral_utils.mk_class_field_name class_tname ni_name in - if CMethod_signature.ms_is_getter ms then - Some (ProcAttributes.Objc_getter field_name) - else if CMethod_signature.ms_is_setter ms then - Some (ProcAttributes.Objc_setter field_name) - else None - | _ -> None) - | _ -> None + match CAst_utils.get_decl_opt_with_decl_ref ivar_decl_ref with + | Some ObjCIvarDecl (_, {ni_name}, _, _, _) + -> let class_tname = + match CMethod_signature.ms_get_name ms with + | Typ.Procname.ObjC_Cpp objc_cpp + -> Typ.Procname.objc_cpp_get_class_type_name objc_cpp + | _ + -> assert false + in + let field_name = CGeneral_utils.mk_class_field_name class_tname ni_name in + if CMethod_signature.ms_is_getter ms then Some (ProcAttributes.Objc_getter field_name) + else if CMethod_signature.ms_is_setter ms then + Some (ProcAttributes.Objc_setter field_name) + else None + | _ + -> None ) + | _ + -> None (** Creates a procedure description. *) -let create_local_procdesc ?(set_objc_accessor_attr=false) trans_unit_ctx cfg tenv ms - fbody captured is_objc_inst_method = +let create_local_procdesc ?(set_objc_accessor_attr= false) trans_unit_ctx cfg tenv ms fbody + captured is_objc_inst_method = let defined = not (Int.equal (List.length fbody) 0) in let proc_name = CMethod_signature.ms_get_name ms in let pname = Typ.Procname.to_string proc_name in let attributes = sil_func_attributes_of_attributes (CMethod_signature.ms_get_attributes ms) in let method_ret_type = CMethod_signature.ms_get_ret_type ms in let method_annotation = - sil_method_annotation_of_args (CMethod_signature.ms_get_args ms) method_ret_type in + sil_method_annotation_of_args (CMethod_signature.ms_get_args ms) method_ret_type + in let is_cpp_inst_method = - CMethod_signature.ms_is_instance ms && - CFrontend_config.equal_clang_lang (CMethod_signature.ms_get_lang ms) CFrontend_config.CPP in + CMethod_signature.ms_is_instance ms + && CFrontend_config.equal_clang_lang (CMethod_signature.ms_get_lang ms) CFrontend_config.CPP + in let is_cpp_nothrow = CMethod_signature.ms_is_cpp_nothrow ms in - let access = match CMethod_signature.ms_get_access ms with - | `None -> PredSymb.Default - | `Private -> PredSymb.Private - | `Protected -> PredSymb.Protected - | `Public -> PredSymb.Protected in + let access = + match CMethod_signature.ms_get_access ms with + | `None + -> PredSymb.Default + | `Private + -> PredSymb.Private + | `Protected + -> PredSymb.Protected + | `Public + -> PredSymb.Protected + in let create_new_procdesc () = let formals = get_formal_parameters tenv ms in - let captured_mangled = List.map ~f:(fun (var, t) -> (Pvar.get_name var), t) captured in + let captured_mangled = List.map ~f:(fun (var, t) -> (Pvar.get_name var, t)) captured in (* Captured variables for blocks are treated as parameters *) let formals = captured_mangled @ formals in - let const_formals = get_const_args_indices - ~shift:(List.length captured_mangled) - (CMethod_signature.ms_get_args ms) in + let const_formals = + get_const_args_indices ~shift:(List.length captured_mangled) + (CMethod_signature.ms_get_args ms) + in let source_range = CMethod_signature.ms_get_loc ms in - L.(debug Capture Verbose) "@\nCreating a new procdesc for function: '%s'@\n@." pname; - L.(debug Capture Verbose) "@\nms = %s@\n@." (CMethod_signature.ms_to_string ms); + L.(debug Capture Verbose) "@\nCreating a new procdesc for function: '%s'@\n@." pname ; + L.(debug Capture Verbose) "@\nms = %s@\n@." (CMethod_signature.ms_to_string ms) ; let loc_start = CLocation.get_sil_location_from_range trans_unit_ctx source_range true in let loc_exit = CLocation.get_sil_location_from_range trans_unit_ctx source_range false in let ret_type = get_return_type tenv ms in let objc_property_accessor = - if set_objc_accessor_attr then get_objc_property_accessor ms - else None in + if set_objc_accessor_attr then get_objc_property_accessor ms else None + in let procdesc = let proc_attributes = { (ProcAttributes.default proc_name Config.Clang) with - ProcAttributes.captured = captured_mangled; - formals; - const_formals; - access = access; - func_attributes = attributes; - is_defined = defined; - is_objc_instance_method = is_objc_inst_method; - is_cpp_instance_method = is_cpp_inst_method; - is_cpp_noexcept_method = is_cpp_nothrow; - is_model = Config.models_mode; - loc = loc_start; - objc_accessor = objc_property_accessor; - translation_unit = Some trans_unit_ctx.CFrontend_config.source_file; - method_annotation; - ret_type; - } in - Cfg.create_proc_desc cfg proc_attributes in - if defined then - (if !Config.arc_mode then - Procdesc.set_flag procdesc Mleak_buckets.objc_arc_flag "true"; - let start_kind = Procdesc.Node.Start_node proc_name in - let start_node = Procdesc.create_node procdesc loc_start start_kind [] in - let exit_kind = Procdesc.Node.Exit_node proc_name in - let exit_node = Procdesc.create_node procdesc loc_exit exit_kind [] in - Procdesc.set_start_node procdesc start_node; - Procdesc.set_exit_node procdesc exit_node) in - if should_create_procdesc cfg proc_name defined set_objc_accessor_attr then - (create_new_procdesc (); true) + ProcAttributes.captured= captured_mangled + ; formals + ; const_formals + ; access + ; func_attributes= attributes + ; is_defined= defined + ; is_objc_instance_method= is_objc_inst_method + ; is_cpp_instance_method= is_cpp_inst_method + ; is_cpp_noexcept_method= is_cpp_nothrow + ; is_model= Config.models_mode + ; loc= loc_start + ; objc_accessor= objc_property_accessor + ; translation_unit= Some trans_unit_ctx.CFrontend_config.source_file + ; method_annotation + ; ret_type } + in + Cfg.create_proc_desc cfg proc_attributes + in + if defined then ( + if !Config.arc_mode then Procdesc.set_flag procdesc Mleak_buckets.objc_arc_flag "true" ; + let start_kind = Procdesc.Node.Start_node proc_name in + let start_node = Procdesc.create_node procdesc loc_start start_kind [] in + let exit_kind = Procdesc.Node.Exit_node proc_name in + let exit_node = Procdesc.create_node procdesc loc_exit exit_kind [] in + Procdesc.set_start_node procdesc start_node ; Procdesc.set_exit_node procdesc exit_node ) + in + if should_create_procdesc cfg proc_name defined set_objc_accessor_attr then ( + create_new_procdesc () ; true ) else false (** Create a procdesc for objc methods whose signature cannot be found. *) let create_external_procdesc cfg proc_name is_objc_inst_method type_opt = match Cfg.find_proc_desc_from_name cfg proc_name with - | Some _ -> () - | None -> - let ret_type, formals = - (match type_opt with - | Some (ret_type, arg_types) -> - ret_type, List.map ~f:(fun typ -> (Mangled.from_string "x", typ)) arg_types - | None -> Typ.mk Typ.Tvoid, []) in + | Some _ + -> () + | None + -> let ret_type, formals = + match type_opt with + | Some (ret_type, arg_types) + -> (ret_type, List.map ~f:(fun typ -> (Mangled.from_string "x", typ)) arg_types) + | None + -> (Typ.mk Typ.Tvoid, []) + in let proc_attributes = { (ProcAttributes.default proc_name Config.Clang) with - ProcAttributes.formals; - is_objc_instance_method = is_objc_inst_method; - ret_type; - } in + ProcAttributes.formals= formals; is_objc_instance_method= is_objc_inst_method; ret_type } + in ignore (Cfg.create_proc_desc cfg proc_attributes) let create_procdesc_with_pointer context pointer class_name_opt name = let open CContext in match method_signature_of_pointer context.translation_unit_context context.tenv pointer with - | Some callee_ms -> - ignore (create_local_procdesc context.translation_unit_context context.cfg context.tenv - callee_ms [] [] false); + | Some callee_ms + -> ignore + (create_local_procdesc context.translation_unit_context context.cfg context.tenv callee_ms + [] [] false) ; CMethod_signature.ms_get_name callee_ms - | None -> - let callee_name = + | None + -> let callee_name = match class_name_opt with - | Some class_name -> - CProcname.NoAstDecl.cpp_method_of_string context.tenv class_name name - | None -> - CProcname.NoAstDecl.c_function_of_string - context.translation_unit_context context.tenv name in - create_external_procdesc context.cfg callee_name false None; - callee_name + | Some class_name + -> CProcname.NoAstDecl.cpp_method_of_string context.tenv class_name name + | None + -> CProcname.NoAstDecl.c_function_of_string context.translation_unit_context context.tenv + name + in + create_external_procdesc context.cfg callee_name false None ; callee_name let add_default_method_for_class trans_unit_ctx class_name decl_info = - let loc = CLocation.get_sil_location_from_range trans_unit_ctx - decl_info.Clang_ast_t.di_source_range true in + let loc = + CLocation.get_sil_location_from_range trans_unit_ctx decl_info.Clang_ast_t.di_source_range true + in let proc_name = Typ.Procname.get_default_objc_class_method class_name in - let attrs = { (ProcAttributes.default proc_name Config.Clang) with loc; } in + let attrs = {(ProcAttributes.default proc_name Config.Clang) with loc} in AttributesTable.store_attributes attrs let get_procname_from_cpp_lambda context dec = match dec with - | Clang_ast_t.CXXRecordDecl (_, _, _, _, _, _, _, cxx_rdi) -> - (match cxx_rdi.xrdi_lambda_call_operator with - | Some dr -> - let name_info, decl_ptr, _ = CAst_utils.get_info_from_decl_ref dr in - create_procdesc_with_pointer context decl_ptr None name_info.ni_name - | _ -> assert false (* We should not get here *)) - | _ -> assert false (* We should not get here *) - + | Clang_ast_t.CXXRecordDecl (_, _, _, _, _, _, _, cxx_rdi) -> ( + match cxx_rdi.xrdi_lambda_call_operator with + | Some dr + -> let name_info, decl_ptr, _ = CAst_utils.get_info_from_decl_ref dr in + create_procdesc_with_pointer context decl_ptr None name_info.ni_name + | _ + -> assert false (* We should not get here *) ) + | _ + -> assert false + +(* We should not get here *) (* let instance_to_method_call_type instance = if instance then MCVirtual diff --git a/infer/src/clang/cMethod_trans.mli b/infer/src/clang/cMethod_trans.mli index bec1baf0c..c7f6d02b1 100644 --- a/infer/src/clang/cMethod_trans.mli +++ b/infer/src/clang/cMethod_trans.mli @@ -16,45 +16,46 @@ open! IStd means that it is an instance method and that the method to be called will be determined at runtime. If it is MCNoVirtual it means that it is an instance method but that the method to be called will be determined at compile time *) -type method_call_type = - | MCVirtual - | MCNoVirtual - | MCStatic [@@deriving compare] +type method_call_type = MCVirtual | MCNoVirtual | MCStatic [@@deriving compare] val equal_method_call_type : method_call_type -> method_call_type -> bool val should_add_return_param : Typ.t -> is_objc_method:bool -> bool -val create_local_procdesc : ?set_objc_accessor_attr:bool -> - CFrontend_config.translation_unit_context -> Cfg.cfg -> Tenv.t -> - CMethod_signature.method_signature -> Clang_ast_t.stmt list -> (Pvar.t * Typ.t) list -> - bool -> bool +val create_local_procdesc : + ?set_objc_accessor_attr:bool -> CFrontend_config.translation_unit_context -> Cfg.cfg -> Tenv.t + -> CMethod_signature.method_signature -> Clang_ast_t.stmt list -> (Pvar.t * Typ.t) list -> bool + -> bool -val create_external_procdesc : Cfg.cfg -> Typ.Procname.t -> bool -> (Typ.t * Typ.t list) option -> unit +val create_external_procdesc : + Cfg.cfg -> Typ.Procname.t -> bool -> (Typ.t * Typ.t list) option -> unit -val get_objc_method_data : Clang_ast_t.obj_c_message_expr_info -> - (string * Clang_ast_t.pointer option * method_call_type) +val get_objc_method_data : + Clang_ast_t.obj_c_message_expr_info -> string * Clang_ast_t.pointer option * method_call_type -val get_class_name_method_call_from_receiver_kind : CContext.t -> - Clang_ast_t.obj_c_message_expr_info -> (Exp.t * Typ.t) list -> Typ.Name.t +val get_class_name_method_call_from_receiver_kind : + CContext.t -> Clang_ast_t.obj_c_message_expr_info -> (Exp.t * Typ.t) list -> Typ.Name.t -val get_class_name_method_call_from_clang : CFrontend_config.translation_unit_context -> Tenv.t -> - Clang_ast_t.obj_c_message_expr_info -> Typ.Name.t option +val get_class_name_method_call_from_clang : + CFrontend_config.translation_unit_context -> Tenv.t -> Clang_ast_t.obj_c_message_expr_info + -> Typ.Name.t option -val method_signature_of_decl : CFrontend_config.translation_unit_context -> Tenv.t -> - Clang_ast_t.decl -> CModule_type.block_data option -> - CMethod_signature.method_signature * Clang_ast_t.stmt option * CModule_type.instr_type list +val method_signature_of_decl : + CFrontend_config.translation_unit_context -> Tenv.t -> Clang_ast_t.decl + -> CModule_type.block_data option + -> CMethod_signature.method_signature * Clang_ast_t.stmt option * CModule_type.instr_type list -val method_signature_of_pointer : CFrontend_config.translation_unit_context -> Tenv.t -> - Clang_ast_t.pointer -> CMethod_signature.method_signature option +val method_signature_of_pointer : + CFrontend_config.translation_unit_context -> Tenv.t -> Clang_ast_t.pointer + -> CMethod_signature.method_signature option -val get_method_name_from_clang : Tenv.t -> CMethod_signature.method_signature option -> - CMethod_signature.method_signature option +val get_method_name_from_clang : + Tenv.t -> CMethod_signature.method_signature option -> CMethod_signature.method_signature option -val create_procdesc_with_pointer : CContext.t -> Clang_ast_t.pointer -> Typ.Name.t option -> - string -> Typ.Procname.t +val create_procdesc_with_pointer : + CContext.t -> Clang_ast_t.pointer -> Typ.Name.t option -> string -> Typ.Procname.t -val add_default_method_for_class : CFrontend_config.translation_unit_context -> Typ.Name.t -> - Clang_ast_t.decl_info -> unit +val add_default_method_for_class : + CFrontend_config.translation_unit_context -> Typ.Name.t -> Clang_ast_t.decl_info -> unit val get_procname_from_cpp_lambda : CContext.t -> Clang_ast_t.decl -> Typ.Procname.t diff --git a/infer/src/clang/cModule_type.ml b/infer/src/clang/cModule_type.ml index dd1a0fd03..5fb350273 100644 --- a/infer/src/clang/cModule_type.ml +++ b/infer/src/clang/cModule_type.ml @@ -11,28 +11,27 @@ open! IStd type block_data = CContext.t * Clang_ast_t.qual_type * Typ.Procname.t * (Pvar.t * Typ.t) list -type instr_type = [ - | `ClangStmt of Clang_ast_t.stmt - | `CXXConstructorInit of Clang_ast_t.cxx_ctor_initializer -] +type instr_type = + [`ClangStmt of Clang_ast_t.stmt | `CXXConstructorInit of Clang_ast_t.cxx_ctor_initializer] -type decl_trans_context = [ `DeclTraversal | `Translation ] +type decl_trans_context = [`DeclTraversal | `Translation] -module type CTranslation = -sig +module type CTranslation = sig (** Translates instructions: (statements and expressions) from the ast into sil *) + val instructions_trans : + CContext.t -> Clang_ast_t.stmt -> instr_type list -> Procdesc.Node.t -> Procdesc.Node.t list (** It receives the context, a list of statements from clang ast, list of custom statments to be added before clang statements and the exit node and it returns a list of cfg nodes that represent the translation of the stmts into sil. *) - val instructions_trans : CContext.t -> Clang_ast_t.stmt -> instr_type list -> - Procdesc.Node.t -> Procdesc.Node.t list end module type CFrontend = sig - val function_decl : CFrontend_config.translation_unit_context -> Tenv.t -> Cfg.cfg -> Cg.t -> - Clang_ast_t.decl -> block_data option -> unit + val function_decl : + CFrontend_config.translation_unit_context -> Tenv.t -> Cfg.cfg -> Cg.t -> Clang_ast_t.decl + -> block_data option -> unit - val translate_one_declaration : CFrontend_config.translation_unit_context -> Tenv.t -> Cg.t -> - Cfg.cfg -> decl_trans_context -> Clang_ast_t.decl -> unit + val translate_one_declaration : + CFrontend_config.translation_unit_context -> Tenv.t -> Cg.t -> Cfg.cfg -> decl_trans_context + -> Clang_ast_t.decl -> unit end diff --git a/infer/src/clang/cPredicates.ml b/infer/src/clang/cPredicates.ml index 4786d2fc3..7c6b6b214 100644 --- a/infer/src/clang/cPredicates.ml +++ b/infer/src/clang/cPredicates.ml @@ -10,7 +10,6 @@ open! IStd open Lexing open Types_lexer - module L = Logging let parsed_type_map : Ctl_parser_types.abs_ctype String.Map.t ref = ref String.Map.empty @@ -19,29 +18,37 @@ let get_available_attr_ios_sdk an = let open Clang_ast_t in let rec get_available_attr attrs = match attrs with - | [] -> None - | AvailabilityAttr attr_info :: rest -> - (match attr_info.ai_parameters with - | "ios" :: version :: _ -> - Some (String.Search_pattern.replace_all - (String.Search_pattern.create "_") ~in_:version ~with_:".") - | _ -> get_available_attr rest) - | _ :: rest -> get_available_attr rest in + | [] + -> None + | (AvailabilityAttr attr_info) :: rest -> ( + match attr_info.ai_parameters with + | "ios" :: version :: _ + -> Some + (String.Search_pattern.replace_all (String.Search_pattern.create "_") ~in_:version + ~with_:".") + | _ + -> get_available_attr rest ) + | _ :: rest + -> get_available_attr rest + in match an with - | Ctl_parser_types.Decl decl -> - let decl_info = Clang_ast_proj.get_decl_tuple decl in + | Ctl_parser_types.Decl decl + -> let decl_info = Clang_ast_proj.get_decl_tuple decl in get_available_attr decl_info.di_attributes - | _ -> None + | _ + -> None let get_ivar_attributes ivar_decl = let open Clang_ast_t in match ivar_decl with - | ObjCIvarDecl (ivar_decl_info, _, _, _, _) -> - (match CAst_utils.get_property_of_ivar ivar_decl_info.Clang_ast_t.di_pointer with - | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) -> - obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes - | _ -> []) - | _ -> [] + | ObjCIvarDecl (ivar_decl_info, _, _, _, _) -> ( + match CAst_utils.get_property_of_ivar ivar_decl_info.Clang_ast_t.di_pointer with + | Some ObjCPropertyDecl (_, _, obj_c_property_decl_info) + -> obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes + | _ + -> [] ) + | _ + -> [] (* list of cxx references captured by decl *) let captured_variables_cxx_ref an = @@ -51,19 +58,22 @@ let captured_variables_cxx_ref an = match CAst_utils.get_decl_opt_with_decl_ref decl_ref_opt with | Some VarDecl (_, named_decl_info, qual_type, _) | Some ParmVarDecl (_, named_decl_info, qual_type, _) - | Some ImplicitParamDecl (_, named_decl_info, qual_type, _) -> - (match CAst_utils.get_desugared_type qual_type.Clang_ast_t.qt_type_ptr with - | Some RValueReferenceType _ | Some LValueReferenceType _ -> - named_decl_info::reference_captured_vars - | _ -> reference_captured_vars) - | _ -> reference_captured_vars in + | Some ImplicitParamDecl (_, named_decl_info, qual_type, _) -> ( + match CAst_utils.get_desugared_type qual_type.Clang_ast_t.qt_type_ptr with + | Some RValueReferenceType _ | Some LValueReferenceType _ + -> named_decl_info :: reference_captured_vars + | _ + -> reference_captured_vars ) + | _ + -> reference_captured_vars + in match an with - | Ctl_parser_types.Decl (BlockDecl (_, bdi)) -> - List.fold ~f:capture_var_is_cxx_ref ~init:[] bdi.bdi_captured_variables - | _ -> [] - -type t = ALVar.formula_id * ALVar.alexp list(* (name, [param1,...,paramK]) *) + | Ctl_parser_types.Decl BlockDecl (_, bdi) + -> List.fold ~f:capture_var_is_cxx_ref ~init:[] bdi.bdi_captured_variables + | _ + -> [] +type t = ALVar.formula_id * (* (name, [param1,...,paramK]) *) ALVar.alexp list let pp_predicate fmt (_name, _arglist) = let name = ALVar.formula_id_to_string _name in @@ -73,355 +83,411 @@ let pp_predicate fmt (_name, _arglist) = (* is an objc interface with name expected_name *) let is_objc_interface_named an expected_name = match an with - | Ctl_parser_types.Decl Clang_ast_t.ObjCInterfaceDecl(_, ni, _, _, _) -> - ALVar.compare_str_with_alexp ni.ni_name expected_name - | _ -> false + | Ctl_parser_types.Decl Clang_ast_t.ObjCInterfaceDecl (_, ni, _, _, _) + -> ALVar.compare_str_with_alexp ni.ni_name expected_name + | _ + -> false (* checkes whether an object is of a certain class *) let is_object_of_class_named receiver cname = let open Clang_ast_t in match receiver with - | PseudoObjectExpr (_, _, ei) - | ImplicitCastExpr (_, _, ei, _) - | ParenExpr (_, _, ei) -> - (match CAst_utils.qual_type_to_objc_interface ei.ei_qual_type with - | Some interface -> - is_objc_interface_named (Ctl_parser_types.Decl interface) cname - | _ -> false) - | _ -> false + | PseudoObjectExpr (_, _, ei) | ImplicitCastExpr (_, _, ei, _) | ParenExpr (_, _, ei) -> ( + match CAst_utils.qual_type_to_objc_interface ei.ei_qual_type with + | Some interface + -> is_objc_interface_named (Ctl_parser_types.Decl interface) cname + | _ + -> false ) + | _ + -> false (* an |= call_method(m) where the name must be exactly m *) let call_method an m = match an with - | Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, _, _, omei)) -> - ALVar.compare_str_with_alexp omei.omei_selector m - | _ -> false + | Ctl_parser_types.Stmt Clang_ast_t.ObjCMessageExpr (_, _, _, omei) + -> ALVar.compare_str_with_alexp omei.omei_selector m + | _ + -> false let is_receiver_kind_class omei cname = let open Clang_ast_t in - match omei.omei_receiver_kind with - | `Class ptr -> - (match CAst_utils.get_desugared_type ptr.Clang_ast_t.qt_type_ptr with - | Some ObjCInterfaceType (_, ptr) -> - (match CAst_utils.get_decl ptr with - | Some ObjCInterfaceDecl (_, ndi, _, _, _) -> - ALVar.compare_str_with_alexp ndi.ni_name cname - | _ -> false) - | _ -> false) - | _ -> false + match omei.omei_receiver_kind with + | `Class ptr -> ( + match CAst_utils.get_desugared_type ptr.Clang_ast_t.qt_type_ptr with + | Some ObjCInterfaceType (_, ptr) -> ( + match CAst_utils.get_decl ptr with + | Some ObjCInterfaceDecl (_, ndi, _, _, _) + -> ALVar.compare_str_with_alexp ndi.ni_name cname + | _ + -> false ) + | _ + -> false ) + | _ + -> false let call_class_method an cname mname = match an with - | Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, _, _, omei)) -> - is_receiver_kind_class omei cname && - ALVar.compare_str_with_alexp omei.omei_selector mname - | _ -> false + | Ctl_parser_types.Stmt Clang_ast_t.ObjCMessageExpr (_, _, _, omei) + -> is_receiver_kind_class omei cname && ALVar.compare_str_with_alexp omei.omei_selector mname + | _ + -> false (* an is a node calling method whose name contains mname of a class whose name contains cname. *) let call_instance_method an cname mname = match an with - | Ctl_parser_types.Stmt (Clang_ast_t.ObjCMessageExpr (_, receiver :: _, _, omei)) -> - is_object_of_class_named receiver cname && - ALVar.compare_str_with_alexp omei.omei_selector mname - | _ -> false + | Ctl_parser_types.Stmt Clang_ast_t.ObjCMessageExpr (_, receiver :: _, _, omei) + -> is_object_of_class_named receiver cname + && ALVar.compare_str_with_alexp omei.omei_selector mname + | _ + -> false let is_objc_extension lcxt = CGeneral_utils.is_objc_extension lcxt.CLintersContext.translation_unit_context let is_syntactically_global_var an = - match an with - | Ctl_parser_types.Decl d -> CAst_utils.is_syntactically_global_var d - | _ -> false + match an with Ctl_parser_types.Decl d -> CAst_utils.is_syntactically_global_var d | _ -> false let is_const_expr_var an = - match an with - | Ctl_parser_types.Decl d -> CAst_utils.is_const_expr_var d - | _ -> false + match an with Ctl_parser_types.Decl d -> CAst_utils.is_const_expr_var d | _ -> false let decl_ref_name ?kind name st = match st with - | Clang_ast_t.DeclRefExpr (_, _, _, drti) -> - (match drti.drti_decl_ref with - | Some dr -> let ndi, _, _ = CAst_utils.get_info_from_decl_ref dr in - let has_right_name = ALVar.compare_str_with_alexp ndi.ni_name name in - (match kind with - | Some decl_kind -> - has_right_name && PVariant.(=) dr.Clang_ast_t.dr_kind decl_kind - | None -> has_right_name) - | _ -> false) - | _ -> false + | Clang_ast_t.DeclRefExpr (_, _, _, drti) -> ( + match drti.drti_decl_ref with + | Some dr + -> ( + let ndi, _, _ = CAst_utils.get_info_from_decl_ref dr in + let has_right_name = ALVar.compare_str_with_alexp ndi.ni_name name in + match kind with + | Some decl_kind + -> has_right_name && PVariant.( = ) dr.Clang_ast_t.dr_kind decl_kind + | None + -> has_right_name ) + | _ + -> false ) + | _ + -> false let declaration_ref_name ?kind an name = - match an with - | Ctl_parser_types.Stmt st -> - decl_ref_name ?kind name st - | _ -> false + match an with Ctl_parser_types.Stmt st -> decl_ref_name ?kind name st | _ -> false let call_function an name = match an with - | Ctl_parser_types.Stmt st -> - CAst_utils.exists_eventually_st (decl_ref_name ~kind:`Function) name st - | _ -> false + | Ctl_parser_types.Stmt st + -> CAst_utils.exists_eventually_st (decl_ref_name ~kind:`Function) name st + | _ + -> false let is_strong_property an = match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCPropertyDecl (_, _, pdi)) -> - ObjcProperty_decl.is_strong_property pdi - | _ -> false + | Ctl_parser_types.Decl Clang_ast_t.ObjCPropertyDecl (_, _, pdi) + -> ObjcProperty_decl.is_strong_property pdi + | _ + -> false let is_assign_property an = match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCPropertyDecl (_, _, pdi)) -> - ObjcProperty_decl.is_assign_property pdi - | _ -> false + | Ctl_parser_types.Decl Clang_ast_t.ObjCPropertyDecl (_, _, pdi) + -> ObjcProperty_decl.is_assign_property pdi + | _ + -> false let is_property_pointer_type an = let open Clang_ast_t in match an with - | Ctl_parser_types.Decl (ObjCPropertyDecl (_, _, pdi)) -> - (match CAst_utils.get_desugared_type pdi.opdi_qual_type.Clang_ast_t.qt_type_ptr with - | Some MemberPointerType _ - | Some ObjCObjectPointerType _ - | Some BlockPointerType _ -> true - | Some TypedefType (_, tti) -> - let typedef_str = CAst_utils.name_of_typedef_type_info tti - |> QualifiedCppName.to_qual_string in - String.equal typedef_str CFrontend_config.id_cl - | exception Not_found -> false - | _ -> false) - | _ -> false - -let context_in_synchronized_block context = - context.CLintersContext.in_synchronized_block + | Ctl_parser_types.Decl ObjCPropertyDecl (_, _, pdi) -> ( + match CAst_utils.get_desugared_type pdi.opdi_qual_type.Clang_ast_t.qt_type_ptr with + | Some MemberPointerType _ | Some ObjCObjectPointerType _ | Some BlockPointerType _ + -> true + | Some TypedefType (_, tti) + -> let typedef_str = + CAst_utils.name_of_typedef_type_info tti |> QualifiedCppName.to_qual_string + in + String.equal typedef_str CFrontend_config.id_cl + | exception Not_found + -> false + | _ + -> false ) + | _ + -> false + +let context_in_synchronized_block context = context.CLintersContext.in_synchronized_block (* checks if ivar is defined among a set of fields and if it is atomic *) let is_ivar_atomic an = match an with - | Ctl_parser_types.Stmt (Clang_ast_t.ObjCIvarRefExpr (_, _, _, irei)) -> + | Ctl_parser_types.Stmt Clang_ast_t.ObjCIvarRefExpr (_, _, _, irei) + -> ( let dr_ref = irei.Clang_ast_t.ovrei_decl_ref in let ivar_pointer = dr_ref.Clang_ast_t.dr_decl_pointer in - (match CAst_utils.get_decl ivar_pointer with - | Some d -> - let attributes = get_ivar_attributes d in - List.exists ~f:(PVariant.(=) `Atomic) attributes - | _ -> false) - | _ -> false + match CAst_utils.get_decl ivar_pointer with + | Some d + -> let attributes = get_ivar_attributes d in + List.exists ~f:(PVariant.( = ) `Atomic) attributes + | _ + -> false ) + | _ + -> false let is_method_property_accessor_of_ivar an context = let open Clang_ast_t in match an with - | Ctl_parser_types.Stmt (ObjCIvarRefExpr (_, _, _, irei)) -> + | Ctl_parser_types.Stmt ObjCIvarRefExpr (_, _, _, irei) + -> ( let dr_ref = irei.Clang_ast_t.ovrei_decl_ref in let ivar_pointer = dr_ref.Clang_ast_t.dr_decl_pointer in - (match context.CLintersContext.current_method with - | Some ObjCMethodDecl (_, _, mdi) -> - if mdi.omdi_is_property_accessor then - let property_opt = mdi.omdi_property_decl in - match CAst_utils.get_decl_opt_with_decl_ref property_opt with - | Some ObjCPropertyDecl (_, _, pdi) -> - (match pdi.opdi_ivar_decl with - | Some decl_ref -> Int.equal decl_ref.dr_decl_pointer ivar_pointer - | None -> false) - | _ -> false - else false - | _ -> false) - | _ -> false + match context.CLintersContext.current_method with + | Some ObjCMethodDecl (_, _, mdi) + -> if mdi.omdi_is_property_accessor then + let property_opt = mdi.omdi_property_decl in + match CAst_utils.get_decl_opt_with_decl_ref property_opt with + | Some ObjCPropertyDecl (_, _, pdi) -> ( + match pdi.opdi_ivar_decl with + | Some decl_ref + -> Int.equal decl_ref.dr_decl_pointer ivar_pointer + | None + -> false ) + | _ + -> false + else false + | _ + -> false ) + | _ + -> false let is_objc_constructor context = match context.CLintersContext.current_method with - | Some method_decl -> - let method_name = (match Clang_ast_proj.get_named_decl_tuple method_decl with - | Some (_, mnd) -> mnd.Clang_ast_t.ni_name - | _ -> "") in + | Some method_decl + -> let method_name = + match Clang_ast_proj.get_named_decl_tuple method_decl with + | Some (_, mnd) + -> mnd.Clang_ast_t.ni_name + | _ + -> "" + in Typ.Procname.is_objc_constructor method_name - | _ -> false - + | _ + -> false let is_objc_dealloc context = match context.CLintersContext.current_method with - | Some method_decl -> - let method_name = (match Clang_ast_proj.get_named_decl_tuple method_decl with - | Some (_, mnd) -> mnd.Clang_ast_t.ni_name - | _ -> "") in + | Some method_decl + -> let method_name = + match Clang_ast_proj.get_named_decl_tuple method_decl with + | Some (_, mnd) + -> mnd.Clang_ast_t.ni_name + | _ + -> "" + in Typ.Procname.is_objc_dealloc method_name - | _ -> false + | _ + -> false -let captures_cxx_references an = - List.length (captured_variables_cxx_ref an) > 0 +let captures_cxx_references an = List.length (captured_variables_cxx_ref an) > 0 let is_binop_with_kind an alexp_kind = let str_kind = ALVar.alexp_to_string alexp_kind in if not (Clang_ast_proj.is_valid_binop_kind_name str_kind) then - failwith ("Binary operator kind " ^ str_kind ^ " is not valid"); + failwith ("Binary operator kind " ^ str_kind ^ " is not valid") ; match an with - | Ctl_parser_types.Stmt (Clang_ast_t.BinaryOperator (_, _, _, boi)) -> - ALVar.compare_str_with_alexp (Clang_ast_proj.string_of_binop_kind boi.boi_kind) alexp_kind - | _ -> false + | Ctl_parser_types.Stmt Clang_ast_t.BinaryOperator (_, _, _, boi) + -> ALVar.compare_str_with_alexp (Clang_ast_proj.string_of_binop_kind boi.boi_kind) alexp_kind + | _ + -> false let is_unop_with_kind an alexp_kind = let str_kind = ALVar.alexp_to_string alexp_kind in if not (Clang_ast_proj.is_valid_unop_kind_name str_kind) then - failwith ("Unary operator kind " ^ str_kind ^ " is not valid"); + failwith ("Unary operator kind " ^ str_kind ^ " is not valid") ; match an with - | Ctl_parser_types.Stmt (Clang_ast_t.UnaryOperator (_, _, _, uoi)) -> - ALVar.compare_str_with_alexp (Clang_ast_proj.string_of_unop_kind uoi.uoi_kind) alexp_kind - | _ -> false + | Ctl_parser_types.Stmt Clang_ast_t.UnaryOperator (_, _, _, uoi) + -> ALVar.compare_str_with_alexp (Clang_ast_proj.string_of_unop_kind uoi.uoi_kind) alexp_kind + | _ + -> false let has_cast_kind an alexp_kind = match an with - | Ctl_parser_types.Decl _ -> false - | Ctl_parser_types.Stmt stmt -> - let str_kind = ALVar.alexp_to_string alexp_kind in + | Ctl_parser_types.Decl _ + -> false + | Ctl_parser_types.Stmt stmt + -> let str_kind = ALVar.alexp_to_string alexp_kind in match Clang_ast_proj.get_cast_kind stmt with - | Some cast_kind -> - let cast_kind_str = Clang_ast_proj.string_of_cast_kind cast_kind in + | Some cast_kind + -> let cast_kind_str = Clang_ast_proj.string_of_cast_kind cast_kind in String.equal cast_kind_str str_kind - | None -> false + | None + -> false let is_node an nodename = let nodename_str = ALVar.alexp_to_string nodename in if not (Clang_ast_proj.is_valid_astnode_kind nodename_str) then - failwith ("Node " ^ nodename_str ^ " is not a valid AST node"); - let an_str = match an with - | Ctl_parser_types.Stmt s -> Clang_ast_proj.get_stmt_kind_string s - | Ctl_parser_types.Decl d -> Clang_ast_proj.get_decl_kind_string d in + failwith ("Node " ^ nodename_str ^ " is not a valid AST node") ; + let an_str = + match an with + | Ctl_parser_types.Stmt s + -> Clang_ast_proj.get_stmt_kind_string s + | Ctl_parser_types.Decl d + -> Clang_ast_proj.get_decl_kind_string d + in ALVar.compare_str_with_alexp an_str nodename let is_ptr_to_objc_class typ class_name = match typ with - | Some Clang_ast_t.ObjCObjectPointerType (_, {Clang_ast_t.qt_type_ptr}) -> - (match CAst_utils.get_desugared_type qt_type_ptr with - | Some ObjCInterfaceType (_, ptr) -> - (match CAst_utils.get_decl ptr with - | Some ObjCInterfaceDecl (_, ndi, _, _, _) -> - ALVar.compare_str_with_alexp ndi.ni_name class_name - | _ -> false) - | _ -> false) - | _ -> false + | Some Clang_ast_t.ObjCObjectPointerType (_, {Clang_ast_t.qt_type_ptr}) -> ( + match CAst_utils.get_desugared_type qt_type_ptr with + | Some ObjCInterfaceType (_, ptr) -> ( + match CAst_utils.get_decl ptr with + | Some ObjCInterfaceDecl (_, ndi, _, _, _) + -> ALVar.compare_str_with_alexp ndi.ni_name class_name + | _ + -> false ) + | _ + -> false ) + | _ + -> false (* node an is of class classname *) let isa an classname = match an with - | Ctl_parser_types.Stmt stmt -> - (match Clang_ast_proj.get_expr_tuple stmt with - | Some (_, _, expr_info) -> - let typ = CAst_utils.get_desugared_type expr_info.ei_qual_type.qt_type_ptr in - is_ptr_to_objc_class typ classname - | _ -> false) - | _ -> false + | Ctl_parser_types.Stmt stmt -> ( + match Clang_ast_proj.get_expr_tuple stmt with + | Some (_, _, expr_info) + -> let typ = CAst_utils.get_desugared_type expr_info.ei_qual_type.qt_type_ptr in + is_ptr_to_objc_class typ classname + | _ + -> false ) + | _ + -> false (* an is a declaration whose name contains a regexp defined by re *) let declaration_has_name an name = match an with - | Ctl_parser_types.Decl d -> - (match Clang_ast_proj.get_named_decl_tuple d with - | Some (_, ndi) -> ALVar.compare_str_with_alexp ndi.ni_name name - | _ -> false) - | _ -> false + | Ctl_parser_types.Decl d -> ( + match Clang_ast_proj.get_named_decl_tuple d with + | Some (_, ndi) + -> ALVar.compare_str_with_alexp ndi.ni_name name + | _ + -> false ) + | _ + -> false let is_class an re = match an with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCInterfaceDecl _) - | Ctl_parser_types.Decl (Clang_ast_t.ObjCImplementationDecl _) -> - declaration_has_name an re - | _ -> false - -let should_use_iphoneos_target_sdk_version (cxt : CLintersContext.context) = - let source_file = cxt.translation_unit_context.source_file in - not (List.exists - ~f:(fun path -> ALVar.str_match_regex (SourceFile.to_rel_path source_file) path) - Config.iphoneos_target_sdk_version_skip_path) - -let decl_unavailable_in_supported_ios_sdk (cxt : CLintersContext.context) an = + | Ctl_parser_types.Decl Clang_ast_t.ObjCInterfaceDecl _ + | Ctl_parser_types.Decl Clang_ast_t.ObjCImplementationDecl _ + -> declaration_has_name an re + | _ + -> false + +let should_use_iphoneos_target_sdk_version (cxt: CLintersContext.context) = + let source_file = cxt.translation_unit_context.source_file in + not + (List.exists + ~f:(fun path -> ALVar.str_match_regex (SourceFile.to_rel_path source_file) path) + Config.iphoneos_target_sdk_version_skip_path) + +let decl_unavailable_in_supported_ios_sdk (cxt: CLintersContext.context) an = let config_iphoneos_target_sdk_version = - if should_use_iphoneos_target_sdk_version cxt then - Config.iphoneos_target_sdk_version - else None in + if should_use_iphoneos_target_sdk_version cxt then Config.iphoneos_target_sdk_version else None + in let allowed_os_versions = - match config_iphoneos_target_sdk_version, - (cxt.if_context : CLintersContext.if_context option) with - | Some iphoneos_target_sdk_version, Some if_context -> - iphoneos_target_sdk_version :: if_context.ios_version_guard - | Some iphoneos_target_sdk_version, None -> [iphoneos_target_sdk_version] - | _ -> [] in + match + (config_iphoneos_target_sdk_version, (cxt.if_context : CLintersContext.if_context option)) + with + | Some iphoneos_target_sdk_version, Some if_context + -> iphoneos_target_sdk_version :: if_context.ios_version_guard + | Some iphoneos_target_sdk_version, None + -> [iphoneos_target_sdk_version] + | _ + -> [] + in let max_allowed_version_opt = List.max_elt allowed_os_versions ~cmp:Utils.compare_versions in let available_attr_ios_sdk = get_available_attr_ios_sdk an in - match available_attr_ios_sdk, max_allowed_version_opt with - | Some available_attr_ios_sdk, Some max_allowed_version -> - (Utils.compare_versions available_attr_ios_sdk max_allowed_version) > 0 - | _ -> false + match (available_attr_ios_sdk, max_allowed_version_opt) with + | Some available_attr_ios_sdk, Some max_allowed_version + -> Utils.compare_versions available_attr_ios_sdk max_allowed_version > 0 + | _ + -> false (* Check whether a type_ptr and a string denote the same type *) let type_ptr_equal_type type_ptr type_str = let pos_str lexbuf = let pos = lexbuf.lex_curr_p in - pos.pos_fname ^ ":" ^ (string_of_int pos.pos_lnum) ^ ":" ^ - (string_of_int (pos.pos_cnum - pos.pos_bol + 1)) in - + pos.pos_fname ^ ":" ^ string_of_int pos.pos_lnum ^ ":" + ^ string_of_int (pos.pos_cnum - pos.pos_bol + 1) + in let parse_type_string str = - L.(debug Linters Medium) "Starting parsing type string '%s'@\n" str; + L.(debug Linters Medium) "Starting parsing type string '%s'@\n" str ; let lexbuf = Lexing.from_string str in - try - (Types_parser.abs_ctype token lexbuf) - with - | Ctl_parser_types.ALParsingException s -> - raise (Ctl_parser_types.ALParsingException - ("Syntax Error when defining type" ^ s )) - | SyntaxError _ - | Types_parser.Error -> - raise (Ctl_parser_types.ALParsingException - ("SYNTAX ERROR at " ^ (pos_str lexbuf))) in + try Types_parser.abs_ctype token lexbuf with + | Ctl_parser_types.ALParsingException s + -> raise (Ctl_parser_types.ALParsingException ("Syntax Error when defining type" ^ s)) + | SyntaxError _ | Types_parser.Error + -> raise (Ctl_parser_types.ALParsingException ("SYNTAX ERROR at " ^ pos_str lexbuf)) + in let abs_ctype = match String.Map.find !parsed_type_map type_str with - | Some abs_ctype' -> abs_ctype' - | None -> let abs_ctype' = parse_type_string type_str in - parsed_type_map := String.Map.add !parsed_type_map ~key:type_str ~data:abs_ctype'; - abs_ctype' in + | Some abs_ctype' + -> abs_ctype' + | None + -> let abs_ctype' = parse_type_string type_str in + parsed_type_map := String.Map.add !parsed_type_map ~key:type_str ~data:abs_ctype' ; + abs_ctype' + in match CAst_utils.get_type type_ptr with - | Some c_type' -> - Ctl_parser_types.c_type_equal c_type' abs_ctype - | _ -> L.(debug Linters Medium) "Couldn't find type....@\n"; false + | Some c_type' + -> Ctl_parser_types.c_type_equal c_type' abs_ctype + | _ + -> L.(debug Linters Medium) "Couldn't find type....@\n" ; false let get_ast_node_type_ptr an = match an with - | Ctl_parser_types.Stmt stmt -> - (match Clang_ast_proj.get_expr_tuple stmt with - | Some (_, _, expr_info) -> Some expr_info.ei_qual_type.qt_type_ptr - | _ -> None) - | Ctl_parser_types.Decl decl -> CAst_utils.type_of_decl decl + | Ctl_parser_types.Stmt stmt -> ( + match Clang_ast_proj.get_expr_tuple stmt with + | Some (_, _, expr_info) + -> Some expr_info.ei_qual_type.qt_type_ptr + | _ + -> None ) + | Ctl_parser_types.Decl decl + -> CAst_utils.type_of_decl decl let has_type an _typ = - match get_ast_node_type_ptr an, _typ with - | Some pt, ALVar.Const typ -> type_ptr_equal_type pt typ - | _ -> false + match (get_ast_node_type_ptr an, _typ) with + | Some pt, ALVar.Const typ + -> type_ptr_equal_type pt typ + | _ + -> false let method_return_type an _typ = - L.(debug Linters Verbose) "@\n Executing method_return_type..."; - match an, _typ with - | Ctl_parser_types.Decl (Clang_ast_t.ObjCMethodDecl (_, _, mdi)), ALVar.Const typ -> - L.(debug Linters Verbose) "@\n with parameter `%s`...." typ; + L.(debug Linters Verbose) "@\n Executing method_return_type..." ; + match (an, _typ) with + | Ctl_parser_types.Decl Clang_ast_t.ObjCMethodDecl (_, _, mdi), ALVar.Const typ + -> L.(debug Linters Verbose) "@\n with parameter `%s`...." typ ; let qual_type = mdi.Clang_ast_t.omdi_result_type in type_ptr_equal_type qual_type.Clang_ast_t.qt_type_ptr typ - | _ -> false + | _ + -> false let rec check_protocol_hiearachy decls_ptr _prot_name = let open Clang_ast_t in let is_this_protocol di_opt = - match di_opt with - | Some di -> ALVar.compare_str_with_alexp di.ni_name _prot_name - | _ -> false in + match di_opt with Some di -> ALVar.compare_str_with_alexp di.ni_name _prot_name | _ -> false + in match decls_ptr with - | [] -> false - | pt :: decls' -> - let di, protocols = (match CAst_utils.get_decl pt with - | Some ObjCProtocolDecl (_, di, _, _, opcdi) -> - Some di, opcdi.opcdi_protocols - | _ -> None, []) in - if (is_this_protocol di) - || List.exists ~f:(fun dr -> is_this_protocol dr.dr_name) protocols then - true + | [] + -> false + | pt :: decls' + -> let di, protocols = + match CAst_utils.get_decl pt with + | Some ObjCProtocolDecl (_, di, _, _, opcdi) + -> (Some di, opcdi.opcdi_protocols) + | _ + -> (None, []) + in + if is_this_protocol di || List.exists ~f:(fun dr -> is_this_protocol dr.dr_name) protocols + then true else let super_prot = List.map ~f:(fun dr -> dr.dr_decl_pointer) protocols in check_protocol_hiearachy (super_prot @ decls') _prot_name @@ -430,55 +496,73 @@ let has_type_subprotocol_of an _prot_name = let open Clang_ast_t in let rec check_subprotocol t = match t with - | Some ObjCObjectPointerType (_, qt) -> - check_subprotocol (CAst_utils.get_type qt.qt_type_ptr) - | Some ObjCObjectType (_, ooti) -> - if List.length ooti.protocol_decls_ptr > 0 then + | Some ObjCObjectPointerType (_, qt) + -> check_subprotocol (CAst_utils.get_type qt.qt_type_ptr) + | Some ObjCObjectType (_, ooti) + -> if List.length ooti.protocol_decls_ptr > 0 then check_protocol_hiearachy ooti.protocol_decls_ptr _prot_name else List.exists - ~f:(fun qt -> check_subprotocol (CAst_utils.get_type qt.qt_type_ptr)) ooti.type_args - | Some ObjCInterfaceType (_, pt) -> - check_protocol_hiearachy [pt] _prot_name - | _ -> false in + ~f:(fun qt -> check_subprotocol (CAst_utils.get_type qt.qt_type_ptr)) + ooti.type_args + | Some ObjCInterfaceType (_, pt) + -> check_protocol_hiearachy [pt] _prot_name + | _ + -> false + in match get_ast_node_type_ptr an with - | Some tp -> check_subprotocol (CAst_utils.get_type tp) - | _ -> false + | Some tp + -> check_subprotocol (CAst_utils.get_type tp) + | _ + -> false -let within_responds_to_selector_block (cxt:CLintersContext.context) an = +let within_responds_to_selector_block (cxt: CLintersContext.context) an = let open Clang_ast_t in match an with - | Ctl_parser_types.Decl (ObjCMethodDecl (_, named_decl_info, _)) -> - (match cxt.if_context with - | Some if_context -> - let in_selector_block = if_context.within_responds_to_selector_block in - List.mem ~equal:String.equal in_selector_block named_decl_info.ni_name - | None -> false) - | _ -> false + | Ctl_parser_types.Decl ObjCMethodDecl (_, named_decl_info, _) -> ( + match cxt.if_context with + | Some if_context + -> let in_selector_block = if_context.within_responds_to_selector_block in + List.mem ~equal:String.equal in_selector_block named_decl_info.ni_name + | None + -> false ) + | _ + -> false let objc_method_has_nth_parameter_of_type an _num _typ = let open Clang_ast_t in - let num = match _num with - | ALVar.Const n -> (try - int_of_string n - with Failure _ -> -1) - | _ -> -1 in - match num, an, _typ with - | -1, _, _ -> false - | _, Ctl_parser_types.Decl (ObjCMethodDecl (_, _, omdi)), ALVar.Const typ -> - (match List.nth omdi.omdi_parameters num with - | Some (ParmVarDecl (_, _, qt, _)) -> - type_ptr_equal_type qt.qt_type_ptr typ - | _ -> false) - | _, _, _ -> false + let num = + match _num with + | ALVar.Const n -> ( + try int_of_string n + with Failure _ -> -1 ) + | _ + -> -1 + in + match (num, an, _typ) with + | -1, _, _ + -> false + | _, Ctl_parser_types.Decl ObjCMethodDecl (_, _, omdi), ALVar.Const typ -> ( + match List.nth omdi.omdi_parameters num with + | Some ParmVarDecl (_, _, qt, _) + -> type_ptr_equal_type qt.qt_type_ptr typ + | _ + -> false ) + | _, _, _ + -> false let using_namespace an namespace = let open Clang_ast_t in match an with - | Ctl_parser_types.Decl (UsingDirectiveDecl (_, _, uddi)) -> - (match uddi.uddi_nominated_namespace with - | Some dr -> (match dr.dr_kind, dr.dr_name with - | `Namespace, Some ni -> ALVar.compare_str_with_alexp ni.ni_name namespace - | _ -> false) - | None -> false) - | _ -> false + | Ctl_parser_types.Decl UsingDirectiveDecl (_, _, uddi) -> ( + match uddi.uddi_nominated_namespace with + | Some dr -> ( + match (dr.dr_kind, dr.dr_name) with + | `Namespace, Some ni + -> ALVar.compare_str_with_alexp ni.ni_name namespace + | _ + -> false ) + | None + -> false ) + | _ + -> false diff --git a/infer/src/clang/cPredicates.mli b/infer/src/clang/cPredicates.mli index 7b435c4a3..840e7ac48 100644 --- a/infer/src/clang/cPredicates.mli +++ b/infer/src/clang/cPredicates.mli @@ -9,15 +9,17 @@ open! IStd -type t = ALVar.formula_id * ALVar.alexp list (* (name, [param1,...,paramK]) *) +type t = ALVar.formula_id * ALVar.alexp list + +(* (name, [param1,...,paramK]) *) val captured_variables_cxx_ref : Ctl_parser_types.ast_node -> Clang_ast_t.named_decl_info list val call_method : Ctl_parser_types.ast_node -> ALVar.alexp -> bool -val call_class_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool +val call_class_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool -val call_instance_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool +val call_instance_method : Ctl_parser_types.ast_node -> ALVar.alexp -> ALVar.alexp -> bool val is_objc_interface_named : Ctl_parser_types.ast_node -> ALVar.alexp -> bool @@ -27,7 +29,7 @@ val is_syntactically_global_var : Ctl_parser_types.ast_node -> bool val is_const_expr_var : Ctl_parser_types.ast_node -> bool -val call_function : Ctl_parser_types.ast_node -> ALVar.alexp -> bool +val call_function : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val is_strong_property : Ctl_parser_types.ast_node -> bool @@ -60,8 +62,8 @@ val is_node : Ctl_parser_types.ast_node -> ALVar.alexp -> bool val declaration_has_name : Ctl_parser_types.ast_node -> ALVar.alexp -> bool -val declaration_ref_name : ?kind:Clang_ast_t.decl_kind -> Ctl_parser_types.ast_node -> - ALVar.alexp -> bool +val declaration_ref_name : + ?kind:Clang_ast_t.decl_kind -> Ctl_parser_types.ast_node -> ALVar.alexp -> bool val is_class : Ctl_parser_types.ast_node -> ALVar.alexp -> bool diff --git a/infer/src/clang/cTL.ml b/infer/src/clang/cTL.ml index 4181d6cdc..ea3e2fa70 100644 --- a/infer/src/clang/cTL.ml +++ b/infer/src/clang/cTL.ml @@ -9,31 +9,31 @@ open! IStd open Ctl_parser_types - module L = Logging (* This module defines a language to define checkers. These checkers are intepreted over the AST of the program. A checker is defined by a CTL formula which express a condition saying when the checker should report a problem *) - (* Transition labels used for example to switch from decl to stmt *) type transitions = - | Body (** decl to stmt *) - | InitExpr (** decl to stmt *) - | Super (** decl to decl *) - | Parameters (** decl to decl *) + | Body (** decl to stmt *) + | InitExpr (** decl to stmt *) + | Super (** decl to decl *) + | Parameters (** decl to decl *) | Cond - | PointerToDecl (** stmt to decl *) - | Protocol (** decl to decl *) + | PointerToDecl (** stmt to decl *) + | Protocol (** decl to decl *) (* In formulas below prefix "E" means "exists a path" "A" means "for all path" *) -type t = (* A ctl formula *) +type t = + (* A ctl formula *) | True - | False (* not really necessary but it makes it evaluation faster *) + | False + (* not really necessary but it makes it evaluation faster *) | Atomic of CPredicates.t | Not of t | And of t * t @@ -52,15 +52,29 @@ type t = (* A ctl formula *) | ET of ALVar.alexp list * transitions option * t | ETX of ALVar.alexp list * transitions option * t -let has_transition phi = match phi with - | True | False | Atomic _ | Not _ | And (_, _) - | Or (_, _) | Implies (_, _) | InNode (_, _) - | EH (_, _) -> false - | AX (trans_opt, _) | AF (trans_opt, _) - | AG (trans_opt, _) | AU (trans_opt, _, _) - | EX (trans_opt, _) | EF (trans_opt, _) - | EG (trans_opt, _) | EU (trans_opt, _, _) - | ET (_, trans_opt, _) | ETX (_, trans_opt, _) -> Option.is_some trans_opt +let has_transition phi = + match phi with + | True + | False + | Atomic _ + | Not _ + | And (_, _) + | Or (_, _) + | Implies (_, _) + | InNode (_, _) + | EH (_, _) + -> false + | AX (trans_opt, _) + | AF (trans_opt, _) + | AG (trans_opt, _) + | AU (trans_opt, _, _) + | EX (trans_opt, _) + | EF (trans_opt, _) + | EG (trans_opt, _) + | EU (trans_opt, _, _) + | ET (_, trans_opt, _) + | ETX (_, trans_opt, _) + -> Option.is_some trans_opt (* "set" clauses are used for defining mandatory variables that will be used by when reporting issues: eg for defining the condition. @@ -80,136 +94,158 @@ let has_transition phi = match phi with *) type clause = - | CLet of ALVar.formula_id * ALVar.t list * t (* Let clause: let id = definifion; *) - | CSet of ALVar.keyword * t (* Set clause: set id = definition *) - | CDesc of ALVar.keyword * string (* Description clause eg: set message = "..." *) - | CPath of [ `WhitelistPath | `BlacklistPath ] * ALVar.t list - -type ctl_checker = { - id : string; (* Checker's id *) - definitions : clause list (* A list of let/set definitions *) -} - -type al_file = { - import_files : string list; - global_macros : clause list; - global_paths : (string * ALVar.alexp list) list; - checkers : ctl_checker list -} - -let equal_ast_node = Poly.(=) + | CLet of ALVar.formula_id * ALVar.t list * t + (* Let clause: let id = definifion; *) + | CSet of ALVar.keyword * t + (* Set clause: set id = definition *) + | CDesc of ALVar.keyword * string + (* Description clause eg: set message = "..." *) + | CPath of [`WhitelistPath | `BlacklistPath] * ALVar.t list + +type ctl_checker = + {id: string; (* Checker's id *) + definitions: clause list (* A list of let/set definitions *)} + +type al_file = + { import_files: string list + ; global_macros: clause list + ; global_paths: (string * ALVar.alexp list) list + ; checkers: ctl_checker list } + +let equal_ast_node = Poly.( = ) module Debug = struct let pp_transition fmt trans_opt = - let pp_aux fmt trans = match trans with - | Body -> Format.pp_print_string fmt "Body" - | InitExpr -> Format.pp_print_string fmt "InitExpr" - | Super -> Format.pp_print_string fmt "Super" - | Parameters -> Format.pp_print_string fmt "Parameters" - | Cond -> Format.pp_print_string fmt "Cond" - | Protocol -> Format.pp_print_string fmt "Protocol" - | PointerToDecl -> Format.pp_print_string fmt "PointerToDecl" in - match trans_opt with - | Some trans -> pp_aux fmt trans - | None -> Format.pp_print_string fmt "_" + let pp_aux fmt trans = + match trans with + | Body + -> Format.pp_print_string fmt "Body" + | InitExpr + -> Format.pp_print_string fmt "InitExpr" + | Super + -> Format.pp_print_string fmt "Super" + | Parameters + -> Format.pp_print_string fmt "Parameters" + | Cond + -> Format.pp_print_string fmt "Cond" + | Protocol + -> Format.pp_print_string fmt "Protocol" + | PointerToDecl + -> Format.pp_print_string fmt "PointerToDecl" + in + match trans_opt with Some trans -> pp_aux fmt trans | None -> Format.pp_print_string fmt "_" (* a flag to print more or less in the dotty graph *) let full_print = true let rec pp_formula fmt phi = - let nodes_to_string nl = - List.map ~f:ALVar.alexp_to_string nl in + let nodes_to_string nl = List.map ~f:ALVar.alexp_to_string nl in match phi with - | True -> Format.fprintf fmt "True" - | False -> Format.fprintf fmt "False" - | Atomic p -> CPredicates.pp_predicate fmt p - | Not phi -> if full_print then Format.fprintf fmt "NOT(%a)" pp_formula phi + | True + -> Format.fprintf fmt "True" + | False + -> Format.fprintf fmt "False" + | Atomic p + -> CPredicates.pp_predicate fmt p + | Not phi + -> if full_print then Format.fprintf fmt "NOT(%a)" pp_formula phi else Format.fprintf fmt "NOT(...)" - | And (phi1, phi2) -> if full_print then - Format.fprintf fmt "(%a AND %a)" pp_formula phi1 pp_formula phi2 - else Format.fprintf fmt "(... AND ...)" - | Or (phi1, phi2) -> if full_print then - Format.fprintf fmt "(%a OR %a)" pp_formula phi1 pp_formula phi2 + | And (phi1, phi2) + -> if full_print then Format.fprintf fmt "(%a AND %a)" pp_formula phi1 pp_formula phi2 + else Format.fprintf fmt "(... AND ...)" + | Or (phi1, phi2) + -> if full_print then Format.fprintf fmt "(%a OR %a)" pp_formula phi1 pp_formula phi2 else Format.fprintf fmt "(... OR ...)" - | Implies (phi1, phi2) -> Format.fprintf fmt "(%a ==> %a)" pp_formula phi1 pp_formula phi2 - | InNode (nl, phi) -> Format.fprintf fmt "IN-NODE %a: (%a)" - (Pp.comma_seq Format.pp_print_string) - (nodes_to_string nl) - pp_formula phi - | AX (trs, phi) -> Format.fprintf fmt "AX[->%a](%a)" pp_transition trs pp_formula phi - | EX (trs, phi) -> Format.fprintf fmt "EX[->%a](%a)" pp_transition trs pp_formula phi - | AF (trs, phi) -> Format.fprintf fmt "AF[->%a](%a)" pp_transition trs pp_formula phi - | EF (trs, phi) -> Format.fprintf fmt "EF[->%a](%a)" pp_transition trs pp_formula phi - | AG (trs, phi) -> Format.fprintf fmt "AG[->%a](%a)" pp_transition trs pp_formula phi - | EG (trs, phi) -> Format.fprintf fmt "EG[->%a](%a)" pp_transition trs pp_formula phi - | AU (trs, phi1, phi2) -> Format.fprintf fmt "A[->%a][%a UNTIL %a]" - pp_transition trs pp_formula phi1 pp_formula phi2 - | EU (trs, phi1, phi2) -> Format.fprintf fmt "E[->%a][%a UNTIL %a]" - pp_transition trs pp_formula phi1 pp_formula phi2 - | EH (arglist, phi) -> Format.fprintf fmt "EH[%a](%a)" - (Pp.comma_seq Format.pp_print_string) - (nodes_to_string arglist) - pp_formula phi - | ET (arglist, trans, phi) -> Format.fprintf fmt "ET[%a][%a](%a)" - (Pp.comma_seq Format.pp_print_string) - (nodes_to_string arglist) - pp_transition trans - pp_formula phi - | ETX (arglist, trans, phi) -> Format.fprintf fmt "ETX[%a][%a](%a)" - (Pp.comma_seq Format.pp_print_string) - (nodes_to_string arglist) - pp_transition trans - pp_formula phi - - let pp_ast ~ast_node_to_highlight ?(prettifier=Fn.id) fmt root = + | Implies (phi1, phi2) + -> Format.fprintf fmt "(%a ==> %a)" pp_formula phi1 pp_formula phi2 + | InNode (nl, phi) + -> Format.fprintf fmt "IN-NODE %a: (%a)" (Pp.comma_seq Format.pp_print_string) + (nodes_to_string nl) pp_formula phi + | AX (trs, phi) + -> Format.fprintf fmt "AX[->%a](%a)" pp_transition trs pp_formula phi + | EX (trs, phi) + -> Format.fprintf fmt "EX[->%a](%a)" pp_transition trs pp_formula phi + | AF (trs, phi) + -> Format.fprintf fmt "AF[->%a](%a)" pp_transition trs pp_formula phi + | EF (trs, phi) + -> Format.fprintf fmt "EF[->%a](%a)" pp_transition trs pp_formula phi + | AG (trs, phi) + -> Format.fprintf fmt "AG[->%a](%a)" pp_transition trs pp_formula phi + | EG (trs, phi) + -> Format.fprintf fmt "EG[->%a](%a)" pp_transition trs pp_formula phi + | AU (trs, phi1, phi2) + -> Format.fprintf fmt "A[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2 + | EU (trs, phi1, phi2) + -> Format.fprintf fmt "E[->%a][%a UNTIL %a]" pp_transition trs pp_formula phi1 pp_formula phi2 + | EH (arglist, phi) + -> Format.fprintf fmt "EH[%a](%a)" (Pp.comma_seq Format.pp_print_string) + (nodes_to_string arglist) pp_formula phi + | ET (arglist, trans, phi) + -> Format.fprintf fmt "ET[%a][%a](%a)" (Pp.comma_seq Format.pp_print_string) + (nodes_to_string arglist) pp_transition trans pp_formula phi + | ETX (arglist, trans, phi) + -> Format.fprintf fmt "ETX[%a][%a](%a)" (Pp.comma_seq Format.pp_print_string) + (nodes_to_string arglist) pp_transition trans pp_formula phi + + let pp_ast ~ast_node_to_highlight ?(prettifier= Fn.id) fmt root = let pp_node_info fmt an = let name = Ctl_parser_types.ast_node_name an in let typ = Ctl_parser_types.ast_node_type an in let cast_kind = Ctl_parser_types.ast_node_cast_kind an in - Format.fprintf fmt " %s %s %s" name typ cast_kind in + Format.fprintf fmt " %s %s %s" name typ cast_kind + in let rec pp_children pp_node wrapper fmt level nodes = match nodes with - | [] -> () - | node :: nodes -> - pp_node fmt (wrapper node) level "|-"; - pp_children pp_node wrapper fmt level nodes in + | [] + -> () + | node :: nodes + -> pp_node fmt (wrapper node) level "|-" ; + pp_children pp_node wrapper fmt level nodes + in let rec pp_ast_aux fmt root level prefix = let get_node_name (an: ast_node) = match an with - | Stmt stmt -> Clang_ast_proj.get_stmt_kind_string stmt - | Decl decl -> Clang_ast_proj.get_decl_kind_string decl in - let should_highlight = match root, ast_node_to_highlight with - | Stmt r, Stmt n -> phys_equal r n - | Decl r, Decl n -> phys_equal r n - | _ -> false in + | Stmt stmt + -> Clang_ast_proj.get_stmt_kind_string stmt + | Decl decl + -> Clang_ast_proj.get_decl_kind_string decl + in + let should_highlight = + match (root, ast_node_to_highlight) with + | Stmt r, Stmt n + -> phys_equal r n + | Decl r, Decl n + -> phys_equal r n + | _ + -> false + in let node_name = let node_name = get_node_name root in - if should_highlight then prettifier node_name else node_name in - let spaces = String.make (level*(String.length prefix)) ' ' in + if should_highlight then prettifier node_name else node_name + in + let spaces = String.make (level * String.length prefix) ' ' in let next_level = level + 1 in - Format.fprintf fmt "%s%s%s %a@\n" spaces prefix node_name pp_node_info root; - (match root with - | Stmt (DeclStmt (_, stmts, ([(VarDecl _)] as var_decl))) -> - (* handling special case of DeclStmt with VarDecl: emit the VarDecl node + Format.fprintf fmt "%s%s%s %a@\n" spaces prefix node_name pp_node_info root ; + match root with + | Stmt DeclStmt (_, stmts, ([(VarDecl _)] as var_decl)) + -> (* handling special case of DeclStmt with VarDecl: emit the VarDecl node then emit the statements in DeclStmt as children of VarDecl. This is because despite being equal, the statements inside VarDecl and those inside DeclStmt belong to different instances, hence they fail the phys_equal check that should colour them *) - pp_children pp_ast_aux (fun n -> Decl n) fmt next_level var_decl; - pp_stmts fmt (next_level+1) stmts - | Stmt stmt -> - let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in - pp_stmts fmt next_level stmts - | Decl decl -> - let decls = - Clang_ast_proj.get_decl_context_tuple decl |> - Option.map ~f:(fun (decls, _) -> decls) |> - Option.value ~default:[] in - pp_decls fmt next_level decls) - and pp_stmts fmt level stmts = - pp_children pp_ast_aux (fun n -> Stmt n) fmt level stmts - and pp_decls fmt level decls = - pp_children pp_ast_aux (fun n -> Decl n) fmt level decls in + pp_children pp_ast_aux (fun n -> Decl n) fmt next_level var_decl ; + pp_stmts fmt (next_level + 1) stmts + | Stmt stmt + -> let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in + pp_stmts fmt next_level stmts + | Decl decl + -> let decls = + Clang_ast_proj.get_decl_context_tuple decl |> Option.map ~f:(fun (decls, _) -> decls) + |> Option.value ~default:[] + in + pp_decls fmt next_level decls + and pp_stmts fmt level stmts = pp_children pp_ast_aux (fun n -> Stmt n) fmt level stmts + and pp_decls fmt level decls = pp_children pp_ast_aux (fun n -> Decl n) fmt level decls in pp_ast_aux fmt root 0 "" module EvaluationTracker = struct @@ -217,19 +253,12 @@ module Debug = struct type eval_result = Eval_undefined | Eval_true | Eval_false - type content = { - ast_node: ast_node; - phi: t; - lcxt: CLintersContext.context; - eval_result: eval_result; - } + type content = + {ast_node: ast_node; phi: t; lcxt: CLintersContext.context; eval_result: eval_result} - type eval_node = { - id: int; - content: content; - } + type eval_node = {id: int; content: content} - type tree = Tree of eval_node * (tree list) + type tree = Tree of eval_node * tree list type ast_node_to_display = (* the node can be used to describe further sub calls in the evaluation stack *) @@ -237,142 +266,154 @@ module Debug = struct (* the node cannot be further used to describe sub calls in the evaluation stack *) | Last_occurrence of ast_node - type t = { - next_id: int; - eval_stack: (tree * ast_node_to_display) Stack.t; - forest: tree list; - breakpoint_line: int option; - debugger_active: bool; - } + type t = + { next_id: int + ; eval_stack: (tree * ast_node_to_display) Stack.t + ; forest: tree list + ; breakpoint_line: int option + ; debugger_active: bool } - let create_content ast_node phi lcxt = - {ast_node; phi; eval_result = Eval_undefined; lcxt = lcxt; } + let create_content ast_node phi lcxt = {ast_node; phi; eval_result= Eval_undefined; lcxt} let create source_file = let breakpoint_token = "INFER_BREAKPOINT" in let breakpoint_line = In_channel.read_lines (SourceFile.to_abs_path source_file) |> List.findi ~f:(fun _ line -> String.is_substring line ~substring:breakpoint_token) - |> Option.map ~f:(fun (i, _) -> i + 1) in - { - next_id = 0; - eval_stack = Stack.create(); - forest = []; - breakpoint_line; - debugger_active = false; - } + |> Option.map ~f:(fun (i, _) -> i + 1) + in + {next_id= 0; eval_stack= Stack.create (); forest= []; breakpoint_line; debugger_active= false} let explain t ~eval_node ~ast_node_to_display = let line_number an = let line_of_source_range (sr: Clang_ast_t.source_range) = let loc_info, _ = sr in - loc_info.sl_line in + loc_info.sl_line + in match an with - | Stmt stmt -> - let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in + | Stmt stmt + -> let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in line_of_source_range stmt_info.si_source_range - | Decl decl -> - let decl_info = Clang_ast_proj.get_decl_tuple decl in - line_of_source_range decl_info.di_source_range in + | Decl decl + -> let decl_info = Clang_ast_proj.get_decl_tuple decl in + line_of_source_range decl_info.di_source_range + in let stop_and_explain_step () = - let highlight_style = match eval_node.content.eval_result with - | Eval_undefined -> ANSITerminal.[Bold] - | Eval_true -> ANSITerminal.[Bold; green] - | Eval_false -> ANSITerminal.[Bold; red] in + let highlight_style = + match eval_node.content.eval_result with + | Eval_undefined + -> ANSITerminal.([Bold]) + | Eval_true + -> ANSITerminal.([Bold; green]) + | Eval_false + -> ANSITerminal.([Bold; red]) + in let ast_node_to_highlight = eval_node.content.ast_node in - let ast_root, is_last_occurrence = match ast_node_to_display with - | Carry_forward n -> n, false - | Last_occurrence n -> n, true in + let ast_root, is_last_occurrence = + match ast_node_to_display with + | Carry_forward n + -> (n, false) + | Last_occurrence n + -> (n, true) + in let ast_str = Format.asprintf "%a" - (pp_ast - ~ast_node_to_highlight ~prettifier:(ANSITerminal.sprintf highlight_style "%s")) - ast_root in - L.progress - "@\nNode ID: %d\tEvaluation stack level: %d\tSource line-number: %s@\n" - eval_node.id - (Stack.length t.eval_stack) - (Option.value_map - ~default:"Unknown" ~f:string_of_int (line_number ast_node_to_highlight)); - let is_eval_result_undefined = match eval_node.content.eval_result with - | Eval_undefined -> true - | _ -> false in + (pp_ast ~ast_node_to_highlight ~prettifier:(ANSITerminal.sprintf highlight_style "%s")) + ast_root + in + L.progress "@\nNode ID: %d\tEvaluation stack level: %d\tSource line-number: %s@\n" + eval_node.id (Stack.length t.eval_stack) + (Option.value_map ~default:"Unknown" ~f:string_of_int (line_number ast_node_to_highlight)) ; + let is_eval_result_undefined = + match eval_node.content.eval_result with Eval_undefined -> true | _ -> false + in if is_last_occurrence && is_eval_result_undefined then - L.progress - "From this step, a transition to a different part of the AST may follow.@\n"; + L.progress "From this step, a transition to a different part of the AST may follow.@\n" ; let phi_str = Format.asprintf "%a" pp_formula eval_node.content.phi in - L.progress "CTL Formula: %s@\n@\n" phi_str; - L.progress "%s@\n" ast_str; + L.progress "CTL Formula: %s@\n@\n" phi_str ; + L.progress "%s@\n" ast_str ; let quit_token = "q" in - L.progress "Press Enter to continue or type %s to quit... @?" quit_token; + L.progress "Press Enter to continue or type %s to quit... @?" quit_token ; match In_channel.input_line_exn In_channel.stdin |> String.lowercase with - | s when String.equal s quit_token -> exit 0 - | _ -> - (* Remove the line at the bottom of terminal with the debug instructions *) - ANSITerminal.( - (* move one line up, as current line is the one generated by pressing enter *) - move_cursor 0 (-1); - move_bol (); (* move to the beginning of the line *) - erase Below; (* erase what follows the cursor's position *) - ) in - match t.debugger_active, t.breakpoint_line, line_number eval_node.content.ast_node with - | false, Some break_point_ln, Some ln when ln >= break_point_ln -> - L.progress "Attaching debugger at line %d" ln; - stop_and_explain_step (); - {t with debugger_active = true} - | true, _, _ -> - stop_and_explain_step (); - t - | _ -> t + | s when String.equal s quit_token + -> exit 0 + | _ + -> (* Remove the line at the bottom of terminal with the debug instructions *) + let open ANSITerminal in + (* move one line up, as current line is the one generated by pressing enter *) + move_cursor 0 (-1) ; move_bol () ; (* move to the beginning of the line *) + erase Below + (* erase what follows the cursor's position *) + in + match (t.debugger_active, t.breakpoint_line, line_number eval_node.content.ast_node) with + | false, Some break_point_ln, Some ln when ln >= break_point_ln + -> L.progress "Attaching debugger at line %d" ln ; + stop_and_explain_step () ; + {t with debugger_active= true} + | true, _, _ + -> stop_and_explain_step () ; t + | _ + -> t let eval_begin t content = - let node = {id = t.next_id; content} in + let node = {id= t.next_id; content} in let create_subtree root = Tree (root, []) in let subtree' = create_subtree node in let ast_node_from_previous_call = match Stack.top t.eval_stack with - | Some (_, Last_occurrence _) -> content.ast_node - | Some (_, Carry_forward an) -> an - | None -> content.ast_node in + | Some (_, Last_occurrence _) + -> content.ast_node + | Some (_, Carry_forward an) + -> an + | None + -> content.ast_node + in let ast_node_to_display = if has_transition content.phi then Last_occurrence ast_node_from_previous_call - else Carry_forward ast_node_from_previous_call in - Stack.push t.eval_stack (subtree', ast_node_to_display); + else Carry_forward ast_node_from_previous_call + in + Stack.push t.eval_stack (subtree', ast_node_to_display) ; let t' = explain t ~eval_node:node ~ast_node_to_display in - {t' with next_id = t.next_id + 1} + {t' with next_id= t.next_id + 1} let eval_end t result = - let eval_result_of_bool = function - | true -> Eval_true - | false -> Eval_false in + let eval_result_of_bool = function true -> Eval_true | false -> Eval_false in if Stack.is_empty t.eval_stack then - raise (Empty_stack "Unbalanced number of eval_begin/eval_end invocations"); - let evaluated_tree, eval_node, ast_node_to_display = match Stack.pop_exn t.eval_stack with - | Tree ({id = _; content} as eval_node, children), ast_node_to_display -> - let content' = {content with eval_result = eval_result_of_bool result} in - let eval_node' = {eval_node with content = content'} in - Tree (eval_node', children), - eval_node', - ast_node_to_display in + raise (Empty_stack "Unbalanced number of eval_begin/eval_end invocations") ; + let evaluated_tree, eval_node, ast_node_to_display = + match Stack.pop_exn t.eval_stack + with Tree (({id= _; content} as eval_node), children), ast_node_to_display -> + let content' = {content with eval_result= eval_result_of_bool result} in + let eval_node' = {eval_node with content= content'} in + (Tree (eval_node', children), eval_node', ast_node_to_display) + in let t' = explain t ~eval_node ~ast_node_to_display in let forest' = if Stack.is_empty t'.eval_stack then evaluated_tree :: t'.forest else - let parent = match Stack.pop_exn t'.eval_stack with - Tree (node, children), ntd -> Tree (node, evaluated_tree :: children), ntd in - Stack.push t'.eval_stack parent; - t'.forest in - {t' with forest = forest'} + let parent = + match Stack.pop_exn t'.eval_stack + with Tree (node, children), ntd -> (Tree (node, evaluated_tree :: children), ntd) + in + Stack.push t'.eval_stack parent ; t'.forest + in + {t' with forest= forest'} module DottyPrinter = struct let dotty_of_ctl_evaluation t = let buffer_content buf = let result = Buffer.contents buf in - Buffer.reset buf; - result in + Buffer.reset buf ; result + in let dotty_of_tree cluster_id tree = - let get_root tree = match tree with Tree (root, _) -> root in - let get_children tree = match tree with Tree (_, children) -> List.rev children in + let get_root tree = + match tree + with Tree (root, _) -> root + in + let get_children tree = + match tree + with Tree (_, children) -> List.rev children + in (* shallow: emit dotty about root node and edges to its children *) let shallow_dotty_of_tree tree = let root_node = get_root tree in @@ -380,119 +421,145 @@ module Debug = struct let edge child_node = if equal_ast_node root_node.content.ast_node child_node.content.ast_node then Printf.sprintf "%d -> %d [style=dotted]" root_node.id child_node.id - else - Printf.sprintf "%d -> %d [style=bold]" root_node.id child_node.id in + else Printf.sprintf "%d -> %d [style=bold]" root_node.id child_node.id + in let color = match root_node.content.eval_result with - | Eval_true -> "green" - | Eval_false -> "red" - | _ -> failwith "Tree is not fully evaluated" in + | Eval_true + -> "green" + | Eval_false + -> "red" + | _ + -> failwith "Tree is not fully evaluated" + in let label = let string_of_lcxt c = match c.CLintersContext.et_evaluation_node with - | Some s -> ("et_evaluation_node = "^s) - | _ -> "et_evaluation_node = NONE" in + | Some s + -> "et_evaluation_node = " ^ s + | _ + -> "et_evaluation_node = NONE" + in let string_of_ast_node an = match an with - | Stmt stmt -> Clang_ast_proj.get_stmt_kind_string stmt - | Decl decl -> Clang_ast_proj.get_decl_kind_string decl in + | Stmt stmt + -> Clang_ast_proj.get_stmt_kind_string stmt + | Decl decl + -> Clang_ast_proj.get_decl_kind_string decl + in let smart_string_of_formula phi = let num_children = List.length children in match phi with - | And _ when Int.equal num_children 2 -> "(...) AND (...)" - | Or _ when Int.equal num_children 2 -> "(...) OR (...)" - | Implies _ when Int.equal num_children 2 -> "(...) ==> (...)" - | Not _ -> "NOT(...)" - | _ -> Format.asprintf "%a" pp_formula phi in - Format.sprintf "(%d)\\n%s\\n%s\\n%s" - root_node.id + | And _ when Int.equal num_children 2 + -> "(...) AND (...)" + | Or _ when Int.equal num_children 2 + -> "(...) OR (...)" + | Implies _ when Int.equal num_children 2 + -> "(...) ==> (...)" + | Not _ + -> "NOT(...)" + | _ + -> Format.asprintf "%a" pp_formula phi + in + Format.sprintf "(%d)\\n%s\\n%s\\n%s" root_node.id (Escape.escape_dotty (string_of_ast_node root_node.content.ast_node)) (Escape.escape_dotty (string_of_lcxt root_node.content.lcxt)) - (Escape.escape_dotty (smart_string_of_formula root_node.content.phi)) in + (Escape.escape_dotty (smart_string_of_formula root_node.content.phi)) + in let edges = let buf = Buffer.create 16 in List.iter - ~f:(fun subtree -> Buffer.add_string buf ((edge (get_root subtree)) ^ "\n")) - children; - buffer_content buf in - Printf.sprintf "%d [label=\"%s\" shape=box color=%s]\n%s\n" - root_node.id label color edges in + ~f:(fun subtree -> Buffer.add_string buf (edge (get_root subtree) ^ "\n")) + children ; + buffer_content buf + in + Printf.sprintf "%d [label=\"%s\" shape=box color=%s]\n%s\n" root_node.id label color + edges + in let rec traverse buf tree = - Buffer.add_string buf (shallow_dotty_of_tree tree); - List.iter ~f:(traverse buf) (get_children tree) in + Buffer.add_string buf (shallow_dotty_of_tree tree) ; + List.iter ~f:(traverse buf) (get_children tree) + in let buf = Buffer.create 16 in - traverse buf tree; - Printf.sprintf "subgraph cluster_%d {\n%s\n}" cluster_id (buffer_content buf) in + traverse buf tree ; + Printf.sprintf "subgraph cluster_%d {\n%s\n}" cluster_id (buffer_content buf) + in let buf = Buffer.create 16 in List.iteri - ~f:(fun cluster_id tree -> Buffer.add_string buf ((dotty_of_tree cluster_id tree) ^ "\n")) - (List.rev t.forest); + ~f:(fun cluster_id tree -> Buffer.add_string buf (dotty_of_tree cluster_id tree ^ "\n")) + (List.rev t.forest) ; Printf.sprintf "digraph CTL_Evaluation {\n%s\n}\n" (buffer_content buf) end end end let print_checker c = - L.(debug Linters Medium) "@\n-------------------- @\n"; - L.(debug Linters Medium) "@\nChecker name: %s@\n" c.id; - List.iter ~f:(fun d -> (match d with - | CSet (keyword, phi) -> - let cn_str = ALVar.keyword_to_string keyword in - L.(debug Linters Medium) " %s= @\n %a@\n@\n" - cn_str Debug.pp_formula phi - | CLet (exp, _, phi) -> - let cn_str = ALVar.formula_id_to_string exp in - L.(debug Linters Medium) " %s= @\n %a@\n@\n" - cn_str Debug.pp_formula phi - | CDesc (keyword, s) -> - let cn_str = ALVar.keyword_to_string keyword in + L.(debug Linters Medium) "@\n-------------------- @\n" ; + L.(debug Linters Medium) "@\nChecker name: %s@\n" c.id ; + List.iter + ~f:(fun d -> + match d with + | CSet (keyword, phi) + -> let cn_str = ALVar.keyword_to_string keyword in + L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str Debug.pp_formula phi + | CLet (exp, _, phi) + -> let cn_str = ALVar.formula_id_to_string exp in + L.(debug Linters Medium) " %s= @\n %a@\n@\n" cn_str Debug.pp_formula phi + | CDesc (keyword, s) + -> let cn_str = ALVar.keyword_to_string keyword in L.(debug Linters Medium) " %s= @\n %s@\n@\n" cn_str s - | CPath (paths_keyword, paths) -> - let keyword = - (match paths_keyword with - | `WhitelistPath -> "whitelist_path" - | _ -> "blacklist_path") in + | CPath (paths_keyword, paths) + -> let keyword = + match paths_keyword with `WhitelistPath -> "whitelist_path" | _ -> "blacklist_path" + in let paths_str = String.concat ~sep:"," (List.map ~f:ALVar.alexp_to_string paths) in L.(debug Linters Medium) " %s= @\n %s@\n@\n" keyword paths_str) - ) c.definitions; + c.definitions ; L.(debug Linters Medium) "@\n-------------------- @\n" - let ctl_evaluation_tracker = ref None let create_ctl_evaluation_tracker source_file = - match Config.linters_developer_mode, !ctl_evaluation_tracker with - | true, None -> ctl_evaluation_tracker := Some (Debug.EvaluationTracker.create source_file) - | true, _ -> failwith "A CTL evaluation tracker has already been created" - | _ -> () + match (Config.linters_developer_mode, !ctl_evaluation_tracker) with + | true, None + -> ctl_evaluation_tracker := Some (Debug.EvaluationTracker.create source_file) + | true, _ + -> failwith "A CTL evaluation tracker has already been created" + | _ + -> () let debug_create_payload ast_node phi lcxt = match !ctl_evaluation_tracker with - | Some _ -> Some (Debug.EvaluationTracker.create_content ast_node phi lcxt) - | None -> None + | Some _ + -> Some (Debug.EvaluationTracker.create_content ast_node phi lcxt) + | None + -> None let debug_eval_begin payload = - match !ctl_evaluation_tracker, payload with - | Some tracker, Some payload -> - ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_begin tracker payload) - | _ -> () + match (!ctl_evaluation_tracker, payload) with + | Some tracker, Some payload + -> ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_begin tracker payload) + | _ + -> () let debug_eval_end result = match !ctl_evaluation_tracker with - | Some tracker -> - ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_end tracker result) - | None -> () + | Some tracker + -> ctl_evaluation_tracker := Some (Debug.EvaluationTracker.eval_end tracker result) + | None + -> () let save_dotty_when_in_debug_mode source_file = match !ctl_evaluation_tracker with - | Some tracker -> - let dotty_dir = Config.results_dir ^/ Config.lint_dotty_dir_name in - Utils.create_dir dotty_dir; + | Some tracker + -> let dotty_dir = Config.results_dir ^/ Config.lint_dotty_dir_name in + Utils.create_dir dotty_dir ; let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in - let file = dotty_dir ^/ (source_file_basename ^ ".dot") in + let file = dotty_dir ^/ source_file_basename ^ ".dot" in let dotty = Debug.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation tracker in Utils.with_file_out file ~f:(fun oc -> Out_channel.output_string oc dotty) - | _ -> () + | _ + -> () (* Helper functions *) @@ -502,32 +569,35 @@ let get_successor_nodes an = TBD: check if a dual is needed for get_stmt_of_decl *) let get_decl_of_stmt st = - match st with - | Clang_ast_t.BlockExpr (_, _, _, d) -> [Decl d] - | _ -> [] in + match st with Clang_ast_t.BlockExpr (_, _, _, d) -> [Decl d] | _ -> [] + in match an with - | Stmt st -> - let _, succs_st = Clang_ast_proj.get_stmt_tuple st in + | Stmt st + -> let _, succs_st = Clang_ast_proj.get_stmt_tuple st in let succs = List.map ~f:(fun s -> Stmt s) succs_st in - succs @ (get_decl_of_stmt st) + succs @ get_decl_of_stmt st | Decl dec -> - (match Clang_ast_proj.get_decl_context_tuple dec with - | Some (decl_list, _) -> List.map ~f:(fun d -> Decl d) decl_list - | None -> []) + match Clang_ast_proj.get_decl_context_tuple dec with + | Some (decl_list, _) + -> List.map ~f:(fun d -> Decl d) decl_list + | None + -> [] let node_to_string an = match an with - | Decl d -> Clang_ast_proj.get_decl_kind_string d - | Stmt s -> Clang_ast_proj.get_stmt_kind_string s + | Decl d + -> Clang_ast_proj.get_decl_kind_string d + | Stmt s + -> Clang_ast_proj.get_stmt_kind_string s let node_to_unique_string_id an = match an with - | Decl d -> - let di = Clang_ast_proj.get_decl_tuple d in - (Clang_ast_proj.get_decl_kind_string d) ^ (string_of_int di.Clang_ast_t.di_pointer) - | Stmt s -> - let si, _ = Clang_ast_proj.get_stmt_tuple s in - Clang_ast_proj.get_stmt_kind_string s ^ (string_of_int si.Clang_ast_t.si_pointer) + | Decl d + -> let di = Clang_ast_proj.get_decl_tuple d in + Clang_ast_proj.get_decl_kind_string d ^ string_of_int di.Clang_ast_t.di_pointer + | Stmt s + -> let si, _ = Clang_ast_proj.get_stmt_tuple s in + Clang_ast_proj.get_stmt_kind_string s ^ string_of_int si.Clang_ast_t.si_pointer (* true iff an ast node is a node of type among the list tl *) let node_has_type tl an = @@ -538,147 +608,190 @@ let node_has_type tl an = let transition_decl_to_stmt d trs = let open Clang_ast_t in let temp_res = - match trs, d with - | Body, ObjCMethodDecl (_, _, omdi) -> omdi.omdi_body + match (trs, d) with + | Body, ObjCMethodDecl (_, _, omdi) + -> omdi.omdi_body | Body, FunctionDecl (_, _, _, fdi) - | Body, CXXMethodDecl (_, _, _, fdi,_ ) + | Body, CXXMethodDecl (_, _, _, fdi, _) | Body, CXXConstructorDecl (_, _, _, fdi, _) | Body, CXXConversionDecl (_, _, _, fdi, _) - | Body, CXXDestructorDecl (_, _, _, fdi, _) -> fdi.fdi_body - | Body, BlockDecl (_, bdi) -> bdi.bdi_body - | InitExpr, VarDecl (_, _ ,_, vdi) -> vdi.vdi_init_expr + | Body, CXXDestructorDecl (_, _, _, fdi, _) + -> fdi.fdi_body + | Body, BlockDecl (_, bdi) + -> bdi.bdi_body + | InitExpr, VarDecl (_, _, _, vdi) + -> vdi.vdi_init_expr | InitExpr, ObjCIvarDecl (_, _, _, fldi, _) | InitExpr, FieldDecl (_, _, _, fldi) - | InitExpr, ObjCAtDefsFieldDecl (_, _, _, fldi)-> fldi.fldi_init_expr + | InitExpr, ObjCAtDefsFieldDecl (_, _, _, fldi) + -> fldi.fldi_init_expr | InitExpr, CXXMethodDecl _ | InitExpr, CXXConstructorDecl _ | InitExpr, CXXConversionDecl _ - | InitExpr, CXXDestructorDecl _ -> - assert false (* to be done. Requires extending to lists *) - | InitExpr, EnumConstantDecl (_, _, _, ecdi) -> ecdi.ecdi_init_expr - | _, _ -> None in - match temp_res with - | Some st -> [Stmt st] - | _ -> [] + | InitExpr, CXXDestructorDecl _ + -> assert false (* to be done. Requires extending to lists *) + | InitExpr, EnumConstantDecl (_, _, _, ecdi) + -> ecdi.ecdi_init_expr + | _, _ + -> None + in + match temp_res with Some st -> [Stmt st] | _ -> [] let transition_decl_to_decl_via_super d = - let decl_opt_to_ast_node_opt d_opt = - match d_opt with - | Some d' -> [Decl d'] - | None -> [] in + let decl_opt_to_ast_node_opt d_opt = match d_opt with Some d' -> [Decl d'] | None -> [] in let do_ObjCImplementationDecl d = match CAst_utils.get_impl_decl_info d with - | Some idi -> - decl_opt_to_ast_node_opt (CAst_utils.get_super_ObjCImplementationDecl idi) - | None -> [] in + | Some idi + -> decl_opt_to_ast_node_opt (CAst_utils.get_super_ObjCImplementationDecl idi) + | None + -> [] + in match d with - | Clang_ast_t.ObjCImplementationDecl _ -> - do_ObjCImplementationDecl d - | Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, idi) -> - decl_opt_to_ast_node_opt (CAst_utils.get_decl_opt_with_decl_ref idi.otdi_super) - | _ -> [] - + | Clang_ast_t.ObjCImplementationDecl _ + -> do_ObjCImplementationDecl d + | Clang_ast_t.ObjCInterfaceDecl (_, _, _, _, idi) + -> decl_opt_to_ast_node_opt (CAst_utils.get_decl_opt_with_decl_ref idi.otdi_super) + | _ + -> [] let transition_decl_to_decl_via_protocol d = let open Clang_ast_t in let get_nodes dr = - match CAst_utils.get_decl dr.dr_decl_pointer with - | Some d -> Some (Decl d) - | None -> None in + match CAst_utils.get_decl dr.dr_decl_pointer with Some d -> Some (Decl d) | None -> None + in match d with - | Clang_ast_t.ObjCProtocolDecl (_, _, _, _, opdi) -> - List.filter_map ~f:get_nodes opdi.opcdi_protocols - | _ -> [] + | Clang_ast_t.ObjCProtocolDecl (_, _, _, _, opdi) + -> List.filter_map ~f:get_nodes opdi.opcdi_protocols + | _ + -> [] let transition_stmt_to_stmt_via_condition st = let open Clang_ast_t in match st with | IfStmt (_, _ :: _ :: cond :: _) - | ConditionalOperator (_, cond:: _, _) + | ConditionalOperator (_, cond :: _, _) | ForStmt (_, [_; _; cond; _; _]) - | WhileStmt (_, [_; cond; _]) -> [Stmt cond] - | _ -> [] + | WhileStmt (_, [_; cond; _]) + -> [Stmt cond] + | _ + -> [] let transition_stmt_to_decl_via_pointer stmt = let open Clang_ast_t in match stmt with - | ObjCMessageExpr (_, _, _, obj_c_message_expr_info) -> - (match CAst_utils.get_decl_opt obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer with - | Some decl -> [Decl decl] - | None -> []) - | DeclRefExpr (_, _, _, decl_ref_expr_info) -> - (match CAst_utils.get_decl_opt_with_decl_ref decl_ref_expr_info.Clang_ast_t.drti_decl_ref with - | Some decl -> [Decl decl] - | None -> []) - | _ -> [] + | ObjCMessageExpr (_, _, _, obj_c_message_expr_info) -> ( + match CAst_utils.get_decl_opt obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer with + | Some decl + -> [Decl decl] + | None + -> [] ) + | DeclRefExpr (_, _, _, decl_ref_expr_info) -> ( + match CAst_utils.get_decl_opt_with_decl_ref decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some decl + -> [Decl decl] + | None + -> [] ) + | _ + -> [] let transition_decl_to_decl_via_parameters dec = let open Clang_ast_t in match dec with - | ObjCMethodDecl (_, _, omdi) -> - List.map ~f:(fun d -> Decl d) omdi.omdi_parameters - | _ -> [] + | ObjCMethodDecl (_, _, omdi) + -> List.map ~f:(fun d -> Decl d) omdi.omdi_parameters + | _ + -> [] (* given a node an returns a list of nodes an' such that an transition to an' via label trans *) let next_state_via_transition an trans = - match an, trans with - | Decl d, Super -> transition_decl_to_decl_via_super d - | Decl d, Parameters -> transition_decl_to_decl_via_parameters d - | Decl d, InitExpr - | Decl d, Body -> transition_decl_to_stmt d trans - | Decl d, Protocol -> transition_decl_to_decl_via_protocol d - | Stmt st, Cond -> transition_stmt_to_stmt_via_condition st - | Stmt st, PointerToDecl -> transition_stmt_to_decl_via_pointer st - | _, _ -> [] + match (an, trans) with + | Decl d, Super + -> transition_decl_to_decl_via_super d + | Decl d, Parameters + -> transition_decl_to_decl_via_parameters d + | Decl d, InitExpr | Decl d, Body + -> transition_decl_to_stmt d trans + | Decl d, Protocol + -> transition_decl_to_decl_via_protocol d + | Stmt st, Cond + -> transition_stmt_to_stmt_via_condition st + | Stmt st, PointerToDecl + -> transition_stmt_to_decl_via_pointer st + | _, _ + -> [] (* Evaluation of formulas *) - (* evaluate an atomic formula (i.e. a predicate) on a ast node an and a linter context lcxt. That is: an, lcxt |= pred_name(params) *) let rec eval_Atomic _pred_name args an lcxt = let pred_name = ALVar.formula_id_to_string _pred_name in - match pred_name, args, an with - | "call_class_method", [c; m], an -> CPredicates.call_class_method an c m - | "call_function", [m], an -> CPredicates.call_function an m - | "call_instance_method", [c; m], an -> CPredicates.call_instance_method an c m - | "call_method", [m], an -> CPredicates.call_method an m - | "captures_cxx_references", [], _ -> CPredicates.captures_cxx_references an - | "context_in_synchronized_block", [], _ -> CPredicates.context_in_synchronized_block lcxt - | "declaration_has_name", [decl_name], an -> CPredicates.declaration_has_name an decl_name - | "declaration_ref_name", [decl_name], an -> CPredicates.declaration_ref_name an decl_name - | "decl_unavailable_in_supported_ios_sdk", [], an -> - CPredicates.decl_unavailable_in_supported_ios_sdk lcxt an - | "has_cast_kind", [name], an -> CPredicates.has_cast_kind an name - | "has_type", [typ], an -> CPredicates.has_type an typ - | "isa", [classname], an -> CPredicates.isa an classname - | "is_assign_property", [], an -> CPredicates.is_assign_property an - | "is_binop_with_kind", [kind], an -> CPredicates.is_binop_with_kind an kind - | "is_class", [cname], an -> CPredicates.is_class an cname - | "is_const_var", [], an -> CPredicates.is_const_expr_var an - | "is_global_var", [], an -> CPredicates.is_syntactically_global_var an - | "is_ivar_atomic", [], an -> CPredicates.is_ivar_atomic an - | "is_method_property_accessor_of_ivar", [], an -> - CPredicates.is_method_property_accessor_of_ivar an lcxt - | "is_node", [nodename], an -> CPredicates.is_node an nodename - | "is_objc_constructor", [], _ -> CPredicates.is_objc_constructor lcxt - | "is_objc_dealloc", [], _ -> CPredicates.is_objc_dealloc lcxt - | "is_objc_extension", [], _ -> CPredicates.is_objc_extension lcxt - | "is_objc_interface_named", [name], an -> CPredicates.is_objc_interface_named an name - | "is_property_pointer_type", [], an -> CPredicates.is_property_pointer_type an - | "is_strong_property", [], an -> CPredicates.is_strong_property an - | "is_unop_with_kind", [kind], an -> CPredicates.is_unop_with_kind an kind - | "method_return_type", [typ], an -> CPredicates.method_return_type an typ - | "within_responds_to_selector_block", [], an -> - CPredicates.within_responds_to_selector_block lcxt an - | "objc_method_has_nth_parameter_of_type", [num; typ], an -> - CPredicates.objc_method_has_nth_parameter_of_type an num typ - | "using_namespace", [namespace], an -> - CPredicates.using_namespace an namespace - | "has_type_subprotocol_of", [protname], an -> - CPredicates.has_type_subprotocol_of an protname - | _ -> failwith - ("ERROR: Undefined Predicate or wrong set of arguments: '" - ^ pred_name ^ "'") + match (pred_name, args, an) with + | "call_class_method", [c; m], an + -> CPredicates.call_class_method an c m + | "call_function", [m], an + -> CPredicates.call_function an m + | "call_instance_method", [c; m], an + -> CPredicates.call_instance_method an c m + | "call_method", [m], an + -> CPredicates.call_method an m + | "captures_cxx_references", [], _ + -> CPredicates.captures_cxx_references an + | "context_in_synchronized_block", [], _ + -> CPredicates.context_in_synchronized_block lcxt + | "declaration_has_name", [decl_name], an + -> CPredicates.declaration_has_name an decl_name + | "declaration_ref_name", [decl_name], an + -> CPredicates.declaration_ref_name an decl_name + | "decl_unavailable_in_supported_ios_sdk", [], an + -> CPredicates.decl_unavailable_in_supported_ios_sdk lcxt an + | "has_cast_kind", [name], an + -> CPredicates.has_cast_kind an name + | "has_type", [typ], an + -> CPredicates.has_type an typ + | "isa", [classname], an + -> CPredicates.isa an classname + | "is_assign_property", [], an + -> CPredicates.is_assign_property an + | "is_binop_with_kind", [kind], an + -> CPredicates.is_binop_with_kind an kind + | "is_class", [cname], an + -> CPredicates.is_class an cname + | "is_const_var", [], an + -> CPredicates.is_const_expr_var an + | "is_global_var", [], an + -> CPredicates.is_syntactically_global_var an + | "is_ivar_atomic", [], an + -> CPredicates.is_ivar_atomic an + | "is_method_property_accessor_of_ivar", [], an + -> CPredicates.is_method_property_accessor_of_ivar an lcxt + | "is_node", [nodename], an + -> CPredicates.is_node an nodename + | "is_objc_constructor", [], _ + -> CPredicates.is_objc_constructor lcxt + | "is_objc_dealloc", [], _ + -> CPredicates.is_objc_dealloc lcxt + | "is_objc_extension", [], _ + -> CPredicates.is_objc_extension lcxt + | "is_objc_interface_named", [name], an + -> CPredicates.is_objc_interface_named an name + | "is_property_pointer_type", [], an + -> CPredicates.is_property_pointer_type an + | "is_strong_property", [], an + -> CPredicates.is_strong_property an + | "is_unop_with_kind", [kind], an + -> CPredicates.is_unop_with_kind an kind + | "method_return_type", [typ], an + -> CPredicates.method_return_type an typ + | "within_responds_to_selector_block", [], an + -> CPredicates.within_responds_to_selector_block lcxt an + | "objc_method_has_nth_parameter_of_type", [num; typ], an + -> CPredicates.objc_method_has_nth_parameter_of_type an num typ + | "using_namespace", [namespace], an + -> CPredicates.using_namespace an namespace + | "has_type_subprotocol_of", [protname], an + -> CPredicates.has_type_subprotocol_of an protname + | _ + -> failwith ("ERROR: Undefined Predicate or wrong set of arguments: '" ^ pred_name ^ "'") (* an, lcxt |= EF phi <=> an, lcxt |= phi or exists an' in Successors(st): an', lcxt |= EF phi @@ -688,13 +801,13 @@ let rec eval_Atomic _pred_name args an lcxt = such that (an', lcxt) satifies EF phi *) and eval_EF phi an lcxt trans = - match trans, an with - | Some _, _ -> - (* Using equivalence EF[->trans] phi = phi OR EX[->trans](EF[->trans] phi)*) + match (trans, an) with + | Some _, _ + -> (* Using equivalence EF[->trans] phi = phi OR EX[->trans](EF[->trans] phi)*) let phi' = Or (phi, EX (trans, EF (trans, phi))) in eval_formula phi' an lcxt - | None, _ -> - eval_formula phi an lcxt + | None, _ + -> eval_formula phi an lcxt || List.exists ~f:(fun an' -> eval_EF phi an' lcxt trans) (get_successor_nodes an) (* an, lcxt |= EX phi <=> exists an' in Successors(st): an', lcxt |= phi @@ -704,9 +817,9 @@ and eval_EF phi an lcxt trans = such that (an', lcxt) satifies phi *) and eval_EX phi an lcxt trans = - let succs = match trans with - | Some l -> next_state_via_transition an l - | None -> get_successor_nodes an in + let succs = + match trans with Some l -> next_state_via_transition an l | None -> get_successor_nodes an + in List.exists ~f:(fun an' -> eval_formula phi an' lcxt) succs (* an, lcxt |= E(phi1 U phi2) evaluated using the equivalence @@ -716,7 +829,7 @@ and eval_EX phi an lcxt trans = an,lcxt satifies the formula phi2 or (phi1 and EX(E(phi1 U phi2))) *) and eval_EU phi1 phi2 an lcxt trans = - let f = Or (phi2, And (phi1, EX (trans, (EU (trans, phi1, phi2))))) in + let f = Or (phi2, And (phi1, EX (trans, EU (trans, phi1, phi2)))) in eval_formula f an lcxt (* an |= A(phi1 U phi2) evaluated using the equivalence @@ -724,7 +837,7 @@ and eval_EU phi1 phi2 an lcxt trans = Same as EU but for the all path quantifier A *) -and eval_AU phi1 phi2 an lcxt trans = +and eval_AU phi1 phi2 an lcxt trans = let f = Or (phi2, And (phi1, AX (trans, AU (trans, phi1, phi2)))) in eval_formula f an lcxt @@ -734,13 +847,13 @@ and eval_AU phi1 phi2 an lcxt trans = and in_node node_type_list phi an lctx = let holds_for_one_node n = match lctx.CLintersContext.et_evaluation_node with - | Some id -> - (String.equal id (node_to_unique_string_id an)) && (eval_formula phi an lctx) - | None -> - (node_has_type [n] an) && (eval_formula phi an lctx) in + | Some id + -> String.equal id (node_to_unique_string_id an) && eval_formula phi an lctx + | None + -> node_has_type [n] an && eval_formula phi an lctx + in List.exists ~f:holds_for_one_node node_type_list - (* Intuitive meaning: (an,lcxt) satifies EH[Classes] phi if the node an is among the declaration specified by the list Classes and there exists a super class in its hierarchy whose declaration satisfy phi. @@ -764,47 +877,77 @@ and eval_EH classes phi an lcxt = or l is unspecified and an,lcxt |= phi *) and eval_ET tl trs phi an lcxt = - let f = match trs with - | Some _ -> EF (None, (InNode (tl, EX (trs, phi)))) - | None -> EF (None, (InNode (tl, phi))) in + let f = + match trs with + | Some _ + -> EF (None, InNode (tl, EX (trs, phi))) + | None + -> EF (None, InNode (tl, phi)) + in eval_formula f an lcxt and eval_ETX tl trs phi an lcxt = - let lcxt', tl' = match lcxt.CLintersContext.et_evaluation_node, node_has_type tl an with - | None, true -> - let an_alexp = ALVar.Const (node_to_string an) in - {lcxt with CLintersContext.et_evaluation_node = Some (node_to_unique_string_id an) }, [an_alexp] - | _, _ -> lcxt, tl in - let f = match trs with - | Some _ -> EF (None, (InNode (tl', EX (trs, phi)))) - | None -> EF (None, (InNode (tl', phi))) in + let lcxt', tl' = + match (lcxt.CLintersContext.et_evaluation_node, node_has_type tl an) with + | None, true + -> let an_alexp = ALVar.Const (node_to_string an) in + ( {lcxt with CLintersContext.et_evaluation_node= Some (node_to_unique_string_id an)} + , [an_alexp] ) + | _, _ + -> (lcxt, tl) + in + let f = + match trs with + | Some _ + -> EF (None, InNode (tl', EX (trs, phi))) + | None + -> EF (None, InNode (tl', phi)) + in eval_formula f an lcxt' (* Formulas are evaluated on a AST node an and a linter context lcxt *) and eval_formula f an lcxt = - debug_eval_begin (debug_create_payload an f lcxt); - let res = match f with - | True -> true - | False -> false - | Atomic (name, params) -> eval_Atomic name params an lcxt - | Not f1 -> not (eval_formula f1 an lcxt) - | And (f1, f2) -> (eval_formula f1 an lcxt) && (eval_formula f2 an lcxt) - | Or (f1, f2) -> (eval_formula f1 an lcxt) || (eval_formula f2 an lcxt) - | Implies (f1, f2) -> - not (eval_formula f1 an lcxt) || (eval_formula f2 an lcxt) - | InNode (node_type_list, f1) -> - in_node node_type_list f1 an lcxt - | AU (trans, f1, f2) -> eval_AU f1 f2 an lcxt trans - | EU (trans, f1, f2) -> eval_EU f1 f2 an lcxt trans - | EF (trans, f1) -> eval_EF f1 an lcxt trans - | AF (trans, f1) -> eval_formula (AU (trans, True, f1)) an lcxt - | AG (trans, f1) -> eval_formula (Not (EF (trans, (Not f1)))) an lcxt - | EX (trans, f1) -> eval_EX f1 an lcxt trans - | AX (trans, f1) -> eval_formula (Not (EX (trans, (Not f1)))) an lcxt - | EH (cl, phi) -> eval_EH cl phi an lcxt - | EG (trans, f1) -> (* st |= EG f1 <=> st |= f1 /\ EX EG f1 *) - eval_formula (And (f1, EX (trans, (EG (trans, f1))))) an lcxt - | ET (tl, sw, phi) -> eval_ET tl sw phi an lcxt - | ETX (tl, sw, phi) -> eval_ETX tl sw phi an lcxt in - debug_eval_end res; - res + debug_eval_begin (debug_create_payload an f lcxt) ; + let res = + match f with + | True + -> true + | False + -> false + | Atomic (name, params) + -> eval_Atomic name params an lcxt + | Not f1 + -> not (eval_formula f1 an lcxt) + | And (f1, f2) + -> eval_formula f1 an lcxt && eval_formula f2 an lcxt + | Or (f1, f2) + -> eval_formula f1 an lcxt || eval_formula f2 an lcxt + | Implies (f1, f2) + -> not (eval_formula f1 an lcxt) || eval_formula f2 an lcxt + | InNode (node_type_list, f1) + -> in_node node_type_list f1 an lcxt + | AU (trans, f1, f2) + -> eval_AU f1 f2 an lcxt trans + | EU (trans, f1, f2) + -> eval_EU f1 f2 an lcxt trans + | EF (trans, f1) + -> eval_EF f1 an lcxt trans + | AF (trans, f1) + -> eval_formula (AU (trans, True, f1)) an lcxt + | AG (trans, f1) + -> eval_formula (Not (EF (trans, Not f1))) an lcxt + | EX (trans, f1) + -> eval_EX f1 an lcxt trans + | AX (trans, f1) + -> eval_formula (Not (EX (trans, Not f1))) an lcxt + | EH (cl, phi) + -> eval_EH cl phi an lcxt + | EG (trans, f1) + -> (* st |= EG f1 <=> st |= f1 /\ EX EG f1 *) + eval_formula (And (f1, EX (trans, EG (trans, f1)))) an lcxt + | ET (tl, sw, phi) + -> eval_ET tl sw phi an lcxt + | ETX (tl, sw, phi) + -> eval_ETX tl sw phi an lcxt + in + debug_eval_end res ; res diff --git a/infer/src/clang/cTL.mli b/infer/src/clang/cTL.mli index 5906dd411..22cf6b2d5 100644 --- a/infer/src/clang/cTL.mli +++ b/infer/src/clang/cTL.mli @@ -14,16 +14,21 @@ open Ctl_parser_types are intepreted over the AST of the program. A checker is defined by a CTL formula which express a condition saying when the checker should report a problem *) - (* Transition labels used for example to switch from decl to stmt *) + type transitions = - | Body (* decl to stmt *) - | InitExpr (* decl to stmt *) - | Super (* decl to decl *) - | Parameters (* decl to decl *) + | Body + (* decl to stmt *) + | InitExpr + (* decl to stmt *) + | Super + (* decl to decl *) + | Parameters + (* decl to decl *) | Cond - | PointerToDecl (* stmt to decl *) - | Protocol (** decl to decl *) + | PointerToDecl + (* stmt to decl *) + | Protocol (** decl to decl *) (* In formulas below prefix "E" means "exists a path" @@ -33,30 +38,39 @@ type transitions = type t = | True | False - | Atomic of CPredicates.t (** Atomic formula *) + | Atomic of CPredicates.t (** Atomic formula *) | Not of t | And of t * t | Or of t * t | Implies of t * t | InNode of ALVar.alexp list * t - | AX of transitions option * t (** AX phi <=> for all children of the current node phi holds *) - | EX of transitions option * t (** EX phi <=> exist a child of the current node such that phi holds *) - | AF of transitions option * t (** AF phi <=> for all path from the current node there is a descendant where phi holds *) - | EF of transitions option * t (** EF phi <=> there exits a a path from the current node with a descendant where phi hold *) - | AG of transitions option * t (** AG phi <=> for all discendant of the current node phi hold *) - | EG of transitions option * t (** EG phi <=> + | AX of transitions option * t (** AX phi <=> for all children of the current node phi holds *) + | EX of transitions option * t + (** EX phi <=> exist a child of the current node such that phi holds *) + | AF of transitions option * t + (** AF phi <=> for all path from the current node there is a descendant where phi holds *) + | EF of transitions option * t + (** EF phi <=> there exits a a path from the current node with a descendant where phi hold *) + | AG of transitions option * t (** AG phi <=> for all discendant of the current node phi hold *) + | EG of transitions option * t + (** EG phi <=> there exists a path (of descendants) from the current node where phi hold at each node of the path *) - | AU of transitions option * t * t (** AU(phi1, phi2) <=> + | AU of transitions option * t * t + (** AU(phi1, phi2) <=> for all paths from the current node phi1 holds in every node until ph2 holds *) - | EU of transitions option * t * t (** EU(phi1, phi2) <=> + | EU of transitions option * t * t + (** EU(phi1, phi2) <=> there exists a path from the current node such that phi1 holds until phi2 holds *) - | EH of ALVar.alexp list * t (** EH[classes]phi <=> + | EH of ALVar.alexp list * t + (** EH[classes]phi <=> there exists a node defining a super class in the hierarchy of the class defined by the current node (if any) where phi holds *) - | ET of ALVar.alexp list * transitions option * t (** ET[T][l] phi <=> + | ET of ALVar.alexp list * transitions option * t + (** ET[T][l] phi <=> there exists a descentant an of the current node such that an is of type in set T making a transition to a node an' via label l, such that in an phi holds. *) - | ETX of ALVar.alexp list * transitions option * t (** ET[T][l] phi <=> + | ETX of ALVar.alexp list * transitions option * t + (** ET[T][l] phi <=> there exists a descentant an of the current node such that an is of type in set T making a transition to a node an' via label l, such that in an phi holds. *) @@ -78,22 +92,23 @@ type t = *) type clause = - | CLet of ALVar.formula_id * ALVar.t list * t (* Let clause: let id = definifion; *) - | CSet of ALVar.keyword * t (* Set clause: set id = definition *) - | CDesc of ALVar.keyword * string (* Description clause eg: set message = "..." *) - | CPath of [ `WhitelistPath | `BlacklistPath ] * ALVar.t list - -type ctl_checker = { - id : string; (* Checker's id *) - definitions : clause list (* A list of let/set definitions *) -} - -type al_file = { - import_files : string list; - global_macros : clause list; - global_paths : (string * ALVar.alexp list) list; - checkers : ctl_checker list -} + | CLet of ALVar.formula_id * ALVar.t list * t + (* Let clause: let id = definifion; *) + | CSet of ALVar.keyword * t + (* Set clause: set id = definition *) + | CDesc of ALVar.keyword * string + (* Description clause eg: set message = "..." *) + | CPath of [`WhitelistPath | `BlacklistPath] * ALVar.t list + +type ctl_checker = + {id: string; (* Checker's id *) + definitions: clause list (* A list of let/set definitions *)} + +type al_file = + { import_files: string list + ; global_macros: clause list + ; global_paths: (string * ALVar.alexp list) list + ; checkers: ctl_checker list } val print_checker : ctl_checker -> unit diff --git a/infer/src/clang/cTrans.ml b/infer/src/clang/cTrans.ml index f7ce4db02..101223c43 100644 --- a/infer/src/clang/cTrans.ml +++ b/infer/src/clang/cTrans.ml @@ -14,11 +14,9 @@ open! PVariant open CTrans_utils open CTrans_utils.Nodes - module L = Logging -module CTrans_funct(F: CModule_type.CFrontend) : CModule_type.CTranslation = -struct +module CTrans_funct (F : CModule_type.CFrontend) : CModule_type.CTranslation = struct (* Returns the procname and whether is instance, according to the selector information and according to the method signature with the following priority: 1. method is a predefined model @@ -26,75 +24,90 @@ struct 3. Method is found by our resolution *) let get_callee_objc_method context obj_c_message_expr_info act_params = let open CContext in - let (selector, method_pointer_opt, mc_type) = - CMethod_trans.get_objc_method_data obj_c_message_expr_info in + let selector, method_pointer_opt, mc_type = + CMethod_trans.get_objc_method_data obj_c_message_expr_info + in let is_instance = mc_type <> CMethod_trans.MCStatic in let method_kind = Typ.Procname.objc_method_kind_of_bool is_instance in let ms_opt = match method_pointer_opt with - | Some pointer -> - CMethod_trans.method_signature_of_pointer context.translation_unit_context context.tenv + | Some pointer + -> CMethod_trans.method_signature_of_pointer context.translation_unit_context context.tenv pointer - | None -> None in + | None + -> None + in let proc_name = match CMethod_trans.get_method_name_from_clang context.tenv ms_opt with - | Some ms -> - CMethod_signature.ms_get_name ms - | None -> (* fall back to our method resolution if clang's fails *) - let class_name = CMethod_trans.get_class_name_method_call_from_receiver_kind context - obj_c_message_expr_info act_params in - CProcname.NoAstDecl.objc_method_of_string_kind class_name selector method_kind in - let predefined_ms_opt = match proc_name with - | Typ.Procname.ObjC_Cpp objc_cpp -> - let class_name = Typ.Procname.objc_cpp_get_class_type_name objc_cpp in + | Some ms + -> CMethod_signature.ms_get_name ms + | None + -> (* fall back to our method resolution if clang's fails *) + let class_name = + CMethod_trans.get_class_name_method_call_from_receiver_kind context + obj_c_message_expr_info act_params + in + CProcname.NoAstDecl.objc_method_of_string_kind class_name selector method_kind + in + let predefined_ms_opt = + match proc_name with + | Typ.Procname.ObjC_Cpp objc_cpp + -> let class_name = Typ.Procname.objc_cpp_get_class_type_name objc_cpp in CTrans_models.get_predefined_model_method_signature class_name selector CProcname.NoAstDecl.objc_method_of_string_kind CFrontend_config.ObjC - | _ -> - None in - match predefined_ms_opt, ms_opt with - | Some ms, _ -> - ignore (CMethod_trans.create_local_procdesc context.translation_unit_context context.cfg - context.tenv ms [] [] is_instance); - CMethod_signature.ms_get_name ms, CMethod_trans.MCNoVirtual - | None, Some ms -> - ignore (CMethod_trans.create_local_procdesc context.translation_unit_context context.cfg - context.tenv ms [] [] is_instance); + | _ + -> None + in + match (predefined_ms_opt, ms_opt) with + | Some ms, _ + -> ignore + (CMethod_trans.create_local_procdesc context.translation_unit_context context.cfg + context.tenv ms [] [] is_instance) ; + (CMethod_signature.ms_get_name ms, CMethod_trans.MCNoVirtual) + | None, Some ms + -> ignore + (CMethod_trans.create_local_procdesc context.translation_unit_context context.cfg + context.tenv ms [] [] is_instance) ; if CMethod_signature.ms_is_getter ms || CMethod_signature.ms_is_setter ms then - proc_name, CMethod_trans.MCNoVirtual - else - proc_name, mc_type - | _ -> - CMethod_trans.create_external_procdesc context.cfg proc_name is_instance None; - proc_name, mc_type - + (proc_name, CMethod_trans.MCNoVirtual) + else (proc_name, mc_type) + | _ + -> CMethod_trans.create_external_procdesc context.cfg proc_name is_instance None ; + (proc_name, mc_type) let add_autorelease_call context exp typ sil_loc = let method_name = Typ.Procname.get_method (Procdesc.get_proc_name context.CContext.procdesc) in - if !Config.arc_mode && - not (CTrans_utils.is_owning_name method_name) && - ObjcInterface_decl.is_pointer_to_objc_class typ then + if !Config.arc_mode && not (CTrans_utils.is_owning_name method_name) + && ObjcInterface_decl.is_pointer_to_objc_class typ + then let fname = BuiltinDecl.__set_autorelease_attribute in let ret_id = Some (Ident.create_fresh Ident.knormal, Typ.mk Typ.Tvoid) in (* TODO(jjb): change ret_id to None? *) let stmt_call = - Sil.Call (ret_id, Exp.Const (Const.Cfun fname), [(exp, typ)], sil_loc, CallFlags.default) in + Sil.Call (ret_id, Exp.Const (Const.Cfun fname), [(exp, typ)], sil_loc, CallFlags.default) + in [stmt_call] else [] let rec is_block_expr s = let open Clang_ast_t in match s with - | BlockExpr _ -> true + | BlockExpr _ + -> true (* the block can be wrapped in ExprWithCleanups or ImplicitCastExpr*) - | ImplicitCastExpr(_, [s'], _, _) - | ExprWithCleanups(_, [s'], _, _) -> is_block_expr s' - | _ -> false + | ImplicitCastExpr (_, [s'], _, _) + | ExprWithCleanups (_, [s'], _, _) + -> is_block_expr s' + | _ + -> false let objc_exp_of_type_block fun_exp_stmt = match fun_exp_stmt with - | Clang_ast_t.ImplicitCastExpr(_, _, ei, _) - when CType.is_block_type ei.Clang_ast_t.ei_qual_type -> true - | _ -> false + | Clang_ast_t.ImplicitCastExpr (_, _, ei, _) + when CType.is_block_type ei.Clang_ast_t.ei_qual_type + -> true + | _ + -> false (* This function add in tenv a class representing an objc block. *) (* An object of this class has type:*) @@ -111,60 +124,69 @@ struct let tname = Typ.Name.C.from_string block_name in let fname = CGeneral_utils.mk_class_field_name tname (Mangled.to_string vname) in let item_annot = Annot.Item.empty in - fname, typ, item_annot in + (fname, typ, item_annot) + in let fields = List.map ~f:mk_field_from_captured_var captured_vars in - L.(debug Capture Verbose) "Block %s field:@\n" block_name; - List.iter ~f:(fun (fn, _, _) -> - L.(debug Capture Verbose) "-----> field: '%s'@\n" (Typ.Fieldname.to_string fn)) fields; + L.(debug Capture Verbose) "Block %s field:@\n" block_name ; + List.iter + ~f:(fun (fn, _, _) -> + L.(debug Capture Verbose) "-----> field: '%s'@\n" (Typ.Fieldname.to_string fn)) + fields ; let block_typename = Typ.Name.Objc.from_string block_name in - ignore (Tenv.mk_struct tenv ~fields block_typename); + ignore (Tenv.mk_struct tenv ~fields block_typename) ; let block_type = Typ.mk (Typ.Tstruct block_typename) in let trans_res = - CTrans_utils.alloc_trans - trans_state loc (Ast_expressions.dummy_stmt_info ()) block_type true None in - let id_block = match trans_res.exps with - | [(Exp.Var id, _)] -> id - | _ -> assert false in + CTrans_utils.alloc_trans trans_state loc (Ast_expressions.dummy_stmt_info ()) block_type true + None + in + let id_block = match trans_res.exps with [(Exp.Var id, _)] -> id | _ -> assert false in let mblock = Mangled.from_string block_name in let block_var = Pvar.mk mblock procname in let declare_block_local = - Sil.Declare_locals ([(block_var, CType.add_pointer_to_typ block_type)], loc) in + Sil.Declare_locals ([(block_var, CType.add_pointer_to_typ block_type)], loc) + in let set_instr = Sil.Store (Exp.Lvar block_var, block_type, Exp.Var id_block, loc) in let create_field_exp (var, typ) = let id = Ident.create_fresh Ident.knormal in - id, Sil.Load (id, Exp.Lvar var, typ, loc) in + (id, Sil.Load (id, Exp.Lvar var, typ, loc)) + in let ids, captured_instrs = List.unzip (List.map ~f:create_field_exp captured_vars) in let fields_ids = List.zip_exn fields ids in - let set_fields = List.map ~f:(fun ((f, t, _), id) -> - Sil.Store (Exp.Lfield (Exp.Var id_block, f, block_type), t, Exp.Var id, loc)) fields_ids in - (declare_block_local :: trans_res.instrs) @ - [set_instr] @ - captured_instrs @ - set_fields + let set_fields = + List.map + ~f:(fun ((f, t, _), id) -> + Sil.Store (Exp.Lfield (Exp.Var id_block, f, block_type), t, Exp.Var id, loc)) + fields_ids + in + declare_block_local :: trans_res.instrs @ [set_instr] @ captured_instrs @ set_fields (* From a list of expression extract blocks from tuples and *) (* returns block names and assignment to temp vars *) let extract_block_from_tuple procname exps loc = let insts = ref [] in let make_function_name typ bn = - let bn'= Typ.Procname.to_string bn in - let bn''= Mangled.from_string bn' in + let bn' = Typ.Procname.to_string bn in + let bn'' = Mangled.from_string bn' in let block = Exp.Lvar (Pvar.mk bn'' procname) in let id = Ident.create_fresh Ident.knormal in - insts := Sil.Load (id, block, typ, loc) :: !insts; - (Exp.Var id, typ) in + insts := Sil.Load (id, block, typ, loc) :: !insts ; + (Exp.Var id, typ) + in let make_arg typ (id, _, _) = (id, typ) in let rec f es = match es with - | [] -> [] - | (Exp.Closure {name; captured_vars}, - ({Typ.desc=Tptr({Typ.desc=Tfun _}, _ )} as t)) :: es' -> - let app = + | [] + -> [] + | (Exp.Closure {name; captured_vars}, ({Typ.desc= Tptr ({Typ.desc= Tfun _}, _)} as t)) :: es' + -> let app = let function_name = make_function_name t name in let args = List.map ~f:(make_arg t) captured_vars in - function_name :: args in - app @ (f es') - | e :: es' -> e :: f es' in + function_name :: args + in + app @ f es' + | e :: es' + -> e :: f es' + in (f exps, !insts) let collect_exprs res_trans_list = @@ -179,24 +201,24 @@ struct (* At the end of block translation, we need to get the proirity back.*) (* the parameter f will be called with function instruction *) let exec_with_block_priority_exception f trans_state e stmt_info = - if (is_block_expr e) && (PriorityNode.own_priority_node trans_state.priority stmt_info) then ( - L.(debug Capture Verbose) "Translating block expression by freeing the priority"; - f { trans_state with priority = Free } e) + if is_block_expr e && PriorityNode.own_priority_node trans_state.priority stmt_info then ( + L.(debug Capture Verbose) "Translating block expression by freeing the priority" ; + f {trans_state with priority= Free} e ) else f trans_state e let exec_with_node_creation f trans_state stmt = let res_trans = f trans_state stmt in if res_trans.instrs <> [] then let stmt_info, _ = Clang_ast_proj.get_stmt_tuple stmt in - let stmt_info' = { stmt_info with - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer () } in + let stmt_info' = {stmt_info with Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer ()} in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info' in let sil_loc = CLocation.get_sil_location stmt_info' trans_state.context in - let to_parent = PriorityNode.compute_results_to_parent - trans_state_pri sil_loc "Fallback node" stmt_info' [res_trans] in - { to_parent with exps = res_trans.exps } - else - res_trans + let to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "Fallback node" stmt_info' + [res_trans] + in + {to_parent with exps= res_trans.exps} + else res_trans (* This is the standard way of dealing with self:Class or a call [a class]. We translate it as sizeof() The only time when we want to translate those expressions differently is @@ -204,40 +226,44 @@ struct expressions, but we take the type and create a static method call from it. This is done in objcMessageExpr_trans. *) let exec_with_self_exception f trans_state stmt = - try - f trans_state stmt + try f trans_state stmt with Self.SelfClassException class_name -> let typ = Typ.mk (Tstruct class_name) in { empty_res_trans with - exps = [Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact}, - Typ.mk (Tint IULong)] } + exps= + [ ( Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + , Typ.mk (Tint IULong) ) ] } - let add_reference_if_glvalue (typ : Typ.t) expr_info = + let add_reference_if_glvalue (typ: Typ.t) expr_info = (* glvalue definition per C++11:*) (* http://en.cppreference.com/w/cpp/language/value_category *) - let is_glvalue = match expr_info.Clang_ast_t.ei_value_kind with - | `LValue | `XValue -> true - | `RValue -> false in - match is_glvalue, typ.desc with - | true, Tptr (_, Pk_reference) -> - (* reference of reference is not allowed in C++ - it's most likely frontend *) + let is_glvalue = + match expr_info.Clang_ast_t.ei_value_kind with `LValue | `XValue -> true | `RValue -> false + in + match (is_glvalue, typ.desc) with + | true, Tptr (_, Pk_reference) + -> (* reference of reference is not allowed in C++ - it's most likely frontend *) (* trying to add same reference to same type twice*) (* this is hacky and should be fixed (t9838691) *) typ - | true, _ -> Typ.mk (Tptr (typ, Pk_reference)) - | _ -> typ + | true, _ + -> Typ.mk (Tptr (typ, Pk_reference)) + | _ + -> typ (** Execute translation and then possibly adjust the type of the result of translation: In C++, when expression returns reference to type T, it will be lvalue to T, not T&, but infer needs it to be T& *) let exec_with_glvalue_as_reference f trans_state stmt = - let expr_info = match Clang_ast_proj.get_expr_tuple stmt with - | Some (_, _, ei) -> ei - | None -> assert false in + let expr_info = + match Clang_ast_proj.get_expr_tuple stmt with Some (_, _, ei) -> ei | None -> assert false + in let res_trans = f trans_state stmt in - let (exp, typ) = extract_exp_from_list res_trans.exps - "[Warning] Need exactly one expression to add reference type@\n" in - { res_trans with exps = [(exp, add_reference_if_glvalue typ expr_info)] } + let exp, typ = + extract_exp_from_list res_trans.exps + "[Warning] Need exactly one expression to add reference type@\n" + in + {res_trans with exps= [(exp, add_reference_if_glvalue typ expr_info)]} (* Execute translation of e forcing to release priority (if it's not free) and then setting it back.*) @@ -245,16 +271,15 @@ struct the priority to be free for the *) (* computation of the expressions*) let exec_with_priority_exception trans_state e f = - if PriorityNode.is_priority_free trans_state then - f trans_state e - else f { trans_state with priority = Free } e + if PriorityNode.is_priority_free trans_state then f trans_state e + else f {trans_state with priority= Free} e let call_translation context decl = let open CContext in (* translation will reset Ident counter, save it's state and restore it afterwards *) let ident_state = Ident.NameGenerator.get_current () in - F.translate_one_declaration context.translation_unit_context context.tenv context.cg context.cfg - `Translation decl; + F.translate_one_declaration context.translation_unit_context context.tenv context.cg + context.cfg `Translation decl ; Ident.NameGenerator.set_current ident_state let mk_temp_sil_var procdesc var_name_suffix = @@ -269,26 +294,30 @@ struct let create_var_exp_tmp_var trans_state expr_info var_name = let context = trans_state.context in let procdesc = context.CContext.procdesc in - let (pvar, typ) = mk_temp_sil_var_for_expr context.CContext.tenv procdesc - var_name expr_info in - Procdesc.append_locals procdesc [(Pvar.get_name pvar, typ)]; - Exp.Lvar pvar, typ - - let create_call_instr trans_state (return_type : Typ.t) function_sil params_sil sil_loc - call_flags ~is_objc_method = - let ret_id = if (Typ.equal_desc return_type.desc Typ.Tvoid) then None - else Some (Ident.create_fresh Ident.knormal, return_type) in + let pvar, typ = mk_temp_sil_var_for_expr context.CContext.tenv procdesc var_name expr_info in + Procdesc.append_locals procdesc [(Pvar.get_name pvar, typ)] ; + (Exp.Lvar pvar, typ) + + let create_call_instr trans_state (return_type: Typ.t) function_sil params_sil sil_loc call_flags + ~is_objc_method = + let ret_id = + if Typ.equal_desc return_type.desc Typ.Tvoid then None + else Some (Ident.create_fresh Ident.knormal, return_type) + in let ret_id', params, initd_exps, ret_exps = (* Assumption: should_add_return_param will return true only for struct types *) if CMethod_trans.should_add_return_param return_type ~is_objc_method then let param_type = Typ.mk (Typ.Tptr (return_type, Typ.Pk_pointer)) in - let var_exp = match trans_state.var_exp_typ with - | Some (exp, _) -> exp - | _ -> - let procdesc = trans_state.context.CContext.procdesc in + let var_exp = + match trans_state.var_exp_typ with + | Some (exp, _) + -> exp + | _ + -> let procdesc = trans_state.context.CContext.procdesc in let pvar = mk_temp_sil_var procdesc "__temp_return_" in - Procdesc.append_locals procdesc [(Pvar.get_name pvar, return_type)]; - Exp.Lvar pvar in + Procdesc.append_locals procdesc [(Pvar.get_name pvar, return_type)] ; + Exp.Lvar pvar + in (* It is very confusing - same expression has two different types in two contexts:*) (* 1. if passed as parameter it's RETURN_TYPE* since we are passing it as rvalue *) (* 2. for return expression it's RETURN_TYPE since backend allows to treat it as lvalue*) @@ -305,28 +334,31 @@ struct (* value doesn't work good anyway. This may need to be revisited later*) let ret_param = (var_exp, param_type) in let ret_exp = (var_exp, return_type) in - None, params_sil @ [ret_param], [var_exp], [ret_exp] - else ret_id, params_sil, [], match ret_id with Some (i,t) -> [(Exp.Var i, t)] | None -> [] in + (None, params_sil @ [ret_param], [var_exp], [ret_exp]) + else + (ret_id, params_sil, [], match ret_id with Some (i, t) -> [(Exp.Var i, t)] | None -> []) + in let call_instr = Sil.Call (ret_id', function_sil, params, sil_loc, call_flags) in - { empty_res_trans with - instrs = [call_instr]; - exps = ret_exps; - initd_exps = initd_exps;} + {empty_res_trans with instrs= [call_instr]; exps= ret_exps; initd_exps} let breakStmt_trans trans_state = match trans_state.continuation with - | Some bn -> { empty_res_trans with root_nodes = bn.break } - | _ -> assert false + | Some bn + -> {empty_res_trans with root_nodes= bn.break} + | _ + -> assert false let continueStmt_trans trans_state = match trans_state.continuation with - | Some bn -> { empty_res_trans with root_nodes = bn.continue } - | _ -> assert false + | Some bn + -> {empty_res_trans with root_nodes= bn.continue} + | _ + -> assert false let stringLiteral_trans trans_state expr_info str = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in - let exp = Exp.Const (Const.Cstr (str)) in - { empty_res_trans with exps = [(exp, typ)]} + let exp = Exp.Const (Const.Cstr str) in + {empty_res_trans with exps= [(exp, typ)]} (* FROM CLANG DOCS: "Implements the GNU __null extension, which is a name for a null pointer constant *) @@ -338,12 +370,12 @@ struct So we implement it as the constant zero *) let gNUNullExpr_trans trans_state expr_info = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in - let exp = Exp.Const (Const.Cint (IntLit.zero)) in - { empty_res_trans with exps = [(exp, typ)]} + let exp = Exp.Const (Const.Cint IntLit.zero) in + {empty_res_trans with exps= [(exp, typ)]} let nullPtrExpr_trans trans_state expr_info = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in - { empty_res_trans with exps = [(Exp.null, typ)]} + {empty_res_trans with exps= [(Exp.null, typ)]} let objCSelectorExpr_trans trans_state expr_info selector = stringLiteral_trans trans_state expr_info selector @@ -353,15 +385,15 @@ struct stringLiteral_trans trans_state expr_info type_raw let objCProtocolExpr_trans trans_state expr_info decl_ref = - let name = (match decl_ref.Clang_ast_t.dr_name with - | Some s -> s.Clang_ast_t.ni_name - | _ -> "") in + let name = + match decl_ref.Clang_ast_t.dr_name with Some s -> s.Clang_ast_t.ni_name | _ -> "" + in stringLiteral_trans trans_state expr_info name let characterLiteral_trans trans_state expr_info n = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in let exp = Exp.Const (Const.Cint (IntLit.of_int n)) in - { empty_res_trans with exps = [(exp, typ)]} + {empty_res_trans with exps= [(exp, typ)]} let booleanValue_trans trans_state expr_info b = characterLiteral_trans trans_state expr_info (if b then 1 else 0) @@ -369,7 +401,7 @@ struct let floatingLiteral_trans trans_state expr_info float_string = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in let exp = Exp.Const (Const.Cfloat (float_of_string float_string)) in - { empty_res_trans with exps = [(exp, typ)]} + {empty_res_trans with exps= [(exp, typ)]} (* Note currently we don't have support for different qual *) (* type like long, unsigned long, etc *) @@ -380,115 +412,126 @@ struct let i = Int64.of_string integer_literal_info.Clang_ast_t.ili_value in let exp = Exp.int (IntLit.of_int64 i) in exp - with - | Failure _ -> - (* Parse error: return a nondeterministic value *) - let id = Ident.create_fresh Ident.knormal in - Exp.Var id in - { empty_res_trans with - exps = [(exp, typ)]; - } + with Failure _ -> + (* Parse error: return a nondeterministic value *) + let id = Ident.create_fresh Ident.knormal in + Exp.Var id + in + {empty_res_trans with exps= [(exp, typ)]} let cxxScalarValueInitExpr_trans trans_state expr_info = let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in (* constant will be different depending on type *) - let zero_opt = match typ.desc with - | Typ.Tfloat _ | Typ.Tptr _ | Typ.Tint _ -> Some (Sil.zero_value_of_numerical_type typ) - | Typ.Tvoid -> None - | _ -> Some (Exp.Const (Const.Cint IntLit.zero)) in + let zero_opt = + match typ.desc with + | Typ.Tfloat _ | Typ.Tptr _ | Typ.Tint _ + -> Some (Sil.zero_value_of_numerical_type typ) + | Typ.Tvoid + -> None + | _ + -> Some (Exp.Const (Const.Cint IntLit.zero)) + in match zero_opt with - | Some zero -> { empty_res_trans with exps = [(zero, typ)] } - | _ -> empty_res_trans + | Some zero + -> {empty_res_trans with exps= [(zero, typ)]} + | _ + -> empty_res_trans let implicitValueInitExpr_trans trans_state expr_info = - let (var_exp, _) = extract_var_exp_or_fail trans_state in + let var_exp, _ = extract_var_exp_or_fail trans_state in let tenv = trans_state.context.CContext.tenv in let typ = CType_decl.get_type_from_expr_info expr_info trans_state.context.CContext.tenv in let exps = var_or_zero_in_init_list tenv var_exp typ ~return_zero:true in - { empty_res_trans with exps = exps } + {empty_res_trans with exps} - let nullStmt_trans succ_nodes = - { empty_res_trans with root_nodes = succ_nodes } + let nullStmt_trans succ_nodes = {empty_res_trans with root_nodes= succ_nodes} (* The stmt seems to be always empty *) let unaryExprOrTypeTraitExpr_trans trans_state expr_info unary_expr_or_type_trait_expr_info = let tenv = trans_state.context.CContext.tenv in let typ = CType_decl.qual_type_to_sil_type tenv expr_info.Clang_ast_t.ei_qual_type in match unary_expr_or_type_trait_expr_info.Clang_ast_t.uttei_kind with - | `SizeOf - | `SizeOfWithSize _ as size -> - let qt_opt = + | `SizeOf | `SizeOfWithSize _ as size + -> let qt_opt = CAst_utils.type_from_unary_expr_or_type_trait_expr_info - unary_expr_or_type_trait_expr_info in + unary_expr_or_type_trait_expr_info + in let sizeof_typ = - match qt_opt with - | Some qt -> CType_decl.qual_type_to_sil_type tenv qt - | None -> typ (* Some default type since the type is missing *) in - let nbytes = match size with - | `SizeOfWithSize nbytes -> Some nbytes - | _ -> None in + match qt_opt with Some qt -> CType_decl.qual_type_to_sil_type tenv qt | None -> typ + (* Some default type since the type is missing *) + in + let nbytes = match size with `SizeOfWithSize nbytes -> Some nbytes | _ -> None in let sizeof_data = - {Exp.typ=sizeof_typ; nbytes; dynamic_length=None; subtype=Subtype.exact} in - { empty_res_trans with exps = [(Exp.Sizeof sizeof_data, sizeof_typ)] } - | k -> L.(debug Capture Medium) - "@\nWARNING: Missing translation of Uniry_Expression_Or_Trait of kind: \ - %s . Expression ignored, returned -1... @\n" - (Clang_ast_j.string_of_unary_expr_or_type_trait_kind k); - { empty_res_trans with exps =[(Exp.minus_one, typ)]} + {Exp.typ= sizeof_typ; nbytes; dynamic_length= None; subtype= Subtype.exact} + in + {empty_res_trans with exps= [(Exp.Sizeof sizeof_data, sizeof_typ)]} + | k + -> L.(debug Capture Medium) + "@\nWARNING: Missing translation of Uniry_Expression_Or_Trait of kind: %s . Expression ignored, returned -1... @\n" + (Clang_ast_j.string_of_unary_expr_or_type_trait_kind k) ; + {empty_res_trans with exps= [(Exp.minus_one, typ)]} (* search the label into the hashtbl - create a fake node eventually *) (* connect that node with this stmt *) let gotoStmt_trans trans_state stmt_info label_name = let sil_loc = CLocation.get_sil_location stmt_info trans_state.context in let root_node' = GotoLabel.find_goto_label trans_state.context label_name sil_loc in - { empty_res_trans with root_nodes = [root_node']; leaf_nodes = trans_state.succ_nodes } + {empty_res_trans with root_nodes= [root_node']; leaf_nodes= trans_state.succ_nodes} - let get_builtin_pname_opt trans_unit_ctx qual_name decl_opt (qual_type : Clang_ast_t.qual_type) = + let get_builtin_pname_opt trans_unit_ctx qual_name decl_opt (qual_type: Clang_ast_t.qual_type) = let get_annotate_attr_arg decl = let open Clang_ast_t in let decl_info = Clang_ast_proj.get_decl_tuple decl in let get_attr_opt = function AnnotateAttr a -> Some a | _ -> None in match List.find_map ~f:get_attr_opt decl_info.di_attributes with - | Some attribute_info -> - (match attribute_info.ai_parameters with - | [_; arg; _] -> Some arg - | _ -> - (* it's not supposed to happen due to hardcoded exporting logic + | Some attribute_info -> ( + match attribute_info.ai_parameters with + | [_; arg; _] + -> Some arg + | _ + -> (* it's not supposed to happen due to hardcoded exporting logic coming from ASTExporter.h in facebook-clang-plugins *) - assert false) - | None -> None in + assert false ) + | None + -> None + in let name = QualifiedCppName.to_qual_string qual_name in let function_attr_opt = Option.bind decl_opt ~f:get_annotate_attr_arg in match function_attr_opt with - | Some attr when CTrans_models.is_modeled_attribute attr -> - Some (Typ.Procname.from_string_c_fun attr) - | _ when CTrans_models.is_modeled_builtin name -> - Some (Typ.Procname.from_string_c_fun (CFrontend_config.infer ^ name)) - | _ when CTrans_models.is_release_builtin name qual_type.qt_type_ptr -> - Some BuiltinDecl.__objc_release_cf - | _ when CTrans_models.is_retain_builtin name qual_type.qt_type_ptr -> - Some BuiltinDecl.__objc_retain_cf - | _ when String.equal name CFrontend_config.malloc && - CGeneral_utils.is_objc_extension trans_unit_ctx -> - Some BuiltinDecl.malloc_no_fail - | _ -> None - + | Some attr when CTrans_models.is_modeled_attribute attr + -> Some (Typ.Procname.from_string_c_fun attr) + | _ when CTrans_models.is_modeled_builtin name + -> Some (Typ.Procname.from_string_c_fun (CFrontend_config.infer ^ name)) + | _ when CTrans_models.is_release_builtin name qual_type.qt_type_ptr + -> Some BuiltinDecl.__objc_release_cf + | _ when CTrans_models.is_retain_builtin name qual_type.qt_type_ptr + -> Some BuiltinDecl.__objc_retain_cf + | _ + when String.equal name CFrontend_config.malloc + && CGeneral_utils.is_objc_extension trans_unit_ctx + -> Some BuiltinDecl.malloc_no_fail + | _ + -> None let function_deref_trans trans_state decl_ref = let open CContext in let context = trans_state.context in let name_info, decl_ptr, qual_type = CAst_utils.get_info_from_decl_ref decl_ref in let decl_opt = CAst_utils.get_function_decl_with_body decl_ptr in - Option.iter ~f:(call_translation context) decl_opt; + Option.iter ~f:(call_translation context) decl_opt ; let qual_name = CAst_utils.get_qualified_name name_info in let typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in let pname = - match get_builtin_pname_opt context.translation_unit_context qual_name decl_opt qual_type with - | Some builtin_pname -> builtin_pname - | None -> - let name = QualifiedCppName.to_qual_string qual_name in - CMethod_trans.create_procdesc_with_pointer context decl_ptr None name in - { empty_res_trans with exps = [(Exp.Const (Const.Cfun pname), typ)] } + match + get_builtin_pname_opt context.translation_unit_context qual_name decl_opt qual_type + with + | Some builtin_pname + -> builtin_pname + | None + -> let name = QualifiedCppName.to_qual_string qual_name in + CMethod_trans.create_procdesc_with_pointer context decl_ptr None name + in + {empty_res_trans with exps= [(Exp.Const (Const.Cfun pname), typ)]} let field_deref_trans trans_state stmt_info pre_trans_result decl_ref ~is_constructor_init = let open CContext in @@ -496,26 +539,27 @@ struct let sil_loc = CLocation.get_sil_location stmt_info context in let name_info, decl_ptr, qual_type = CAst_utils.get_info_from_decl_ref decl_ref in let field_string = name_info.Clang_ast_t.ni_name in - L.(debug Capture Verbose) "!!!!! Dealing with field '%s' @." field_string; + L.(debug Capture Verbose) "!!!!! Dealing with field '%s' @." field_string ; let field_typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in - let (obj_sil, class_typ) = extract_exp_from_list pre_trans_result.exps - "WARNING: in Field dereference we expect to know the object@\n" in - let is_pointer_typ = match class_typ.desc with - | Typ.Tptr _ -> true - | _ -> false in - let class_typ = - match class_typ.desc with - | Typ.Tptr (t, _) -> t - | _ -> class_typ in - L.(debug Capture Verbose) "Type is '%s' @." (Typ.to_string class_typ); - let class_tname = match CAst_utils.get_decl decl_ptr with + let obj_sil, class_typ = + extract_exp_from_list pre_trans_result.exps + "WARNING: in Field dereference we expect to know the object@\n" + in + let is_pointer_typ = match class_typ.desc with Typ.Tptr _ -> true | _ -> false in + let class_typ = match class_typ.desc with Typ.Tptr (t, _) -> t | _ -> class_typ in + L.(debug Capture Verbose) "Type is '%s' @." (Typ.to_string class_typ) ; + let class_tname = + match CAst_utils.get_decl decl_ptr with | Some FieldDecl ({di_parent_pointer}, _, _, _) | Some ObjCIvarDecl ({di_parent_pointer}, _, _, _, _) -> ( - match CAst_utils.get_decl_opt di_parent_pointer with - | Some decl -> CType_decl.get_record_typename ~tenv:context.tenv decl - | _ -> assert false - ) - | _ -> assert false (* di_parent_pointer should be always set for fields/ivars *) + match CAst_utils.get_decl_opt di_parent_pointer with + | Some decl + -> CType_decl.get_record_typename ~tenv:context.tenv decl + | _ + -> assert false ) + | _ + -> assert false + (* di_parent_pointer should be always set for fields/ivars *) in let field_name = CGeneral_utils.mk_class_field_name class_tname field_string in let field_exp = Exp.Lfield (obj_sil, field_name, class_typ) in @@ -526,16 +570,18 @@ struct (* 2. Field has reference type - we need to add extra dereference in same fashion*) (* it's done in var_deref_trans. The only exception is during field initialization in*) (* constructor's initializer list (when reference itself is initialized) *) - let should_add_deref = (not is_pointer_typ) || - (not is_constructor_init && CType.is_reference_type qual_type) in - let exp, deref_instrs = if should_add_deref then + let should_add_deref = + not is_pointer_typ || not is_constructor_init && CType.is_reference_type qual_type + in + let exp, deref_instrs = + if should_add_deref then let id = Ident.create_fresh Ident.knormal in let deref_instr = Sil.Load (id, field_exp, field_typ, sil_loc) in - Exp.Var id, [deref_instr] - else - field_exp, [] in + (Exp.Var id, [deref_instr]) + else (field_exp, []) + in let instrs = pre_trans_result.instrs @ deref_instrs in - { pre_trans_result with instrs; exps = [(exp, field_typ)] } + {pre_trans_result with instrs; exps= [(exp, field_typ)]} let method_deref_trans trans_state pre_trans_result decl_ref stmt_info decl_kind = let open CContext in @@ -543,71 +589,87 @@ struct let sil_loc = CLocation.get_sil_location stmt_info context in let name_info, decl_ptr, qual_type = CAst_utils.get_info_from_decl_ref decl_ref in let decl_opt = CAst_utils.get_function_decl_with_body decl_ptr in - Option.iter ~f:(call_translation context) decl_opt; + Option.iter ~f:(call_translation context) decl_opt ; let method_name = CAst_utils.get_unqualified_name name_info in - L.(debug Capture Verbose) "!!!!! Dealing with method '%s' @." method_name; + L.(debug Capture Verbose) "!!!!! Dealing with method '%s' @." method_name ; let method_typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in - let ms_opt = CMethod_trans.method_signature_of_pointer - context.translation_unit_context context.tenv decl_ptr in - let is_instance_method = match ms_opt with - | Some ms -> CMethod_signature.ms_is_instance ms - | _ -> true (* might happen for methods that are not exported yet (some templates). *) in - let is_cpp_virtual = match ms_opt with - | Some ms -> CMethod_signature.ms_is_cpp_virtual ms - | _ -> false in - let extra_exps, extra_instrs = if is_instance_method then ( - (* pre_trans_result.exps may contain expr for 'this' parameter:*) - (* if it comes from CXXMemberCallExpr it will be there *) - (* if it comes from CXXOperatorCallExpr it won't be there and will be added later *) - (* In case of CXXMemberCallExpr it's possible that type of 'this' parameter *) - (* won't have a pointer - if that happens add a pointer to type of the object *) - match pre_trans_result.exps with - | [] -> [], [] + let ms_opt = + CMethod_trans.method_signature_of_pointer context.translation_unit_context context.tenv + decl_ptr + in + let is_instance_method = + match ms_opt with Some ms -> CMethod_signature.ms_is_instance ms | _ -> true + (* might happen for methods that are not exported yet (some templates). *) + in + let is_cpp_virtual = + match ms_opt with Some ms -> CMethod_signature.ms_is_cpp_virtual ms | _ -> false + in + let extra_exps, extra_instrs = + if is_instance_method then + match + (* pre_trans_result.exps may contain expr for 'this' parameter:*) + (* if it comes from CXXMemberCallExpr it will be there *) + (* if it comes from CXXOperatorCallExpr it won't be there and will be added later *) + (* In case of CXXMemberCallExpr it's possible that type of 'this' parameter *) + (* won't have a pointer - if that happens add a pointer to type of the object *) + pre_trans_result.exps + with + | [] + -> ([], []) (* We need to add a dereference before a method call to find null dereferences when *) (* calling a method with null *) - | [(exp, {Typ.desc=Tptr (typ, _)})] when decl_kind <> `CXXConstructor -> - let no_id = Ident.create_none () in + | [(exp, {Typ.desc= Tptr (typ, _)})] + when decl_kind <> `CXXConstructor + -> let no_id = Ident.create_none () in let extra_instrs = [Sil.Load (no_id, exp, typ, sil_loc)] in - pre_trans_result.exps, extra_instrs - | [(_, {Typ.desc=Tptr _})] -> pre_trans_result.exps, [] - | [(sil, typ)] -> [(sil, Typ.mk (Tptr (typ, Typ.Pk_reference)))], [] - | _ -> assert false - ) - else - (* don't add 'this' expression for static methods *) - [], [] in + (pre_trans_result.exps, extra_instrs) + | [(_, {Typ.desc= Tptr _})] + -> (pre_trans_result.exps, []) + | [(sil, typ)] + -> ([(sil, Typ.mk (Tptr (typ, Typ.Pk_reference)))], []) + | _ + -> assert false + else (* don't add 'this' expression for static methods *) + ([], []) + in (* unlike field access, for method calls there is no need to expand class type *) - (* use qualified method name for builtin matching, but use unqualified name elsewhere *) let qual_method_name = CAst_utils.get_qualified_name name_info in let pname = - match get_builtin_pname_opt context.translation_unit_context qual_method_name decl_opt - qual_type with - | Some builtin_pname -> builtin_pname - | None -> - let class_typename = Typ.Name.Cpp.from_qual_name Typ.NoTemplate - (CAst_utils.get_class_name_from_member name_info) in + match + get_builtin_pname_opt context.translation_unit_context qual_method_name decl_opt qual_type + with + | Some builtin_pname + -> builtin_pname + | None + -> let class_typename = + Typ.Name.Cpp.from_qual_name Typ.NoTemplate + (CAst_utils.get_class_name_from_member name_info) + in CMethod_trans.create_procdesc_with_pointer context decl_ptr (Some class_typename) - method_name in + method_name + in let method_exp = (Exp.Const (Const.Cfun pname), method_typ) in { pre_trans_result with - is_cpp_call_virtual = is_cpp_virtual; - exps = [method_exp] @ extra_exps; - instrs = pre_trans_result.instrs @ extra_instrs; - } + is_cpp_call_virtual= is_cpp_virtual + ; exps= [method_exp] @ extra_exps + ; instrs= pre_trans_result.instrs @ extra_instrs } let destructor_deref_trans trans_state pvar_trans_result class_qual_type si = let open Clang_ast_t in let destruct_decl_ref_opt = match CAst_utils.get_decl_from_typ_ptr class_qual_type.Clang_ast_t.qt_type_ptr with - | Some CXXRecordDecl (_, _, _ , _, _, _, _, cxx_record_info) - | Some ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, cxx_record_info, _) -> - cxx_record_info.xrdi_destructor - | _ -> None in + | Some CXXRecordDecl (_, _, _, _, _, _, _, cxx_record_info) + | Some ClassTemplateSpecializationDecl (_, _, _, _, _, _, _, cxx_record_info, _) + -> cxx_record_info.xrdi_destructor + | _ + -> None + in match destruct_decl_ref_opt with - | Some decl_ref -> - method_deref_trans trans_state pvar_trans_result decl_ref si `CXXDestructor - | None -> empty_res_trans + | Some decl_ref + -> method_deref_trans trans_state pvar_trans_result decl_ref si `CXXDestructor + | None + -> empty_res_trans let this_expr_trans trans_state sil_loc class_qual_type = let context = trans_state.context in @@ -616,9 +678,9 @@ struct let pvar = Pvar.mk (Mangled.from_string name) procname in let exp = Exp.Lvar pvar in let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv class_qual_type in - let exps = [(exp, typ)] in + let exps = [(exp, typ)] in (* there is no cast operation in AST, but backend needs it *) - dereference_value_from_result sil_loc { empty_res_trans with exps = exps } ~strip_pointer:false + dereference_value_from_result sil_loc {empty_res_trans with exps} ~strip_pointer:false let cxxThisExpr_trans trans_state stmt_info expr_info = let sil_loc = CLocation.get_sil_location stmt_info trans_state.context in @@ -627,105 +689,120 @@ struct let rec labelStmt_trans trans_state stmt_info stmt_list label_name = let context = trans_state.context in (* go ahead with the translation *) - let res_trans = match stmt_list with - | [stmt] -> - instruction trans_state stmt - | _ -> assert false (* expected a stmt or at most a compoundstmt *) in + let res_trans = + match stmt_list with [stmt] -> instruction trans_state stmt | _ -> assert false + (* expected a stmt or at most a compoundstmt *) + in (* create the label root node into the hashtbl *) let sil_loc = CLocation.get_sil_location stmt_info context in let root_node' = GotoLabel.find_goto_label trans_state.context label_name sil_loc in - Procdesc.node_set_succs_exn context.procdesc root_node' res_trans.root_nodes []; - { empty_res_trans with root_nodes = [root_node']; leaf_nodes = trans_state.succ_nodes } + Procdesc.node_set_succs_exn context.procdesc root_node' res_trans.root_nodes [] ; + {empty_res_trans with root_nodes= [root_node']; leaf_nodes= trans_state.succ_nodes} - and var_deref_trans trans_state stmt_info (decl_ref : Clang_ast_t.decl_ref) = + and var_deref_trans trans_state stmt_info (decl_ref: Clang_ast_t.decl_ref) = let context = trans_state.context in let _, _, qual_type = CAst_utils.get_info_from_decl_ref decl_ref in let ast_typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in let typ = match ast_typ.Typ.desc with - | Tstruct _ when decl_ref.dr_kind = `ParmVar -> - if CGeneral_utils.is_cpp_translation context.translation_unit_context then + | Tstruct _ when decl_ref.dr_kind = `ParmVar + -> if CGeneral_utils.is_cpp_translation context.translation_unit_context then Typ.mk (Tptr (ast_typ, Pk_reference)) else ast_typ - | _ -> ast_typ in + | _ + -> ast_typ + in let procname = Procdesc.get_proc_name context.procdesc in let sil_loc = CLocation.get_sil_location stmt_info context in let pvar = CVar_decl.sil_var_of_decl_ref context decl_ref procname in - CContext.add_block_static_var context procname (pvar, typ); + CContext.add_block_static_var context procname (pvar, typ) ; let var_exp = Exp.Lvar pvar in - let exps = if Self.is_var_self pvar (CContext.is_objc_method context) then + let exps = + if Self.is_var_self pvar (CContext.is_objc_method context) then let class_typename = CContext.get_curr_class_typename context in - if (CType.is_class typ) then - raise (Self.SelfClassException class_typename) + if CType.is_class typ then raise (Self.SelfClassException class_typename) else let typ = CType.add_pointer_to_typ (Typ.mk (Tstruct class_typename)) in [(var_exp, typ)] - else [(var_exp, typ)] in - L.(debug Capture Verbose) "@\n@\n PVAR ='%s'@\n@\n" (Pvar.to_string pvar); - let res_trans = { empty_res_trans with exps } in + else [(var_exp, typ)] + in + L.(debug Capture Verbose) "@\n@\n PVAR ='%s'@\n@\n" (Pvar.to_string pvar) ; + let res_trans = {empty_res_trans with exps} in match typ.desc with - | Tptr (_, Pk_reference) -> - (* dereference pvar due to the behavior of reference types in clang's AST *) + | Tptr (_, Pk_reference) + -> (* dereference pvar due to the behavior of reference types in clang's AST *) dereference_value_from_result sil_loc res_trans ~strip_pointer:false - | _ -> res_trans + | _ + -> res_trans and decl_ref_trans trans_state pre_trans_result stmt_info decl_ref ~is_constructor_init = - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; let decl_kind = decl_ref.Clang_ast_t.dr_kind in match decl_kind with - | `EnumConstant -> enum_constant_trans trans_state decl_ref - | `Function -> function_deref_trans trans_state decl_ref - | `Var | `ImplicitParam | `ParmVar -> - var_deref_trans trans_state stmt_info decl_ref - | `Field | `ObjCIvar -> - field_deref_trans trans_state stmt_info pre_trans_result decl_ref ~is_constructor_init - | `CXXMethod | `CXXConversion | `CXXConstructor | `CXXDestructor -> - method_deref_trans trans_state pre_trans_result decl_ref stmt_info decl_kind - | _ -> - let print_error decl_kind = + | `EnumConstant + -> enum_constant_trans trans_state decl_ref + | `Function + -> function_deref_trans trans_state decl_ref + | `Var | `ImplicitParam | `ParmVar + -> var_deref_trans trans_state stmt_info decl_ref + | `Field | `ObjCIvar + -> field_deref_trans trans_state stmt_info pre_trans_result decl_ref ~is_constructor_init + | `CXXMethod | `CXXConversion | `CXXConstructor | `CXXDestructor + -> method_deref_trans trans_state pre_trans_result decl_ref stmt_info decl_kind + | _ + -> let print_error decl_kind = L.(debug Capture Medium) "Warning: Decl ref expression %s with pointer %d still needs to be translated " - (Clang_ast_j.string_of_decl_kind decl_kind) - decl_ref.Clang_ast_t.dr_decl_pointer in - print_error decl_kind; assert false + (Clang_ast_j.string_of_decl_kind decl_kind) decl_ref.Clang_ast_t.dr_decl_pointer + in + print_error decl_kind ; + assert false and declRefExpr_trans trans_state stmt_info decl_ref_expr_info _ = - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); - let decl_ref = match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with - | Some dr -> dr - | None -> assert false in + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; + let decl_ref = + match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some dr + -> dr + | None + -> assert false + in decl_ref_trans trans_state empty_res_trans stmt_info decl_ref ~is_constructor_init:false (* evaluates an enum constant *) and enum_const_eval context enum_constant_pointer prev_enum_constant_opt zero = match CAst_utils.get_decl enum_constant_pointer with - | Some Clang_ast_t.EnumConstantDecl (_, _, _, enum_constant_decl_info) -> - (match enum_constant_decl_info.Clang_ast_t.ecdi_init_expr with - | Some stmt -> - expression_trans context stmt - "WARNING: Expression in Enumeration constant not found@\n" - | None -> - match prev_enum_constant_opt with - | Some prev_constant_pointer -> - let previous_exp = get_enum_constant_expr context prev_constant_pointer in - CArithmetic_trans.sil_const_plus_one previous_exp - | None -> zero) - | _ -> zero + | Some Clang_ast_t.EnumConstantDecl (_, _, _, enum_constant_decl_info) -> ( + match enum_constant_decl_info.Clang_ast_t.ecdi_init_expr with + | Some stmt + -> expression_trans context stmt "WARNING: Expression in Enumeration constant not found@\n" + | None -> + match prev_enum_constant_opt with + | Some prev_constant_pointer + -> let previous_exp = get_enum_constant_expr context prev_constant_pointer in + CArithmetic_trans.sil_const_plus_one previous_exp + | None + -> zero ) + | _ + -> zero (* get the sil value of the enum constant from the map or by evaluating it *) and get_enum_constant_expr context enum_constant_pointer = let zero = Exp.Const (Const.Cint IntLit.zero) in try - let (prev_enum_constant_opt, sil_exp_opt) = - CAst_utils.get_enum_constant_exp enum_constant_pointer in + let prev_enum_constant_opt, sil_exp_opt = + CAst_utils.get_enum_constant_exp enum_constant_pointer + in match sil_exp_opt with - | Some exp -> exp - | None -> - let exp = enum_const_eval context enum_constant_pointer prev_enum_constant_opt zero in - CAst_utils.update_enum_map enum_constant_pointer exp; - exp + | Some exp + -> exp + | None + -> let exp = enum_const_eval context enum_constant_pointer prev_enum_constant_opt zero in + CAst_utils.update_enum_map enum_constant_pointer exp ; exp with Not_found -> zero and enum_constant_trans trans_state decl_ref = @@ -733,113 +810,131 @@ struct let _, _, qual_type = CAst_utils.get_info_from_decl_ref decl_ref in let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in let const_exp = get_enum_constant_expr context decl_ref.Clang_ast_t.dr_decl_pointer in - { empty_res_trans with exps = [(const_exp, typ)] } + {empty_res_trans with exps= [(const_exp, typ)]} and arraySubscriptExpr_trans trans_state expr_info stmt_list = let context = trans_state.context in let typ = CType_decl.get_type_from_expr_info expr_info context.tenv in - let array_stmt, idx_stmt = (match stmt_list with - | [a; i] -> a, i (* Assumption: the statement list contains 2 elements, + let array_stmt, idx_stmt = + match stmt_list with + | [a; i] + -> (a, i) + (* Assumption: the statement list contains 2 elements, the first is the array expr and the second the index *) - | _ -> assert false (* Let's get notified if the assumption is wrong...*) ) in + | _ + -> assert false + (* Let's get notified if the assumption is wrong...*) + in let res_trans_a = instruction trans_state array_stmt in let res_trans_idx = instruction trans_state idx_stmt in - let (a_exp, _) = extract_exp_from_list res_trans_a.exps - "WARNING: In ArraySubscriptExpr there was a problem in translating array exp.@\n" in - let (i_exp, _) = extract_exp_from_list res_trans_idx.exps - "WARNING: In ArraySubscriptExpr there was a problem in translating index exp.@\n" in + let a_exp, _ = + extract_exp_from_list res_trans_a.exps + "WARNING: In ArraySubscriptExpr there was a problem in translating array exp.@\n" + in + let i_exp, _ = + extract_exp_from_list res_trans_idx.exps + "WARNING: In ArraySubscriptExpr there was a problem in translating index exp.@\n" + in let array_exp = Exp.Lindex (a_exp, i_exp) in - let root_nodes = - if res_trans_a.root_nodes <> [] - then res_trans_a.root_nodes - else res_trans_idx.root_nodes in + if res_trans_a.root_nodes <> [] then res_trans_a.root_nodes else res_trans_idx.root_nodes + in let leaf_nodes = - if res_trans_idx.leaf_nodes <> [] - then res_trans_idx.leaf_nodes - else res_trans_a.leaf_nodes in - - if res_trans_idx.root_nodes <> [] - then + if res_trans_idx.leaf_nodes <> [] then res_trans_idx.leaf_nodes else res_trans_a.leaf_nodes + in + if res_trans_idx.root_nodes <> [] then List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n res_trans_idx.root_nodes []) - res_trans_a.leaf_nodes; - + res_trans_a.leaf_nodes ; (* Note the order of res_trans_idx.ids @ res_trans_a.ids is important. *) (* We expect to use only res_trans_idx.ids in construction of other operation. *) (* res_trans_a.ids is passed to be Removed.*) { empty_res_trans with - root_nodes; - leaf_nodes; - instrs = res_trans_a.instrs @ res_trans_idx.instrs; - exps = [(array_exp, typ)]; - initd_exps = res_trans_idx.initd_exps @ res_trans_a.initd_exps; } + root_nodes + ; leaf_nodes + ; instrs= res_trans_a.instrs @ res_trans_idx.instrs + ; exps= [(array_exp, typ)] + ; initd_exps= res_trans_idx.initd_exps @ res_trans_a.initd_exps } and binaryOperator_trans trans_state binary_operator_info stmt_info expr_info stmt_list = let bok = - Clang_ast_j.string_of_binary_operator_kind binary_operator_info.Clang_ast_t.boi_kind in - L.(debug Capture Verbose) " BinaryOperator '%s' " bok; - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + Clang_ast_j.string_of_binary_operator_kind binary_operator_info.Clang_ast_t.boi_kind + in + L.(debug Capture Verbose) " BinaryOperator '%s' " bok ; + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; let context = trans_state.context in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let nname = "BinaryOperatorStmt: "^ (CArithmetic_trans.bin_op_to_string binary_operator_info) in - let trans_state' = { trans_state_pri with succ_nodes = [] } in + let nname = "BinaryOperatorStmt: " ^ CArithmetic_trans.bin_op_to_string binary_operator_info in + let trans_state' = {trans_state_pri with succ_nodes= []} in let sil_loc = CLocation.get_sil_location stmt_info context in let typ = - CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type in + CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type + in match stmt_list with - | [s1; s2] -> (* Assumption: We expect precisely 2 stmt corresponding to the 2 operands*) + | [s1; s2] + -> (* Assumption: We expect precisely 2 stmt corresponding to the 2 operands*) let rhs_owning_method = CTrans_utils.is_owning_method s2 in (* NOTE: we create a node only if required. In that case this node *) (* becomes the successor of the nodes that may be created when *) (* translating the operands. *) let res_trans_e1 = exec_with_self_exception instruction trans_state' s1 in - let (var_exp, var_exp_typ) = extract_exp_from_list res_trans_e1.exps - "@\nWARNING: Missing LHS operand in BinOp. Returning -1. Fix needed...@\n" in - let trans_state'' = { trans_state' with var_exp_typ = Some (var_exp, var_exp_typ) } in + let var_exp, var_exp_typ = + extract_exp_from_list res_trans_e1.exps + "@\nWARNING: Missing LHS operand in BinOp. Returning -1. Fix needed...@\n" + in + let trans_state'' = {trans_state' with var_exp_typ= Some (var_exp, var_exp_typ)} in let res_trans_e2 = (* translation of s2 is done taking care of block special case *) exec_with_block_priority_exception (exec_with_self_exception instruction) trans_state'' - s2 stmt_info in - let (sil_e2, _) = extract_exp_from_list res_trans_e2.exps - "@\nWARNING: Missing RHS operand in BinOp. Returning -1. Fix needed...@\n" in + s2 stmt_info + in + let sil_e2, _ = + extract_exp_from_list res_trans_e2.exps + "@\nWARNING: Missing RHS operand in BinOp. Returning -1. Fix needed...@\n" + in let binop_res_trans, exp_to_parent = - if List.exists ~f:(Exp.equal var_exp) res_trans_e2.initd_exps then [], [] + if List.exists ~f:(Exp.equal var_exp) res_trans_e2.initd_exps then ([], []) else let exp_op, instr_bin = - CArithmetic_trans.binary_operation_instruction - binary_operator_info var_exp typ sil_e2 sil_loc rhs_owning_method in - + CArithmetic_trans.binary_operation_instruction binary_operator_info var_exp typ + sil_e2 sil_loc rhs_owning_method + in (* Create a node if the priority if free and there are instructions *) let creating_node = - (PriorityNode.own_priority_node trans_state_pri.priority stmt_info) && - (List.length instr_bin >0) in + PriorityNode.own_priority_node trans_state_pri.priority stmt_info + && List.length instr_bin > 0 + in let extra_instrs, exp_to_parent = - if (is_binary_assign_op binary_operator_info) - (* assignment operator result is lvalue in CPP, rvalue in C, *) - (* hence the difference *) - && (not (CGeneral_utils.is_cpp_translation context.translation_unit_context)) - && ((not creating_node) || (is_return_temp trans_state.continuation)) then ( + if is_binary_assign_op binary_operator_info + (* assignment operator result is lvalue in CPP, rvalue in C, *) + (* hence the difference *) + && not (CGeneral_utils.is_cpp_translation context.translation_unit_context) + && (not creating_node || is_return_temp trans_state.continuation) + then (* We are in this case when an assignment is inside *) (* another operator that creates a node. Eg. another *) (* assignment. *) (* As no node is created here ids are passed to the parent *) let id = Ident.create_fresh Ident.knormal in let res_instr = Sil.Load (id, var_exp, var_exp_typ, sil_loc) in - [res_instr], Exp.Var id - ) else ( - [], exp_op) in - let binop_res_trans = { empty_res_trans with - instrs = instr_bin @ extra_instrs - } in - [binop_res_trans], [(exp_to_parent, var_exp_typ)] in + ([res_instr], Exp.Var id) + else ([], exp_op) + in + let binop_res_trans = {empty_res_trans with instrs= instr_bin @ extra_instrs} in + ([binop_res_trans], [(exp_to_parent, var_exp_typ)]) + in let all_res_trans = [res_trans_e1; res_trans_e2] @ binop_res_trans in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - nname stmt_info all_res_trans in - { res_trans_to_parent with exps = exp_to_parent } - | _ -> assert false (* Binary operator should have two operands *) + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname stmt_info + all_res_trans + in + {res_trans_to_parent with exps= exp_to_parent} + | _ + -> assert false + (* Binary operator should have two operands *) and callExpr_trans trans_state si stmt_list expr_info = let context = trans_state.context in let fn_type_no_ref = CType_decl.get_type_from_expr_info expr_info context.CContext.tenv in @@ -847,116 +942,123 @@ struct let procname = Procdesc.get_proc_name context.CContext.procdesc in let sil_loc = CLocation.get_sil_location si context in (* First stmt is the function expr and the rest are params *) - let fun_exp_stmt, params_stmt = (match stmt_list with - | fe :: params -> fe, params - | _ -> assert false) in + let fun_exp_stmt, params_stmt = + match stmt_list with fe :: params -> (fe, params) | _ -> assert false + in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in (* claim priority if no ancestors has claimed priority before *) - let trans_state_callee = { trans_state_pri with succ_nodes = [] } in + let trans_state_callee = {trans_state_pri with succ_nodes= []} in let res_trans_callee = instruction trans_state_callee fun_exp_stmt in - let (sil_fe, _) = extract_exp_from_list res_trans_callee.exps - "WARNING: The translation of fun_exp did not return an expression.\ - Returning -1. NEED TO BE FIXED" in + let sil_fe, _ = + extract_exp_from_list res_trans_callee.exps + "WARNING: The translation of fun_exp did not return an expression.Returning -1. NEED TO BE FIXED" + in let callee_pname_opt = - match sil_fe with - | Exp.Const (Const.Cfun pn) -> - Some pn - | _ -> None (* function pointer *) in + match sil_fe with Exp.Const Const.Cfun pn -> Some pn | _ -> None + (* function pointer *) + in (* we cannot translate the arguments of __builtin_object_size because preprocessing copies them verbatim from a call to a different function, and they might be side-effecting *) let should_translate_args = - not (Option.value_map ~f:CTrans_models.is_builtin_object_size ~default:false callee_pname_opt) in - let params_stmt = if should_translate_args then params_stmt - else [] in + not + (Option.value_map ~f:CTrans_models.is_builtin_object_size ~default:false callee_pname_opt) + in + let params_stmt = if should_translate_args then params_stmt else [] in (* As we may have nodes coming from different parameters we need to *) (* call instruction for each parameter and collect the results *) (* afterwards. The 'instructions' function does not do that *) - let trans_state_param = - { trans_state_pri with succ_nodes = []; var_exp_typ = None } in + let trans_state_param = {trans_state_pri with succ_nodes= []; var_exp_typ= None} in let result_trans_subexprs = let instruction' = exec_with_self_exception (exec_with_glvalue_as_reference instruction) in let res_trans_p = List.map ~f:(instruction' trans_state_param) params_stmt in - res_trans_callee :: res_trans_p in - match Option.bind callee_pname_opt - ~f:(CTrans_utils.builtin_trans - trans_state_pri sil_loc si function_type result_trans_subexprs) with - | Some builtin -> builtin - | None -> - let is_cf_retain_release = - Option.value_map - ~f:CTrans_models.is_cf_retain_release ~default:false callee_pname_opt in + res_trans_callee :: res_trans_p + in + match + Option.bind callee_pname_opt + ~f: + (CTrans_utils.builtin_trans trans_state_pri sil_loc si function_type + result_trans_subexprs) + with + | Some builtin + -> builtin + | None + -> let is_cf_retain_release = + Option.value_map ~f:CTrans_models.is_cf_retain_release ~default:false callee_pname_opt + in let act_params = let params = List.tl_exn (collect_exprs result_trans_subexprs) in - if Int.equal (List.length params) (List.length params_stmt) then - params + if Int.equal (List.length params) (List.length params_stmt) then params else ( - L.internal_error "ERROR: stmt_list and res_trans_par.exps must have same size@\n"; - assert false) in - let act_params = if is_cf_retain_release then - (Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint Typ.IBool)) :: act_params - else act_params in + L.internal_error "ERROR: stmt_list and res_trans_par.exps must have same size@\n" ; + assert false ) + in + let act_params = + if is_cf_retain_release then (Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint Typ.IBool)) + :: act_params + else act_params + in let res_trans_call = let cast_trans_fun = cast_trans act_params sil_loc function_type in match Option.bind callee_pname_opt ~f:cast_trans_fun with - | Some (instr, cast_exp) -> - { empty_res_trans with - instrs = [instr]; - exps = [(cast_exp, function_type)]; } - | _ -> - let is_call_to_block = objc_exp_of_type_block fun_exp_stmt in + | Some (instr, cast_exp) + -> {empty_res_trans with instrs= [instr]; exps= [(cast_exp, function_type)]} + | _ + -> let is_call_to_block = objc_exp_of_type_block fun_exp_stmt in let call_flags = - { CallFlags.default with CallFlags.cf_is_objc_block = is_call_to_block; } in - create_call_instr trans_state function_type sil_fe act_params sil_loc - call_flags ~is_objc_method:false in - let nname = "Call "^(Exp.to_string sil_fe) in + {CallFlags.default with CallFlags.cf_is_objc_block= is_call_to_block} + in + create_call_instr trans_state function_type sil_fe act_params sil_loc call_flags + ~is_objc_method:false + in + let nname = "Call " ^ Exp.to_string sil_fe in let all_res_trans = result_trans_subexprs @ [res_trans_call] in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri - sil_loc nname si all_res_trans in - let add_cg_edge callee_pname = - Cg.add_edge context.CContext.cg procname callee_pname + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans in - Option.iter ~f:add_cg_edge callee_pname_opt; - { res_trans_to_parent with exps = res_trans_call.exps } + let add_cg_edge callee_pname = Cg.add_edge context.CContext.cg procname callee_pname in + Option.iter ~f:add_cg_edge callee_pname_opt ; + {res_trans_to_parent with exps= res_trans_call.exps} - and cxx_method_construct_call_trans trans_state_pri result_trans_callee params_stmt - si function_type is_cpp_call_virtual extra_res_trans = + and cxx_method_construct_call_trans trans_state_pri result_trans_callee params_stmt si + function_type is_cpp_call_virtual extra_res_trans = let open CContext in let context = trans_state_pri.context in let procname = Procdesc.get_proc_name context.procdesc in let sil_loc = CLocation.get_sil_location si context in (* first for method address, second for 'this' expression *) - assert (Int.equal (List.length result_trans_callee.exps) 2); - let (sil_method, _) = List.hd_exn result_trans_callee.exps in + assert (Int.equal (List.length result_trans_callee.exps) 2) ; + let sil_method, _ = List.hd_exn result_trans_callee.exps in let callee_pname = - match sil_method with - | Exp.Const (Const.Cfun pn) -> pn - | _ -> assert false (* method pointer not implemented, this shouldn't happen *) in + match sil_method with Exp.Const Const.Cfun pn -> pn | _ -> assert false + (* method pointer not implemented, this shouldn't happen *) + in (* As we may have nodes coming from different parameters we need to *) (* call instruction for each parameter and collect the results *) (* afterwards. The 'instructions' function does not do that *) let result_trans_subexprs = - let trans_state_param = - { trans_state_pri with succ_nodes = []; var_exp_typ = None } in + let trans_state_param = {trans_state_pri with succ_nodes= []; var_exp_typ= None} in let instruction' = exec_with_self_exception (exec_with_glvalue_as_reference instruction) in let res_trans_p = List.map ~f:(instruction' trans_state_param) params_stmt in - result_trans_callee :: res_trans_p in + result_trans_callee :: res_trans_p + in (* first expr is method address, rest are params including 'this' parameter *) let actual_params = List.tl_exn (collect_exprs result_trans_subexprs) in match cxx_method_builtin_trans trans_state_pri sil_loc result_trans_subexprs callee_pname with - | Some builtin -> builtin - | _ -> - let call_flags = { - CallFlags.default with - CallFlags.cf_virtual = is_cpp_call_virtual; - } in - let res_trans_call = create_call_instr trans_state_pri function_type sil_method - actual_params sil_loc call_flags ~is_objc_method:false in - let nname = "Call " ^ (Exp.to_string sil_method) in + | Some builtin + -> builtin + | _ + -> let call_flags = {CallFlags.default with CallFlags.cf_virtual= is_cpp_call_virtual} in + let res_trans_call = + create_call_instr trans_state_pri function_type sil_method actual_params sil_loc + call_flags ~is_objc_method:false + in + let nname = "Call " ^ Exp.to_string sil_method in let all_res_trans = result_trans_subexprs @ [res_trans_call; extra_res_trans] in let result_trans_to_parent = - PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans in - Cg.add_edge context.CContext.cg procname callee_pname; - { result_trans_to_parent with exps = res_trans_call.exps } + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans + in + Cg.add_edge context.CContext.cg procname callee_pname ; + {result_trans_to_parent with exps= res_trans_call.exps} and cxxMemberCallExpr_trans trans_state si stmt_list expr_info = let context = trans_state.context in @@ -964,50 +1066,59 @@ struct (* CXXMemberCallExpr: first stmt is method+this expr and the rest are normal params *) (* CXXOperatorCallExpr: First stmt is method/function deref without this expr and the *) (* rest are params, possibly including 'this' *) - let fun_exp_stmt, params_stmt = (match stmt_list with - | fe :: params -> fe, params - | _ -> assert false) in + let fun_exp_stmt, params_stmt = + match stmt_list with fe :: params -> (fe, params) | _ -> assert false + in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in (* claim priority if no ancestors has claimed priority before *) - let trans_state_callee = { trans_state_pri with succ_nodes = [] } in + let trans_state_callee = {trans_state_pri with succ_nodes= []} in let result_trans_callee = instruction trans_state_callee fun_exp_stmt in let is_cpp_call_virtual = result_trans_callee.is_cpp_call_virtual in let fn_type_no_ref = CType_decl.get_type_from_expr_info expr_info context.CContext.tenv in let function_type = add_reference_if_glvalue fn_type_no_ref expr_info in - cxx_method_construct_call_trans trans_state_pri result_trans_callee params_stmt - si function_type is_cpp_call_virtual empty_res_trans + cxx_method_construct_call_trans trans_state_pri result_trans_callee params_stmt si + function_type is_cpp_call_virtual empty_res_trans and cxxConstructExpr_trans trans_state si params_stmt ei cxx_constr_info = let context = trans_state.context in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in let sil_loc = CLocation.get_sil_location si context in let decl_ref = cxx_constr_info.Clang_ast_t.xcei_decl_ref in - let var_exp, class_type = match trans_state.var_exp_typ with - | Some exp_typ -> exp_typ - | None -> - let procdesc = trans_state.context.CContext.procdesc in + let var_exp, class_type = + match trans_state.var_exp_typ with + | Some exp_typ + -> exp_typ + | None + -> let procdesc = trans_state.context.CContext.procdesc in let pvar = Pvar.mk_tmp "__temp_construct_" (Procdesc.get_proc_name procdesc) in let class_type = CType_decl.get_type_from_expr_info ei context.CContext.tenv in - Procdesc.append_locals procdesc [(Pvar.get_name pvar, class_type)]; - Exp.Lvar pvar, class_type in + Procdesc.append_locals procdesc [(Pvar.get_name pvar, class_type)] ; + (Exp.Lvar pvar, class_type) + in let this_type = CType.add_pointer_to_typ class_type in - let this_res_trans = { empty_res_trans with - exps = [(var_exp, this_type)]; - initd_exps = [var_exp]; - } in - let tmp_res_trans = { empty_res_trans with exps = [(var_exp, class_type)] } in + let this_res_trans = + {empty_res_trans with exps= [(var_exp, this_type)]; initd_exps= [var_exp]} + in + let tmp_res_trans = {empty_res_trans with exps= [(var_exp, class_type)]} in (* When class type is translated as pointer (std::shared_ptr for example), there needs to be extra Load instruction before returning the trans_result of constructorExpr. There is no LValueToRvalue cast in the AST afterwards since clang doesn't know that class type is translated as pointer type. It gets added here instead. *) - let extra_res_trans = match class_type.desc with - | Typ.Tptr _ -> dereference_value_from_result sil_loc tmp_res_trans ~strip_pointer:false - | _ -> tmp_res_trans in - let res_trans_callee = decl_ref_trans trans_state this_res_trans si decl_ref - ~is_constructor_init:false in - let res_trans = cxx_method_construct_call_trans trans_state_pri res_trans_callee - params_stmt si (Typ.mk Tvoid) false extra_res_trans in - { res_trans with exps=extra_res_trans.exps } + let extra_res_trans = + match class_type.desc with + | Typ.Tptr _ + -> dereference_value_from_result sil_loc tmp_res_trans ~strip_pointer:false + | _ + -> tmp_res_trans + in + let res_trans_callee = + decl_ref_trans trans_state this_res_trans si decl_ref ~is_constructor_init:false + in + let res_trans = + cxx_method_construct_call_trans trans_state_pri res_trans_callee params_stmt si + (Typ.mk Tvoid) false extra_res_trans + in + {res_trans with exps= extra_res_trans.exps} and cxx_destructor_call_trans trans_state si this_res_trans class_qual_type = let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in @@ -1018,113 +1129,126 @@ struct is_cpp_call_virtual empty_res_trans else empty_res_trans - and objCMessageExpr_trans_special_cases trans_state si obj_c_message_expr_info - method_type trans_state_pri sil_loc act_params = + and objCMessageExpr_trans_special_cases trans_state si obj_c_message_expr_info method_type + trans_state_pri sil_loc act_params = let context = trans_state.context in let receiver_kind = obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind in let selector = obj_c_message_expr_info.Clang_ast_t.omei_selector in (* class method *) if String.equal selector CFrontend_config.class_method && CType.is_class method_type then - let class_name = CMethod_trans.get_class_name_method_call_from_receiver_kind context - obj_c_message_expr_info act_params in - raise (Self.SelfClassException class_name) - (* alloc or new *) - else if String.equal selector CFrontend_config.alloc || - String.equal selector CFrontend_config.new_str then + let class_name = + CMethod_trans.get_class_name_method_call_from_receiver_kind context obj_c_message_expr_info + act_params + in + raise (Self.SelfClassException class_name) (* alloc or new *) + else if String.equal selector CFrontend_config.alloc + || String.equal selector CFrontend_config.new_str + then match receiver_kind with - | `Class qual_type -> - let class_opt = - CMethod_trans.get_class_name_method_call_from_clang - context.translation_unit_context context.CContext.tenv obj_c_message_expr_info in + | `Class qual_type + -> let class_opt = + CMethod_trans.get_class_name_method_call_from_clang context.translation_unit_context + context.CContext.tenv obj_c_message_expr_info + in Some (new_or_alloc_trans trans_state_pri sil_loc si qual_type class_opt selector) - | _ -> None - (* assertions *) + | _ + -> None (* assertions *) else if CTrans_models.is_handleFailureInMethod selector then Some (CTrans_utils.trans_assertion trans_state sil_loc) else None - (* If the first argument of the call is self in a static context, remove it as an argument *) (* and change the call from instance to static *) and objCMessageExpr_deal_with_static_self trans_state_param stmt_list obj_c_message_expr_info = match stmt_list with - | stmt :: rest -> - let obj_c_message_expr_info, fst_res_trans = + | stmt :: rest + -> let obj_c_message_expr_info, fst_res_trans = try let fst_res_trans = instruction trans_state_param stmt in - obj_c_message_expr_info, fst_res_trans + (obj_c_message_expr_info, fst_res_trans) with Self.SelfClassException class_typename -> let pointer = obj_c_message_expr_info.Clang_ast_t.omei_decl_pointer in let selector = obj_c_message_expr_info.Clang_ast_t.omei_selector in let obj_c_message_expr_info = - Ast_expressions.make_obj_c_message_expr_info_class selector class_typename pointer in - obj_c_message_expr_info, empty_res_trans in - let instruction' = - exec_with_self_exception (exec_with_glvalue_as_reference instruction) in + Ast_expressions.make_obj_c_message_expr_info_class selector class_typename pointer + in + (obj_c_message_expr_info, empty_res_trans) + in + let instruction' = exec_with_self_exception (exec_with_glvalue_as_reference instruction) in let l = List.map ~f:(instruction' trans_state_param) rest in - obj_c_message_expr_info, fst_res_trans :: l - | [] -> obj_c_message_expr_info, [empty_res_trans] + (obj_c_message_expr_info, fst_res_trans :: l) + | [] + -> (obj_c_message_expr_info, [empty_res_trans]) and objCMessageExpr_trans trans_state si obj_c_message_expr_info stmt_list expr_info = - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; let context = trans_state.context in let sil_loc = CLocation.get_sil_location si context in let method_type_no_ref = CType_decl.get_type_from_expr_info expr_info context.CContext.tenv in let method_type = add_reference_if_glvalue method_type_no_ref expr_info in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in - let trans_state_param = { trans_state_pri with succ_nodes = []; var_exp_typ = None } in + let trans_state_param = {trans_state_pri with succ_nodes= []; var_exp_typ= None} in let obj_c_message_expr_info, res_trans_subexpr_list = - objCMessageExpr_deal_with_static_self trans_state_param stmt_list obj_c_message_expr_info in + objCMessageExpr_deal_with_static_self trans_state_param stmt_list obj_c_message_expr_info + in let subexpr_exprs = collect_exprs res_trans_subexpr_list in - match objCMessageExpr_trans_special_cases trans_state si obj_c_message_expr_info - method_type trans_state_pri sil_loc subexpr_exprs with - | Some res -> res - | None -> - let procname = Procdesc.get_proc_name context.CContext.procdesc in - let callee_name, method_call_type = get_callee_objc_method context obj_c_message_expr_info - subexpr_exprs in - let res_trans_add_self = Self.add_self_parameter_for_super_instance context procname sil_loc - obj_c_message_expr_info in + match + objCMessageExpr_trans_special_cases trans_state si obj_c_message_expr_info method_type + trans_state_pri sil_loc subexpr_exprs + with + | Some res + -> res + | None + -> let procname = Procdesc.get_proc_name context.CContext.procdesc in + let callee_name, method_call_type = + get_callee_objc_method context obj_c_message_expr_info subexpr_exprs + in + let res_trans_add_self = + Self.add_self_parameter_for_super_instance context procname sil_loc + obj_c_message_expr_info + in let res_trans_subexpr_list = res_trans_add_self :: res_trans_subexpr_list in let subexpr_exprs = collect_exprs res_trans_subexpr_list in let is_virtual = - CMethod_trans.equal_method_call_type method_call_type CMethod_trans.MCVirtual in - Cg.add_edge context.CContext.cg procname callee_name; - + CMethod_trans.equal_method_call_type method_call_type CMethod_trans.MCVirtual + in + Cg.add_edge context.CContext.cg procname callee_name ; let param_exps, instr_block_param = - extract_block_from_tuple procname subexpr_exprs sil_loc in - let res_trans_block = { empty_res_trans with - instrs = instr_block_param; - } in - let call_flags = { CallFlags.default with CallFlags.cf_virtual = is_virtual; } in + extract_block_from_tuple procname subexpr_exprs sil_loc + in + let res_trans_block = {empty_res_trans with instrs= instr_block_param} in + let call_flags = {CallFlags.default with CallFlags.cf_virtual= is_virtual} in let method_sil = Exp.Const (Const.Cfun callee_name) in - let res_trans_call = create_call_instr trans_state method_type method_sil param_exps - sil_loc call_flags ~is_objc_method:true in + let res_trans_call = + create_call_instr trans_state method_type method_sil param_exps sil_loc call_flags + ~is_objc_method:true + in let selector = obj_c_message_expr_info.Clang_ast_t.omei_selector in - let nname = "Message Call: "^selector in + let nname = "Message Call: " ^ selector in let all_res_trans = res_trans_subexpr_list @ [res_trans_block; res_trans_call] in let res_trans_to_parent = - PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans in - { res_trans_to_parent with exps = res_trans_call.exps } - + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si all_res_trans + in + {res_trans_to_parent with exps= res_trans_call.exps} and dispatch_function_trans trans_state stmt_info stmt_list n = - L.(debug Capture Verbose) "@\n Call to a dispatch function treated as special case...@\n"; + L.(debug Capture Verbose) "@\n Call to a dispatch function treated as special case...@\n" ; let transformed_stmt = Ast_expressions.translate_dispatch_function stmt_info stmt_list n in instruction trans_state transformed_stmt and block_enumeration_trans trans_state stmt_info stmt_list ei = L.(debug Capture Verbose) - "@\n Call to a block enumeration function treated as special case...@\n@."; + "@\n Call to a block enumeration function treated as special case...@\n@." ; let procname = Procdesc.get_proc_name trans_state.context.CContext.procdesc in let pvar = CProcname.get_next_block_pvar procname in let transformed_stmt, _ = - Ast_expressions.translate_block_enumerate (Pvar.to_string pvar) stmt_info stmt_list ei in + Ast_expressions.translate_block_enumerate (Pvar.to_string pvar) stmt_info stmt_list ei + in instruction trans_state transformed_stmt - and compoundStmt_trans trans_state stmt_list = - instructions trans_state stmt_list + and compoundStmt_trans trans_state stmt_list = instructions trans_state stmt_list and conditionalOperator_trans trans_state stmt_info stmt_list expr_info = let context = trans_state.context in @@ -1133,74 +1257,81 @@ struct let sil_loc = CLocation.get_sil_location stmt_info context in let do_branch branch stmt var_typ prune_nodes join_node pvar = let trans_state_pri = PriorityNode.force_claim_priority_node trans_state stmt_info in - let trans_state' = { trans_state_pri with succ_nodes = [] } in + let trans_state' = {trans_state_pri with succ_nodes= []} in let res_trans_b = instruction trans_state' stmt in - let (e', _) = extract_exp_from_list res_trans_b.exps - "@\nWARNING: Missing branch expression for Conditional operator. Need to be fixed@\n" in - let set_temp_var = [ - Sil.Store (Exp.Lvar pvar, var_typ, e', sil_loc) - ] in - let tmp_var_res_trans = { empty_res_trans with instrs = set_temp_var } in - let trans_state'' = { trans_state' with succ_nodes = [join_node] } in + let e', _ = + extract_exp_from_list res_trans_b.exps + "@\nWARNING: Missing branch expression for Conditional operator. Need to be fixed@\n" + in + let set_temp_var = [Sil.Store (Exp.Lvar pvar, var_typ, e', sil_loc)] in + let tmp_var_res_trans = {empty_res_trans with instrs= set_temp_var} in + let trans_state'' = {trans_state' with succ_nodes= [join_node]} in let all_res_trans = [res_trans_b; tmp_var_res_trans] in - let res_trans = PriorityNode.compute_results_to_parent trans_state'' sil_loc - "ConditinalStmt Branch" stmt_info all_res_trans in + let res_trans = + PriorityNode.compute_results_to_parent trans_state'' sil_loc "ConditinalStmt Branch" + stmt_info all_res_trans + in let prune_nodes_t, prune_nodes_f = List.partition_tf ~f:is_true_prune_node prune_nodes in let prune_nodes' = if branch then prune_nodes_t else prune_nodes_f in List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n res_trans.root_nodes []) - prune_nodes' in - (match stmt_list with - | [cond; exp1; exp2] -> - let typ = - CType_decl.qual_type_to_sil_type - context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type in - let var_typ = add_reference_if_glvalue typ expr_info in - let join_node = create_node (Procdesc.Node.Join_node) [] sil_loc context in - Procdesc.node_set_succs_exn context.procdesc join_node succ_nodes []; - let pvar = mk_temp_sil_var procdesc "SIL_temp_conditional___" in - Procdesc.append_locals procdesc [(Pvar.get_name pvar, var_typ)]; - let continuation' = mk_cond_continuation trans_state.continuation in - let trans_state' = { trans_state with continuation = continuation'; succ_nodes = [] } in - let res_trans_cond = exec_with_priority_exception trans_state' cond cond_trans in - (* Note: by contruction prune nodes are leafs_nodes_cond *) - do_branch true exp1 var_typ res_trans_cond.leaf_nodes join_node pvar; - do_branch false exp2 var_typ res_trans_cond.leaf_nodes join_node pvar; - let id = Ident.create_fresh Ident.knormal in - let instrs = [Sil.Load (id, Exp.Lvar pvar, var_typ, sil_loc)] in - { empty_res_trans with - root_nodes = res_trans_cond.root_nodes; - leaf_nodes = [join_node]; - instrs = instrs; - exps = [(Exp.Var id, typ)]; - initd_exps = []; (* TODO we should get exps from branches+cond *) - } - | _ -> assert false) + prune_nodes' + in + match stmt_list with + | [cond; exp1; exp2] + -> let typ = + CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type + in + let var_typ = add_reference_if_glvalue typ expr_info in + let join_node = create_node Procdesc.Node.Join_node [] sil_loc context in + Procdesc.node_set_succs_exn context.procdesc join_node succ_nodes [] ; + let pvar = mk_temp_sil_var procdesc "SIL_temp_conditional___" in + Procdesc.append_locals procdesc [(Pvar.get_name pvar, var_typ)] ; + let continuation' = mk_cond_continuation trans_state.continuation in + let trans_state' = {trans_state with continuation= continuation'; succ_nodes= []} in + let res_trans_cond = exec_with_priority_exception trans_state' cond cond_trans in + (* Note: by contruction prune nodes are leafs_nodes_cond *) + do_branch true exp1 var_typ res_trans_cond.leaf_nodes join_node pvar ; + do_branch false exp2 var_typ res_trans_cond.leaf_nodes join_node pvar ; + let id = Ident.create_fresh Ident.knormal in + let instrs = [Sil.Load (id, Exp.Lvar pvar, var_typ, sil_loc)] in + { empty_res_trans with + root_nodes= res_trans_cond.root_nodes + ; leaf_nodes= [join_node] + ; instrs + ; exps= [(Exp.Var id, typ)] + ; initd_exps= [] (* TODO we should get exps from branches+cond *) } + | _ + -> assert false (* The GNU extension to the conditional operator which allows the middle operand to be omitted. *) and binaryConditionalOperator_trans trans_state stmt_info stmt_list expr_info = match stmt_list with | [stmt1; ostmt1; ostmt2; stmt2] - when contains_opaque_value_expr ostmt1 && contains_opaque_value_expr ostmt2 -> - let sil_loc = CLocation.get_sil_location stmt_info trans_state.context in + when contains_opaque_value_expr ostmt1 && contains_opaque_value_expr ostmt2 + -> let sil_loc = CLocation.get_sil_location stmt_info trans_state.context in let trans_state_pri = PriorityNode.force_claim_priority_node trans_state stmt_info in - let trans_state_cond = { trans_state_pri with - continuation = mk_cond_continuation trans_state_pri.continuation - } in + let trans_state_cond = + {trans_state_pri with continuation= mk_cond_continuation trans_state_pri.continuation} + in (* evaluate stmt1 once. Then, use it as replacement for OpaqueValueExpr*) (* when translating ostmt1 and ostmt2 *) let init_res_trans = instruction trans_state_cond stmt1 in let opaque_exp = extract_exp_from_list init_res_trans.exps "" in - let trans_state' = { trans_state_pri with opaque_exp = Some opaque_exp } in - let op_res_trans = conditionalOperator_trans trans_state' stmt_info - [ostmt1; ostmt2; stmt2] expr_info in - let trans_state'' = { trans_state_cond with succ_nodes = op_res_trans.root_nodes } in - let init_res_trans' = PriorityNode.compute_results_to_parent trans_state'' sil_loc - "BinaryConditinalStmt Init" stmt_info [init_res_trans] in + let trans_state' = {trans_state_pri with opaque_exp= Some opaque_exp} in + let op_res_trans = + conditionalOperator_trans trans_state' stmt_info [ostmt1; ostmt2; stmt2] expr_info + in + let trans_state'' = {trans_state_cond with succ_nodes= op_res_trans.root_nodes} in + let init_res_trans' = + PriorityNode.compute_results_to_parent trans_state'' sil_loc "BinaryConditinalStmt Init" + stmt_info [init_res_trans] + in let root_nodes = init_res_trans'.root_nodes in let root_nodes' = if root_nodes <> [] then root_nodes else op_res_trans.root_nodes in - { op_res_trans with root_nodes = root_nodes'; } - | _ -> L.(debug Capture Medium) "BinaryConditionalOperator not translated@."; + {op_res_trans with root_nodes= root_nodes'} + | _ + -> L.(debug Capture Medium) "BinaryConditionalOperator not translated@." ; assert false (* Translate a condition for if/loops statement. It shorts-circuit and/or. *) @@ -1210,21 +1341,20 @@ struct let context = trans_state.context in let si, _ = Clang_ast_proj.get_stmt_tuple cond in let sil_loc = CLocation.get_sil_location si context in - let mk_prune_node b e ins = - create_prune_node b e ins sil_loc (Sil.Ik_if) context in + let mk_prune_node b e ins = create_prune_node b e ins sil_loc Sil.Ik_if context in let extract_exp el = extract_exp_from_list el - "@\nWARNING: Missing expression for Conditional operator. Need to be fixed" in + "@\nWARNING: Missing expression for Conditional operator. Need to be fixed" + in (* this function translate cond without doing shortcircuit *) let no_short_circuit_cond ~is_cmp = - L.(debug Capture Verbose) " No short-circuit condition@\n"; + L.(debug Capture Verbose) " No short-circuit condition@\n" ; let res_trans_cond = - if is_null_stmt cond then { - empty_res_trans with exps = [(Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint Typ.IBool))] - } - (* Assumption: If it's a null_stmt, it is a loop with no bound, so we set condition to 1 *) - else - if is_cmp then + if is_null_stmt cond then + { empty_res_trans with + exps= [(Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint Typ.IBool))] } + (* Assumption: If it's a null_stmt, it is a loop with no bound, so we set condition to 1 *) + else if is_cmp then let open Clang_ast_t in (* If we have a comparision here, do not dispatch it to `instruction` function, which * invokes binaryOperator_trans_with_cond -> conditionalOperator_trans -> cond_trans. @@ -1234,28 +1364,27 @@ struct * she need to change both the codes here and the `match` in * binaryOperator_trans_with_cond *) match cond with - | BinaryOperator (si, ss, ei, boi) -> - binaryOperator_trans trans_state boi si ei ss - | _ -> instruction trans_state cond - else - instruction trans_state cond in + | BinaryOperator (si, ss, ei, boi) + -> binaryOperator_trans trans_state boi si ei ss + | _ + -> instruction trans_state cond + else instruction trans_state cond + in let e', instrs' = - define_condition_side_effects res_trans_cond.exps res_trans_cond.instrs sil_loc in + define_condition_side_effects res_trans_cond.exps res_trans_cond.instrs sil_loc + in let prune_t = mk_prune_node true e' instrs' in let prune_f = mk_prune_node false e' instrs' in List.iter ~f:(fun n' -> Procdesc.node_set_succs_exn context.procdesc n' [prune_t; prune_f] []) - res_trans_cond.leaf_nodes; - let rnodes = if Int.equal (List.length res_trans_cond.root_nodes) 0 then - [prune_t; prune_f] - else res_trans_cond.root_nodes in + res_trans_cond.leaf_nodes ; + let rnodes = + if Int.equal (List.length res_trans_cond.root_nodes) 0 then [prune_t; prune_f] + else res_trans_cond.root_nodes + in { empty_res_trans with - root_nodes = rnodes; - leaf_nodes = [prune_t; prune_f]; - instrs = instrs'; - exps = e'; - } in - + root_nodes= rnodes; leaf_nodes= [prune_t; prune_f]; instrs= instrs'; exps= e' } + in (* This function translate (s1 binop s2) doing shortcircuit for '&&' and '||' *) (* At the high level it does cond_trans s1; cond_trans s2; glue_nodes *) (* The glue_nodes partitions the prune nodes of s1's translation.*) @@ -1266,96 +1395,103 @@ struct let short_circuit binop s1 s2 = let res_trans_s1 = cond_trans trans_state s1 in let prune_nodes_t, prune_nodes_f = - List.partition_tf ~f:is_true_prune_node res_trans_s1.leaf_nodes in + List.partition_tf ~f:is_true_prune_node res_trans_s1.leaf_nodes + in let res_trans_s2 = cond_trans trans_state s2 in (* prune_to_s2 is the prune node that is connected with the root node of the *) (* translation of s2.*) (* prune_to_short_c is the prune node that is connected directly with the branch *) (* where the control flow goes in case of short circuit *) - let prune_to_s2, prune_to_short_c = (match binop with - | Binop.LAnd -> prune_nodes_t, prune_nodes_f - | Binop.LOr -> prune_nodes_f, prune_nodes_t - | _ -> assert false) in + let prune_to_s2, prune_to_short_c = + match binop with + | Binop.LAnd + -> (prune_nodes_t, prune_nodes_f) + | Binop.LOr + -> (prune_nodes_f, prune_nodes_t) + | _ + -> assert false + in List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n res_trans_s2.root_nodes []) - prune_to_s2; + prune_to_s2 ; let root_nodes_to_parent = - if Int.equal (List.length res_trans_s1.root_nodes) 0 - then res_trans_s1.leaf_nodes - else res_trans_s1.root_nodes in - let (exp1, typ1) = extract_exp res_trans_s1.exps in - let (exp2, _) = extract_exp res_trans_s2.exps in + if Int.equal (List.length res_trans_s1.root_nodes) 0 then res_trans_s1.leaf_nodes + else res_trans_s1.root_nodes + in + let exp1, typ1 = extract_exp res_trans_s1.exps in + let exp2, _ = extract_exp res_trans_s2.exps in let e_cond = Exp.BinOp (binop, exp1, exp2) in { empty_res_trans with - root_nodes = root_nodes_to_parent; - leaf_nodes = prune_to_short_c@res_trans_s2.leaf_nodes; - instrs = res_trans_s1.instrs@res_trans_s2.instrs; - exps = [(e_cond, typ1)]; - } in + root_nodes= root_nodes_to_parent + ; leaf_nodes= prune_to_short_c @ res_trans_s2.leaf_nodes + ; instrs= res_trans_s1.instrs @ res_trans_s2.instrs + ; exps= [(e_cond, typ1)] } + in L.(debug Capture Verbose) - "Translating Condition for If-then-else/Loop/Conditional Operator @\n"; + "Translating Condition for If-then-else/Loop/Conditional Operator @\n" ; let open Clang_ast_t in match cond with - | BinaryOperator(_, [s1; s2], _, boi) -> - (match boi.Clang_ast_t.boi_kind with - | `LAnd -> short_circuit (Binop.LAnd) s1 s2 - | `LOr -> short_circuit (Binop.LOr) s1 s2 - | `LT | `GT | `LE | `GE | `EQ | `NE -> - no_short_circuit_cond ~is_cmp:true - | _ -> no_short_circuit_cond ~is_cmp:false) - | ParenExpr(_,[s], _) -> (* condition can be wrapped in parenthesys *) + | BinaryOperator (_, [s1; s2], _, boi) -> ( + match boi.Clang_ast_t.boi_kind with + | `LAnd + -> short_circuit Binop.LAnd s1 s2 + | `LOr + -> short_circuit Binop.LOr s1 s2 + | `LT | `GT | `LE | `GE | `EQ | `NE + -> no_short_circuit_cond ~is_cmp:true + | _ + -> no_short_circuit_cond ~is_cmp:false ) + | ParenExpr (_, [s], _) + -> (* condition can be wrapped in parenthesys *) cond_trans trans_state s - | _ -> no_short_circuit_cond ~is_cmp:false + | _ + -> no_short_circuit_cond ~is_cmp:false and declStmt_in_condition_trans trans_state decl_stmt res_trans_cond = match decl_stmt with - | Clang_ast_t.DeclStmt(stmt_info, _, decl_list) -> - let trans_state_decl = { trans_state with - succ_nodes = res_trans_cond.root_nodes - } in + | Clang_ast_t.DeclStmt (stmt_info, _, decl_list) + -> let trans_state_decl = {trans_state with succ_nodes= res_trans_cond.root_nodes} in declStmt_trans trans_state_decl decl_list stmt_info - | _ -> res_trans_cond + | _ + -> res_trans_cond and ifStmt_trans trans_state stmt_info stmt_list = let context = trans_state.context in let succ_nodes = trans_state.succ_nodes in let sil_loc = CLocation.get_sil_location stmt_info context in - let join_node = create_node (Procdesc.Node.Join_node) [] sil_loc context in - Procdesc.node_set_succs_exn context.procdesc join_node succ_nodes []; - let trans_state' = { trans_state with succ_nodes = [join_node] } in + let join_node = create_node Procdesc.Node.Join_node [] sil_loc context in + Procdesc.node_set_succs_exn context.procdesc join_node succ_nodes [] ; + let trans_state' = {trans_state with succ_nodes= [join_node]} in let do_branch branch stmt_branch prune_nodes = (* leaf nodes are ignored here as they will be already attached to join_node *) let res_trans_b = instruction trans_state' stmt_branch in let nodes_branch = - (match res_trans_b.root_nodes with - | [] -> - [create_node - (Procdesc.Node.Stmt_node "IfStmt Branch") res_trans_b.instrs sil_loc context] - | _ -> - res_trans_b.root_nodes) in + match res_trans_b.root_nodes with + | [] + -> [ create_node (Procdesc.Node.Stmt_node "IfStmt Branch") res_trans_b.instrs sil_loc + context ] + | _ + -> res_trans_b.root_nodes + in let prune_nodes_t, prune_nodes_f = List.partition_tf ~f:is_true_prune_node prune_nodes in let prune_nodes' = if branch then prune_nodes_t else prune_nodes_f in List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n nodes_branch []) - prune_nodes' in - (match stmt_list with - | [_; decl_stmt; cond; stmt1; stmt2] -> - (* set the flat to inform that we are translating a condition of a if *) - let continuation' = mk_cond_continuation trans_state.continuation in - let trans_state'' = { trans_state with - continuation = continuation'; - succ_nodes = [] - } in - let res_trans_cond = cond_trans trans_state'' cond in - let res_trans_decl = declStmt_in_condition_trans trans_state decl_stmt res_trans_cond in - (* Note: by contruction prune nodes are leafs_nodes_cond *) - do_branch true stmt1 res_trans_cond.leaf_nodes; - do_branch false stmt2 res_trans_cond.leaf_nodes; - { empty_res_trans with - root_nodes = res_trans_decl.root_nodes; - leaf_nodes = [join_node]; - } - | _ -> assert false) + prune_nodes' + in + match stmt_list with + | [_; decl_stmt; cond; stmt1; stmt2] + -> (* set the flat to inform that we are translating a condition of a if *) + let continuation' = mk_cond_continuation trans_state.continuation in + let trans_state'' = {trans_state with continuation= continuation'; succ_nodes= []} in + let res_trans_cond = cond_trans trans_state'' cond in + let res_trans_decl = declStmt_in_condition_trans trans_state decl_stmt res_trans_cond in + (* Note: by contruction prune nodes are leafs_nodes_cond *) + do_branch true stmt1 res_trans_cond.leaf_nodes ; + do_branch false stmt2 res_trans_cond.leaf_nodes ; + {empty_res_trans with root_nodes= res_trans_decl.root_nodes; leaf_nodes= [join_node]} + | _ + -> assert false (* Assumption: the CompoundStmt can be made of different stmts, not just CaseStmts *) and switchStmt_trans trans_state stmt_info switch_stmt_list = @@ -1365,150 +1501,171 @@ struct let sil_loc = CLocation.get_sil_location stmt_info context in let open Clang_ast_t in match switch_stmt_list with - | [_; decl_stmt; cond; CompoundStmt(stmt_info, stmt_list)] -> - let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let trans_state' ={ trans_state_pri with succ_nodes = []} in + | [_; decl_stmt; cond; (CompoundStmt (stmt_info, stmt_list))] + -> let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let trans_state' = {trans_state_pri with succ_nodes= []} in let res_trans_cond_tmp = instruction trans_state' cond in let switch_special_cond_node = let node_kind = Procdesc.Node.Stmt_node "Switch_stmt" in - create_node node_kind res_trans_cond_tmp.instrs sil_loc context in + create_node node_kind res_trans_cond_tmp.instrs sil_loc context + in List.iter ~f:(fun n' -> - Procdesc.node_set_succs_exn context.procdesc n' [switch_special_cond_node] []) - res_trans_cond_tmp.leaf_nodes; + Procdesc.node_set_succs_exn context.procdesc n' [switch_special_cond_node] []) + res_trans_cond_tmp.leaf_nodes ; let root_nodes = if res_trans_cond_tmp.root_nodes <> [] then res_trans_cond_tmp.root_nodes - else [switch_special_cond_node] in - let (switch_e_cond', switch_e_cond'_typ) = + else [switch_special_cond_node] + in + let switch_e_cond', switch_e_cond'_typ = extract_exp_from_list res_trans_cond_tmp.exps - "@\nWARNING: The condition of the SwitchStmt is not singleton. Need to be fixed@\n" in - let res_trans_cond = { res_trans_cond_tmp with - root_nodes = root_nodes; - leaf_nodes = [switch_special_cond_node] - } in + "@\nWARNING: The condition of the SwitchStmt is not singleton. Need to be fixed@\n" + in + let res_trans_cond = + {res_trans_cond_tmp with root_nodes; leaf_nodes= [switch_special_cond_node]} + in let res_trans_decl = declStmt_in_condition_trans trans_state decl_stmt res_trans_cond in let trans_state_no_pri = - if PriorityNode.own_priority_node trans_state_pri.priority stmt_info - then - { trans_state_pri with priority = Free } - else trans_state_pri in + if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then + {trans_state_pri with priority= Free} + else trans_state_pri + in let switch_exit_point = succ_nodes in let continuation' = match continuation with - | Some cont -> Some { cont with break = switch_exit_point } - | None -> Some { break = switch_exit_point; continue = []; return_temp = false } in - let trans_state'' = { trans_state_no_pri with continuation = continuation'} in - let merge_into_cases stmt_list = (* returns list_of_cases * before_any_case_instrs *) + | Some cont + -> Some {cont with break= switch_exit_point} + | None + -> Some {break= switch_exit_point; continue= []; return_temp= false} + in + let trans_state'' = {trans_state_no_pri with continuation= continuation'} in + let merge_into_cases stmt_list = + (* returns list_of_cases * before_any_case_instrs *) let rec aux rev_stmt_list acc cases = - (match rev_stmt_list with - | CaseStmt (info, a :: b :: CaseStmt x :: c) :: rest -> (* case x: case y: ... *) - if c <> [] - (* empty case with nested case, then followed by some instructions *) - then assert false; - let rest' = CaseStmt (info, a :: b :: []) :: rest in - let rev_stmt_list' = (CaseStmt x) :: rest' in - aux rev_stmt_list' acc cases - | CaseStmt (info, a :: b :: DefaultStmt x :: c) :: rest -> - (* case x: default: ... *) - if c <> [] - (* empty case with nested case, then followed by some instructions *) - then assert false; - let rest' = CaseStmt(info, a :: b :: []) :: rest in - let rev_stmt_list' = DefaultStmt x :: rest' in - aux rev_stmt_list' acc cases - | DefaultStmt (info, CaseStmt x :: c) :: rest -> (* default: case x: ... *) - if c <> [] - (* empty case with nested case, then followed by some instructions *) - then assert false; - let rest' = DefaultStmt (info, []) :: rest in - let rev_stmt_list' = CaseStmt x :: rest' in - aux rev_stmt_list' acc cases - | CaseStmt (info, a :: b :: c) :: rest -> - aux rest [] (CaseStmt (info, a :: b :: c @ acc) :: cases) - | DefaultStmt (info, c) :: rest -> (* default is always the last in the list *) - aux rest [] (DefaultStmt(info, c @ acc) :: cases) - | x :: rest -> - aux rest (x :: acc) cases - | [] -> - cases, acc) in - aux (List.rev stmt_list) [] [] in + match rev_stmt_list with + | (CaseStmt (info, a :: b :: (CaseStmt x) :: c)) :: rest + -> (* case x: case y: ... *) + if c <> [] (* empty case with nested case, then followed by some instructions *) + then assert false ; + let rest' = CaseStmt (info, [a; b]) :: rest in + let rev_stmt_list' = CaseStmt x :: rest' in + aux rev_stmt_list' acc cases + | (CaseStmt (info, a :: b :: (DefaultStmt x) :: c)) :: rest + -> (* case x: default: ... *) + if c <> [] (* empty case with nested case, then followed by some instructions *) + then assert false ; + let rest' = CaseStmt (info, [a; b]) :: rest in + let rev_stmt_list' = DefaultStmt x :: rest' in + aux rev_stmt_list' acc cases + | (DefaultStmt (info, (CaseStmt x) :: c)) :: rest + -> (* default: case x: ... *) + if c <> [] (* empty case with nested case, then followed by some instructions *) + then assert false ; + let rest' = DefaultStmt (info, []) :: rest in + let rev_stmt_list' = CaseStmt x :: rest' in + aux rev_stmt_list' acc cases + | (CaseStmt (info, a :: b :: c)) :: rest + -> aux rest [] (CaseStmt (info, a :: b :: c @ acc) :: cases) + | (DefaultStmt (info, c)) :: rest + -> (* default is always the last in the list *) + aux rest [] (DefaultStmt (info, c @ acc) :: cases) + | x :: rest + -> aux rest (x :: acc) cases + | [] + -> (cases, acc) + in + aux (List.rev stmt_list) [] [] + in let list_of_cases, pre_case_stmts = merge_into_cases stmt_list in let rec connected_instruction rev_instr_list successor_nodes = (* returns the entry point of the translated set of instr *) match rev_instr_list with - | [] -> successor_nodes - | instr :: rest -> - let trans_state''' = { trans_state'' with succ_nodes = successor_nodes } in + | [] + -> successor_nodes + | instr :: rest + -> let trans_state''' = {trans_state'' with succ_nodes= successor_nodes} in let res_trans_instr = instruction trans_state''' instr in let instr_entry_points = res_trans_instr.root_nodes in - connected_instruction rest instr_entry_points in + connected_instruction rest instr_entry_points + in let rec translate_and_connect_cases cases next_nodes next_prune_nodes = let create_prune_nodes_for_case case = match case with - | CaseStmt (stmt_info, case_const :: _ :: _) -> - let trans_state_pri = - PriorityNode.try_claim_priority_node trans_state'' stmt_info in + | CaseStmt (stmt_info, case_const :: _ :: _) + -> let trans_state_pri = + PriorityNode.try_claim_priority_node trans_state'' stmt_info + in let res_trans_case_const = instruction trans_state_pri case_const in let e_const = res_trans_case_const.exps in - let e_const' = - match e_const with - | [(head, _)] -> head - | _ -> assert false in + let e_const' = match e_const with [(head, _)] -> head | _ -> assert false in let sil_eq_cond = Exp.BinOp (Binop.Eq, switch_e_cond', e_const') in let sil_loc = CLocation.get_sil_location stmt_info context in let true_prune_node = create_prune_node true [(sil_eq_cond, switch_e_cond'_typ)] - res_trans_case_const.instrs sil_loc Sil.Ik_switch context in + res_trans_case_const.instrs sil_loc Sil.Ik_switch context + in let false_prune_node = create_prune_node false [(sil_eq_cond, switch_e_cond'_typ)] - res_trans_case_const.instrs sil_loc Sil.Ik_switch context in + res_trans_case_const.instrs sil_loc Sil.Ik_switch context + in (true_prune_node, false_prune_node) - | _ -> assert false in - match cases with (* top-down to handle default cases *) - | [] -> next_nodes, next_prune_nodes - | CaseStmt(_, _ :: _ :: case_content) as case :: rest -> - let last_nodes, last_prune_nodes = - translate_and_connect_cases rest next_nodes next_prune_nodes in + | _ + -> assert false + in + match cases with + | (* top-down to handle default cases *) + [] + -> (next_nodes, next_prune_nodes) + | (CaseStmt (_, _ :: _ :: case_content) as case) :: rest + -> let last_nodes, last_prune_nodes = + translate_and_connect_cases rest next_nodes next_prune_nodes + in let case_entry_point = connected_instruction (List.rev case_content) last_nodes in (* connects between cases, then continuation has priority about breaks *) let prune_node_t, prune_node_f = create_prune_nodes_for_case case in - Procdesc.node_set_succs_exn context.procdesc prune_node_t case_entry_point []; - Procdesc.node_set_succs_exn context.procdesc prune_node_f last_prune_nodes []; - case_entry_point, [prune_node_t; prune_node_f] - | DefaultStmt(stmt_info, default_content) :: rest -> - let sil_loc = CLocation.get_sil_location stmt_info context in + Procdesc.node_set_succs_exn context.procdesc prune_node_t case_entry_point [] ; + Procdesc.node_set_succs_exn context.procdesc prune_node_f last_prune_nodes [] ; + (case_entry_point, [prune_node_t; prune_node_f]) + | (DefaultStmt (stmt_info, default_content)) :: rest + -> let sil_loc = CLocation.get_sil_location stmt_info context in let placeholder_entry_point = - create_node - (Procdesc.Node.Stmt_node "DefaultStmt_placeholder") [] sil_loc context in + create_node (Procdesc.Node.Stmt_node "DefaultStmt_placeholder") [] sil_loc context + in let last_nodes, last_prune_nodes = - translate_and_connect_cases rest next_nodes [placeholder_entry_point] in + translate_and_connect_cases rest next_nodes [placeholder_entry_point] + in let default_entry_point = - connected_instruction (List.rev default_content) last_nodes in - Procdesc.node_set_succs_exn - context.procdesc placeholder_entry_point default_entry_point []; - default_entry_point, last_prune_nodes - | _ -> assert false in + connected_instruction (List.rev default_content) last_nodes + in + Procdesc.node_set_succs_exn context.procdesc placeholder_entry_point + default_entry_point [] ; + (default_entry_point, last_prune_nodes) + | _ + -> assert false + in let top_entry_point, top_prune_nodes = - translate_and_connect_cases list_of_cases succ_nodes succ_nodes in + translate_and_connect_cases list_of_cases succ_nodes succ_nodes + in let _ = connected_instruction (List.rev pre_case_stmts) top_entry_point in - Procdesc.node_set_succs_exn - context.procdesc switch_special_cond_node top_prune_nodes []; + Procdesc.node_set_succs_exn context.procdesc switch_special_cond_node top_prune_nodes [] ; let top_nodes = res_trans_decl.root_nodes in - List.iter - ~f:(fun n' -> Procdesc.Node.append_instrs n' []) succ_nodes; + List.iter ~f:(fun n' -> Procdesc.Node.append_instrs n' []) succ_nodes ; (* succ_nodes will remove the temps *) - { empty_res_trans with root_nodes = top_nodes; leaf_nodes = succ_nodes } - | _ -> assert false + {empty_res_trans with root_nodes= top_nodes; leaf_nodes= succ_nodes} + | _ + -> assert false and stmtExpr_trans trans_state stmt_list = let stmt = - extract_stmt_from_singleton stmt_list "ERROR: StmtExpr should have only one statement.@\n" in + extract_stmt_from_singleton stmt_list "ERROR: StmtExpr should have only one statement.@\n" + in let res_trans_stmt = instruction trans_state stmt in let exps' = List.rev res_trans_stmt.exps in match exps' with - | last_exp :: _ -> - { res_trans_stmt with exps = [last_exp]; } - | [] -> res_trans_stmt + | last_exp :: _ + -> {res_trans_stmt with exps= [last_exp]} + | [] + -> res_trans_stmt and loop_instruction trans_state loop_kind stmt_info = let outer_continuation = trans_state.continuation in @@ -1516,76 +1673,93 @@ struct let succ_nodes = trans_state.succ_nodes in let sil_loc = CLocation.get_sil_location stmt_info context in let join_node = create_node Procdesc.Node.Join_node [] sil_loc context in - let continuation = Some { break = succ_nodes; continue = [join_node]; return_temp = false } in + let continuation = Some {break= succ_nodes; continue= [join_node]; return_temp= false} in (* set the flat to inform that we are translating a condition of a if *) let continuation_cond = mk_cond_continuation outer_continuation in let init_incr_nodes = match loop_kind with - | Loops.For (init, _, _, incr, _) -> - let trans_state' = { - trans_state with - succ_nodes = [join_node]; - continuation = continuation; - } in + | Loops.For (init, _, _, incr, _) + -> let trans_state' = {trans_state with succ_nodes= [join_node]; continuation} in let res_trans_init = instruction trans_state' init in let res_trans_incr = instruction trans_state' incr in Some (res_trans_init.root_nodes, res_trans_incr.root_nodes) - | _ -> None in + | _ + -> None + in let cond_stmt = Loops.get_cond loop_kind in - let trans_state_cond = { - trans_state with - continuation = continuation_cond; - succ_nodes = []; - } in + let trans_state_cond = {trans_state with continuation= continuation_cond; succ_nodes= []} in let res_trans_cond = cond_trans trans_state_cond cond_stmt in - let decl_stmt_opt = match loop_kind with - | Loops.For (_, decl_stmt, _, _, _) -> Some decl_stmt - | Loops.While (decl_stmt_opt, _, _) -> decl_stmt_opt - | _ -> None in - let res_trans_decl = match decl_stmt_opt with - | Some decl_stmt -> declStmt_in_condition_trans trans_state decl_stmt res_trans_cond - | _ -> res_trans_cond in + let decl_stmt_opt = + match loop_kind with + | Loops.For (_, decl_stmt, _, _, _) + -> Some decl_stmt + | Loops.While (decl_stmt_opt, _, _) + -> decl_stmt_opt + | _ + -> None + in + let res_trans_decl = + match decl_stmt_opt with + | Some decl_stmt + -> declStmt_in_condition_trans trans_state decl_stmt res_trans_cond + | _ + -> res_trans_cond + in let body_succ_nodes = match loop_kind with - | Loops.For _ -> (match init_incr_nodes with - | Some (_, nodes_incr) -> nodes_incr - | None -> assert false) - | Loops.While _ -> [join_node] - | Loops.DoWhile _ -> res_trans_cond.root_nodes in - let body_continuation = match continuation, init_incr_nodes with - | Some c, Some (_, nodes_incr) -> - Some { c with continue = nodes_incr } - | _ -> continuation in + | Loops.For _ -> ( + match init_incr_nodes with Some (_, nodes_incr) -> nodes_incr | None -> assert false ) + | Loops.While _ + -> [join_node] + | Loops.DoWhile _ + -> res_trans_cond.root_nodes + in + let body_continuation = + match (continuation, init_incr_nodes) with + | Some c, Some (_, nodes_incr) + -> Some {c with continue= nodes_incr} + | _ + -> continuation + in let res_trans_body = let trans_state_body = - { trans_state with - succ_nodes = body_succ_nodes; - continuation = body_continuation } in - instruction trans_state_body (Loops.get_body loop_kind) in + {trans_state with succ_nodes= body_succ_nodes; continuation= body_continuation} + in + instruction trans_state_body (Loops.get_body loop_kind) + in let join_succ_nodes = match loop_kind with - | Loops.For _ | Loops.While _ -> res_trans_decl.root_nodes - | Loops.DoWhile _ -> res_trans_body.root_nodes in + | Loops.For _ | Loops.While _ + -> res_trans_decl.root_nodes + | Loops.DoWhile _ + -> res_trans_body.root_nodes + in (* Note: prune nodes are by contruction the res_trans_cond.leaf_nodes *) let prune_nodes_t, prune_nodes_f = - List.partition_tf ~f:is_true_prune_node res_trans_cond.leaf_nodes in + List.partition_tf ~f:is_true_prune_node res_trans_cond.leaf_nodes + in let prune_t_succ_nodes = match loop_kind with - | Loops.For _ | Loops.While _ -> res_trans_body.root_nodes - | Loops.DoWhile _ -> [join_node] in - Procdesc.node_set_succs_exn context.procdesc join_node join_succ_nodes []; + | Loops.For _ | Loops.While _ + -> res_trans_body.root_nodes + | Loops.DoWhile _ + -> [join_node] + in + Procdesc.node_set_succs_exn context.procdesc join_node join_succ_nodes [] ; List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n prune_t_succ_nodes []) - prune_nodes_t; + prune_nodes_t ; List.iter ~f:(fun n -> Procdesc.node_set_succs_exn context.procdesc n succ_nodes []) - prune_nodes_f; + prune_nodes_f ; let root_nodes = match loop_kind with - | Loops.For _ -> - (match init_incr_nodes with | Some (nodes_init, _) -> nodes_init | None -> assert false) - | Loops.While _ | Loops.DoWhile _ -> [join_node] in - { empty_res_trans with root_nodes = root_nodes; leaf_nodes = prune_nodes_f } + | Loops.For _ -> ( + match init_incr_nodes with Some (nodes_init, _) -> nodes_init | None -> assert false ) + | Loops.While _ | Loops.DoWhile _ + -> [join_node] + in + {empty_res_trans with root_nodes; leaf_nodes= prune_nodes_f} and forStmt_trans trans_state init decl_stmt cond incr body stmt_info = let for_kind = Loops.For (init, decl_stmt, cond, incr, body) in @@ -1599,7 +1773,6 @@ struct let dowhile_kind = Loops.DoWhile (cond, body) in loop_instruction trans_state dowhile_kind stmt_info - (* Iteration over colection for (v : C) { body; } @@ -1618,14 +1791,16 @@ struct and cxxForRangeStmt_trans trans_state stmt_info stmt_list = let open Clang_ast_t in match stmt_list with - | [iterator_decl; begin_stmt; end_stmt; exit_cond; increment; assign_current_index; loop_body] -> - let loop_body' = CompoundStmt (stmt_info, [assign_current_index; loop_body]) in + | [iterator_decl; begin_stmt; end_stmt; exit_cond; increment; assign_current_index; loop_body] + -> let loop_body' = CompoundStmt (stmt_info, [assign_current_index; loop_body]) in let null_stmt = NullStmt (stmt_info, []) in let beginend_stmt = CompoundStmt (stmt_info, [begin_stmt; end_stmt]) in let for_loop = - ForStmt (stmt_info, [beginend_stmt; null_stmt; exit_cond; increment; loop_body']) in + ForStmt (stmt_info, [beginend_stmt; null_stmt; exit_cond; increment; loop_body']) + in instruction trans_state (CompoundStmt (stmt_info, [iterator_decl; for_loop])) - | _ -> assert false + | _ + -> assert false (* Fast iteration for colection for (type_it i in collection) { body } @@ -1641,40 +1816,49 @@ struct (* variable item but we still need to add the variable to the locals *) let assign_next_object, cond = Ast_expressions.make_next_object_exp stmt_info item items in let body' = Clang_ast_t.CompoundStmt (stmt_info, [body; assign_next_object]) in - let null_stmt = Clang_ast_t.NullStmt (stmt_info,[]) in + let null_stmt = Clang_ast_t.NullStmt (stmt_info, []) in let loop = Clang_ast_t.WhileStmt (stmt_info, [null_stmt; cond; body']) in instruction trans_state (Clang_ast_t.CompoundStmt (stmt_info, [assign_next_object; loop])) and initListExpr_trans trans_state stmt_info expr_info stmts = let context = trans_state.context in let tenv = context.tenv in - let is_array typ = match typ.Typ.desc with | Typ.Tarray _ -> true | _ -> false in - let (var_exp, typ) = + let is_array typ = match typ.Typ.desc with Typ.Tarray _ -> true | _ -> false in + let var_exp, typ = match trans_state.var_exp_typ with - | Some var_exp_typ -> var_exp_typ - | None -> create_var_exp_tmp_var trans_state expr_info "SIL_init_list__" in - let trans_state = { trans_state with var_exp_typ = Some (var_exp, typ) } in + | Some var_exp_typ + -> var_exp_typ + | None + -> create_var_exp_tmp_var trans_state expr_info "SIL_init_list__" + in + let trans_state = {trans_state with var_exp_typ= Some (var_exp, typ)} in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in let sil_loc = CLocation.get_sil_location stmt_info context in let var_type = - CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type in + CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type + in let lh = var_or_zero_in_init_list tenv var_exp var_type ~return_zero:false in let res_trans_subexpr_list = - initListExpr_initializers_trans trans_state var_exp 0 stmts typ false stmt_info in + initListExpr_initializers_trans trans_state var_exp 0 stmts typ false stmt_info + in let rh_exps = collect_exprs res_trans_subexpr_list in if Int.equal (List.length rh_exps) 0 then let exps = match Sil.zero_value_of_numerical_type_option var_type with - | Some zero_exp -> [(zero_exp, typ)] - | None -> [] in - { empty_res_trans with root_nodes = trans_state.succ_nodes; exps = exps; } + | Some zero_exp + -> [(zero_exp, typ)] + | None + -> [] + in + {empty_res_trans with root_nodes= trans_state.succ_nodes; exps} else (* For arrays, the size in the type may be an overapproximation of the number *) (* of literals the array is initialized with *) let lh = if is_array var_type && List.length lh > List.length rh_exps then List.take lh (List.length rh_exps) - else lh in + else lh + in if Int.equal (List.length rh_exps) (List.length lh) then (* Creating new instructions by assigning right hand side to left hand side expressions *) let assign_instr (lh_exp, lh_t) (rh_exp, _) = Sil.Store (lh_exp, lh_t, rh_exp, sil_loc) in @@ -1684,58 +1868,67 @@ struct (* by some constructor call, which we can tell by the fact that the index is returned *) (* in initd_exps, then we assume that all the indices were initialized and *) (* we don't need any assignments. *) - if List.exists - ~f:((fun arr index -> Exp.is_array_index_of index arr) var_exp) - initd_exps + if List.exists ~f:((fun arr index -> Exp.is_array_index_of index arr) var_exp) initd_exps then [] - else List.map2_exn ~f:assign_instr lh rh_exps in + else List.map2_exn ~f:assign_instr lh rh_exps + in let initlist_expr_res = { empty_res_trans with - exps = [(var_exp, var_type)]; - initd_exps = [var_exp]; - instrs = assign_instrs; - } in + exps= [(var_exp, var_type)]; initd_exps= [var_exp]; instrs= assign_instrs } + in let all_res_trans = res_trans_subexpr_list @ [initlist_expr_res] in let nname = "InitListExp" in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - nname stmt_info all_res_trans in - { res_trans_to_parent with exps = initlist_expr_res.exps } - else (* If the right hand expressions are not as many as the left hand expressions *) + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname stmt_info + all_res_trans + in + {res_trans_to_parent with exps= initlist_expr_res.exps} + else + (* If the right hand expressions are not as many as the left hand expressions *) (* something's wrong *) - { empty_res_trans with root_nodes = trans_state.succ_nodes } + {empty_res_trans with root_nodes= trans_state.succ_nodes} and init_dynamic_array trans_state array_exp_typ array_stmt_info dynlength_stmt_pointer = - let dynlength_stmt = Int.Table.find_exn ClangPointers.pointer_stmt_table - dynlength_stmt_pointer in + let dynlength_stmt = + Int.Table.find_exn ClangPointers.pointer_stmt_table dynlength_stmt_pointer + in let dynlength_stmt_info, _ = Clang_ast_proj.get_stmt_tuple dynlength_stmt in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state array_stmt_info in let dynlength_trans_result = instruction trans_state_pri dynlength_stmt in - let dynlength_exp_typ = extract_exp_from_list dynlength_trans_result.exps - "WARNING: There should be one expression.@\n" in + let dynlength_exp_typ = + extract_exp_from_list dynlength_trans_result.exps + "WARNING: There should be one expression.@\n" + in let sil_loc = CLocation.get_sil_location dynlength_stmt_info trans_state_pri.context in let call_instr = let call_exp = Exp.Const (Const.Cfun BuiltinDecl.__set_array_length) in let actuals = [array_exp_typ; dynlength_exp_typ] in - Sil.Call (None, call_exp, actuals, sil_loc, CallFlags.default) in - let call_trans_result = { empty_res_trans with instrs = [call_instr] } in - let res_trans = PriorityNode.compute_results_to_parent trans_state_pri sil_loc + Sil.Call (None, call_exp, actuals, sil_loc, CallFlags.default) + in + let call_trans_result = {empty_res_trans with instrs= [call_instr]} in + let res_trans = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "Initialize dynamic array length" dynlength_stmt_info - [dynlength_trans_result; call_trans_result] in - { res_trans with exps = [] } + [dynlength_trans_result; call_trans_result] + in + {res_trans with exps= []} and init_expr_trans trans_state var_exp_typ ?qual_type var_stmt_info init_expr_opt = match init_expr_opt with | None -> ( - match Option.map ~f:(fun qt -> qt.Clang_ast_t.qt_type_ptr) qual_type - |> Option.find_map ~f:CAst_utils.get_type with - | Some (Clang_ast_t.VariableArrayType (_, _, stmt_pointer)) -> - (* Set the dynamic length of the variable length array. Variable length array cannot + match + Option.map ~f:(fun qt -> qt.Clang_ast_t.qt_type_ptr) qual_type + |> Option.find_map ~f:CAst_utils.get_type + with + | Some Clang_ast_t.VariableArrayType (_, _, stmt_pointer) + -> (* Set the dynamic length of the variable length array. Variable length array cannot have an initialization expression. *) - init_dynamic_array trans_state var_exp_typ var_stmt_info stmt_pointer - | _ -> - (* Nothing to do if no init expression and not a variable length array *) - { empty_res_trans with root_nodes = trans_state.succ_nodes}) - | Some ie -> (*For init expr, translate how to compute it and assign to the var*) + init_dynamic_array trans_state var_exp_typ var_stmt_info stmt_pointer + | _ + -> (* Nothing to do if no init expression and not a variable length array *) + {empty_res_trans with root_nodes= trans_state.succ_nodes} ) + | Some ie + -> (*For init expr, translate how to compute it and assign to the var*) let var_exp, _ = var_exp_typ in let context = trans_state.context in let sil_loc = CLocation.get_sil_location var_stmt_info context in @@ -1743,37 +1936,43 @@ struct (* if ie is a block the translation need to be done with the block special cases by exec_with_block_priority *) let res_trans_ie = - let trans_state' = { trans_state_pri with - succ_nodes = []; - var_exp_typ = Some var_exp_typ } in + let trans_state' = + {trans_state_pri with succ_nodes= []; var_exp_typ= Some var_exp_typ} + in let instruction' = - exec_with_self_exception (exec_with_glvalue_as_reference instruction) in - exec_with_block_priority_exception instruction' trans_state' ie var_stmt_info in - let (sil_e1', ie_typ) = extract_exp_from_list res_trans_ie.exps - "WARNING: In DeclStmt we expect only one expression returned in recursive call@\n" in + exec_with_self_exception (exec_with_glvalue_as_reference instruction) + in + exec_with_block_priority_exception instruction' trans_state' ie var_stmt_info + in + let sil_e1', ie_typ = + extract_exp_from_list res_trans_ie.exps + "WARNING: In DeclStmt we expect only one expression returned in recursive call@\n" + in let rhs_owning_method = CTrans_utils.is_owning_method ie in let _, instrs_assign = (* variable might be initialized already - do nothing in that case*) if List.exists ~f:(Exp.equal var_exp) res_trans_ie.initd_exps then ([], []) - else if !Config.arc_mode && - (CTrans_utils.is_method_call ie || - ObjcInterface_decl.is_pointer_to_objc_class ie_typ) + else if !Config.arc_mode + && ( CTrans_utils.is_method_call ie + || ObjcInterface_decl.is_pointer_to_objc_class ie_typ ) then (* In arc mode, if it's a method call or we are initializing with a pointer to objc class *) (* we need to add retain/release *) - let (e, instrs) = - CArithmetic_trans.assignment_arc_mode - var_exp ie_typ sil_e1' sil_loc rhs_owning_method true in + let e, instrs = + CArithmetic_trans.assignment_arc_mode var_exp ie_typ sil_e1' sil_loc + rhs_owning_method true + in ([(e, ie_typ)], instrs) - else - ([], [Sil.Store (var_exp, ie_typ, sil_e1', sil_loc)]) in - let res_trans_assign = { empty_res_trans with - instrs = instrs_assign } in + else ([], [Sil.Store (var_exp, ie_typ, sil_e1', sil_loc)]) + in + let res_trans_assign = {empty_res_trans with instrs= instrs_assign} in let all_res_trans = [res_trans_ie; res_trans_assign] in - let res_trans = PriorityNode.compute_results_to_parent trans_state_pri sil_loc "DeclStmt" - var_stmt_info all_res_trans in - { res_trans with exps = [(var_exp, ie_typ)] } + let res_trans = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "DeclStmt" var_stmt_info + all_res_trans + in + {res_trans with exps= [(var_exp, ie_typ)]} and collect_all_decl trans_state var_decls next_nodes stmt_info = let open Clang_ast_t in @@ -1784,28 +1983,31 @@ struct let var_decl = VarDecl (di, var_name, qual_type, vdi) in let pvar = CVar_decl.sil_var_of_decl context var_decl procname in let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in - CVar_decl.add_var_to_locals procdesc var_decl typ pvar; - let trans_state' = { trans_state with succ_nodes = next_node } in + CVar_decl.add_var_to_locals procdesc var_decl typ pvar ; + let trans_state' = {trans_state with succ_nodes= next_node} in init_expr_trans trans_state' (Exp.Lvar pvar, typ) ~qual_type stmt_info - vdi.Clang_ast_t.vdi_init_expr in - + vdi.Clang_ast_t.vdi_init_expr + in match var_decls with - | [] -> { empty_res_trans with root_nodes = next_nodes } - | VarDecl (di, n, qt, vdi) :: var_decls' -> - (* Var are defined when procdesc is created, here we only take care of initialization*) + | [] + -> {empty_res_trans with root_nodes= next_nodes} + | (VarDecl (di, n, qt, vdi)) :: var_decls' + -> (* Var are defined when procdesc is created, here we only take care of initialization*) let res_trans_vd = collect_all_decl trans_state var_decls' next_nodes stmt_info in let res_trans_tmp = do_var_dec (di, n, qt, vdi) res_trans_vd.root_nodes in { empty_res_trans with - root_nodes = res_trans_tmp.root_nodes; leaf_nodes = []; - instrs = res_trans_tmp.instrs @ res_trans_vd.instrs; - exps = []; - initd_exps = res_trans_tmp.initd_exps @ res_trans_vd.initd_exps; - } - | CXXRecordDecl _ :: var_decls' (*C++/C record decl treated in the same way *) - | RecordDecl _ :: var_decls' -> - (* Record declaration is done in the beginning when procdesc is defined.*) + root_nodes= res_trans_tmp.root_nodes + ; leaf_nodes= [] + ; instrs= res_trans_tmp.instrs @ res_trans_vd.instrs + ; exps= [] + ; initd_exps= res_trans_tmp.initd_exps @ res_trans_vd.initd_exps } + | (CXXRecordDecl _) :: var_decls' + (*C++/C record decl treated in the same way *) + | (RecordDecl _) :: var_decls' + -> (* Record declaration is done in the beginning when procdesc is defined.*) collect_all_decl trans_state var_decls' next_nodes stmt_info - | _ -> assert false + | _ + -> assert false (* stmt_list is ignored because it contains the same instructions as *) (* the init expression. We use the latter info. *) @@ -1814,33 +2016,36 @@ struct let res_trans = let open Clang_ast_t in match decl_list with - | VarDecl _ :: _ -> (* Case for simple variable declarations*) + | (VarDecl _) :: _ + -> (* Case for simple variable declarations*) collect_all_decl trans_state decl_list succ_nodes stmt_info - | CXXRecordDecl _ :: _ (*C++/C record decl treated in the same way *) - | RecordDecl _ :: _ -> (* Case for struct *) + | (CXXRecordDecl _) :: _ (*C++/C record decl treated in the same way *) | (RecordDecl _) :: _ + -> (* Case for struct *) collect_all_decl trans_state decl_list succ_nodes stmt_info - | _ -> - L.(debug Capture Medium) - "WARNING: In DeclStmt found an unknown declaration type. \ - RETURNING empty list of declaration. NEED TO BE FIXED"; - empty_res_trans in - { res_trans with leaf_nodes = [] } + | _ + -> L.(debug Capture Medium) + "WARNING: In DeclStmt found an unknown declaration type. RETURNING empty list of declaration. NEED TO BE FIXED" ; + empty_res_trans + in + {res_trans with leaf_nodes= []} and objCPropertyRefExpr_trans trans_state stmt_list = - match stmt_list with - | [stmt] -> instruction trans_state stmt - | _ -> assert false + match stmt_list with [stmt] -> instruction trans_state stmt | _ -> assert false (* For OpaqueValueExpr we return the translation generated from its source expression*) and opaqueValueExpr_trans trans_state opaque_value_expr_info = - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; match trans_state.opaque_exp with - | Some exp -> { empty_res_trans with exps = [exp] } + | Some exp + -> {empty_res_trans with exps= [exp]} | _ -> - (match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with - | Some stmt -> instruction trans_state stmt - | _ -> assert false) + match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with + | Some stmt + -> instruction trans_state stmt + | _ + -> assert false (* NOTE: This translation has several hypothesis. Need to be verified when we have more*) (* experience with this construct. Assert false will help to see if we encounter programs*) @@ -1861,50 +2066,61 @@ struct (* to translate the CallToSetter which is how x.f = a is actually implemented by the runtime.*) and pseudoObjectExpr_trans trans_state stmt_list = - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; let rec do_semantic_elements el = let open Clang_ast_t in match el with - | OpaqueValueExpr _ :: el' -> do_semantic_elements el' - | stmt :: _ -> instruction trans_state stmt - | _ -> assert false in + | (OpaqueValueExpr _) :: el' + -> do_semantic_elements el' + | stmt :: _ + -> instruction trans_state stmt + | _ + -> assert false + in match stmt_list with - | _ :: semantic_form -> - do_semantic_elements semantic_form - | _ -> assert false + | _ :: semantic_form + -> do_semantic_elements semantic_form + | _ + -> assert false (* Cast expression are treated the same apart from the cast operation kind*) and cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_expr_info = let context = trans_state.context in - L.(debug Capture Verbose) " priority node free = '%s'@\n@." - (string_of_bool (PriorityNode.is_priority_free trans_state)); + L.(debug Capture Verbose) + " priority node free = '%s'@\n@." + (string_of_bool (PriorityNode.is_priority_free trans_state)) ; let sil_loc = CLocation.get_sil_location stmt_info context in - let stmt = extract_stmt_from_singleton stmt_list - "WARNING: In CastExpr There must be only one stmt defining the expression to be cast.@\n" in + let stmt = + extract_stmt_from_singleton stmt_list + "WARNING: In CastExpr There must be only one stmt defining the expression to be cast.@\n" + in let res_trans_stmt = instruction trans_state stmt in let typ = - CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type in + CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type + in let cast_kind = cast_expr_info.Clang_ast_t.cei_cast_kind in (* This gives the differnece among cast operations kind*) let is_objc_bridged_cast_expr _ stmt = - match stmt with | Clang_ast_t.ObjCBridgedCastExpr _ -> true | _ -> false in + match stmt with Clang_ast_t.ObjCBridgedCastExpr _ -> true | _ -> false + in let is_objc_bridged = CAst_utils.exists_eventually_st is_objc_bridged_cast_expr () stmt in let cast_inst, cast_exp = - cast_operation trans_state cast_kind res_trans_stmt.exps typ sil_loc is_objc_bridged in - { res_trans_stmt with - instrs = res_trans_stmt.instrs @ cast_inst; - exps = [cast_exp]; - } + cast_operation trans_state cast_kind res_trans_stmt.exps typ sil_loc is_objc_bridged + in + {res_trans_stmt with instrs= res_trans_stmt.instrs @ cast_inst; exps= [cast_exp]} (* function used in the computation for both Member_Expr and ObjCIVarRefExpr *) - and do_memb_ivar_ref_exp trans_state stmt_info stmt_list decl_ref = - let exp_stmt = extract_stmt_from_singleton stmt_list - "WARNING: in MemberExpr there must be only one stmt defining its expression.@\n" in + and do_memb_ivar_ref_exp trans_state stmt_info stmt_list decl_ref = + let exp_stmt = + extract_stmt_from_singleton stmt_list + "WARNING: in MemberExpr there must be only one stmt defining its expression.@\n" + in (* Don't pass var_exp_typ to child of MemberExpr - this may lead to initializing variable *) (* with wrong value. For example, we don't want p to be initialized with X(1) for:*) (* int p = X(1).field; *) - let trans_state' = { trans_state with var_exp_typ = None } in + let trans_state' = {trans_state with var_exp_typ= None} in let result_trans_exp_stmt = exec_with_glvalue_as_reference instruction trans_state' exp_stmt in decl_ref_trans trans_state result_trans_exp_stmt stmt_info decl_ref ~is_constructor_init:false @@ -1916,33 +2132,37 @@ struct let decl_ref = member_expr_info.Clang_ast_t.mei_decl_ref in let res_trans = do_memb_ivar_ref_exp trans_state stmt_info stmt_list decl_ref in let is_virtual_dispatch = member_expr_info.Clang_ast_t.mei_performs_virtual_dispatch in - { res_trans with is_cpp_call_virtual = res_trans.is_cpp_call_virtual && is_virtual_dispatch } + {res_trans with is_cpp_call_virtual= res_trans.is_cpp_call_virtual && is_virtual_dispatch} and unaryOperator_trans trans_state stmt_info expr_info stmt_list unary_operator_info = let context = trans_state.context in let sil_loc = CLocation.get_sil_location stmt_info context in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let stmt = extract_stmt_from_singleton stmt_list - "WARNING: We expect only one element in stmt list defining \ - the operand in UnaryOperator. NEED FIXING@\n" in - let trans_state' = { trans_state_pri with succ_nodes = [] } in + let stmt = + extract_stmt_from_singleton stmt_list + "WARNING: We expect only one element in stmt list defining the operand in UnaryOperator. NEED FIXING@\n" + in + let trans_state' = {trans_state_pri with succ_nodes= []} in let res_trans_stmt = instruction trans_state' stmt in (* Assumption: the operand does not create a cfg node*) - let (sil_e', _) = + let sil_e', _ = extract_exp_from_list res_trans_stmt.exps - "@\nWARNING: Missing operand in unary operator. NEED FIXING.@\n" in + "@\nWARNING: Missing operand in unary operator. NEED FIXING.@\n" + in let ret_typ = - CType_decl.qual_type_to_sil_type - context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type in + CType_decl.qual_type_to_sil_type context.CContext.tenv expr_info.Clang_ast_t.ei_qual_type + in let exp_op, instr_op = - CArithmetic_trans.unary_operation_instruction - context.translation_unit_context unary_operator_info sil_e' ret_typ sil_loc in - let unary_op_res_trans = { empty_res_trans with instrs = instr_op } in - let all_res_trans = [ res_trans_stmt; unary_op_res_trans ] in + CArithmetic_trans.unary_operation_instruction context.translation_unit_context + unary_operator_info sil_e' ret_typ sil_loc + in + let unary_op_res_trans = {empty_res_trans with instrs= instr_op} in + let all_res_trans = [res_trans_stmt; unary_op_res_trans] in let nname = "UnaryOperator" in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname - stmt_info all_res_trans in - { res_trans_to_parent with exps = [(exp_op, ret_typ)] } + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname stmt_info all_res_trans + in + {res_trans_to_parent with exps= [(exp_op, ret_typ)]} and returnStmt_trans trans_state stmt_info stmt_list = let context = trans_state.context in @@ -1951,54 +2171,63 @@ struct let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in let mk_ret_node instrs = let ret_node = create_node (Procdesc.Node.Stmt_node "Return Stmt") instrs sil_loc context in - Procdesc.node_set_succs_exn - context.procdesc - ret_node [(Procdesc.get_exit_node context.CContext.procdesc)] []; - ret_node in - let trans_result = (match stmt_list with - | [stmt] -> (* return exp; *) - let procdesc = context.CContext.procdesc in - let ret_type = Procdesc.get_ret_type procdesc in - let ret_exp, ret_typ, var_instrs = match context.CContext.return_param_typ with - | Some ret_param_typ -> - let name = CFrontend_config.return_param in - let procname = Procdesc.get_proc_name procdesc in - let pvar = Pvar.mk (Mangled.from_string name) procname in - let id = Ident.create_fresh Ident.knormal in - let instr = Sil.Load (id, Exp.Lvar pvar, ret_param_typ, sil_loc) in - let ret_typ = match ret_param_typ.desc with Typ.Tptr (t, _) -> t | _ -> assert false in - Exp.Var id, ret_typ, [instr] - | None -> - Exp.Lvar (Procdesc.get_ret_var procdesc), ret_type, [] in - let trans_state' = { trans_state_pri with - succ_nodes = []; - var_exp_typ = Some (ret_exp, ret_typ) } in - let res_trans_stmt = exec_with_self_exception instruction trans_state' stmt in - let (sil_expr, _) = extract_exp_from_list res_trans_stmt.exps - "WARNING: There should be only one return expression.@\n" in - - let ret_instrs = if List.exists ~f:(Exp.equal ret_exp) res_trans_stmt.initd_exps - then [] - else [Sil.Store (ret_exp, ret_type, sil_expr, sil_loc)] in - let autorelease_instrs = - add_autorelease_call context sil_expr ret_type sil_loc in - let instrs = var_instrs @ res_trans_stmt.instrs @ ret_instrs @ autorelease_instrs in - let ret_node = mk_ret_node instrs in - List.iter - ~f:(fun n -> Procdesc.node_set_succs_exn procdesc n [ret_node] []) - res_trans_stmt.leaf_nodes; - let root_nodes_to_parent = - if List.length res_trans_stmt.root_nodes >0 - then res_trans_stmt.root_nodes - else [ret_node] in - { empty_res_trans with root_nodes = root_nodes_to_parent; leaf_nodes = []} - | [] -> (* return; *) - let ret_node = mk_ret_node [] in - { empty_res_trans with root_nodes = [ret_node]; leaf_nodes = []} - | _ -> L.(debug Capture Verbose) - "@\nWARNING: Missing translation of Return Expression. \ - Return Statement ignored. Need fixing!@\n"; - { empty_res_trans with root_nodes = succ_nodes }) in + Procdesc.node_set_succs_exn context.procdesc ret_node + [Procdesc.get_exit_node context.CContext.procdesc] [] ; + ret_node + in + let trans_result = + match stmt_list with + | [stmt] + -> (* return exp; *) + let procdesc = context.CContext.procdesc in + let ret_type = Procdesc.get_ret_type procdesc in + let ret_exp, ret_typ, var_instrs = + match context.CContext.return_param_typ with + | Some ret_param_typ + -> let name = CFrontend_config.return_param in + let procname = Procdesc.get_proc_name procdesc in + let pvar = Pvar.mk (Mangled.from_string name) procname in + let id = Ident.create_fresh Ident.knormal in + let instr = Sil.Load (id, Exp.Lvar pvar, ret_param_typ, sil_loc) in + let ret_typ = + match ret_param_typ.desc with Typ.Tptr (t, _) -> t | _ -> assert false + in + (Exp.Var id, ret_typ, [instr]) + | None + -> (Exp.Lvar (Procdesc.get_ret_var procdesc), ret_type, []) + in + let trans_state' = + {trans_state_pri with succ_nodes= []; var_exp_typ= Some (ret_exp, ret_typ)} + in + let res_trans_stmt = exec_with_self_exception instruction trans_state' stmt in + let sil_expr, _ = + extract_exp_from_list res_trans_stmt.exps + "WARNING: There should be only one return expression.@\n" + in + let ret_instrs = + if List.exists ~f:(Exp.equal ret_exp) res_trans_stmt.initd_exps then [] + else [Sil.Store (ret_exp, ret_type, sil_expr, sil_loc)] + in + let autorelease_instrs = add_autorelease_call context sil_expr ret_type sil_loc in + let instrs = var_instrs @ res_trans_stmt.instrs @ ret_instrs @ autorelease_instrs in + let ret_node = mk_ret_node instrs in + List.iter + ~f:(fun n -> Procdesc.node_set_succs_exn procdesc n [ret_node] []) + res_trans_stmt.leaf_nodes ; + let root_nodes_to_parent = + if List.length res_trans_stmt.root_nodes > 0 then res_trans_stmt.root_nodes + else [ret_node] + in + {empty_res_trans with root_nodes= root_nodes_to_parent; leaf_nodes= []} + | [] + -> (* return; *) + let ret_node = mk_ret_node [] in + {empty_res_trans with root_nodes= [ret_node]; leaf_nodes= []} + | _ + -> L.(debug Capture Verbose) + "@\nWARNING: Missing translation of Return Expression. Return Statement ignored. Need fixing!@\n" ; + {empty_res_trans with root_nodes= succ_nodes} + in (* We expect a return with only one expression *) trans_result @@ -2007,23 +2236,30 @@ struct (* For ParenExpression we translate its body composed by the stmt_list. *) (* In paren expression there should be only one stmt that defines the expression *) and parenExpr_trans trans_state stmt_list = - let stmt = extract_stmt_from_singleton stmt_list - "WARNING: In ParenExpression there should be only one stmt.@\n" in + let stmt = + extract_stmt_from_singleton stmt_list + "WARNING: In ParenExpression there should be only one stmt.@\n" + in instruction trans_state stmt and objCBoxedExpr_trans trans_state info sel stmt_info stmts = let typ = - CType_decl.class_from_pointer_type - trans_state.context.CContext.tenv info.Clang_ast_t.ei_qual_type in - let obj_c_message_expr_info = Ast_expressions.make_obj_c_message_expr_info_class sel typ None in + CType_decl.class_from_pointer_type trans_state.context.CContext.tenv + info.Clang_ast_t.ei_qual_type + in + let obj_c_message_expr_info = + Ast_expressions.make_obj_c_message_expr_info_class sel typ None + in let message_stmt = - Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_message_expr_info) in + Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_message_expr_info) + in instruction trans_state message_stmt and objCArrayLiteral_trans trans_state info stmt_info stmts = let typ = - CType_decl.class_from_pointer_type - trans_state.context.CContext.tenv info.Clang_ast_t.ei_qual_type in + CType_decl.class_from_pointer_type trans_state.context.CContext.tenv + info.Clang_ast_t.ei_qual_type + in let meth = CFrontend_config.array_with_objects_count_m in let obj_c_mes_expr_info = Ast_expressions.make_obj_c_message_expr_info_class meth typ None in let stmts = stmts @ [Ast_expressions.create_nil stmt_info] in @@ -2032,30 +2268,37 @@ struct and objCDictionaryLiteral_trans trans_state info stmt_info stmts = let typ = - CType_decl.class_from_pointer_type - trans_state.context.CContext.tenv info.Clang_ast_t.ei_qual_type in + CType_decl.class_from_pointer_type trans_state.context.CContext.tenv + info.Clang_ast_t.ei_qual_type + in let dictionary_literal_pname = BuiltinDecl.__objc_dictionary_literal in let dictionary_literal_s = Typ.Procname.get_method dictionary_literal_pname in let obj_c_message_expr_info = - Ast_expressions.make_obj_c_message_expr_info_class dictionary_literal_s typ None in + Ast_expressions.make_obj_c_message_expr_info_class dictionary_literal_s typ None + in let stmts = CGeneral_utils.swap_elements_list stmts in let stmts = stmts @ [Ast_expressions.create_nil stmt_info] in let message_stmt = - Clang_ast_t.ObjCMessageExpr - (stmt_info, stmts, info, obj_c_message_expr_info) in + Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_message_expr_info) + in instruction trans_state message_stmt and objCStringLiteral_trans trans_state stmt_info stmts info = let char_star_typ = - Ast_expressions.create_char_star_type ~quals:(Typ.mk_type_quals ~is_const:true ()) () in - let stmts = [Ast_expressions.create_implicit_cast_expr stmt_info stmts char_star_typ - `ArrayToPointerDecay] in + Ast_expressions.create_char_star_type ~quals:(Typ.mk_type_quals ~is_const:true ()) () + in + let stmts = + [Ast_expressions.create_implicit_cast_expr stmt_info stmts char_star_typ `ArrayToPointerDecay] + in let typ = - CType_decl.class_from_pointer_type - trans_state.context.CContext.tenv info.Clang_ast_t.ei_qual_type in + CType_decl.class_from_pointer_type trans_state.context.CContext.tenv + info.Clang_ast_t.ei_qual_type + in let meth = CFrontend_config.string_with_utf8_m in let obj_c_mess_expr_info = Ast_expressions.make_obj_c_message_expr_info_class meth typ None in - let message_stmt = Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_mess_expr_info) in + let message_stmt = + Clang_ast_t.ObjCMessageExpr (stmt_info, stmts, info, obj_c_mess_expr_info) + in instruction trans_state message_stmt (** When objects are autoreleased, they get added a flag AUTORELEASE. All these objects will be @@ -2070,12 +2313,12 @@ struct let autorelease_pool_vars = CVar_decl.compute_autorelease_pool_vars context stmts in let stmt_call = Sil.Call - (ret_id, (Exp.Const (Const.Cfun fname)), - autorelease_pool_vars, sil_loc, CallFlags.default) in - let node_kind = Procdesc.Node.Stmt_node ("Release the autorelease pool") in + (ret_id, Exp.Const (Const.Cfun fname), autorelease_pool_vars, sil_loc, CallFlags.default) + in + let node_kind = Procdesc.Node.Stmt_node "Release the autorelease pool" in let call_node = create_node node_kind [stmt_call] sil_loc context in - Procdesc.node_set_succs_exn context.procdesc call_node trans_state.succ_nodes []; - let trans_state'={ trans_state with continuation = None; succ_nodes =[call_node] } in + Procdesc.node_set_succs_exn context.procdesc call_node trans_state.succ_nodes [] ; + let trans_state' = {trans_state with continuation= None; succ_nodes= [call_node]} in instructions trans_state' stmts (* Assumption: stmt_list contains 2 items, the first can be ObjCMessageExpr or ParenExpr *) @@ -2084,91 +2327,106 @@ struct is related with the ObjCAtSynchronizedStmt construct *) (* Finally we recursively work on the CompoundStmt, the second item of stmt_list *) and objCAtSynchronizedStmt_trans trans_state stmt_list = - (match stmt_list with - | [_; compound_stmt] -> instruction trans_state compound_stmt - | _ -> assert false) + match stmt_list with + | [_; compound_stmt] + -> instruction trans_state compound_stmt + | _ + -> assert false and blockExpr_trans trans_state stmt_info expr_info decl = let context = trans_state.context in let procname = Procdesc.get_proc_name context.CContext.procdesc in let loc = - (match stmt_info.Clang_ast_t.si_source_range with (l1, _) -> - CLocation.clang_to_sil_location context.CContext.translation_unit_context l1) in + match stmt_info.Clang_ast_t.si_source_range + with l1, _ -> CLocation.clang_to_sil_location context.CContext.translation_unit_context l1 + in (* Given a captured var, return the instruction to assign it to a temp *) let assign_captured_var (cvar, typ) = let id = Ident.create_fresh Ident.knormal in - let instr = Sil.Load (id, (Exp.Lvar cvar), typ, loc) in - (id, instr) in + let instr = Sil.Load (id, Exp.Lvar cvar, typ, loc) in + (id, instr) + in match decl with - | Clang_ast_t.BlockDecl (_, block_decl_info) -> - let open CContext in + | Clang_ast_t.BlockDecl (_, block_decl_info) + -> let open CContext in let qual_type = expr_info.Clang_ast_t.ei_qual_type in let block_pname = CProcname.mk_fresh_block_procname procname in let typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in (* We need to set the explicit dependency between the newly created block and the *) (* defining procedure. We add an edge in the call graph.*) - Cg.add_edge context.cg procname block_pname; + Cg.add_edge context.cg procname block_pname ; let captured_block_vars = block_decl_info.Clang_ast_t.bdi_captured_variables in let captureds = CVar_decl.captured_vars_from_block_info context captured_block_vars in let ids_instrs = List.map ~f:assign_captured_var captureds in let ids, instrs = List.unzip ids_instrs in let block_data = (context, qual_type, block_pname, captureds) in F.function_decl context.translation_unit_context context.tenv context.cfg context.cg decl - (Some block_data); + (Some block_data) ; let captured_vars = - List.map2_exn ~f:(fun id (pvar, typ) -> (Exp.Var id, pvar, typ)) ids captureds in - let closure = Exp.Closure { name=block_pname; captured_vars } in + List.map2_exn ~f:(fun id (pvar, typ) -> (Exp.Var id, pvar, typ)) ids captureds + in + let closure = Exp.Closure {name= block_pname; captured_vars} in let block_name = Typ.Procname.to_string block_pname in let static_vars = CContext.static_vars_for_block context block_pname in let captured_static_vars = captureds @ static_vars in - let alloc_block_instr = - allocate_block trans_state block_name captured_static_vars loc in - { empty_res_trans with - instrs = alloc_block_instr @ instrs; - exps = [(closure, typ)]; - } - | _ -> assert false + let alloc_block_instr = allocate_block trans_state block_name captured_static_vars loc in + {empty_res_trans with instrs= alloc_block_instr @ instrs; exps= [(closure, typ)]} + | _ + -> assert false and initListExpr_initializers_trans trans_state var_exp n stmts typ is_dyn_array stmt_info = - let (var_exp_inside, typ_inside) = match typ.Typ.desc with - | Typ.Tarray (t, _, _) when Typ.is_array_of_cpp_class typ -> - Exp.Lindex (var_exp, Exp.Const (Const.Cint (IntLit.of_int n))), t - | _ when is_dyn_array -> - Exp.Lindex (var_exp, Exp.Const (Const.Cint (IntLit.of_int n))), typ - | _ -> var_exp, typ in - let trans_state' = { trans_state with var_exp_typ = Some (var_exp_inside, typ_inside) } in + let var_exp_inside, typ_inside = + match typ.Typ.desc with + | Typ.Tarray (t, _, _) when Typ.is_array_of_cpp_class typ + -> (Exp.Lindex (var_exp, Exp.Const (Const.Cint (IntLit.of_int n))), t) + | _ when is_dyn_array + -> (Exp.Lindex (var_exp, Exp.Const (Const.Cint (IntLit.of_int n))), typ) + | _ + -> (var_exp, typ) + in + let trans_state' = {trans_state with var_exp_typ= Some (var_exp_inside, typ_inside)} in match stmts with - | [] -> [] - | stmt :: rest -> - let rest_stmts_res_trans = initListExpr_initializers_trans trans_state var_exp (n + 1) rest - typ is_dyn_array stmt_info in + | [] + -> [] + | stmt :: rest + -> let rest_stmts_res_trans = + initListExpr_initializers_trans trans_state var_exp (n + 1) rest typ is_dyn_array + stmt_info + in match stmt with - | Clang_ast_t.InitListExpr (_ , stmts , _) -> - let inside_stmts_res_trans = initListExpr_initializers_trans trans_state var_exp_inside - 0 stmts typ_inside is_dyn_array stmt_info in + | Clang_ast_t.InitListExpr (_, stmts, _) + -> let inside_stmts_res_trans = + initListExpr_initializers_trans trans_state var_exp_inside 0 stmts typ_inside + is_dyn_array stmt_info + in inside_stmts_res_trans @ rest_stmts_res_trans - | _ -> - let stmt_res_trans = if is_dyn_array then - let init_stmt_info = { stmt_info with - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer () } in - init_expr_trans trans_state' (var_exp_inside, typ_inside) init_stmt_info (Some stmt) - else instruction trans_state' stmt in + | _ + -> let stmt_res_trans = + if is_dyn_array then + let init_stmt_info = + {stmt_info with Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer ()} + in + init_expr_trans trans_state' (var_exp_inside, typ_inside) init_stmt_info + (Some stmt) + else instruction trans_state' stmt + in stmt_res_trans :: rest_stmts_res_trans and lambdaExpr_trans trans_state expr_info decl = let open CContext in let qual_type = expr_info.Clang_ast_t.ei_qual_type in let context = trans_state.context in - call_translation context decl; + call_translation context decl ; let procname = Procdesc.get_proc_name context.procdesc in let lambda_pname = CMethod_trans.get_procname_from_cpp_lambda context decl in let typ = CType_decl.qual_type_to_sil_type context.tenv qual_type in (* We need to set the explicit dependency between the newly created lambda and the *) (* defining procedure. We add an edge in the call graph.*) - Cg.add_edge context.cg procname lambda_pname; - let captured_vars = [] in (* TODO *) - let closure = Exp.Closure { name = lambda_pname; captured_vars } in - { empty_res_trans with exps = [(closure, typ)] } + Cg.add_edge context.cg procname lambda_pname ; + let captured_vars = [] in + (* TODO *) + let closure = Exp.Closure {name= lambda_pname; captured_vars} in + {empty_res_trans with exps= [(closure, typ)]} and cxxNewExpr_trans trans_state stmt_info expr_info cxx_new_expr_info = let context = trans_state.context in @@ -2179,162 +2437,192 @@ struct let size_exp_opt, res_trans_size = if is_dyn_array then match CAst_utils.get_stmt_opt cxx_new_expr_info.Clang_ast_t.xnei_array_size_expr with - | Some stmt -> - let trans_state_size = { trans_state_pri with succ_nodes = []; } in + | Some stmt + -> ( + let trans_state_size = {trans_state_pri with succ_nodes= []} in let res_trans_size = instruction trans_state_size stmt in - (match res_trans_size.exps with - | [(exp, _)] -> Some exp, res_trans_size - | _ -> None, empty_res_trans) - | None -> Some (Exp.Const (Const.Cint (IntLit.minus_one))), empty_res_trans - else None, empty_res_trans in + match res_trans_size.exps with + | [(exp, _)] + -> (Some exp, res_trans_size) + | _ + -> (None, empty_res_trans) ) + | None + -> (Some (Exp.Const (Const.Cint IntLit.minus_one)), empty_res_trans) + else (None, empty_res_trans) + in let res_trans_new = cpp_new_trans sil_loc typ size_exp_opt in let stmt_opt = CAst_utils.get_stmt_opt cxx_new_expr_info.Clang_ast_t.xnei_initializer_expr in - let trans_state_init = { trans_state_pri with succ_nodes = []; } in - let var_exp_typ = match res_trans_new.exps with - | [var_exp, {Typ.desc=Tptr (t, _)}] -> (var_exp, t) - | _ -> assert false in + let trans_state_init = {trans_state_pri with succ_nodes= []} in + let var_exp_typ = + match res_trans_new.exps with + | [(var_exp, {Typ.desc= Tptr (t, _)})] + -> (var_exp, t) + | _ + -> assert false + in (* Need a new stmt_info for the translation of the initializer, so that it can create nodes *) (* if it needs to, with the same stmt_info it doesn't work. *) - let init_stmt_info = { stmt_info with - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer () } in + let init_stmt_info = + {stmt_info with Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer ()} + in let res_trans_init = if is_dyn_array && Typ.is_pointer_to_cpp_class typ then let rec create_stmts stmt_opt size_exp_opt = - match stmt_opt, size_exp_opt with - | Some stmt, Some (Exp.Const (Const.Cint n)) when not (IntLit.iszero n) -> - let n_minus_1 = Some ((Exp.Const (Const.Cint (IntLit.sub n IntLit.one)))) in + match (stmt_opt, size_exp_opt) with + | Some stmt, Some Exp.Const Const.Cint n when not (IntLit.iszero n) + -> let n_minus_1 = Some (Exp.Const (Const.Cint (IntLit.sub n IntLit.one))) in stmt :: create_stmts stmt_opt n_minus_1 - | _ -> [] in + | _ + -> [] + in let stmts = create_stmts stmt_opt size_exp_opt in - let (var_exp, typ) = var_exp_typ in - let res_trans_init_list = initListExpr_initializers_trans trans_state_init var_exp 0 stmts - typ is_dyn_array stmt_info in + let var_exp, typ = var_exp_typ in + let res_trans_init_list = + initListExpr_initializers_trans trans_state_init var_exp 0 stmts typ is_dyn_array + stmt_info + in CTrans_utils.collect_res_trans context.procdesc res_trans_init_list - else init_expr_trans trans_state_init var_exp_typ init_stmt_info stmt_opt in + else init_expr_trans trans_state_init var_exp_typ init_stmt_info stmt_opt + in let all_res_trans = [res_trans_size; res_trans_new; res_trans_init] in let nname = "CXXNewExpr" in - let result_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - nname stmt_info all_res_trans in - { result_trans_to_parent with exps = res_trans_new.exps } + let result_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname stmt_info all_res_trans + in + {result_trans_to_parent with exps= res_trans_new.exps} and cxxDeleteExpr_trans trans_state stmt_info stmt_list delete_expr_info = let context = trans_state.context in let sil_loc = CLocation.get_sil_location stmt_info context in let is_array = delete_expr_info.Clang_ast_t.xdei_is_array in - let fname = - if is_array then BuiltinDecl.__delete_array - else BuiltinDecl.__delete in + let fname = if is_array then BuiltinDecl.__delete_array else BuiltinDecl.__delete in let param = match stmt_list with [p] -> p | _ -> assert false in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let trans_state_param = { trans_state_pri with succ_nodes = [] } in + let trans_state_param = {trans_state_pri with succ_nodes= []} in let result_trans_param = exec_with_self_exception instruction trans_state_param param in - let exp = extract_exp_from_list result_trans_param.exps - "WARNING: There should be one expression to delete. @\n" in + let exp = + extract_exp_from_list result_trans_param.exps + "WARNING: There should be one expression to delete. @\n" + in let call_instr = - Sil.Call (None, Exp.Const (Const.Cfun fname), [exp], sil_loc, CallFlags.default) in - let call_res_trans = { empty_res_trans with instrs = [call_instr] } in - let all_res_trans = if false then + Sil.Call (None, Exp.Const (Const.Cfun fname), [exp], sil_loc, CallFlags.default) + in + let call_res_trans = {empty_res_trans with instrs= [call_instr]} in + let all_res_trans = + if false then (* FIXME (t10135167): call destructor on deleted pointer if it's not null *) (* Right now it's dead code hidden by the 'false' flag *) let deleted_type = delete_expr_info.Clang_ast_t.xdei_destroyed_type in (* create stmt_info with new pointer so that destructor call doesn't create a node *) - let destruct_stmt_info = { stmt_info with - Clang_ast_t.si_pointer = CAst_utils.get_fresh_pointer () } in + let destruct_stmt_info = + {stmt_info with Clang_ast_t.si_pointer= CAst_utils.get_fresh_pointer ()} + in (* use empty_res_trans to avoid ending up with same instruction twice *) (* otherwise it would happen due to structutre of all_res_trans *) - let this_res_trans_destruct = { empty_res_trans with exps = result_trans_param.exps } in - let destruct_res_trans = cxx_destructor_call_trans trans_state_pri destruct_stmt_info - this_res_trans_destruct deleted_type in - [ result_trans_param; destruct_res_trans; call_res_trans] - (* --- END OF DEAD CODE --- *) - else - [ result_trans_param; call_res_trans] in - - let res_trans = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - "Call delete" stmt_info all_res_trans in - { res_trans with exps = [] } + let this_res_trans_destruct = {empty_res_trans with exps= result_trans_param.exps} in + let destruct_res_trans = + cxx_destructor_call_trans trans_state_pri destruct_stmt_info this_res_trans_destruct + deleted_type + in + [result_trans_param; destruct_res_trans; call_res_trans] (* --- END OF DEAD CODE --- *) + else [result_trans_param; call_res_trans] + in + let res_trans = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "Call delete" stmt_info + all_res_trans + in + {res_trans with exps= []} and materializeTemporaryExpr_trans trans_state stmt_info stmt_list expr_info = let context = trans_state.context in let procdesc = context.CContext.procdesc in (* typ_tmp is 'best guess' type of variable - translation may decide to use different type later *) - let (pvar, typ_tmp) = mk_temp_sil_var_for_expr context.CContext.tenv procdesc - "SIL_materialize_temp__" expr_info in + let pvar, typ_tmp = + mk_temp_sil_var_for_expr context.CContext.tenv procdesc "SIL_materialize_temp__" expr_info + in let temp_exp = match stmt_list with [p] -> p | _ -> assert false in let var_exp_typ = (Exp.Lvar pvar, typ_tmp) in let res_trans = init_expr_trans trans_state var_exp_typ stmt_info (Some temp_exp) in - let _, typ = extract_exp_from_list res_trans.exps - "MaterializeExpr initializer missing@\n" in - Procdesc.append_locals procdesc [(Pvar.get_name pvar, typ)]; + let _, typ = extract_exp_from_list res_trans.exps "MaterializeExpr initializer missing@\n" in + Procdesc.append_locals procdesc [(Pvar.get_name pvar, typ)] ; res_trans and compoundLiteralExpr_trans trans_state stmt_list expr_info = let stmt = match stmt_list with [stmt] -> stmt | _ -> assert false in let var_exp_typ = if Option.is_some trans_state.var_exp_typ then trans_state.var_exp_typ - else - Some (create_var_exp_tmp_var trans_state expr_info "SIL_compound_literal__") in - let trans_state' = { trans_state with var_exp_typ = var_exp_typ } in + else Some (create_var_exp_tmp_var trans_state expr_info "SIL_compound_literal__") + in + let trans_state' = {trans_state with var_exp_typ} in instruction trans_state' stmt and cxxDynamicCastExpr_trans trans_state stmt_info stmts cast_qual_type = let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let trans_state' = { trans_state_pri with succ_nodes = [] } in + let trans_state' = {trans_state_pri with succ_nodes= []} in let context = trans_state.context in let subtype = Subtype.subtypes_cast in let tenv = context.CContext.tenv in let sil_loc = CLocation.get_sil_location stmt_info context in let cast_type = CType_decl.qual_type_to_sil_type tenv cast_qual_type in - let sizeof_expr = match cast_type.desc with - | Typ.Tptr (typ, _) -> Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype} - | _ -> assert false in + let sizeof_expr = + match cast_type.desc with + | Typ.Tptr (typ, _) + -> Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype} + | _ + -> assert false + in let builtin = Exp.Const (Const.Cfun BuiltinDecl.__cast) in let stmt = match stmts with [stmt] -> stmt | _ -> assert false in let res_trans_stmt = exec_with_glvalue_as_reference instruction trans_state' stmt in - let exp = match res_trans_stmt.exps with | [e] -> e | _ -> assert false in + let exp = match res_trans_stmt.exps with [e] -> e | _ -> assert false in let args = [exp; (sizeof_expr, Typ.mk Tvoid)] in let ret_id = Ident.create_fresh Ident.knormal in let call = Sil.Call (Some (ret_id, cast_type), builtin, args, sil_loc, CallFlags.default) in let res_ex = Exp.Var ret_id in - let res_trans_dynamic_cast = { empty_res_trans with instrs = [call]; } in - let all_res_trans = [ res_trans_stmt; res_trans_dynamic_cast ] in + let res_trans_dynamic_cast = {empty_res_trans with instrs= [call]} in + let all_res_trans = [res_trans_stmt; res_trans_dynamic_cast] in let nname = "CxxDynamicCast" in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname - stmt_info all_res_trans in - { res_trans_to_parent with exps = [(res_ex, cast_type)] } + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname stmt_info all_res_trans + in + {res_trans_to_parent with exps= [(res_ex, cast_type)]} and cxxDefaultExpr_trans trans_state default_expr_info = match default_expr_info.Clang_ast_t.xdaei_init_expr with - | Some exp -> instruction trans_state exp - | None -> assert false + | Some exp + -> instruction trans_state exp + | None + -> assert false and call_function_with_args instr_name pname trans_state stmt_info stmts = let sil_loc = CLocation.get_sil_location stmt_info trans_state.context in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let trans_state_param = { trans_state_pri with succ_nodes = [] } in + let trans_state_param = {trans_state_pri with succ_nodes= []} in let res_trans_subexpr_list = - List.map ~f:(exec_with_glvalue_as_reference instruction trans_state_param) stmts in - let params = collect_exprs res_trans_subexpr_list in + List.map ~f:(exec_with_glvalue_as_reference instruction trans_state_param) stmts + in + let params = collect_exprs res_trans_subexpr_list in let sil_fun = Exp.Const (Const.Cfun pname) in let call_instr = Sil.Call (None, sil_fun, params, sil_loc, CallFlags.default) in - let res_trans_call = { empty_res_trans with - instrs = [call_instr]; - exps = []; } in + let res_trans_call = {empty_res_trans with instrs= [call_instr]; exps= []} in let all_res_trans = res_trans_subexpr_list @ [res_trans_call] in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - instr_name stmt_info all_res_trans in - { res_trans_to_parent with exps = res_trans_call.exps } + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc instr_name stmt_info + all_res_trans + in + {res_trans_to_parent with exps= res_trans_call.exps} and gccAsmStmt_trans trans_state = let pname = Typ.Procname.from_string_c_fun CFrontend_config.infer_skip_gcc_asm_stmt in call_function_with_args "GCCAsmStmt" pname trans_state + and objc_cxx_throw_trans trans_state = call_function_with_args "ObjCCPPThrow" BuiltinDecl.objc_cpp_throw trans_state and cxxPseudoDestructorExpr_trans () = let fun_name = Typ.Procname.from_string_c_fun CFrontend_config.infer_skip_fun in - { empty_res_trans with exps = [(Exp.Const (Const.Cfun fun_name), Typ.mk Tvoid)] } + {empty_res_trans with exps= [(Exp.Const (Const.Cfun fun_name), Typ.mk Tvoid)]} and cxxTypeidExpr_trans trans_state stmt_info stmts expr_info = let tenv = trans_state.context.CContext.tenv in @@ -2343,30 +2631,34 @@ struct let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in let res_trans_subexpr = match stmts with - | [stmt] -> - let trans_state_param = { trans_state_pri with succ_nodes = [] } in + | [stmt] + -> let trans_state_param = {trans_state_pri with succ_nodes= []} in instruction trans_state_param stmt - | _ -> empty_res_trans in + | _ + -> empty_res_trans + in let fun_name = BuiltinDecl.__cxx_typeid in let sil_fun = Exp.Const (Const.Cfun fun_name) in let ret_id = Ident.create_fresh Ident.knormal in let void_typ = Typ.mk Tvoid in - let type_info_objc = (Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact}, - void_typ) in + let type_info_objc = + (Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact}, void_typ) + in let class_tname = - Typ.Name.Cpp.from_qual_name Typ.NoTemplate (QualifiedCppName.of_list ["std"; "type_info"]) in + Typ.Name.Cpp.from_qual_name Typ.NoTemplate (QualifiedCppName.of_list ["std"; "type_info"]) + in let field_name = CGeneral_utils.mk_class_field_name class_tname "__type_name" in let ret_exp = Exp.Var ret_id in let field_exp = Exp.Lfield (ret_exp, field_name, typ) in let args = type_info_objc :: (field_exp, void_typ) :: res_trans_subexpr.exps in let call_instr = Sil.Call (Some (ret_id, typ), sil_fun, args, sil_loc, CallFlags.default) in - let res_trans_call = { empty_res_trans with - instrs = [call_instr]; - exps = [(ret_exp, typ)]; } in + let res_trans_call = {empty_res_trans with instrs= [call_instr]; exps= [(ret_exp, typ)]} in let all_res_trans = [res_trans_subexpr; res_trans_call] in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - "CXXTypeidExpr" stmt_info all_res_trans in - { res_trans_to_parent with exps = res_trans_call.exps } + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "CXXTypeidExpr" stmt_info + all_res_trans + in + {res_trans_to_parent with exps= res_trans_call.exps} and cxxStdInitializerListExpr_trans trans_state stmt_info stmts expr_info = let context = trans_state.context in @@ -2375,52 +2667,58 @@ struct let typ = CType_decl.qual_type_to_sil_type tenv expr_info.Clang_ast_t.ei_qual_type in let fun_name = Typ.Procname.from_string_c_fun CFrontend_config.infer_skip_fun in let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in - let trans_state_param = { trans_state_pri with succ_nodes = [] } in + let trans_state_param = {trans_state_pri with succ_nodes= []} in let res_trans_subexpr_list = List.map ~f:(instruction trans_state_param) stmts in - let params = collect_exprs res_trans_subexpr_list in + let params = collect_exprs res_trans_subexpr_list in let sil_fun = Exp.Const (Const.Cfun fun_name) in let ret_id = Ident.create_fresh Ident.knormal in let ret_exp = Exp.Var ret_id in let call_instr = Sil.Call (Some (ret_id, typ), sil_fun, params, sil_loc, CallFlags.default) in - let res_trans_call = { empty_res_trans with - instrs = [call_instr]; - exps = [(ret_exp, typ)]; } in + let res_trans_call = {empty_res_trans with instrs= [call_instr]; exps= [(ret_exp, typ)]} in let all_res_trans = res_trans_subexpr_list @ [res_trans_call] in - let res_trans_to_parent = PriorityNode.compute_results_to_parent trans_state_pri sil_loc - "CXXStdInitializerListExpr" stmt_info all_res_trans in - { res_trans_to_parent with exps = res_trans_call.exps } + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "CXXStdInitializerListExpr" + stmt_info all_res_trans + in + {res_trans_to_parent with exps= res_trans_call.exps} and objCBridgedCastExpr_trans trans_state stmts expr_info = let stmt = extract_stmt_from_singleton stmts "" in let tenv = trans_state.context.CContext.tenv in let typ = CType_decl.get_type_from_expr_info expr_info tenv in - let trans_state' = { trans_state with obj_bridged_cast_typ = Some typ } in + let trans_state' = {trans_state with obj_bridged_cast_typ= Some typ} in instruction trans_state' stmt and binaryOperator_trans_with_cond trans_state stmt_info stmt_list expr_info binop_info = let open Clang_ast_t in match binop_info.boi_kind with - | `LAnd | `LOr | `LT | `GT | `LE | `GE | `EQ | `NE -> - (* For LAnd/LOr/comparison operators we compiles a binary expression bo into an semantic + | `LAnd | `LOr | `LT | `GT | `LE | `GE | `EQ | `NE + -> (* For LAnd/LOr/comparison operators we compiles a binary expression bo into an semantic equivalent conditional operator 'bo ? 1:0'. The conditional operator takes care of shortcircuit when/where needed *) let bo = BinaryOperator (stmt_info, stmt_list, expr_info, binop_info) in let cond = Ast_expressions.trans_with_conditional stmt_info expr_info [bo] in instruction trans_state cond - | _ -> binaryOperator_trans trans_state binop_info stmt_info expr_info stmt_list + | _ + -> binaryOperator_trans trans_state binop_info stmt_info expr_info stmt_list + and attributedStmt_trans trans_state stmts attrs = let open Clang_ast_t in - match stmts, attrs with - | [stmt], [attr] -> - (match stmt, attr with - | NullStmt _, FallThroughAttr _ -> nullStmt_trans trans_state.succ_nodes - | _ -> assert false (* More cases to come. With the assert false we can find them *) ) - | _ -> assert false (* Expect that this doesn't happen *) - + match (stmts, attrs) with + | [stmt], [attr] -> ( + match (stmt, attr) with + | NullStmt _, FallThroughAttr _ + -> nullStmt_trans trans_state.succ_nodes + | _ + -> assert false (* More cases to come. With the assert false we can find them *) ) + | _ + -> assert false + + (* Expect that this doesn't happen *) and trans_into_undefined_expr trans_state expr_info = let tenv = trans_state.context.CContext.tenv in let typ = CType_decl.get_type_from_expr_info expr_info tenv in - { empty_res_trans with exps = [CTrans_utils.undefined_expression (), typ] } + {empty_res_trans with exps= [(CTrans_utils.undefined_expression (), typ)]} (* Translates a clang instruction into SIL instructions. It takes a *) (* a trans_state containing current info on the translation and it returns *) @@ -2429,299 +2727,226 @@ struct let stmt_kind = Clang_ast_proj.get_stmt_kind_string instr in let stmt_info, _ = Clang_ast_proj.get_stmt_tuple instr in let stmt_pointer = stmt_info.Clang_ast_t.si_pointer in - L.(debug Capture Verbose) "@\nPassing from %s '%d' @\n" stmt_kind stmt_pointer; + L.(debug Capture Verbose) "@\nPassing from %s '%d' @\n" stmt_kind stmt_pointer ; let open Clang_ast_t in match instr with - | GotoStmt(stmt_info, _, { Clang_ast_t.gsi_label = label_name; _ }) -> - gotoStmt_trans trans_state stmt_info label_name - - | LabelStmt(stmt_info, stmt_list, label_name) -> - labelStmt_trans trans_state stmt_info stmt_list label_name - - | ArraySubscriptExpr(_, stmt_list, expr_info) -> - arraySubscriptExpr_trans trans_state expr_info stmt_list - - | BinaryOperator (stmt_info, stmt_list, expr_info, binop_info) -> - binaryOperator_trans_with_cond trans_state stmt_info stmt_list expr_info binop_info - - | CallExpr(stmt_info, stmt_list, ei) -> - (match is_dispatch_function stmt_list with - | Some block_arg_pos -> - dispatch_function_trans trans_state stmt_info stmt_list block_arg_pos - | None -> - callExpr_trans trans_state stmt_info stmt_list ei) - - | CXXMemberCallExpr(stmt_info, stmt_list, ei) -> - cxxMemberCallExpr_trans trans_state stmt_info stmt_list ei - - | CXXOperatorCallExpr(stmt_info, stmt_list, ei) -> - callExpr_trans trans_state stmt_info stmt_list ei - + | GotoStmt (stmt_info, _, {Clang_ast_t.gsi_label= label_name; _}) + -> gotoStmt_trans trans_state stmt_info label_name + | LabelStmt (stmt_info, stmt_list, label_name) + -> labelStmt_trans trans_state stmt_info stmt_list label_name + | ArraySubscriptExpr (_, stmt_list, expr_info) + -> arraySubscriptExpr_trans trans_state expr_info stmt_list + | BinaryOperator (stmt_info, stmt_list, expr_info, binop_info) + -> binaryOperator_trans_with_cond trans_state stmt_info stmt_list expr_info binop_info + | CallExpr (stmt_info, stmt_list, ei) -> ( + match is_dispatch_function stmt_list with + | Some block_arg_pos + -> dispatch_function_trans trans_state stmt_info stmt_list block_arg_pos + | None + -> callExpr_trans trans_state stmt_info stmt_list ei ) + | CXXMemberCallExpr (stmt_info, stmt_list, ei) + -> cxxMemberCallExpr_trans trans_state stmt_info stmt_list ei + | CXXOperatorCallExpr (stmt_info, stmt_list, ei) + -> callExpr_trans trans_state stmt_info stmt_list ei | CXXConstructExpr (stmt_info, stmt_list, expr_info, cxx_constr_info) - | CXXTemporaryObjectExpr (stmt_info, stmt_list, expr_info, cxx_constr_info) -> - cxxConstructExpr_trans trans_state stmt_info stmt_list expr_info cxx_constr_info - - | ObjCMessageExpr(stmt_info, stmt_list, expr_info, obj_c_message_expr_info) -> - if is_block_enumerate_function obj_c_message_expr_info then + | CXXTemporaryObjectExpr (stmt_info, stmt_list, expr_info, cxx_constr_info) + -> cxxConstructExpr_trans trans_state stmt_info stmt_list expr_info cxx_constr_info + | ObjCMessageExpr (stmt_info, stmt_list, expr_info, obj_c_message_expr_info) + -> if is_block_enumerate_function obj_c_message_expr_info then block_enumeration_trans trans_state stmt_info stmt_list expr_info else objCMessageExpr_trans trans_state stmt_info obj_c_message_expr_info stmt_list expr_info - - | CompoundStmt (_, stmt_list) -> - (* No node for this statement. We just collect its statement list*) + | CompoundStmt (_, stmt_list) + -> (* No node for this statement. We just collect its statement list*) compoundStmt_trans trans_state stmt_list - - | ConditionalOperator(stmt_info, stmt_list, expr_info) -> - (* Ternary operator "cond ? exp1 : exp2" *) + | ConditionalOperator (stmt_info, stmt_list, expr_info) + -> (* Ternary operator "cond ? exp1 : exp2" *) conditionalOperator_trans trans_state stmt_info stmt_list expr_info - - | IfStmt(stmt_info, stmt_list) -> - ifStmt_trans trans_state stmt_info stmt_list - - | SwitchStmt (stmt_info, switch_stmt_list) -> - switchStmt_trans trans_state stmt_info switch_stmt_list - - | CaseStmt _ -> - L.(debug Capture Verbose) - "FATAL: Passing from CaseStmt outside of SwitchStmt, terminating.@\n"; + | IfStmt (stmt_info, stmt_list) + -> ifStmt_trans trans_state stmt_info stmt_list + | SwitchStmt (stmt_info, switch_stmt_list) + -> switchStmt_trans trans_state stmt_info switch_stmt_list + | CaseStmt _ + -> L.(debug Capture Verbose) + "FATAL: Passing from CaseStmt outside of SwitchStmt, terminating.@\n" ; assert false - - | StmtExpr(_, stmt_list, _) -> - stmtExpr_trans trans_state stmt_list - - | ForStmt(stmt_info, [init; decl_stmt; cond; incr; body]) -> - forStmt_trans trans_state init decl_stmt cond incr body stmt_info - - | WhileStmt(stmt_info, [decl_stmt; cond; body]) -> - whileStmt_trans trans_state decl_stmt cond body stmt_info - - | DoStmt(stmt_info, [body; cond]) -> - doStmt_trans trans_state stmt_info cond body - - | CXXForRangeStmt (stmt_info, stmt_list) -> - cxxForRangeStmt_trans trans_state stmt_info stmt_list - - | ObjCForCollectionStmt(stmt_info, [item; items; body]) -> - objCForCollectionStmt_trans trans_state item items body stmt_info - - | NullStmt _ -> - nullStmt_trans trans_state.succ_nodes - - | CompoundAssignOperator (stmt_info, stmt_list, expr_info, binary_operator_info, _) -> - binaryOperator_trans trans_state binary_operator_info stmt_info expr_info stmt_list - - | DeclStmt(stmt_info, _, decl_list) -> - declStmt_trans trans_state decl_list stmt_info - - | DeclRefExpr(stmt_info, _, _, decl_ref_expr_info) as d -> - declRefExpr_trans trans_state stmt_info decl_ref_expr_info d - - | ObjCPropertyRefExpr(_, stmt_list, _, _) -> - objCPropertyRefExpr_trans trans_state stmt_list - - | CXXThisExpr(stmt_info, _, expr_info) -> cxxThisExpr_trans trans_state stmt_info expr_info - - | OpaqueValueExpr(_, _, _, opaque_value_expr_info) -> - opaqueValueExpr_trans trans_state opaque_value_expr_info - - | PseudoObjectExpr(_, stmt_list, _) -> - pseudoObjectExpr_trans trans_state stmt_list - - | UnaryExprOrTypeTraitExpr(_, _, expr_info, ei) -> - unaryExprOrTypeTraitExpr_trans trans_state expr_info ei - - | ObjCBridgedCastExpr(_, stmt_list, expr_info, _, _) -> - objCBridgedCastExpr_trans trans_state stmt_list expr_info - - | ImplicitCastExpr(stmt_info, stmt_list, expr_info, cast_kind) - | CStyleCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _) - | CXXReinterpretCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _, _) - | CXXConstCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _, _) - | CXXStaticCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _, _) - | CXXFunctionalCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _)-> - cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_kind - - | IntegerLiteral(_, _, expr_info, integer_literal_info) -> - integerLiteral_trans trans_state expr_info integer_literal_info - - | StringLiteral(_, _, expr_info, str) -> - stringLiteral_trans trans_state expr_info str - - | GNUNullExpr(_, _, expr_info) -> - gNUNullExpr_trans trans_state expr_info - - | CXXNullPtrLiteralExpr(_, _, expr_info) -> - nullPtrExpr_trans trans_state expr_info - - | ObjCSelectorExpr(_, _, expr_info, selector) -> - objCSelectorExpr_trans trans_state expr_info selector - - | ObjCEncodeExpr(_, _, expr_info, objc_encode_expr_info) -> - objCEncodeExpr_trans trans_state expr_info objc_encode_expr_info - - | ObjCProtocolExpr(_, _, expr_info, decl_ref) -> - objCProtocolExpr_trans trans_state expr_info decl_ref - - | ObjCIvarRefExpr(stmt_info, stmt_list, _, obj_c_ivar_ref_expr_info) -> - objCIvarRefExpr_trans trans_state stmt_info stmt_list obj_c_ivar_ref_expr_info - - | MemberExpr(stmt_info, stmt_list, _, member_expr_info) -> - memberExpr_trans trans_state stmt_info stmt_list member_expr_info - - | UnaryOperator(stmt_info, stmt_list, expr_info, unary_operator_info) -> - if is_logical_negation_of_int - trans_state.context.CContext.tenv expr_info unary_operator_info then + | StmtExpr (_, stmt_list, _) + -> stmtExpr_trans trans_state stmt_list + | ForStmt (stmt_info, [init; decl_stmt; cond; incr; body]) + -> forStmt_trans trans_state init decl_stmt cond incr body stmt_info + | WhileStmt (stmt_info, [decl_stmt; cond; body]) + -> whileStmt_trans trans_state decl_stmt cond body stmt_info + | DoStmt (stmt_info, [body; cond]) + -> doStmt_trans trans_state stmt_info cond body + | CXXForRangeStmt (stmt_info, stmt_list) + -> cxxForRangeStmt_trans trans_state stmt_info stmt_list + | ObjCForCollectionStmt (stmt_info, [item; items; body]) + -> objCForCollectionStmt_trans trans_state item items body stmt_info + | NullStmt _ + -> nullStmt_trans trans_state.succ_nodes + | CompoundAssignOperator (stmt_info, stmt_list, expr_info, binary_operator_info, _) + -> binaryOperator_trans trans_state binary_operator_info stmt_info expr_info stmt_list + | DeclStmt (stmt_info, _, decl_list) + -> declStmt_trans trans_state decl_list stmt_info + | DeclRefExpr (stmt_info, _, _, decl_ref_expr_info) as d + -> declRefExpr_trans trans_state stmt_info decl_ref_expr_info d + | ObjCPropertyRefExpr (_, stmt_list, _, _) + -> objCPropertyRefExpr_trans trans_state stmt_list + | CXXThisExpr (stmt_info, _, expr_info) + -> cxxThisExpr_trans trans_state stmt_info expr_info + | OpaqueValueExpr (_, _, _, opaque_value_expr_info) + -> opaqueValueExpr_trans trans_state opaque_value_expr_info + | PseudoObjectExpr (_, stmt_list, _) + -> pseudoObjectExpr_trans trans_state stmt_list + | UnaryExprOrTypeTraitExpr (_, _, expr_info, ei) + -> unaryExprOrTypeTraitExpr_trans trans_state expr_info ei + | ObjCBridgedCastExpr (_, stmt_list, expr_info, _, _) + -> objCBridgedCastExpr_trans trans_state stmt_list expr_info + | ImplicitCastExpr (stmt_info, stmt_list, expr_info, cast_kind) + | CStyleCastExpr (stmt_info, stmt_list, expr_info, cast_kind, _) + | CXXReinterpretCastExpr (stmt_info, stmt_list, expr_info, cast_kind, _, _) + | CXXConstCastExpr (stmt_info, stmt_list, expr_info, cast_kind, _, _) + | CXXStaticCastExpr (stmt_info, stmt_list, expr_info, cast_kind, _, _) + | CXXFunctionalCastExpr (stmt_info, stmt_list, expr_info, cast_kind, _) + -> cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_kind + | IntegerLiteral (_, _, expr_info, integer_literal_info) + -> integerLiteral_trans trans_state expr_info integer_literal_info + | StringLiteral (_, _, expr_info, str) + -> stringLiteral_trans trans_state expr_info str + | GNUNullExpr (_, _, expr_info) + -> gNUNullExpr_trans trans_state expr_info + | CXXNullPtrLiteralExpr (_, _, expr_info) + -> nullPtrExpr_trans trans_state expr_info + | ObjCSelectorExpr (_, _, expr_info, selector) + -> objCSelectorExpr_trans trans_state expr_info selector + | ObjCEncodeExpr (_, _, expr_info, objc_encode_expr_info) + -> objCEncodeExpr_trans trans_state expr_info objc_encode_expr_info + | ObjCProtocolExpr (_, _, expr_info, decl_ref) + -> objCProtocolExpr_trans trans_state expr_info decl_ref + | ObjCIvarRefExpr (stmt_info, stmt_list, _, obj_c_ivar_ref_expr_info) + -> objCIvarRefExpr_trans trans_state stmt_info stmt_list obj_c_ivar_ref_expr_info + | MemberExpr (stmt_info, stmt_list, _, member_expr_info) + -> memberExpr_trans trans_state stmt_info stmt_list member_expr_info + | UnaryOperator (stmt_info, stmt_list, expr_info, unary_operator_info) + -> if is_logical_negation_of_int trans_state.context.CContext.tenv expr_info + unary_operator_info + then let conditional = - Ast_expressions.trans_negation_with_conditional stmt_info expr_info stmt_list in + Ast_expressions.trans_negation_with_conditional stmt_info expr_info stmt_list + in instruction trans_state conditional - else - unaryOperator_trans trans_state stmt_info expr_info stmt_list unary_operator_info - - | ReturnStmt (stmt_info, stmt_list) -> - returnStmt_trans trans_state stmt_info stmt_list - + else unaryOperator_trans trans_state stmt_info expr_info stmt_list unary_operator_info + | ReturnStmt (stmt_info, stmt_list) + -> returnStmt_trans trans_state stmt_info stmt_list (* We analyze the content of the expr. We treat ExprWithCleanups as a wrapper. *) (* It may be that later on (when we treat ARC) some info can be taken from it. *) - | ExprWithCleanups(_, stmt_list, _, _) - | ParenExpr(_, stmt_list, _) -> - parenExpr_trans trans_state stmt_list - + | ExprWithCleanups (_, stmt_list, _, _) + | ParenExpr (_, stmt_list, _) + -> parenExpr_trans trans_state stmt_list | ObjCBoolLiteralExpr (_, _, expr_info, n) | CharacterLiteral (_, _, expr_info, n) - | CXXBoolLiteralExpr (_, _, expr_info, n) -> - characterLiteral_trans trans_state expr_info n - - | FloatingLiteral (_, _, expr_info, float_string) -> - floatingLiteral_trans trans_state expr_info float_string - - | CXXScalarValueInitExpr (_, _, expr_info) -> - cxxScalarValueInitExpr_trans trans_state expr_info - - | ObjCBoxedExpr (stmt_info, stmts, info, boxed_expr_info) -> - (match boxed_expr_info.Clang_ast_t.obei_boxing_method with - | Some sel -> - objCBoxedExpr_trans trans_state info sel stmt_info stmts - | None -> assert false) - - | ObjCArrayLiteral (stmt_info, stmts, info) -> - objCArrayLiteral_trans trans_state info stmt_info stmts - - | ObjCDictionaryLiteral (stmt_info, stmts, info) -> - objCDictionaryLiteral_trans trans_state info stmt_info stmts - - | ObjCStringLiteral(stmt_info, stmts, info) -> - objCStringLiteral_trans trans_state stmt_info stmts info - - | BreakStmt _ -> breakStmt_trans trans_state - - | ContinueStmt _ -> continueStmt_trans trans_state - - | ObjCAtSynchronizedStmt(_, stmt_list) -> - objCAtSynchronizedStmt_trans trans_state stmt_list - - | ObjCIndirectCopyRestoreExpr (_, stmt_list, _) -> - instructions trans_state stmt_list - - | BlockExpr(stmt_info, _ , expr_info, decl) -> - blockExpr_trans trans_state stmt_info expr_info decl - - | ObjCAutoreleasePoolStmt (stmt_info, stmts) -> - objcAutoreleasePool_trans trans_state stmt_info stmts - - | ObjCAtTryStmt (_, stmts) -> - compoundStmt_trans trans_state stmts - | CXXTryStmt (_, stmts) -> - (L.(debug Capture Medium) - "@\n!!!!WARNING: found statement %s. @\nTranslation need to be improved.... @\n" - (Clang_ast_proj.get_stmt_kind_string instr); - compoundStmt_trans trans_state stmts) - - | ObjCAtThrowStmt (stmt_info, stmts) - | CXXThrowExpr (stmt_info, stmts, _) -> - objc_cxx_throw_trans trans_state stmt_info stmts - - | ObjCAtFinallyStmt (_, stmts) -> + | CXXBoolLiteralExpr (_, _, expr_info, n) + -> characterLiteral_trans trans_state expr_info n + | FloatingLiteral (_, _, expr_info, float_string) + -> floatingLiteral_trans trans_state expr_info float_string + | CXXScalarValueInitExpr (_, _, expr_info) + -> cxxScalarValueInitExpr_trans trans_state expr_info + | ObjCBoxedExpr (stmt_info, stmts, info, boxed_expr_info) -> ( + match boxed_expr_info.Clang_ast_t.obei_boxing_method with + | Some sel + -> objCBoxedExpr_trans trans_state info sel stmt_info stmts + | None + -> assert false ) + | ObjCArrayLiteral (stmt_info, stmts, info) + -> objCArrayLiteral_trans trans_state info stmt_info stmts + | ObjCDictionaryLiteral (stmt_info, stmts, info) + -> objCDictionaryLiteral_trans trans_state info stmt_info stmts + | ObjCStringLiteral (stmt_info, stmts, info) + -> objCStringLiteral_trans trans_state stmt_info stmts info + | BreakStmt _ + -> breakStmt_trans trans_state + | ContinueStmt _ + -> continueStmt_trans trans_state + | ObjCAtSynchronizedStmt (_, stmt_list) + -> objCAtSynchronizedStmt_trans trans_state stmt_list + | ObjCIndirectCopyRestoreExpr (_, stmt_list, _) + -> instructions trans_state stmt_list + | BlockExpr (stmt_info, _, expr_info, decl) + -> blockExpr_trans trans_state stmt_info expr_info decl + | ObjCAutoreleasePoolStmt (stmt_info, stmts) + -> objcAutoreleasePool_trans trans_state stmt_info stmts + | ObjCAtTryStmt (_, stmts) + -> compoundStmt_trans trans_state stmts + | CXXTryStmt (_, stmts) + -> L.(debug Capture Medium) + "@\n!!!!WARNING: found statement %s. @\nTranslation need to be improved.... @\n" + (Clang_ast_proj.get_stmt_kind_string instr) ; compoundStmt_trans trans_state stmts - - | ObjCAtCatchStmt _ - | CXXCatchStmt _ -> - compoundStmt_trans trans_state [] - - | PredefinedExpr (_, _, expr_info, _) -> - stringLiteral_trans trans_state expr_info "" - - | BinaryConditionalOperator (stmt_info, stmts, expr_info) -> - binaryConditionalOperator_trans trans_state stmt_info stmts expr_info - | CXXNewExpr (stmt_info, _, expr_info, cxx_new_expr_info) -> - cxxNewExpr_trans trans_state stmt_info expr_info cxx_new_expr_info - | CXXDeleteExpr (stmt_info, stmt_list, _, delete_expr_info) -> - cxxDeleteExpr_trans trans_state stmt_info stmt_list delete_expr_info - | MaterializeTemporaryExpr (stmt_info, stmt_list, expr_info, _) -> - materializeTemporaryExpr_trans trans_state stmt_info stmt_list expr_info - | CompoundLiteralExpr (_, stmt_list, expr_info) -> - compoundLiteralExpr_trans trans_state stmt_list expr_info - | InitListExpr (stmt_info, stmts, expr_info) -> - initListExpr_trans trans_state stmt_info expr_info stmts - - | CXXBindTemporaryExpr (_, stmt_list, _, _) -> - (* right now we ignore this expression and try to translate the child node *) + | ObjCAtThrowStmt (stmt_info, stmts) | CXXThrowExpr (stmt_info, stmts, _) + -> objc_cxx_throw_trans trans_state stmt_info stmts + | ObjCAtFinallyStmt (_, stmts) + -> compoundStmt_trans trans_state stmts + | ObjCAtCatchStmt _ | CXXCatchStmt _ + -> compoundStmt_trans trans_state [] + | PredefinedExpr (_, _, expr_info, _) + -> stringLiteral_trans trans_state expr_info "" + | BinaryConditionalOperator (stmt_info, stmts, expr_info) + -> binaryConditionalOperator_trans trans_state stmt_info stmts expr_info + | CXXNewExpr (stmt_info, _, expr_info, cxx_new_expr_info) + -> cxxNewExpr_trans trans_state stmt_info expr_info cxx_new_expr_info + | CXXDeleteExpr (stmt_info, stmt_list, _, delete_expr_info) + -> cxxDeleteExpr_trans trans_state stmt_info stmt_list delete_expr_info + | MaterializeTemporaryExpr (stmt_info, stmt_list, expr_info, _) + -> materializeTemporaryExpr_trans trans_state stmt_info stmt_list expr_info + | CompoundLiteralExpr (_, stmt_list, expr_info) + -> compoundLiteralExpr_trans trans_state stmt_list expr_info + | InitListExpr (stmt_info, stmts, expr_info) + -> initListExpr_trans trans_state stmt_info expr_info stmts + | CXXBindTemporaryExpr (_, stmt_list, _, _) + -> (* right now we ignore this expression and try to translate the child node *) parenExpr_trans trans_state stmt_list - - | CXXDynamicCastExpr (stmt_info, stmts, _, _, qual_type, _) -> - cxxDynamicCastExpr_trans trans_state stmt_info stmts qual_type - + | CXXDynamicCastExpr (stmt_info, stmts, _, _, qual_type, _) + -> cxxDynamicCastExpr_trans trans_state stmt_info stmts qual_type | CXXDefaultArgExpr (_, _, _, default_expr_info) - | CXXDefaultInitExpr (_, _, _, default_expr_info) -> - cxxDefaultExpr_trans trans_state default_expr_info - - | ImplicitValueInitExpr (_, _, expr_info) -> - implicitValueInitExpr_trans trans_state expr_info - | GenericSelectionExpr _ (* to be fixed when we dump the right info in the ast *) - | SizeOfPackExpr _ -> - { empty_res_trans with exps = [(Exp.get_undefined false, Typ.mk Tvoid)] } - - | GCCAsmStmt (stmt_info, stmts) -> - gccAsmStmt_trans trans_state stmt_info stmts - - | CXXPseudoDestructorExpr _ -> - cxxPseudoDestructorExpr_trans () - - | CXXTypeidExpr (stmt_info, stmts, expr_info) -> - cxxTypeidExpr_trans trans_state stmt_info stmts expr_info - - | CXXStdInitializerListExpr (stmt_info, stmts, expr_info) -> - cxxStdInitializerListExpr_trans trans_state stmt_info stmts expr_info - - | LambdaExpr(_, _, expr_info, lambda_expr_info) -> - let trans_state' = { trans_state with priority = Free } in + | CXXDefaultInitExpr (_, _, _, default_expr_info) + -> cxxDefaultExpr_trans trans_state default_expr_info + | ImplicitValueInitExpr (_, _, expr_info) + -> implicitValueInitExpr_trans trans_state expr_info + | GenericSelectionExpr _ + (* to be fixed when we dump the right info in the ast *) + | SizeOfPackExpr _ + -> {empty_res_trans with exps= [(Exp.get_undefined false, Typ.mk Tvoid)]} + | GCCAsmStmt (stmt_info, stmts) + -> gccAsmStmt_trans trans_state stmt_info stmts + | CXXPseudoDestructorExpr _ + -> cxxPseudoDestructorExpr_trans () + | CXXTypeidExpr (stmt_info, stmts, expr_info) + -> cxxTypeidExpr_trans trans_state stmt_info stmts expr_info + | CXXStdInitializerListExpr (stmt_info, stmts, expr_info) + -> cxxStdInitializerListExpr_trans trans_state stmt_info stmts expr_info + | LambdaExpr (_, _, expr_info, lambda_expr_info) + -> let trans_state' = {trans_state with priority= Free} in let decl = lambda_expr_info.Clang_ast_t.lei_lambda_decl in lambdaExpr_trans trans_state' expr_info decl - - | AttributedStmt (_, stmts, attrs) -> - attributedStmt_trans trans_state stmts attrs - - | TypeTraitExpr (_, _, expr_info, type_trait_info) -> - booleanValue_trans trans_state expr_info type_trait_info.Clang_ast_t.xtti_value - - | CXXNoexceptExpr (_, _, expr_info, cxx_noexcept_expr_info) -> - booleanValue_trans trans_state expr_info cxx_noexcept_expr_info.Clang_ast_t.xnee_value - - | OffsetOfExpr (_, _, expr_info) - | VAArgExpr (_, _, expr_info) -> - trans_into_undefined_expr trans_state expr_info - - + | AttributedStmt (_, stmts, attrs) + -> attributedStmt_trans trans_state stmts attrs + | TypeTraitExpr (_, _, expr_info, type_trait_info) + -> booleanValue_trans trans_state expr_info type_trait_info.Clang_ast_t.xtti_value + | CXXNoexceptExpr (_, _, expr_info, cxx_noexcept_expr_info) + -> booleanValue_trans trans_state expr_info cxx_noexcept_expr_info.Clang_ast_t.xnee_value + | OffsetOfExpr (_, _, expr_info) | VAArgExpr (_, _, expr_info) + -> trans_into_undefined_expr trans_state expr_info (* Infer somehow ended up in templated non instantiated code - right now it's not supported and failure in those cases is expected. *) | SubstNonTypeTemplateParmExpr _ | SubstNonTypeTemplateParmPackExpr _ - | CXXDependentScopeMemberExpr _ -> raise (CTrans_utils.TemplatedCodeException instr) - - | s -> (L.(debug Capture Medium) - "@\n!!!!WARNING: found statement %s. @\nACTION REQUIRED: \ - Translation need to be defined. Statement ignored.... @\n" - (Clang_ast_proj.get_stmt_kind_string s); - assert false) + | CXXDependentScopeMemberExpr _ + -> raise (CTrans_utils.TemplatedCodeException instr) + | s + -> L.(debug Capture Medium) + "@\n!!!!WARNING: found statement %s. @\nACTION REQUIRED: Translation need to be defined. Statement ignored.... @\n" + (Clang_ast_proj.get_stmt_kind_string s) ; + assert false (* Function similar to instruction function, but it takes C++ constructor initializer as an input parameter. *) @@ -2729,62 +2954,77 @@ struct let context = trans_state.context in let class_ptr = CContext.get_curr_class_decl_ptr context.CContext.curr_class in let source_range = ctor_init.Clang_ast_t.xci_source_range in - let sil_loc = CLocation.get_sil_location_from_range context.CContext.translation_unit_context - source_range true in + let sil_loc = + CLocation.get_sil_location_from_range context.CContext.translation_unit_context source_range + true + in (* its pointer will be used in PriorityNode *) let this_stmt_info = Ast_expressions.dummy_stmt_info () in (* this will be used to avoid creating node in init_expr_trans *) let child_stmt_info = - { (Ast_expressions.dummy_stmt_info ()) with Clang_ast_t.si_source_range = source_range } in + {(Ast_expressions.dummy_stmt_info ()) with Clang_ast_t.si_source_range= source_range} + in let trans_state' = PriorityNode.try_claim_priority_node trans_state this_stmt_info in let class_qual_type = - Ast_expressions.create_pointer_qual_type (CAst_utils.qual_type_of_decl_ptr class_ptr) in + Ast_expressions.create_pointer_qual_type (CAst_utils.qual_type_of_decl_ptr class_ptr) + in let this_res_trans = this_expr_trans trans_state' sil_loc class_qual_type in - let var_res_trans = match ctor_init.Clang_ast_t.xci_subject with - | `Delegating _ | `BaseClass _ -> - let this_exp, this_typ = extract_exp_from_list this_res_trans.exps - "WARNING: There should be one expression for 'this' in constructor. @\n" in + let var_res_trans = + match ctor_init.Clang_ast_t.xci_subject with + | `Delegating _ | `BaseClass _ + -> let this_exp, this_typ = + extract_exp_from_list this_res_trans.exps + "WARNING: There should be one expression for 'this' in constructor. @\n" + in (* Hack: Strip pointer from type here since cxxConstructExpr_trans expects it this way *) (* it will add pointer back before making it a parameter to a call *) let class_typ = match this_typ.Typ.desc with Tptr (t, _) -> t | _ -> assert false in - { this_res_trans with exps = [this_exp, class_typ] } - | `Member (decl_ref) -> - decl_ref_trans trans_state' this_res_trans child_stmt_info decl_ref - ~is_constructor_init:true in - let var_exp_typ = extract_exp_from_list var_res_trans.exps - "WARNING: There should be one expression to initialize in constructor initializer. @\n" in + {this_res_trans with exps= [(this_exp, class_typ)]} + | `Member decl_ref + -> decl_ref_trans trans_state' this_res_trans child_stmt_info decl_ref + ~is_constructor_init:true + in + let var_exp_typ = + extract_exp_from_list var_res_trans.exps + "WARNING: There should be one expression to initialize in constructor initializer. @\n" + in let init_expr = ctor_init.Clang_ast_t.xci_init_expr in let init_res_trans = init_expr_trans trans_state' var_exp_typ child_stmt_info init_expr in - PriorityNode.compute_results_to_parent trans_state' sil_loc "Constructor Init" - this_stmt_info [var_res_trans; init_res_trans] + PriorityNode.compute_results_to_parent trans_state' sil_loc "Constructor Init" this_stmt_info + [var_res_trans; init_res_trans] (** Given a translation state and list of translation functions it executes translation *) and exec_trans_instrs trans_state trans_stmt_fun_list = - let rec exec_trans_instrs_no_rev trans_state rev_trans_fun_list = match rev_trans_fun_list with - | [] -> { empty_res_trans with root_nodes = trans_state.succ_nodes } - | trans_stmt_fun :: trans_stmt_fun_list' -> - let res_trans_s = trans_stmt_fun trans_state in + let rec exec_trans_instrs_no_rev trans_state rev_trans_fun_list = + match rev_trans_fun_list with + | [] + -> {empty_res_trans with root_nodes= trans_state.succ_nodes} + | trans_stmt_fun :: trans_stmt_fun_list' + -> let res_trans_s = trans_stmt_fun trans_state in let trans_state' = - if res_trans_s.root_nodes <> [] - then { trans_state with succ_nodes = res_trans_s.root_nodes } - else trans_state in + if res_trans_s.root_nodes <> [] then + {trans_state with succ_nodes= res_trans_s.root_nodes} + else trans_state + in let res_trans_tail = exec_trans_instrs_no_rev trans_state' trans_stmt_fun_list' in { empty_res_trans with - root_nodes = res_trans_tail.root_nodes; - leaf_nodes = []; - instrs = res_trans_tail.instrs @ res_trans_s.instrs; - exps = res_trans_tail.exps @ res_trans_s.exps; - initd_exps = res_trans_tail.initd_exps @ res_trans_s.initd_exps; - } in + root_nodes= res_trans_tail.root_nodes + ; leaf_nodes= [] + ; instrs= res_trans_tail.instrs @ res_trans_s.instrs + ; exps= res_trans_tail.exps @ res_trans_s.exps + ; initd_exps= res_trans_tail.initd_exps @ res_trans_s.initd_exps } + in exec_trans_instrs_no_rev trans_state (List.rev trans_stmt_fun_list) - and get_clang_stmt_trans stmt = - fun trans_state -> exec_with_node_creation instruction trans_state stmt + and get_clang_stmt_trans stmt trans_state = exec_with_node_creation instruction trans_state stmt (* TODO write translate function for cxx constructor exprs *) - and get_custom_stmt_trans stmt = match stmt with - | `ClangStmt stmt -> get_clang_stmt_trans stmt - | `CXXConstructorInit instr -> cxx_constructor_init_trans instr + and get_custom_stmt_trans stmt = + match stmt with + | `ClangStmt stmt + -> get_clang_stmt_trans stmt + | `CXXConstructorInit instr + -> cxx_constructor_init_trans instr (** Given a translation state, this function translates a list of clang statements. *) and instructions trans_state stmt_list = @@ -2792,31 +3032,30 @@ struct exec_trans_instrs trans_state stmt_trans_fun and expression_trans context stmt warning = - let trans_state = { - context = context; - succ_nodes = []; - continuation = None; - priority = Free; - var_exp_typ = None; - opaque_exp = None; - obj_bridged_cast_typ = None; - } in + let trans_state = + { context + ; succ_nodes= [] + ; continuation= None + ; priority= Free + ; var_exp_typ= None + ; opaque_exp= None + ; obj_bridged_cast_typ= None } + in let res_trans_stmt = instruction trans_state stmt in fst (CTrans_utils.extract_exp_from_list res_trans_stmt.exps warning) let instructions_trans context body extra_instrs exit_node = - let trans_state = { - context = context; - succ_nodes = [exit_node]; - continuation = None; - priority = Free; - var_exp_typ = None; - opaque_exp = None; - obj_bridged_cast_typ = None - } in + let trans_state = + { context + ; succ_nodes= [exit_node] + ; continuation= None + ; priority= Free + ; var_exp_typ= None + ; opaque_exp= None + ; obj_bridged_cast_typ= None } + in let instrs = extra_instrs @ [`ClangStmt body] in let instrs_trans = List.map ~f:get_custom_stmt_trans instrs in let res_trans = exec_trans_instrs trans_state instrs_trans in res_trans.root_nodes - end diff --git a/infer/src/clang/cTrans.mli b/infer/src/clang/cTrans.mli index 287d9fda0..f62674627 100644 --- a/infer/src/clang/cTrans.mli +++ b/infer/src/clang/cTrans.mli @@ -9,4 +9,4 @@ open! IStd -module CTrans_funct(F: CModule_type.CFrontend) : CModule_type.CTranslation +module CTrans_funct (F : CModule_type.CFrontend) : CModule_type.CTranslation diff --git a/infer/src/clang/cTrans_models.ml b/infer/src/clang/cTrans_models.ml index 22ff854f8..336e16677 100644 --- a/infer/src/clang/cTrans_models.ml +++ b/infer/src/clang/cTrans_models.ml @@ -8,7 +8,6 @@ *) open! IStd - open Objc_models let class_equal class_typename class_name = String.equal (Typ.Name.name class_typename) class_name @@ -16,8 +15,7 @@ let class_equal class_typename class_name = String.equal (Typ.Name.name class_ty let is_cf_non_null_alloc pname = String.equal (Typ.Procname.to_string pname) CFrontend_config.cf_non_null_alloc -let is_alloc pname = - String.equal (Typ.Procname.to_string pname) CFrontend_config.cf_alloc +let is_alloc pname = String.equal (Typ.Procname.to_string pname) CFrontend_config.cf_alloc let is_alloc_model typ pname = if Specs.summary_exists pname then false @@ -43,78 +41,74 @@ let is_retain_predefined_model typ pname = let is_release_predefined_model typ pname = let funct = Typ.Procname.to_string pname in - Core_foundation_model.is_core_lib_release typ funct || - Core_foundation_model.is_core_graphics_release typ funct + Core_foundation_model.is_core_lib_release typ funct + || Core_foundation_model.is_core_graphics_release typ funct -let is_retain_method funct = - String.equal funct CFrontend_config.retain +let is_retain_method funct = String.equal funct CFrontend_config.retain -let is_release_method funct = - String.equal funct CFrontend_config.release +let is_release_method funct = String.equal funct CFrontend_config.release -let is_autorelease_method funct = - String.equal funct CFrontend_config.autorelease +let is_autorelease_method funct = String.equal funct CFrontend_config.autorelease let get_builtinname method_name = - if is_retain_method method_name then - Some BuiltinDecl.__objc_retain - else if is_autorelease_method method_name then - Some BuiltinDecl.__set_autorelease_attribute - else if is_release_method method_name then - Some BuiltinDecl.__objc_release + if is_retain_method method_name then Some BuiltinDecl.__objc_retain + else if is_autorelease_method method_name then Some BuiltinDecl.__set_autorelease_attribute + else if is_release_method method_name then Some BuiltinDecl.__objc_release else None -let is_modeled_builtin funct = - String.equal funct CFrontend_config.builtin_memset_chk +let is_modeled_builtin funct = String.equal funct CFrontend_config.builtin_memset_chk let is_modeled_attribute attr_name = List.mem ~equal:String.equal CFrontend_config.modeled_function_attributes attr_name let get_first_param_typedef_string_opt type_ptr = match CAst_utils.get_desugared_type type_ptr with - | Some Clang_ast_t.FunctionProtoType (_, _, {pti_params_type = [param_ptr]}) -> - CAst_utils.name_opt_of_typedef_qual_type param_ptr + | Some Clang_ast_t.FunctionProtoType (_, _, {pti_params_type= [param_ptr]}) + -> CAst_utils.name_opt_of_typedef_qual_type param_ptr |> Option.map ~f:QualifiedCppName.to_qual_string - | _ -> None + | _ + -> None let is_release_builtin funct fun_type = let pn = Typ.Procname.from_string_c_fun funct in if Specs.summary_exists pn then false - else match get_first_param_typedef_string_opt fun_type with - | Some typ -> is_release_predefined_model typ pn - | _ -> false + else + match get_first_param_typedef_string_opt fun_type with + | Some typ + -> is_release_predefined_model typ pn + | _ + -> false let is_retain_builtin funct fun_type = let pn = Typ.Procname.from_string_c_fun funct in if Specs.summary_exists pn then false - else match get_first_param_typedef_string_opt fun_type with - | Some typ -> is_retain_predefined_model typ pn - | _ -> false + else + match get_first_param_typedef_string_opt fun_type with + | Some typ + -> is_retain_predefined_model typ pn + | _ + -> false let is_assert_log_s funct = - String.equal funct CFrontend_config.assert_rtn || - String.equal funct CFrontend_config.assert_fail || - String.equal funct CFrontend_config.fbAssertWithSignalAndLogFunctionHelper || - String.is_substring ~substring:CFrontend_config.google_MakeCheckOpString funct + String.equal funct CFrontend_config.assert_rtn || String.equal funct CFrontend_config.assert_fail + || String.equal funct CFrontend_config.fbAssertWithSignalAndLogFunctionHelper + || String.is_substring ~substring:CFrontend_config.google_MakeCheckOpString funct -let is_assert_log_method m = - String.equal m CFrontend_config.google_LogMessageFatal +let is_assert_log_method m = String.equal m CFrontend_config.google_LogMessageFatal let is_handleFailureInMethod funct = - String.equal funct CFrontend_config.handleFailureInMethod || - String.equal funct CFrontend_config.handleFailureInFunction + String.equal funct CFrontend_config.handleFailureInMethod + || String.equal funct CFrontend_config.handleFailureInFunction let is_retain_or_release funct = - is_retain_method funct || - is_release_method funct || - is_autorelease_method funct + is_retain_method funct || is_release_method funct || is_autorelease_method funct let is_toll_free_bridging pn = - let funct = (Typ.Procname.to_string pn) in - String.equal funct CFrontend_config.cf_bridging_release || - String.equal funct CFrontend_config.cf_bridging_retain || - String.equal funct CFrontend_config.cf_autorelease || - String.equal funct CFrontend_config.ns_make_collectable + let funct = Typ.Procname.to_string pn in + String.equal funct CFrontend_config.cf_bridging_release + || String.equal funct CFrontend_config.cf_bridging_retain + || String.equal funct CFrontend_config.cf_autorelease + || String.equal funct CFrontend_config.ns_make_collectable let is_cf_retain_release pn = Typ.Procname.equal pn BuiltinDecl.__objc_retain_cf @@ -123,34 +117,41 @@ let is_cf_retain_release pn = (** If the function is a builtin model, return the model, otherwise return the function *) let is_assert_log pname = match pname with - | Typ.Procname.ObjC_Cpp _ -> - is_assert_log_method (Typ.Procname.to_string pname) - | Typ.Procname.C _ -> is_assert_log_s (Typ.Procname.to_string pname) - | _ -> false - + | Typ.Procname.ObjC_Cpp _ + -> is_assert_log_method (Typ.Procname.to_string pname) + | Typ.Procname.C _ + -> is_assert_log_s (Typ.Procname.to_string pname) + | _ + -> false -let is_objc_memory_model_controlled o = - Core_foundation_model.is_objc_memory_model_controlled o +let is_objc_memory_model_controlled o = Core_foundation_model.is_objc_memory_model_controlled o let get_predefined_ms_method condition class_name method_name method_kind mk_procname lang arguments return_type attributes builtin = if condition then let procname = match builtin with - | Some procname -> procname - | None -> mk_procname class_name method_name method_kind in - let ms = CMethod_signature.make_ms procname arguments return_type attributes - (Ast_expressions.dummy_source_range ()) false lang None None None `None in + | Some procname + -> procname + | None + -> mk_procname class_name method_name method_kind + in + let ms = + CMethod_signature.make_ms procname arguments return_type attributes + (Ast_expressions.dummy_source_range ()) false lang None None None `None + in Some ms else None let get_predefined_ms_stringWithUTF8String class_name method_name mk_procname lang = let condition = - class_equal class_name CFrontend_config.nsstring_cl && - String.equal method_name CFrontend_config.string_with_utf8_m in + class_equal class_name CFrontend_config.nsstring_cl + && String.equal method_name CFrontend_config.string_with_utf8_m + in let id_type = Ast_expressions.create_id_type in let char_star_type = - Ast_expressions.create_char_star_type ~quals:(Typ.mk_type_quals ~is_const:true ()) () in + Ast_expressions.create_char_star_type ~quals:(Typ.mk_type_quals ~is_const:true ()) () + in let args = [(Mangled.from_string "x", char_star_type)] in get_predefined_ms_method condition class_name method_name Typ.Procname.ObjCClassMethod mk_procname lang args id_type [] None @@ -158,8 +159,10 @@ let get_predefined_ms_stringWithUTF8String class_name method_name mk_procname la let get_predefined_ms_retain_release method_name mk_procname lang = let condition = is_retain_or_release method_name in let return_type = - if is_retain_method method_name || is_autorelease_method method_name - then Ast_expressions.create_id_type else Ast_expressions.create_void_type in + if is_retain_method method_name || is_autorelease_method method_name then + Ast_expressions.create_id_type + else Ast_expressions.create_void_type + in let class_typename = Typ.Name.Objc.from_string CFrontend_config.nsobject_cl in let class_type = Ast_expressions.create_class_qual_type class_typename in let args = [(Mangled.from_string CFrontend_config.self, class_type)] in @@ -168,8 +171,9 @@ let get_predefined_ms_retain_release method_name mk_procname lang = let get_predefined_ms_autoreleasepool_init class_name method_name mk_procname lang = let condition = - String.equal method_name CFrontend_config.init && - class_equal class_name CFrontend_config.nsautorelease_pool_cl in + String.equal method_name CFrontend_config.init + && class_equal class_name CFrontend_config.nsautorelease_pool_cl + in let class_type = Ast_expressions.create_class_qual_type class_name in get_predefined_ms_method condition class_name method_name Typ.Procname.ObjCInstanceMethod mk_procname lang [(Mangled.from_string CFrontend_config.self, class_type)] @@ -177,53 +181,49 @@ let get_predefined_ms_autoreleasepool_init class_name method_name mk_procname la let get_predefined_ms_nsautoreleasepool_release class_name method_name mk_procname lang = let condition = - (String.equal method_name CFrontend_config.release || - String.equal method_name CFrontend_config.drain) - && - class_equal class_name CFrontend_config.nsautorelease_pool_cl in + ( String.equal method_name CFrontend_config.release + || String.equal method_name CFrontend_config.drain ) + && class_equal class_name CFrontend_config.nsautorelease_pool_cl + in let class_type = Ast_expressions.create_class_qual_type class_name in let args = [(Mangled.from_string CFrontend_config.self, class_type)] in get_predefined_ms_method condition class_name method_name Typ.Procname.ObjCInstanceMethod - mk_procname lang args Ast_expressions.create_void_type - [] (Some BuiltinDecl.__objc_release_autorelease_pool) + mk_procname lang args Ast_expressions.create_void_type [] + (Some BuiltinDecl.__objc_release_autorelease_pool) let get_predefined_ms_is_kind_of_class class_name method_name mk_procname lang = let condition = String.equal method_name CFrontend_config.is_kind_of_class in let class_type = Ast_expressions.create_class_qual_type class_name in let args = [(Mangled.from_string CFrontend_config.self, class_type)] in get_predefined_ms_method condition class_name method_name Typ.Procname.ObjCInstanceMethod - mk_procname lang args Ast_expressions.create_BOOL_type - [] (Some BuiltinDecl.__instanceof) + mk_procname lang args Ast_expressions.create_BOOL_type [] (Some BuiltinDecl.__instanceof) let get_predefined_model_method_signature class_name method_name mk_procname lang = - let next_predefined f = function - | Some _ as x -> x - | None -> f method_name mk_procname lang in + let next_predefined f = function Some _ as x -> x | None -> f method_name mk_procname lang in get_predefined_ms_nsautoreleasepool_release class_name method_name mk_procname lang |> next_predefined get_predefined_ms_retain_release |> next_predefined (get_predefined_ms_stringWithUTF8String class_name) |> next_predefined (get_predefined_ms_autoreleasepool_init class_name) |> next_predefined (get_predefined_ms_is_kind_of_class class_name) -let dispatch_functions = [ - ("_dispatch_once", 1); - ("dispatch_async", 1); - ("dispatch_sync", 1); - ("dispatch_after", 2); - ("dispatch_group_async", 2); - ("dispatch_group_notify", 2); - ("dispatch_group_wait", 2); - ("dispatch_barrier_async", 1); - ("dispatch_source_set_cancel_handler", 1); - ("dispatch_source_set_event_handler", 1); -] +let dispatch_functions = + [ ("_dispatch_once", 1) + ; ("dispatch_async", 1) + ; ("dispatch_sync", 1) + ; ("dispatch_after", 2) + ; ("dispatch_group_async", 2) + ; ("dispatch_group_notify", 2) + ; ("dispatch_group_wait", 2) + ; ("dispatch_barrier_async", 1) + ; ("dispatch_source_set_cancel_handler", 1) + ; ("dispatch_source_set_event_handler", 1) ] let is_dispatch_function_name function_name = let rec is_dispatch functions = match functions with - | [] -> None - | (el, block_arg_pos):: rest -> - if (String.equal el function_name) - then Some (el, block_arg_pos) - else is_dispatch rest in + | [] + -> None + | (el, block_arg_pos) :: rest + -> if String.equal el function_name then Some (el, block_arg_pos) else is_dispatch rest + in is_dispatch dispatch_functions diff --git a/infer/src/clang/cTrans_models.mli b/infer/src/clang/cTrans_models.mli index f8416e03d..47accefe9 100644 --- a/infer/src/clang/cTrans_models.mli +++ b/infer/src/clang/cTrans_models.mli @@ -39,8 +39,9 @@ val is_toll_free_bridging : Typ.Procname.t -> bool val is_cf_retain_release : Typ.Procname.t -> bool -val get_predefined_model_method_signature : Typ.Name.t -> string -> - (Typ.Name.t -> string -> Typ.Procname.objc_cpp_method_kind -> Typ.Procname.t) -> - CFrontend_config.clang_lang -> CMethod_signature.method_signature option +val get_predefined_model_method_signature : + Typ.Name.t -> string + -> (Typ.Name.t -> string -> Typ.Procname.objc_cpp_method_kind -> Typ.Procname.t) + -> CFrontend_config.clang_lang -> CMethod_signature.method_signature option val is_dispatch_function_name : string -> (string * int) option diff --git a/infer/src/clang/cTrans_utils.ml b/infer/src/clang/cTrans_utils.ml index 986250e8a..6bd373f58 100644 --- a/infer/src/clang/cTrans_utils.ml +++ b/infer/src/clang/cTrans_utils.ml @@ -21,9 +21,7 @@ exception TemplatedCodeException of Clang_ast_t.stmt (* assume in many places that a list is just a singleton. We use the *) (* warning if to see which assumption was not correct *) let extract_item_from_singleton l warning_string failure_val = - match l with - | [item] -> item - | _ -> L.(debug Capture Medium) "%s" warning_string; failure_val + match l with [item] -> item | _ -> L.(debug Capture Medium) "%s" warning_string ; failure_val let dummy_exp = (Exp.minus_one, Typ.mk (Tint Typ.IInt)) @@ -33,128 +31,146 @@ let dummy_exp = (Exp.minus_one, Typ.mk (Tint Typ.IInt)) let extract_exp_from_list el warning_string = extract_item_from_singleton el warning_string dummy_exp -module Nodes = -struct - - let prune_kind b = Procdesc.Node.Prune_node(b, Sil.Ik_bexp , ((string_of_bool b)^" Branch")) +module Nodes = struct + let prune_kind b = Procdesc.Node.Prune_node (b, Sil.Ik_bexp, string_of_bool b ^ " Branch") let is_join_node n = - match Procdesc.Node.get_kind n with - | Procdesc.Node.Join_node -> true - | _ -> false + match Procdesc.Node.get_kind n with Procdesc.Node.Join_node -> true | _ -> false let is_prune_node n = - match Procdesc.Node.get_kind n with - | Procdesc.Node.Prune_node _ -> true - | _ -> false + match Procdesc.Node.get_kind n with Procdesc.Node.Prune_node _ -> true | _ -> false let is_true_prune_node n = match Procdesc.Node.get_kind n with - | Procdesc.Node.Prune_node(true, _, _) -> true - | _ -> false + | Procdesc.Node.Prune_node (true, _, _) + -> true + | _ + -> false let create_node node_kind instrs loc context = let procdesc = CContext.get_procdesc context in Procdesc.create_node procdesc loc node_kind instrs let create_prune_node branch e_cond instrs_cond loc ik context = - let (e_cond', _) = extract_exp_from_list e_cond - "@\nWARNING: Missing expression for Conditional operator. Need to be fixed" in + let e_cond', _ = + extract_exp_from_list e_cond + "@\nWARNING: Missing expression for Conditional operator. Need to be fixed" + in let e_cond'' = - if branch then - Exp.BinOp(Binop.Ne, e_cond', Exp.zero) - else - Exp.BinOp(Binop.Eq, e_cond', Exp.zero) in - let instrs_cond'= instrs_cond @ [Sil.Prune(e_cond'', loc, branch, ik)] in + if branch then Exp.BinOp (Binop.Ne, e_cond', Exp.zero) + else Exp.BinOp (Binop.Eq, e_cond', Exp.zero) + in + let instrs_cond' = instrs_cond @ [Sil.Prune (e_cond'', loc, branch, ik)] in create_node (prune_kind branch) instrs_cond' loc context (** Check if this binary opertor requires the creation of a node in the cfg. *) let is_binary_assign_op boi = match boi.Clang_ast_t.boi_kind with - | `Assign | `MulAssign | `DivAssign | `RemAssign | `AddAssign | `SubAssign - | `ShlAssign | `ShrAssign | `AndAssign | `XorAssign | `OrAssign -> true - | `PtrMemD | `PtrMemI | `Mul | `Div | `Rem | `Add | `Sub | `Shl | `Shr - | `LT | `GT | `LE | `GE | `EQ | `NE | `And | `Xor | `Or | `LAnd | `LOr - | `Comma -> false + | `Assign + | `MulAssign + | `DivAssign + | `RemAssign + | `AddAssign + | `SubAssign + | `ShlAssign + | `ShrAssign + | `AndAssign + | `XorAssign + | `OrAssign + -> true + | `PtrMemD + | `PtrMemI + | `Mul + | `Div + | `Rem + | `Add + | `Sub + | `Shl + | `Shr + | `LT + | `GT + | `LE + | `GE + | `EQ + | `NE + | `And + | `Xor + | `Or + | `LAnd + | `LOr + | `Comma + -> false (** Check if this unary opertor requires the creation of a node in the cfg. *) let need_unary_op_node uoi = match uoi.Clang_ast_t.uoi_kind with - | `PostInc | `PostDec | `PreInc | `PreDec | `AddrOf | `Deref | `Plus -> true - | `Minus | `Not | `LNot | `Real | `Imag | `Extension | `Coawait -> false - + | `PostInc | `PostDec | `PreInc | `PreDec | `AddrOf | `Deref | `Plus + -> true + | `Minus | `Not | `LNot | `Real | `Imag | `Extension | `Coawait + -> false end -module GotoLabel = -struct - +module GotoLabel = struct let find_goto_label context label sil_loc = - try - Hashtbl.find context.CContext.label_map label + try Hashtbl.find context.CContext.label_map label with Not_found -> let node_name = Format.sprintf "GotoLabel_%s" label in let new_node = Nodes.create_node (Procdesc.Node.Skip_node node_name) [] sil_loc context in - Hashtbl.add context.CContext.label_map label new_node; - new_node + Hashtbl.add context.CContext.label_map label new_node ; new_node end -type continuation = { - break: Procdesc.Node.t list; - continue: Procdesc.Node.t list; - return_temp : bool; (* true if temps should not be removed in the node but returned to ancestors *) -} +type continuation = + { break: Procdesc.Node.t list + ; continue: Procdesc.Node.t list + ; return_temp: bool + (* true if temps should not be removed in the node but returned to ancestors *) } let is_return_temp continuation = - match continuation with - | Some cont -> cont.return_temp - | _ -> false + match continuation with Some cont -> cont.return_temp | _ -> false -let ids_to_parent cont ids = - if is_return_temp cont then ids else [] +let ids_to_parent cont ids = if is_return_temp cont then ids else [] -let ids_to_node cont ids = - if is_return_temp cont then [] else ids +let ids_to_node cont ids = if is_return_temp cont then [] else ids let mk_cond_continuation cont = match cont with - | Some cont' -> Some { cont' with return_temp = true; } - | None -> Some { break =[]; continue =[]; return_temp = true;} + | Some cont' + -> Some {cont' with return_temp= true} + | None + -> Some {break= []; continue= []; return_temp= true} -type priority_node = - | Free - | Busy of Clang_ast_t.pointer +type priority_node = Free | Busy of Clang_ast_t.pointer (* A translation state. It provides the translation function with the info*) (* it need to carry on the tranlsation. *) -type trans_state = { - context: CContext.t; (* current context of the translation *) - succ_nodes: Procdesc.Node.t list; (* successor nodes in the cfg *) - continuation: continuation option; (* current continuation *) - priority: priority_node; - var_exp_typ: (Exp.t * Typ.t) option; - opaque_exp: (Exp.t * Typ.t) option; - obj_bridged_cast_typ : Typ.t option -} +type trans_state = + { context: CContext.t + ; (* current context of the translation *) + succ_nodes: Procdesc.Node.t list + ; (* successor nodes in the cfg *) + continuation: continuation option + ; (* current continuation *) + priority: priority_node + ; var_exp_typ: (Exp.t * Typ.t) option + ; opaque_exp: (Exp.t * Typ.t) option + ; obj_bridged_cast_typ: Typ.t option } (* A translation result. It is returned by the translation function. *) -type trans_result = { - root_nodes: Procdesc.Node.t list; (* Top cfg nodes (root) created by the translation *) - leaf_nodes: Procdesc.Node.t list; (* Bottom cfg nodes (leaf) created by the translate *) - instrs: Sil.instr list; (* list of SIL instruction that need to be placed in cfg nodes of the parent*) - exps: (Exp.t * Typ.t) list; (* SIL expressions resulting from translation of clang stmt *) - initd_exps: Exp.t list; - is_cpp_call_virtual : bool; -} +type trans_result = + { root_nodes: Procdesc.Node.t list + ; (* Top cfg nodes (root) created by the translation *) + leaf_nodes: Procdesc.Node.t list + ; (* Bottom cfg nodes (leaf) created by the translate *) + instrs: Sil.instr list + ; (* list of SIL instruction that need to be placed in cfg nodes of the parent*) + exps: (Exp.t * Typ.t) list + ; (* SIL expressions resulting from translation of clang stmt *) + initd_exps: Exp.t list + ; is_cpp_call_virtual: bool } (* Empty result translation *) -let empty_res_trans = { - root_nodes = []; - leaf_nodes = []; - instrs = []; - exps = []; - initd_exps = []; - is_cpp_call_virtual = false; -} +let empty_res_trans = + {root_nodes= []; leaf_nodes= []; instrs= []; exps= []; initd_exps= []; is_cpp_call_virtual= false} let undefined_expression () = Exp.Var (Ident.create_fresh Ident.knormal) @@ -162,37 +178,28 @@ let undefined_expression () = Exp.Var (Ident.create_fresh Ident.knormal) let collect_res_trans pdesc l = let rec collect l rt = match l with - | [] -> rt - | rt':: l' -> - let root_nodes = - if rt.root_nodes <> [] then rt.root_nodes - else rt'.root_nodes in - let leaf_nodes = - if rt'.leaf_nodes <> [] then rt'.leaf_nodes - else rt.leaf_nodes in + | [] + -> rt + | rt' :: l' + -> let root_nodes = if rt.root_nodes <> [] then rt.root_nodes else rt'.root_nodes in + let leaf_nodes = if rt'.leaf_nodes <> [] then rt'.leaf_nodes else rt.leaf_nodes in if rt'.root_nodes <> [] then List.iter ~f:(fun n -> Procdesc.node_set_succs_exn pdesc n rt'.root_nodes []) - rt.leaf_nodes; + rt.leaf_nodes ; collect l' - { root_nodes = root_nodes; - leaf_nodes = leaf_nodes; - instrs = List.rev_append rt'.instrs rt.instrs; - exps = List.rev_append rt'.exps rt.exps; - initd_exps = List.rev_append rt'.initd_exps rt.initd_exps; - is_cpp_call_virtual = false; } in + { root_nodes + ; leaf_nodes + ; instrs= List.rev_append rt'.instrs rt.instrs + ; exps= List.rev_append rt'.exps rt.exps + ; initd_exps= List.rev_append rt'.initd_exps rt.initd_exps + ; is_cpp_call_virtual= false } + in let rt = collect l empty_res_trans in - { - rt with - instrs = List.rev rt.instrs; - exps = List.rev rt.exps; - initd_exps = List.rev rt.initd_exps; - } + {rt with instrs= List.rev rt.instrs; exps= List.rev rt.exps; initd_exps= List.rev rt.initd_exps} let extract_var_exp_or_fail transt_state = - match transt_state.var_exp_typ with - | Some var_exp_typ -> var_exp_typ - | None -> assert false + match transt_state.var_exp_typ with Some var_exp_typ -> var_exp_typ | None -> assert false (* priority_node is used to enforce some kind of policy for creating nodes *) (* in the cfg. Certain elements of the AST _must_ create nodes therefore *) @@ -206,35 +213,27 @@ let extract_var_exp_or_fail transt_state = (* has claimed priority. If priority is already claimed E does not have to *) (* create a node. If priority is free then it means E has to create the *) (* node. Then E claims priority and release it afterward. *) -module PriorityNode = -struct - +module PriorityNode = struct type t = priority_node let try_claim_priority_node trans_state stmt_info = match trans_state.priority with - | Free -> - L.(debug Capture Verbose) "Priority is free. Locking priority node in %d@\n@." - stmt_info.Clang_ast_t.si_pointer; - { trans_state with priority = Busy stmt_info.Clang_ast_t.si_pointer } - | _ -> - L.(debug Capture Verbose) "Priority busy in %d. No claim possible@\n@." - stmt_info.Clang_ast_t.si_pointer; + | Free + -> L.(debug Capture Verbose) + "Priority is free. Locking priority node in %d@\n@." stmt_info.Clang_ast_t.si_pointer ; + {trans_state with priority= Busy stmt_info.Clang_ast_t.si_pointer} + | _ + -> L.(debug Capture Verbose) + "Priority busy in %d. No claim possible@\n@." stmt_info.Clang_ast_t.si_pointer ; trans_state let force_claim_priority_node trans_state stmt_info = - { trans_state with priority = Busy stmt_info.Clang_ast_t.si_pointer } + {trans_state with priority= Busy stmt_info.Clang_ast_t.si_pointer} - - let is_priority_free trans_state = - match trans_state.priority with - | Free -> true - | _ -> false + let is_priority_free trans_state = match trans_state.priority with Free -> true | _ -> false let own_priority_node pri stmt_info = - match pri with - | Busy p when Int.equal p stmt_info.Clang_ast_t.si_pointer -> true - | _ -> false + match pri with Busy p when Int.equal p stmt_info.Clang_ast_t.si_pointer -> true | _ -> false (* Used by translation functions to handle potenatial cfg nodes. *) (* It connects nodes returned by translation of stmt children and *) @@ -245,50 +244,51 @@ struct let create_node = own_priority_node trans_state.priority stmt_info && res_state.instrs <> [] in if create_node then (* We need to create a node *) - let node_kind = Procdesc.Node.Stmt_node (nd_name) in + let node_kind = Procdesc.Node.Stmt_node nd_name in let node = Nodes.create_node node_kind res_state.instrs loc trans_state.context in - Procdesc.node_set_succs_exn trans_state.context.procdesc node trans_state.succ_nodes []; + Procdesc.node_set_succs_exn trans_state.context.procdesc node trans_state.succ_nodes [] ; List.iter ~f:(fun leaf -> Procdesc.node_set_succs_exn trans_state.context.procdesc leaf [node] []) - res_state.leaf_nodes; + res_state.leaf_nodes ; (* Invariant: if root_nodes is empty then the params have not created a node.*) - let root_nodes = (if res_state.root_nodes <> [] then res_state.root_nodes - else [node]) in - { res_state with - root_nodes = root_nodes; - leaf_nodes = [node]; - instrs = []; - exps = []; - } + let root_nodes = if res_state.root_nodes <> [] then res_state.root_nodes else [node] in + {res_state with root_nodes; leaf_nodes= [node]; instrs= []; exps= []} else (* The node is created by the parent. We just pass back nodes/leafs params *) - { res_state with exps = []} - + {res_state with exps= []} end -module Loops = -struct - +module Loops = struct type loop_kind = - | For of Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt + | For of + Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt (* init, decl_stmt, condition, increment and body *) | While of Clang_ast_t.stmt option * Clang_ast_t.stmt * Clang_ast_t.stmt (* decl_stmt, condition and body *) - | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt + + (* condition and body *) let loop_kind_to_if_kind loop_kind = match loop_kind with - | For _ -> Sil.Ik_for - | While _ -> Sil.Ik_while - | DoWhile _ -> Sil.Ik_dowhile + | For _ + -> Sil.Ik_for + | While _ + -> Sil.Ik_while + | DoWhile _ + -> Sil.Ik_dowhile let get_body loop_kind = - match loop_kind with - | For (_, _, _, _, body) | While (_, _, body) | DoWhile (_, body) -> body + match loop_kind + with For (_, _, _, _, body) | While (_, _, body) | DoWhile (_, body) -> body let get_cond loop_kind = - match loop_kind with - | For (_, _, cond, _, _) | While (_, cond, _) | DoWhile (cond, _) -> cond + match loop_kind + with For (_, _, cond, _, _) | While (_, cond, _) | DoWhile (cond, _) -> cond end (** This function handles ObjC new/alloc and C++ new calls *) @@ -298,67 +298,87 @@ let create_alloc_instrs sil_loc function_type fname size_exp_opt procname_opt = | Tptr (styp, Typ.Pk_pointer) | Tptr (styp, Typ.Pk_objc_weak) | Tptr (styp, Typ.Pk_objc_unsafe_unretained) - | Tptr (styp, Typ.Pk_objc_autoreleasing) -> - function_type, styp - | _ -> CType.add_pointer_to_typ function_type, function_type in - let sizeof_exp_ = Exp.Sizeof {typ=function_type_np; nbytes=None; - dynamic_length=None; subtype=Subtype.exact} in - let sizeof_exp = match size_exp_opt with - | Some exp -> Exp.BinOp (Binop.Mult, sizeof_exp_, exp) - | None -> sizeof_exp_ in + | Tptr (styp, Typ.Pk_objc_autoreleasing) + -> (function_type, styp) + | _ + -> (CType.add_pointer_to_typ function_type, function_type) + in + let sizeof_exp_ = + Exp.Sizeof {typ= function_type_np; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in + let sizeof_exp = + match size_exp_opt with + | Some exp + -> Exp.BinOp (Binop.Mult, sizeof_exp_, exp) + | None + -> sizeof_exp_ + in let exp = (sizeof_exp, Typ.mk (Tint Typ.IULong)) in - let procname_arg = match procname_opt with - | Some procname -> [Exp.Const (Const.Cfun (procname)), Typ.mk Tvoid] - | None -> [] in + let procname_arg = + match procname_opt with + | Some procname + -> [(Exp.Const (Const.Cfun procname), Typ.mk Tvoid)] + | None + -> [] + in let args = exp :: procname_arg in let ret_id = Ident.create_fresh Ident.knormal in let ret_id_typ = Some (ret_id, function_type) in let stmt_call = - Sil.Call (ret_id_typ, Exp.Const (Const.Cfun fname), args, sil_loc, CallFlags.default) in + Sil.Call (ret_id_typ, Exp.Const (Const.Cfun fname), args, sil_loc, CallFlags.default) + in (function_type, stmt_call, Exp.Var ret_id) let alloc_trans trans_state loc stmt_info function_type is_cf_non_null_alloc procname_opt = - let fname = if is_cf_non_null_alloc then - BuiltinDecl.__objc_alloc_no_fail - else - BuiltinDecl.__objc_alloc in - let (function_type, stmt_call, exp) = - create_alloc_instrs loc function_type fname None procname_opt in - let res_trans_tmp = { empty_res_trans with instrs =[stmt_call]} in + let fname = + if is_cf_non_null_alloc then BuiltinDecl.__objc_alloc_no_fail else BuiltinDecl.__objc_alloc + in + let function_type, stmt_call, exp = + create_alloc_instrs loc function_type fname None procname_opt + in + let res_trans_tmp = {empty_res_trans with instrs= [stmt_call]} in let res_trans = let nname = "Call alloc" in - PriorityNode.compute_results_to_parent trans_state loc nname stmt_info [res_trans_tmp] in - { res_trans with exps =[(exp, function_type)]} + PriorityNode.compute_results_to_parent trans_state loc nname stmt_info [res_trans_tmp] + in + {res_trans with exps= [(exp, function_type)]} let objc_new_trans trans_state loc stmt_info cls_name function_type = let fname = BuiltinDecl.__objc_alloc_no_fail in - let (alloc_ret_type, alloc_stmt_call, alloc_ret_exp) = - create_alloc_instrs loc function_type fname None None in + let alloc_ret_type, alloc_stmt_call, alloc_ret_exp = + create_alloc_instrs loc function_type fname None None + in let init_ret_id = Ident.create_fresh Ident.knormal in let is_instance = true in - let call_flags = { CallFlags.default with CallFlags.cf_virtual = is_instance; } in + let call_flags = {CallFlags.default with CallFlags.cf_virtual= is_instance} in let pname = - CProcname.NoAstDecl.objc_method_of_string_kind - cls_name CFrontend_config.init Typ.Procname.ObjCInstanceMethod in - CMethod_trans.create_external_procdesc trans_state.context.CContext.cfg pname is_instance None; + CProcname.NoAstDecl.objc_method_of_string_kind cls_name CFrontend_config.init + Typ.Procname.ObjCInstanceMethod + in + CMethod_trans.create_external_procdesc trans_state.context.CContext.cfg pname is_instance None ; let args = [(alloc_ret_exp, alloc_ret_type)] in let ret_id_typ = Some (init_ret_id, alloc_ret_type) in let init_stmt_call = - Sil.Call (ret_id_typ, Exp.Const (Const.Cfun pname), args, loc, call_flags) in + Sil.Call (ret_id_typ, Exp.Const (Const.Cfun pname), args, loc, call_flags) + in let instrs = [alloc_stmt_call; init_stmt_call] in - let res_trans_tmp = { empty_res_trans with instrs = instrs } in + let res_trans_tmp = {empty_res_trans with instrs} in let res_trans = let nname = "Call objC new" in - PriorityNode.compute_results_to_parent trans_state loc nname stmt_info [res_trans_tmp] in - { res_trans with exps = [(Exp.Var init_ret_id, alloc_ret_type)]} + PriorityNode.compute_results_to_parent trans_state loc nname stmt_info [res_trans_tmp] + in + {res_trans with exps= [(Exp.Var init_ret_id, alloc_ret_type)]} let new_or_alloc_trans trans_state loc stmt_info qual_type class_name_opt selector = let tenv = trans_state.context.CContext.tenv in let function_type = CType_decl.qual_type_to_sil_type tenv qual_type in let class_name = match class_name_opt with - | Some class_name -> class_name - | None -> CType.objc_classname_of_type function_type in + | Some class_name + -> class_name + | None + -> CType.objc_classname_of_type function_type + in if String.equal selector CFrontend_config.alloc then alloc_trans trans_state loc stmt_info function_type true None else if String.equal selector CFrontend_config.new_str then @@ -367,30 +387,32 @@ let new_or_alloc_trans trans_state loc stmt_info qual_type class_name_opt select let cpp_new_trans sil_loc function_type size_exp_opt = let fname = - match size_exp_opt with - | Some _ -> BuiltinDecl.__new_array - | None -> BuiltinDecl.__new in - let (function_type, stmt_call, exp) = - create_alloc_instrs sil_loc function_type fname size_exp_opt None in - { empty_res_trans with instrs = [stmt_call]; exps = [(exp, function_type)] } + match size_exp_opt with Some _ -> BuiltinDecl.__new_array | None -> BuiltinDecl.__new + in + let function_type, stmt_call, exp = + create_alloc_instrs sil_loc function_type fname size_exp_opt None + in + {empty_res_trans with instrs= [stmt_call]; exps= [(exp, function_type)]} let create_cast_instrs exp cast_from_typ cast_to_typ sil_loc = let ret_id = Ident.create_fresh Ident.knormal in let ret_id_typ = Some (ret_id, cast_to_typ) in let typ = CType.remove_pointer_to_typ cast_to_typ in - let sizeof_exp = Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact} in + let sizeof_exp = Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} in let pname = BuiltinDecl.__objc_cast in let args = [(exp, cast_from_typ); (sizeof_exp, Typ.mk (Tint Typ.IULong))] in let stmt_call = - Sil.Call (ret_id_typ, Exp.Const (Const.Cfun pname), args, sil_loc, CallFlags.default) in + Sil.Call (ret_id_typ, Exp.Const (Const.Cfun pname), args, sil_loc, CallFlags.default) + in (stmt_call, Exp.Var ret_id) let cast_trans exps sil_loc function_type pname = if CTrans_models.is_toll_free_bridging pname then match exps with - | [exp, typ] -> - Some (create_cast_instrs exp typ function_type sil_loc) - | _ -> assert false + | [(exp, typ)] + -> Some (create_cast_instrs exp typ function_type sil_loc) + | _ + -> assert false else None let dereference_var_sil (exp, typ) sil_loc = @@ -401,151 +423,133 @@ let dereference_var_sil (exp, typ) sil_loc = (** Given trans_result with ONE expression, create temporary variable with value of an expression assigned to it *) let dereference_value_from_result sil_loc trans_result ~strip_pointer = - let (obj_sil, class_typ) = extract_exp_from_list trans_result.exps "" in - let typ_no_ptr = match class_typ.Typ.desc with | Tptr (typ, _) -> typ | _ -> assert false in + let obj_sil, class_typ = extract_exp_from_list trans_result.exps "" in + let typ_no_ptr = match class_typ.Typ.desc with Tptr (typ, _) -> typ | _ -> assert false in let cast_typ = if strip_pointer then typ_no_ptr else class_typ in let cast_inst, cast_exp = dereference_var_sil (obj_sil, cast_typ) sil_loc in - { trans_result with - instrs = trans_result.instrs @ cast_inst; - exps = [(cast_exp, cast_typ)] - } - + {trans_result with instrs= trans_result.instrs @ cast_inst; exps= [(cast_exp, cast_typ)]} let cast_operation trans_state cast_kind exps cast_typ sil_loc is_objc_bridged = - let (exp, typ) = extract_exp_from_list exps "" in + let exp, typ = extract_exp_from_list exps "" in let is_objc_bridged = Option.is_some trans_state.obj_bridged_cast_typ || is_objc_bridged in match cast_kind with - | `NoOp - | `DerivedToBase - | `UncheckedDerivedToBase -> (* These casts ignore change of type *) + | `NoOp | `DerivedToBase | `UncheckedDerivedToBase + -> (* These casts ignore change of type *) ([], (exp, typ)) - | `BitCast - | `IntegralCast - | `IntegralToBoolean -> (* This is treated as a nop by returning the same expressions exps*) + | `BitCast | `IntegralCast | `IntegralToBoolean + -> (* This is treated as a nop by returning the same expressions exps*) ([], (exp, cast_typ)) - | `CPointerToObjCPointerCast - | `ARCProduceObject - | `ARCConsumeObject when is_objc_bridged -> - (* Translation of __bridge_transfer or __bridge_retained *) + | (`CPointerToObjCPointerCast | `ARCProduceObject | `ARCConsumeObject) when is_objc_bridged + -> (* Translation of __bridge_transfer or __bridge_retained *) let objc_cast_typ = - match trans_state.obj_bridged_cast_typ with - | Some typ -> typ - | None -> cast_typ in + match trans_state.obj_bridged_cast_typ with Some typ -> typ | None -> cast_typ + in let instr, exp = create_cast_instrs exp typ objc_cast_typ sil_loc in - [instr], (exp, cast_typ) - | `LValueToRValue -> - (* Takes an LValue and allow it to use it as RValue. *) + ([instr], (exp, cast_typ)) + | `LValueToRValue + -> (* Takes an LValue and allow it to use it as RValue. *) (* So we assign the LValue to a temp and we pass it to the parent.*) let instrs, deref_exp = dereference_var_sil (exp, cast_typ) sil_loc in - instrs, (deref_exp, cast_typ) - | `NullToPointer -> - if Exp.is_zero exp then ([], (Exp.null, cast_typ)) - else ([], (exp, cast_typ)) - | _ -> - L.(debug Capture Verbose) + (instrs, (deref_exp, cast_typ)) + | `NullToPointer + -> if Exp.is_zero exp then ([], (Exp.null, cast_typ)) else ([], (exp, cast_typ)) + | _ + -> L.(debug Capture Verbose) "@\nWARNING: Missing translation for Cast Kind %s. The construct has been ignored...@\n" - (Clang_ast_j.string_of_cast_kind cast_kind); + (Clang_ast_j.string_of_cast_kind cast_kind) ; ([], (exp, cast_typ)) -let trans_assertion_failure sil_loc (context : CContext.t) = +let trans_assertion_failure sil_loc (context: CContext.t) = let assert_fail_builtin = Exp.Const (Const.Cfun BuiltinDecl.__infer_fail) in - let args = [Exp.Const (Const.Cstr Config.default_failure_name), Typ.mk Tvoid] in + let args = [(Exp.Const (Const.Cstr Config.default_failure_name), Typ.mk Tvoid)] in let call_instr = Sil.Call (None, assert_fail_builtin, args, sil_loc, CallFlags.default) in let exit_node = Procdesc.get_exit_node (CContext.get_procdesc context) and failure_node = - Nodes.create_node (Procdesc.Node.Stmt_node "Assertion failure") [call_instr] sil_loc context in - Procdesc.node_set_succs_exn context.procdesc failure_node [exit_node] []; - { empty_res_trans with root_nodes = [failure_node]; } + Nodes.create_node (Procdesc.Node.Stmt_node "Assertion failure") [call_instr] sil_loc context + in + Procdesc.node_set_succs_exn context.procdesc failure_node [exit_node] [] ; + {empty_res_trans with root_nodes= [failure_node]} -let trans_assume_false sil_loc (context : CContext.t) succ_nodes = +let trans_assume_false sil_loc (context: CContext.t) succ_nodes = let instrs_cond = [Sil.Prune (Exp.zero, sil_loc, true, Sil.Ik_land_lor)] in let prune_node = Nodes.create_node (Nodes.prune_kind true) instrs_cond sil_loc context in - Procdesc.node_set_succs_exn context.procdesc prune_node succ_nodes []; - { empty_res_trans with root_nodes = [prune_node]; leaf_nodes = [prune_node] } + Procdesc.node_set_succs_exn context.procdesc prune_node succ_nodes [] ; + {empty_res_trans with root_nodes= [prune_node]; leaf_nodes= [prune_node]} let trans_assertion trans_state sil_loc = let context = trans_state.context in - if Config.report_custom_error then - trans_assertion_failure sil_loc context + if Config.report_custom_error then trans_assertion_failure sil_loc context else trans_assume_false sil_loc context trans_state.succ_nodes let trans_builtin_expect params_trans_res = (* Translate call to __builtin_expect as the first argument *) (* for simpler symbolic execution *) - match params_trans_res with - | [_; fst_arg_res; _] -> Some fst_arg_res - | _ -> None + match params_trans_res with [_; fst_arg_res; _] -> Some fst_arg_res | _ -> None let trans_replace_with_deref_first_arg sil_loc params_trans_res ~cxx_method_call = - let first_arg_res_trans = match params_trans_res with - | _ :: fst_arg_res :: _ when not cxx_method_call -> fst_arg_res - | ({exps= _method_exp :: this_exp} as fst_arg_res) :: _ when cxx_method_call -> - (* method_deref_trans uses different format to store first argument - it stores + let first_arg_res_trans = + match params_trans_res with + | _ :: fst_arg_res :: _ when not cxx_method_call + -> fst_arg_res + | ({exps= _method_exp :: this_exp} as fst_arg_res) :: _ when cxx_method_call + -> (* method_deref_trans uses different format to store first argument - it stores two things in exps: [method_exp; this_exp]. We need to get rid of first exp before calling dereference_value_from_result *) - { fst_arg_res with exps = this_exp } - | _ -> assert false in + {fst_arg_res with exps= this_exp} + | _ + -> assert false + in dereference_value_from_result sil_loc first_arg_res_trans ~strip_pointer:true let builtin_trans trans_state loc stmt_info function_type params_trans_res pname = - if CTrans_models.is_cf_non_null_alloc pname || - CTrans_models.is_alloc_model function_type pname then - Some (alloc_trans trans_state loc stmt_info function_type true (Some pname)) + if CTrans_models.is_cf_non_null_alloc pname || CTrans_models.is_alloc_model function_type pname + then Some (alloc_trans trans_state loc stmt_info function_type true (Some pname)) else if CTrans_models.is_alloc pname then Some (alloc_trans trans_state loc stmt_info function_type false None) - else if CTrans_models.is_assert_log pname then - Some (trans_assertion trans_state loc) - else if CTrans_models.is_builtin_expect pname then - trans_builtin_expect params_trans_res + else if CTrans_models.is_assert_log pname then Some (trans_assertion trans_state loc) + else if CTrans_models.is_builtin_expect pname then trans_builtin_expect params_trans_res else if CTrans_models.is_replace_with_deref_first_arg pname then Some (trans_replace_with_deref_first_arg loc params_trans_res ~cxx_method_call:false) else None let cxx_method_builtin_trans trans_state loc params_trans_res pname = - if CTrans_models.is_assert_log pname then - Some (trans_assertion trans_state loc) + if CTrans_models.is_assert_log pname then Some (trans_assertion trans_state loc) else if CTrans_models.is_replace_with_deref_first_arg pname then Some (trans_replace_with_deref_first_arg loc params_trans_res ~cxx_method_call:true) - else - None + else None let define_condition_side_effects e_cond instrs_cond sil_loc = - let (e', typ) = extract_exp_from_list e_cond - "@\nWARNING: Missing expression in IfStmt. Need to be fixed@\n" in + let e', typ = + extract_exp_from_list e_cond "@\nWARNING: Missing expression in IfStmt. Need to be fixed@\n" + in match e' with - | Exp.Lvar pvar -> - let id = Ident.create_fresh Ident.knormal in - [(Exp.Var id, typ)], - [Sil.Load (id, Exp.Lvar pvar, typ, sil_loc)] - | _ -> [(e', typ)], instrs_cond + | Exp.Lvar pvar + -> let id = Ident.create_fresh Ident.knormal in + ([(Exp.Var id, typ)], [Sil.Load (id, Exp.Lvar pvar, typ, sil_loc)]) + | _ + -> ([(e', typ)], instrs_cond) let is_superinstance mei = - match mei.Clang_ast_t.omei_receiver_kind with - | `SuperInstance -> true - | _ -> false + match mei.Clang_ast_t.omei_receiver_kind with `SuperInstance -> true | _ -> false let get_selector_receiver obj_c_message_expr_info = - obj_c_message_expr_info.Clang_ast_t.omei_selector, obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind + ( obj_c_message_expr_info.Clang_ast_t.omei_selector + , obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind ) -let is_member_exp stmt = - match stmt with - | Clang_ast_t.MemberExpr _ -> true - | _ -> false +let is_member_exp stmt = match stmt with Clang_ast_t.MemberExpr _ -> true | _ -> false let is_enumeration_constant stmt = match stmt with - | Clang_ast_t.DeclRefExpr(_, _, _, drei) -> - (match drei.Clang_ast_t.drti_decl_ref with - | Some d -> (match d.Clang_ast_t.dr_kind with - | `EnumConstant -> true - | _ -> false) - | _ -> false) - | _ -> false - -let is_null_stmt s = - match s with - | Clang_ast_t.NullStmt _ -> true - | _ -> false + | Clang_ast_t.DeclRefExpr (_, _, _, drei) -> ( + match drei.Clang_ast_t.drti_decl_ref with + | Some d -> ( + match d.Clang_ast_t.dr_kind with `EnumConstant -> true | _ -> false ) + | _ + -> false ) + | _ + -> false + +let is_null_stmt s = match s with Clang_ast_t.NullStmt _ -> true | _ -> false let extract_stmt_from_singleton stmt_list warning_string = extract_item_from_singleton stmt_list warning_string (Ast_expressions.dummy_stmt ()) @@ -553,52 +557,57 @@ let extract_stmt_from_singleton stmt_list warning_string = let rec get_type_from_exp_stmt stmt = let do_decl_ref_exp i = match i.Clang_ast_t.drti_decl_ref with - | Some d -> (match d.Clang_ast_t.dr_qual_type with - | Some n -> n - | _ -> assert false ) - | _ -> assert false in + | Some d -> ( + match d.Clang_ast_t.dr_qual_type with Some n -> n | _ -> assert false ) + | _ + -> assert false + in let open Clang_ast_t in match stmt with - | CXXOperatorCallExpr(_, _, ei) - | CallExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type - | MemberExpr (_, _, ei, _) -> ei.Clang_ast_t.ei_qual_type - | ParenExpr (_, _, ei) -> ei.Clang_ast_t.ei_qual_type - | ArraySubscriptExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type - | ObjCIvarRefExpr (_, _, ei, _) -> ei.Clang_ast_t.ei_qual_type - | ObjCMessageExpr (_, _, ei, _ ) -> ei.Clang_ast_t.ei_qual_type - | PseudoObjectExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type - | CStyleCastExpr(_, stmt_list, _, _, _) - | UnaryOperator(_, stmt_list, _, _) - | ImplicitCastExpr(_, stmt_list, _, _) -> - get_type_from_exp_stmt (extract_stmt_from_singleton stmt_list "WARNING: We expect only one stmt.") - | DeclRefExpr(_, _, _, info) -> do_decl_ref_exp info - | _ -> - L.internal_error "Failing with: %s@\n%!" (Clang_ast_j.string_of_stmt stmt); + | CXXOperatorCallExpr (_, _, ei) | CallExpr (_, _, ei) + -> ei.Clang_ast_t.ei_qual_type + | MemberExpr (_, _, ei, _) + -> ei.Clang_ast_t.ei_qual_type + | ParenExpr (_, _, ei) + -> ei.Clang_ast_t.ei_qual_type + | ArraySubscriptExpr (_, _, ei) + -> ei.Clang_ast_t.ei_qual_type + | ObjCIvarRefExpr (_, _, ei, _) + -> ei.Clang_ast_t.ei_qual_type + | ObjCMessageExpr (_, _, ei, _) + -> ei.Clang_ast_t.ei_qual_type + | PseudoObjectExpr (_, _, ei) + -> ei.Clang_ast_t.ei_qual_type + | CStyleCastExpr (_, stmt_list, _, _, _) + | UnaryOperator (_, stmt_list, _, _) + | ImplicitCastExpr (_, stmt_list, _, _) + -> get_type_from_exp_stmt + (extract_stmt_from_singleton stmt_list "WARNING: We expect only one stmt.") + | DeclRefExpr (_, _, _, info) + -> do_decl_ref_exp info + | _ + -> L.internal_error "Failing with: %s@\n%!" (Clang_ast_j.string_of_stmt stmt) ; assert false -module Self = -struct - +module Self = struct exception SelfClassException of Typ.Name.t let add_self_parameter_for_super_instance context procname loc mei = if is_superinstance mei then let typ, self_expr, ins = let t' = - CType.add_pointer_to_typ - (Typ.mk (Tstruct (CContext.get_curr_class_typename context))) in + CType.add_pointer_to_typ (Typ.mk (Tstruct (CContext.get_curr_class_typename context))) + in let e = Exp.Lvar (Pvar.mk (Mangled.from_string CFrontend_config.self) procname) in let id = Ident.create_fresh Ident.knormal in - t', Exp.Var id, [Sil.Load (id, e, t', loc)] in - { empty_res_trans with - exps = [(self_expr, typ)]; - instrs = ins } + (t', Exp.Var id, [Sil.Load (id, e, t', loc)]) + in + {empty_res_trans with exps= [(self_expr, typ)]; instrs= ins} else empty_res_trans let is_var_self pvar is_objc_method = let is_self = String.equal (Mangled.to_string (Pvar.get_name pvar)) CFrontend_config.self in is_self && is_objc_method - end (* From the manual: A selector is in a certain selector family if, ignoring any leading underscores, *) @@ -607,104 +616,127 @@ end (* For example: '__perform:with' and 'performWith:' would fall into the 'perform' family (if we had one),*) (* but 'performing:with' would not. *) let is_owning_name n = - let is_family fam s'= + let is_family fam s' = if String.length s' < String.length fam then false - else ( + else let prefix = Str.string_before s' (String.length fam) in let suffix = Str.string_after s' (String.length fam) in String.equal prefix fam && not (Str.string_match (Str.regexp "[a-z]") suffix 0) - ) in + in match Str.split (Str.regexp_string ":") n with - | fst:: _ -> - (match Str.split (Str.regexp "['_']+") fst with - | [no_und] - | _:: no_und:: _ -> - is_family CFrontend_config.alloc no_und || - is_family CFrontend_config.copy no_und || - is_family CFrontend_config.new_str no_und || - is_family CFrontend_config.mutableCopy no_und || - is_family CFrontend_config.init no_und - | _ -> assert false) - | _ -> assert false + | fst :: _ -> ( + match Str.split (Str.regexp "['_']+") fst with + | [no_und] | _ :: no_und :: _ + -> is_family CFrontend_config.alloc no_und || is_family CFrontend_config.copy no_und + || is_family CFrontend_config.new_str no_und + || is_family CFrontend_config.mutableCopy no_und || is_family CFrontend_config.init no_und + | _ + -> assert false ) + | _ + -> assert false let rec is_owning_method s = match s with - | Clang_ast_t.ObjCMessageExpr(_, _ , _, mei) -> - is_owning_name mei.Clang_ast_t.omei_selector - | _ -> (match snd (Clang_ast_proj.get_stmt_tuple s) with - | [] -> false - | s'':: _ -> is_owning_method s'') + | Clang_ast_t.ObjCMessageExpr (_, _, _, mei) + -> is_owning_name mei.Clang_ast_t.omei_selector + | _ -> + match snd (Clang_ast_proj.get_stmt_tuple s) with + | [] + -> false + | s'' :: _ + -> is_owning_method s'' let rec is_method_call s = match s with - | Clang_ast_t.ObjCMessageExpr _ -> true - | _ -> (match snd (Clang_ast_proj.get_stmt_tuple s) with - | [] -> false - | s'':: _ -> is_method_call s'') + | Clang_ast_t.ObjCMessageExpr _ + -> true + | _ -> + match snd (Clang_ast_proj.get_stmt_tuple s) with [] -> false | s'' :: _ -> is_method_call s'' let rec get_decl_ref_info s = match s with - | Clang_ast_t.DeclRefExpr (_, _, _, decl_ref_expr_info) -> - (match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with - | Some decl_ref -> decl_ref - | None -> assert false) + | Clang_ast_t.DeclRefExpr (_, _, _, decl_ref_expr_info) -> ( + match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some decl_ref + -> decl_ref + | None + -> assert false ) | _ -> - match Clang_ast_proj.get_stmt_tuple s with - | _, [] -> assert false - | _, s'':: _ -> - get_decl_ref_info s'' + match Clang_ast_proj.get_stmt_tuple s with + | _, [] + -> assert false + | _, s'' :: _ + -> get_decl_ref_info s'' let rec contains_opaque_value_expr s = match s with - | Clang_ast_t.OpaqueValueExpr _ -> true - | _ -> match snd (Clang_ast_proj.get_stmt_tuple s) with - | [] -> false - | s'':: _ -> contains_opaque_value_expr s'' + | Clang_ast_t.OpaqueValueExpr _ + -> true + | _ -> + match snd (Clang_ast_proj.get_stmt_tuple s) with + | [] + -> false + | s'' :: _ + -> contains_opaque_value_expr s'' (* checks if a unary operator is a logic negation applied to integers*) let is_logical_negation_of_int tenv ei uoi = - match (CType_decl.qual_type_to_sil_type tenv ei.Clang_ast_t.ei_qual_type).desc, - uoi.Clang_ast_t.uoi_kind with - | Typ.Tint _,`LNot -> true - | _, _ -> false + match + ( (CType_decl.qual_type_to_sil_type tenv ei.Clang_ast_t.ei_qual_type).desc + , uoi.Clang_ast_t.uoi_kind ) + with + | Typ.Tint _, `LNot + -> true + | _, _ + -> false let rec is_block_stmt stmt = let open Clang_ast_t in match stmt with - | BlockExpr _ -> true - | DeclRefExpr (_, _, expr_info, _) -> - let qt = expr_info.Clang_ast_t.ei_qual_type in + | BlockExpr _ + -> true + | DeclRefExpr (_, _, expr_info, _) + -> let qt = expr_info.Clang_ast_t.ei_qual_type in CType.is_block_type qt - | _ -> (match snd (Clang_ast_proj.get_stmt_tuple stmt) with - | [sub_stmt] -> is_block_stmt sub_stmt - | _ -> false) + | _ -> + match snd (Clang_ast_proj.get_stmt_tuple stmt) with + | [sub_stmt] + -> is_block_stmt sub_stmt + | _ + -> false (* Checks if stmt_list is a call to a special dispatch function *) let is_dispatch_function stmt_list = let open Clang_ast_t in let rec is_dispatch_function stmt arg_stmts = match stmt with - | DeclRefExpr (_, _, _, di) -> - (match di.Clang_ast_t.drti_decl_ref with - | None -> None - | Some d -> - (match d.Clang_ast_t.dr_kind, d.Clang_ast_t.dr_name with - | `Function, Some name_info -> - let s = name_info.Clang_ast_t.ni_name in - (match (CTrans_models.is_dispatch_function_name s) with - | None -> None - | Some (_, block_arg_pos) -> - try - let arg_stmt = List.nth_exn arg_stmts block_arg_pos in - if is_block_stmt arg_stmt then Some block_arg_pos else None - with Failure _ -> None) - | _ -> None)) - | _ -> match snd (Clang_ast_proj.get_stmt_tuple stmt) with - | [sub_stmt] -> is_dispatch_function sub_stmt arg_stmts - | _ -> None in - match stmt_list with - | stmt:: arg_stmts -> is_dispatch_function stmt arg_stmts - | _ -> None + | DeclRefExpr (_, _, _, di) -> ( + match di.Clang_ast_t.drti_decl_ref with + | None + -> None + | Some d -> + match (d.Clang_ast_t.dr_kind, d.Clang_ast_t.dr_name) with + | `Function, Some name_info + -> ( + let s = name_info.Clang_ast_t.ni_name in + match CTrans_models.is_dispatch_function_name s with + | None + -> None + | Some (_, block_arg_pos) -> + try + let arg_stmt = List.nth_exn arg_stmts block_arg_pos in + if is_block_stmt arg_stmt then Some block_arg_pos else None + with Failure _ -> None ) + | _ + -> None ) + | _ -> + match snd (Clang_ast_proj.get_stmt_tuple stmt) with + | [sub_stmt] + -> is_dispatch_function sub_stmt arg_stmts + | _ + -> None + in + match stmt_list with stmt :: arg_stmts -> is_dispatch_function stmt arg_stmts | _ -> None let is_block_enumerate_function mei = String.equal mei.Clang_ast_t.omei_selector CFrontend_config.enumerateObjectsUsingBlock @@ -713,36 +745,39 @@ let is_block_enumerate_function mei = (* for each of its fields (also recursively, such that each field access is of a basic type) *) (* If the flag return_zero is true, the list will be a list of zero values, otherwise it will *) (* be a list of LField expressions *) -let var_or_zero_in_init_list tenv e typ ~return_zero:return_zero = +let var_or_zero_in_init_list tenv e typ ~return_zero = let rec var_or_zero_in_init_list' e typ tns = let open CGeneral_utils in match typ.Typ.desc with | Tstruct tn -> ( - match Tenv.lookup tenv tn with - | Some { fields } -> - let lh_exprs = - List.map ~f:(fun (fieldname, _, _) -> Exp.Lfield (e, fieldname, typ)) fields in - let lh_types = List.map ~f:(fun (_, fieldtype, _) -> fieldtype) fields in - let exp_types = zip lh_exprs lh_types in - List.map ~f:(fun (e, t) -> List.concat (var_or_zero_in_init_list' e t tns)) exp_types - | None -> - assert false - ) - | Tarray (arrtyp, Some n, _) -> - let size = IntLit.to_int n in + match Tenv.lookup tenv tn with + | Some {fields} + -> let lh_exprs = + List.map ~f:(fun (fieldname, _, _) -> Exp.Lfield (e, fieldname, typ)) fields + in + let lh_types = List.map ~f:(fun (_, fieldtype, _) -> fieldtype) fields in + let exp_types = zip lh_exprs lh_types in + List.map ~f:(fun (e, t) -> List.concat (var_or_zero_in_init_list' e t tns)) exp_types + | None + -> assert false ) + | Tarray (arrtyp, Some n, _) + -> let size = IntLit.to_int n in let indices = list_range 0 (size - 1) in let index_constants = - List.map ~f:(fun i -> (Exp.Const (Const.Cint (IntLit.of_int i)))) indices in + List.map ~f:(fun i -> Exp.Const (Const.Cint (IntLit.of_int i))) indices + in let lh_exprs = - List.map ~f:(fun index_expr -> Exp.Lindex (e, index_expr)) index_constants in + List.map ~f:(fun index_expr -> Exp.Lindex (e, index_expr)) index_constants + in let lh_types = replicate size arrtyp in let exp_types = zip lh_exprs lh_types in - List.map ~f:(fun (e, t) -> - List.concat (var_or_zero_in_init_list' e t tns)) exp_types - | Tint _ | Tfloat _ | Tptr _ -> - let exp = if return_zero then Sil.zero_value_of_numerical_type typ else e in - [ [(exp, typ)] ] - | Tfun _ | Tvoid | Tarray _ | TVar _ -> assert false in + List.map ~f:(fun (e, t) -> List.concat (var_or_zero_in_init_list' e t tns)) exp_types + | Tint _ | Tfloat _ | Tptr _ + -> let exp = if return_zero then Sil.zero_value_of_numerical_type typ else e in + [[(exp, typ)]] + | Tfun _ | Tvoid | Tarray _ | TVar _ + -> assert false + in List.concat (var_or_zero_in_init_list' e typ String.Set.empty) (* diff --git a/infer/src/clang/cTrans_utils.mli b/infer/src/clang/cTrans_utils.mli index 82fd6312e..7cd4d9f4f 100644 --- a/infer/src/clang/cTrans_utils.mli +++ b/infer/src/clang/cTrans_utils.mli @@ -11,62 +11,58 @@ open! IStd (** Utility methods to support the translation of clang ast constructs into sil instructions. *) -type continuation = { - break: Procdesc.Node.t list; - continue: Procdesc.Node.t list; - return_temp : bool; (* true if temps should not be removed in the node but returned to ancestors *) -} - -type priority_node = - | Free - | Busy of Clang_ast_t.pointer - -type trans_state = { - context: CContext.t; - succ_nodes: Procdesc.Node.t list; - continuation: continuation option; - priority: priority_node; - var_exp_typ: (Exp.t * Typ.t) option; - opaque_exp: (Exp.t * Typ.t) option; - obj_bridged_cast_typ : Typ.t option -} - -type trans_result = { - root_nodes: Procdesc.Node.t list; - leaf_nodes: Procdesc.Node.t list; - instrs: Sil.instr list; - exps: (Exp.t * Typ.t) list; - initd_exps: Exp.t list; - is_cpp_call_virtual : bool; -} +type continuation = + { break: Procdesc.Node.t list + ; continue: Procdesc.Node.t list + ; return_temp: bool + (* true if temps should not be removed in the node but returned to ancestors *) } + +type priority_node = Free | Busy of Clang_ast_t.pointer + +type trans_state = + { context: CContext.t + ; succ_nodes: Procdesc.Node.t list + ; continuation: continuation option + ; priority: priority_node + ; var_exp_typ: (Exp.t * Typ.t) option + ; opaque_exp: (Exp.t * Typ.t) option + ; obj_bridged_cast_typ: Typ.t option } + +type trans_result = + { root_nodes: Procdesc.Node.t list + ; leaf_nodes: Procdesc.Node.t list + ; instrs: Sil.instr list + ; exps: (Exp.t * Typ.t) list + ; initd_exps: Exp.t list + ; is_cpp_call_virtual: bool } exception TemplatedCodeException of Clang_ast_t.stmt -val empty_res_trans: trans_result +val empty_res_trans : trans_result -val undefined_expression: unit -> Exp.t +val undefined_expression : unit -> Exp.t val collect_res_trans : Procdesc.t -> trans_result list -> trans_result val extract_var_exp_or_fail : trans_state -> Exp.t * Typ.t -val is_return_temp: continuation option -> bool +val is_return_temp : continuation option -> bool -val ids_to_parent: continuation option -> Ident.t list -> Ident.t list +val ids_to_parent : continuation option -> Ident.t list -> Ident.t list -val ids_to_node: continuation option -> Ident.t list -> Ident.t list +val ids_to_node : continuation option -> Ident.t list -> Ident.t list val mk_cond_continuation : continuation option -> continuation option val extract_item_from_singleton : 'a list -> string -> 'a -> 'a -val extract_exp_from_list : (Exp.t * Typ.t) list -> string -> (Exp.t * Typ.t) +val extract_exp_from_list : (Exp.t * Typ.t) list -> string -> Exp.t * Typ.t -val get_selector_receiver : Clang_ast_t.obj_c_message_expr_info -> string * Clang_ast_t.receiver_kind +val get_selector_receiver : + Clang_ast_t.obj_c_message_expr_info -> string * Clang_ast_t.receiver_kind val define_condition_side_effects : - (Exp.t * Typ.t) list -> Sil.instr list -> Location.t -> - (Exp.t * Typ.t) list * Sil.instr list + (Exp.t * Typ.t) list -> Sil.instr list -> Location.t -> (Exp.t * Typ.t) list * Sil.instr list val extract_stmt_from_singleton : Clang_ast_t.stmt list -> string -> Clang_ast_t.stmt @@ -78,15 +74,16 @@ val is_member_exp : Clang_ast_t.stmt -> bool val get_type_from_exp_stmt : Clang_ast_t.stmt -> Clang_ast_t.qual_type +val dereference_value_from_result : + Location.t -> trans_result -> strip_pointer:bool -> trans_result (** Given trans_result with ONE expression, create temporary variable with dereferenced value of an expression assigned to it *) -val dereference_value_from_result : Location.t -> trans_result -> strip_pointer:bool -> trans_result val cast_operation : - trans_state -> Clang_ast_t.cast_kind -> (Exp.t * Typ.t) list -> Typ.t -> Location.t -> - bool -> Sil.instr list * (Exp.t * Typ.t) + trans_state -> Clang_ast_t.cast_kind -> (Exp.t * Typ.t) list -> Typ.t -> Location.t -> bool + -> Sil.instr list * (Exp.t * Typ.t) -val trans_assertion: trans_state -> Location.t -> trans_result +val trans_assertion : trans_state -> Location.t -> trans_result val is_owning_method : Clang_ast_t.stmt -> bool @@ -98,18 +95,20 @@ val contains_opaque_value_expr : Clang_ast_t.stmt -> bool val get_decl_ref_info : Clang_ast_t.stmt -> Clang_ast_t.decl_ref -val builtin_trans : trans_state -> Location.t -> Clang_ast_t.stmt_info -> - Typ.t -> trans_result list -> Typ.Procname.t -> trans_result option +val builtin_trans : + trans_state -> Location.t -> Clang_ast_t.stmt_info -> Typ.t -> trans_result list + -> Typ.Procname.t -> trans_result option -val cxx_method_builtin_trans : trans_state -> Location.t -> trans_result list -> - Typ.Procname.t -> trans_result option +val cxx_method_builtin_trans : + trans_state -> Location.t -> trans_result list -> Typ.Procname.t -> trans_result option val alloc_trans : - trans_state -> Location.t -> Clang_ast_t.stmt_info -> Typ.t -> bool -> - Typ.Procname.t option -> trans_result + trans_state -> Location.t -> Clang_ast_t.stmt_info -> Typ.t -> bool -> Typ.Procname.t option + -> trans_result -val new_or_alloc_trans : trans_state -> Location.t -> Clang_ast_t.stmt_info -> - Clang_ast_t.qual_type -> Typ.Name.t option -> string -> trans_result +val new_or_alloc_trans : + trans_state -> Location.t -> Clang_ast_t.stmt_info -> Clang_ast_t.qual_type -> Typ.Name.t option + -> string -> trans_result val cpp_new_trans : Location.t -> Typ.t -> Exp.t option -> trans_result @@ -119,8 +118,7 @@ val cast_trans : val dereference_var_sil : Exp.t * Typ.t -> Location.t -> Sil.instr list * Exp.t (** Module for creating cfg nodes and other utility functions related to them. *) -module Nodes : -sig +module Nodes : sig val is_binary_assign_op : Clang_ast_t.binary_operator_info -> bool val need_unary_op_node : Clang_ast_t.unary_operator_info -> bool @@ -131,15 +129,14 @@ sig val is_join_node : Procdesc.Node.t -> bool val create_prune_node : - bool -> (Exp.t * Typ.t) list -> Sil.instr list -> Location.t -> Sil.if_kind -> - CContext.t -> Procdesc.Node.t + bool -> (Exp.t * Typ.t) list -> Sil.instr list -> Location.t -> Sil.if_kind -> CContext.t + -> Procdesc.Node.t val is_prune_node : Procdesc.Node.t -> bool val is_true_prune_node : Procdesc.Node.t -> bool val prune_kind : bool -> Procdesc.Node.nodekind - end (** priority_node is used to enforce some kind of policy for creating nodes in the cfg. Certain @@ -151,9 +148,7 @@ end finished it frees the priority. In general an AST element E checks if an ancestor has claimed priority. If priority is already claimed E does not have to create a node. If priority is free then it means E has to create the node. Then E claims priority and release it afterward. *) -module PriorityNode : -sig - +module PriorityNode : sig type t = priority_node val is_priority_free : trans_state -> bool @@ -168,45 +163,48 @@ sig (* It connects nodes returned by translation of stmt children and *) (* deals with creating or not a cfg node depending of owning the *) (* priority_node. It returns nodes, ids, instrs that should be passed to parent *) - val compute_results_to_parent : trans_state -> Location.t -> string -> Clang_ast_t.stmt_info -> - trans_result list -> trans_result + val compute_results_to_parent : + trans_state -> Location.t -> string -> Clang_ast_t.stmt_info -> trans_result list + -> trans_result end (** Module for translating goto instructions by keeping a map of labels. *) -module GotoLabel : -sig +module GotoLabel : sig val find_goto_label : CContext.t -> string -> Location.t -> Procdesc.Node.t end (** Module that provides utility functions for translating different types of loops. *) -module Loops : -sig +module Loops : sig type loop_kind = - | For of Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt + | For of + Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt + * Clang_ast_t.stmt (* init, decl_stmt, condition, increment and body *) | While of Clang_ast_t.stmt option * Clang_ast_t.stmt * Clang_ast_t.stmt (* decl_stmt, condition and body *) - | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt + + (* condition and body *) val loop_kind_to_if_kind : loop_kind -> Sil.if_kind val get_cond : loop_kind -> Clang_ast_t.stmt val get_body : loop_kind -> Clang_ast_t.stmt - end (** This module handles the translation of the variable self which is challenging because self is used both as a variable in instance method calls and also as a type in class method calls. *) -module Self : -sig - +module Self : sig exception SelfClassException of Typ.Name.t val add_self_parameter_for_super_instance : - CContext.t -> Typ.Procname.t -> Location.t -> Clang_ast_t.obj_c_message_expr_info -> - trans_result + CContext.t -> Typ.Procname.t -> Location.t -> Clang_ast_t.obj_c_message_expr_info + -> trans_result val is_var_self : Pvar.t -> bool -> bool end @@ -218,5 +216,4 @@ val is_dispatch_function : Clang_ast_t.stmt list -> int option val is_block_enumerate_function : Clang_ast_t.obj_c_message_expr_info -> bool -val var_or_zero_in_init_list : Tenv.t -> Exp.t -> Typ.t -> return_zero:bool -> - (Exp.t * Typ.t) list +val var_or_zero_in_init_list : Tenv.t -> Exp.t -> Typ.t -> return_zero:bool -> (Exp.t * Typ.t) list diff --git a/infer/src/clang/cType_to_sil_type.ml b/infer/src/clang/cType_to_sil_type.ml index 36695deb9..e1a7ca8d7 100644 --- a/infer/src/clang/cType_to_sil_type.ml +++ b/infer/src/clang/cType_to_sil_type.ml @@ -8,61 +8,83 @@ *) open! IStd - module L = Logging let get_builtin_objc_typename builtin_type = match builtin_type with - | `ObjCId -> Typ.Name.C.from_string CFrontend_config.objc_object - | `ObjCClass -> Typ.Name.C.from_string CFrontend_config.objc_class + | `ObjCId + -> Typ.Name.C.from_string CFrontend_config.objc_object + | `ObjCClass + -> Typ.Name.C.from_string CFrontend_config.objc_class let get_builtin_objc_type builtin_type = let typ = Typ.mk (Tstruct (get_builtin_objc_typename builtin_type)) in - match builtin_type with - | `ObjCId -> typ.Typ.desc - | `ObjCClass -> Typ.Tptr (typ, Typ.Pk_pointer) - + match builtin_type with `ObjCId -> typ.Typ.desc | `ObjCClass -> Typ.Tptr (typ, Typ.Pk_pointer) let type_desc_of_builtin_type_kind builtin_type_kind = match builtin_type_kind with - | `Void -> Typ.Tvoid - | `Bool -> Typ.Tint IBool - | `Char_U -> Typ.Tint IUChar - | `UChar -> Typ.Tint IUChar - | `WChar_U -> Typ.Tint IUChar - | `Char_S -> Typ.Tint IChar - | `SChar -> Typ.Tint ISChar - | `WChar_S - | `Char16 - | `Char32 -> Typ.Tint IChar - | `UShort -> Typ.Tint IUShort - | `Short -> Typ.Tint IShort - | `UInt - | `UInt128 -> Typ.Tint IUInt - | `ULong -> Typ.Tint IULong - | `ULongLong -> Typ.Tint IULongLong - | `Int - | `Int128 -> Typ.Tint IInt - | `Long -> Typ.Tint ILong - | `LongLong -> Typ.Tint ILongLong - | `Half -> Typ.Tint IShort (*?*) - | `Float -> Typ.Tfloat FFloat - | `Double -> Typ.Tfloat FDouble - | `LongDouble -> Typ.Tfloat FLongDouble - | `NullPtr -> Typ.Tint IInt - | `ObjCId -> get_builtin_objc_type `ObjCId - | `ObjCClass -> get_builtin_objc_type `ObjCClass - | _ -> Typ.Tvoid + | `Void + -> Typ.Tvoid + | `Bool + -> Typ.Tint IBool + | `Char_U + -> Typ.Tint IUChar + | `UChar + -> Typ.Tint IUChar + | `WChar_U + -> Typ.Tint IUChar + | `Char_S + -> Typ.Tint IChar + | `SChar + -> Typ.Tint ISChar + | `WChar_S | `Char16 | `Char32 + -> Typ.Tint IChar + | `UShort + -> Typ.Tint IUShort + | `Short + -> Typ.Tint IShort + | `UInt | `UInt128 + -> Typ.Tint IUInt + | `ULong + -> Typ.Tint IULong + | `ULongLong + -> Typ.Tint IULongLong + | `Int | `Int128 + -> Typ.Tint IInt + | `Long + -> Typ.Tint ILong + | `LongLong + -> Typ.Tint ILongLong + | `Half + -> Typ.Tint IShort (*?*) + | `Float + -> Typ.Tfloat FFloat + | `Double + -> Typ.Tfloat FDouble + | `LongDouble + -> Typ.Tfloat FLongDouble + | `NullPtr + -> Typ.Tint IInt + | `ObjCId + -> get_builtin_objc_type `ObjCId + | `ObjCClass + -> get_builtin_objc_type `ObjCClass + | _ + -> Typ.Tvoid let pointer_attribute_of_objc_attribute attr_info = match attr_info.Clang_ast_t.ati_lifetime with - | `OCL_None | `OCL_Strong -> Typ.Pk_pointer - | `OCL_ExplicitNone -> Typ.Pk_objc_unsafe_unretained - | `OCL_Weak -> Typ.Pk_objc_weak - | `OCL_Autoreleasing -> Typ.Pk_objc_autoreleasing + | `OCL_None | `OCL_Strong + -> Typ.Pk_pointer + | `OCL_ExplicitNone + -> Typ.Pk_objc_unsafe_unretained + | `OCL_Weak + -> Typ.Pk_objc_weak + | `OCL_Autoreleasing + -> Typ.Pk_objc_autoreleasing -let rec build_array_type translate_decl tenv (qual_type : Clang_ast_t.qual_type) - length_opt stride_opt = +let rec build_array_type translate_decl tenv (qual_type: Clang_ast_t.qual_type) length_opt + stride_opt = let array_type = qual_type_to_sil_type translate_decl tenv qual_type in let length = Option.map ~f:IntLit.of_int length_opt in let stride = Option.map ~f:IntLit.of_int stride_opt in @@ -70,117 +92,128 @@ let rec build_array_type translate_decl tenv (qual_type : Clang_ast_t.qual_type) and type_desc_of_attr_type translate_decl tenv type_info attr_info = match type_info.Clang_ast_t.ti_desugared_type with - | Some type_ptr -> - (match CAst_utils.get_type type_ptr with - | Some Clang_ast_t.ObjCObjectPointerType (_, qual_type) -> - let typ = qual_type_to_sil_type translate_decl tenv qual_type in - Typ.Tptr (typ, pointer_attribute_of_objc_attribute attr_info) - | _ -> type_ptr_to_type_desc translate_decl tenv type_ptr) - | None -> Typ.Tvoid + | Some type_ptr -> ( + match CAst_utils.get_type type_ptr with + | Some Clang_ast_t.ObjCObjectPointerType (_, qual_type) + -> let typ = qual_type_to_sil_type translate_decl tenv qual_type in + Typ.Tptr (typ, pointer_attribute_of_objc_attribute attr_info) + | _ + -> type_ptr_to_type_desc translate_decl tenv type_ptr ) + | None + -> Typ.Tvoid and type_desc_of_c_type translate_decl tenv c_type : Typ.desc = let open Clang_ast_t in match c_type with - | NoneType _ -> Tvoid - | BuiltinType (_, builtin_type_kind) -> - type_desc_of_builtin_type_kind builtin_type_kind - | PointerType (_, qual_type) - | ObjCObjectPointerType (_, qual_type) -> - let typ = qual_type_to_sil_type translate_decl tenv qual_type in + | NoneType _ + -> Tvoid + | BuiltinType (_, builtin_type_kind) + -> type_desc_of_builtin_type_kind builtin_type_kind + | PointerType (_, qual_type) | ObjCObjectPointerType (_, qual_type) + -> let typ = qual_type_to_sil_type translate_decl tenv qual_type in let desc = typ.Typ.desc in - if Typ.equal_desc desc (get_builtin_objc_type `ObjCClass) then - desc + if Typ.equal_desc desc (get_builtin_objc_type `ObjCClass) then desc else Typ.Tptr (typ, Typ.Pk_pointer) - | ObjCObjectType (_, objc_object_type_info) -> - type_ptr_to_type_desc translate_decl tenv objc_object_type_info.Clang_ast_t.base_type - | BlockPointerType (_, qual_type) -> - let typ = qual_type_to_sil_type translate_decl tenv qual_type in + | ObjCObjectType (_, objc_object_type_info) + -> type_ptr_to_type_desc translate_decl tenv objc_object_type_info.Clang_ast_t.base_type + | BlockPointerType (_, qual_type) + -> let typ = qual_type_to_sil_type translate_decl tenv qual_type in Typ.Tptr (typ, Typ.Pk_pointer) | IncompleteArrayType (_, {arti_element_type; arti_stride}) - | DependentSizedArrayType (_, {arti_element_type; arti_stride}) -> - build_array_type translate_decl tenv arti_element_type None arti_stride - | VariableArrayType (_, {arti_element_type; arti_stride}, _) -> - build_array_type translate_decl tenv arti_element_type None arti_stride - | ConstantArrayType (_, {arti_element_type; arti_stride}, n) -> - build_array_type translate_decl tenv arti_element_type (Some n) arti_stride - | FunctionProtoType _ - | FunctionNoProtoType _ -> - Typ.Tfun false - | ParenType (_, qual_type) -> - (qual_type_to_sil_type translate_decl tenv qual_type).Typ.desc - | DecayedType (_, qual_type) -> - (qual_type_to_sil_type translate_decl tenv qual_type).Typ.desc - | RecordType (_, pointer) - | EnumType (_, pointer) -> - decl_ptr_to_type_desc translate_decl tenv pointer - | ElaboratedType (type_info) -> - (match type_info.Clang_ast_t.ti_desugared_type with (* TODO desugar to qualtype *) - Some type_ptr -> type_ptr_to_type_desc translate_decl tenv type_ptr - | None -> Typ.Tvoid) - | ObjCInterfaceType (_, pointer) -> - decl_ptr_to_type_desc translate_decl tenv pointer - | RValueReferenceType (_, qual_type) - | LValueReferenceType (_, qual_type) -> - let typ = qual_type_to_sil_type translate_decl tenv qual_type in + | DependentSizedArrayType (_, {arti_element_type; arti_stride}) + -> build_array_type translate_decl tenv arti_element_type None arti_stride + | VariableArrayType (_, {arti_element_type; arti_stride}, _) + -> build_array_type translate_decl tenv arti_element_type None arti_stride + | ConstantArrayType (_, {arti_element_type; arti_stride}, n) + -> build_array_type translate_decl tenv arti_element_type (Some n) arti_stride + | FunctionProtoType _ | FunctionNoProtoType _ + -> Typ.Tfun false + | ParenType (_, qual_type) + -> (qual_type_to_sil_type translate_decl tenv qual_type).Typ.desc + | DecayedType (_, qual_type) + -> (qual_type_to_sil_type translate_decl tenv qual_type).Typ.desc + | RecordType (_, pointer) | EnumType (_, pointer) + -> decl_ptr_to_type_desc translate_decl tenv pointer + | ElaboratedType type_info -> ( + match type_info.Clang_ast_t.ti_desugared_type with + (* TODO desugar to qualtype *) + | Some type_ptr + -> type_ptr_to_type_desc translate_decl tenv type_ptr + | None + -> Typ.Tvoid ) + | ObjCInterfaceType (_, pointer) + -> decl_ptr_to_type_desc translate_decl tenv pointer + | RValueReferenceType (_, qual_type) | LValueReferenceType (_, qual_type) + -> let typ = qual_type_to_sil_type translate_decl tenv qual_type in Typ.Tptr (typ, Typ.Pk_reference) - | AttributedType (type_info, attr_info) -> (* TODO desugar to qualtyp *) + | AttributedType (type_info, attr_info) + -> (* TODO desugar to qualtyp *) type_desc_of_attr_type translate_decl tenv type_info attr_info - | _ -> (* TypedefType, etc *) + | _ + -> (* TypedefType, etc *) let type_info = Clang_ast_proj.get_type_tuple c_type in - match type_info.Clang_ast_t.ti_desugared_type with (* TODO desugar typedeftype to qualtype *) - | Some typ -> type_ptr_to_type_desc translate_decl tenv typ - | None -> Typ.Tvoid + match type_info.Clang_ast_t.ti_desugared_type with + (* TODO desugar typedeftype to qualtype *) + | Some typ + -> type_ptr_to_type_desc translate_decl tenv typ + | None + -> Typ.Tvoid and decl_ptr_to_type_desc translate_decl tenv decl_ptr : Typ.desc = let open Clang_ast_t in let typ = Clang_ast_extend.DeclPtr decl_ptr in try Clang_ast_extend.TypePointerMap.find typ !CFrontend_config.sil_types_map with Not_found -> - match CAst_utils.get_decl decl_ptr with - | Some (CXXRecordDecl _ as d) - | Some (RecordDecl _ as d) - | Some (ClassTemplateSpecializationDecl _ as d) - | Some (ObjCInterfaceDecl _ as d) - | Some (ObjCImplementationDecl _ as d) - | Some (ObjCProtocolDecl _ as d) - | Some (ObjCCategoryDecl _ as d) - | Some (ObjCCategoryImplDecl _ as d) - | Some (EnumDecl _ as d) -> translate_decl tenv d - | Some _ -> - L.(debug Capture Verbose) "Warning: Wrong decl found for pointer %s " - (Clang_ast_j.string_of_pointer decl_ptr); - Typ.Tvoid - | None -> - L.(debug Capture Verbose) "Warning: Decl pointer %s not found." - (Clang_ast_j.string_of_pointer decl_ptr); - Typ.Tvoid + match CAst_utils.get_decl decl_ptr with + | Some (CXXRecordDecl _ as d) + | Some (RecordDecl _ as d) + | Some (ClassTemplateSpecializationDecl _ as d) + | Some (ObjCInterfaceDecl _ as d) + | Some (ObjCImplementationDecl _ as d) + | Some (ObjCProtocolDecl _ as d) + | Some (ObjCCategoryDecl _ as d) + | Some (ObjCCategoryImplDecl _ as d) + | Some (EnumDecl _ as d) + -> translate_decl tenv d + | Some _ + -> L.(debug Capture Verbose) + "Warning: Wrong decl found for pointer %s " (Clang_ast_j.string_of_pointer decl_ptr) ; + Typ.Tvoid + | None + -> L.(debug Capture Verbose) + "Warning: Decl pointer %s not found." (Clang_ast_j.string_of_pointer decl_ptr) ; + Typ.Tvoid and clang_type_ptr_to_type_desc translate_decl tenv type_ptr = - try - Clang_ast_extend.TypePointerMap.find type_ptr !CFrontend_config.sil_types_map + try Clang_ast_extend.TypePointerMap.find type_ptr !CFrontend_config.sil_types_map with Not_found -> - (match CAst_utils.get_type type_ptr with - | Some c_type -> - let type_desc = type_desc_of_c_type translate_decl tenv c_type in - CAst_utils.update_sil_types_map type_ptr type_desc; - type_desc - | _ -> Typ.Tvoid) + match CAst_utils.get_type type_ptr with + | Some c_type + -> let type_desc = type_desc_of_c_type translate_decl tenv c_type in + CAst_utils.update_sil_types_map type_ptr type_desc ; type_desc + | _ + -> Typ.Tvoid and type_ptr_to_type_desc translate_decl tenv type_ptr : Typ.desc = match type_ptr with - | Clang_ast_types.TypePtr.Ptr _ -> clang_type_ptr_to_type_desc translate_decl tenv type_ptr - | Clang_ast_extend.Builtin kind -> type_desc_of_builtin_type_kind kind - | Clang_ast_extend.PointerOf typ -> - let sil_typ = qual_type_to_sil_type translate_decl tenv typ in + | Clang_ast_types.TypePtr.Ptr _ + -> clang_type_ptr_to_type_desc translate_decl tenv type_ptr + | Clang_ast_extend.Builtin kind + -> type_desc_of_builtin_type_kind kind + | Clang_ast_extend.PointerOf typ + -> let sil_typ = qual_type_to_sil_type translate_decl tenv typ in Typ.Tptr (sil_typ, Pk_pointer) - | Clang_ast_extend.ReferenceOf typ -> - let sil_typ = qual_type_to_sil_type translate_decl tenv typ in + | Clang_ast_extend.ReferenceOf typ + -> let sil_typ = qual_type_to_sil_type translate_decl tenv typ in Typ.Tptr (sil_typ, Pk_reference) - | Clang_ast_extend.ClassType typename -> - Typ.Tstruct typename - | Clang_ast_extend.DeclPtr ptr -> decl_ptr_to_type_desc translate_decl tenv ptr - | Clang_ast_extend.ErrorType -> Typ.Tvoid - | _ -> raise (invalid_arg "unknown variant for type_ptr") + | Clang_ast_extend.ClassType typename + -> Typ.Tstruct typename + | Clang_ast_extend.DeclPtr ptr + -> decl_ptr_to_type_desc translate_decl tenv ptr + | Clang_ast_extend.ErrorType + -> Typ.Tvoid + | _ + -> raise (invalid_arg "unknown variant for type_ptr") and qual_type_to_sil_type translate_decl tenv qual_type = let desc = type_ptr_to_type_desc translate_decl tenv qual_type.Clang_ast_t.qt_type_ptr in diff --git a/infer/src/clang/cType_to_sil_type.mli b/infer/src/clang/cType_to_sil_type.mli index d6ea8dc82..82b0c1b6a 100644 --- a/infer/src/clang/cType_to_sil_type.mli +++ b/infer/src/clang/cType_to_sil_type.mli @@ -9,7 +9,7 @@ open! IStd -val get_builtin_objc_typename : [< `ObjCClass | `ObjCId ] -> Typ.Name.t +val get_builtin_objc_typename : [< `ObjCClass | `ObjCId] -> Typ.Name.t -val qual_type_to_sil_type : (Tenv.t -> Clang_ast_t.decl -> Typ.desc) -> - Tenv.t -> Clang_ast_t.qual_type -> Typ.t +val qual_type_to_sil_type : + (Tenv.t -> Clang_ast_t.decl -> Typ.desc) -> Tenv.t -> Clang_ast_t.qual_type -> Typ.t diff --git a/infer/src/clang/cVar_decl.ml b/infer/src/clang/cVar_decl.ml index 199014b26..307a5e097 100644 --- a/infer/src/clang/cVar_decl.ml +++ b/infer/src/clang/cVar_decl.ml @@ -11,80 +11,82 @@ open! IStd open! PVariant (** Process variable declarations by saving them as local or global variables. *) + (** Computes the local variables of a function or method to be added to the procdesc *) module L = Logging -let is_custom_var_pointer pointer = - pointer <= 0 +let is_custom_var_pointer pointer = pointer <= 0 let sil_var_of_decl context var_decl procname = let outer_procname = CContext.get_outer_procname context in let trans_unit_ctx = context.CContext.translation_unit_context in let open Clang_ast_t in match var_decl with - | VarDecl (decl_info, name_info, qual_type, var_decl_info) -> - let shoud_be_mangled = - not (is_custom_var_pointer decl_info.Clang_ast_t.di_pointer) in - let var_decl_details = Some - (decl_info, qual_type, var_decl_info, shoud_be_mangled) in + | VarDecl (decl_info, name_info, qual_type, var_decl_info) + -> let shoud_be_mangled = not (is_custom_var_pointer decl_info.Clang_ast_t.di_pointer) in + let var_decl_details = Some (decl_info, qual_type, var_decl_info, shoud_be_mangled) in CGeneral_utils.mk_sil_var trans_unit_ctx name_info var_decl_details procname outer_procname - | ParmVarDecl (decl_info, name_info, qual_type, var_decl_info) -> - let var_decl_details = Some - (decl_info, qual_type, var_decl_info, false) in + | ParmVarDecl (decl_info, name_info, qual_type, var_decl_info) + -> let var_decl_details = Some (decl_info, qual_type, var_decl_info, false) in CGeneral_utils.mk_sil_var trans_unit_ctx name_info var_decl_details procname outer_procname - | _ -> assert false + | _ + -> assert false let sil_var_of_decl_ref context decl_ref procname = let name = - match decl_ref.Clang_ast_t.dr_name with - | Some name_info -> name_info - | None -> assert false in + match decl_ref.Clang_ast_t.dr_name with Some name_info -> name_info | None -> assert false + in let pointer = decl_ref.Clang_ast_t.dr_decl_pointer in match decl_ref.Clang_ast_t.dr_kind with - | `ImplicitParam -> - let outer_procname = CContext.get_outer_procname context in + | `ImplicitParam + -> let outer_procname = CContext.get_outer_procname context in let trans_unit_ctx = context.CContext.translation_unit_context in CGeneral_utils.mk_sil_var trans_unit_ctx name None procname outer_procname - | _ -> - if is_custom_var_pointer pointer then + | _ + -> if is_custom_var_pointer pointer then Pvar.mk (Mangled.from_string name.Clang_ast_t.ni_name) procname - else match CAst_utils.get_decl decl_ref.Clang_ast_t.dr_decl_pointer with - | Some var_decl -> sil_var_of_decl context var_decl procname - | None -> assert false + else + match CAst_utils.get_decl decl_ref.Clang_ast_t.dr_decl_pointer with + | Some var_decl + -> sil_var_of_decl context var_decl procname + | None + -> assert false let add_var_to_locals procdesc var_decl sil_typ pvar = let open Clang_ast_t in match var_decl with - | VarDecl (_, _, _, vdi) -> - if not vdi.Clang_ast_t.vdi_is_global then + | VarDecl (_, _, _, vdi) + -> if not vdi.Clang_ast_t.vdi_is_global then Procdesc.append_locals procdesc [(Pvar.get_name pvar, sil_typ)] - | _ -> assert false + | _ + -> assert false let compute_autorelease_pool_vars context stmts = let rec do_stmts map = function - | [] -> - map - | Clang_ast_t.DeclRefExpr (_, _, _, drei):: stmts' -> - let map1 = match drei.Clang_ast_t.drti_decl_ref with - | Some decl_ref -> - (match decl_ref.Clang_ast_t.dr_qual_type with - | Some qual_type when decl_ref.Clang_ast_t.dr_kind = `Var -> - let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in - let procname = Procdesc.get_proc_name context.CContext.procdesc in - let pvar = sil_var_of_decl_ref context decl_ref procname in - if Pvar.is_local pvar then - Exp.Map.add (Exp.Lvar pvar) typ map - else map - | _ -> - map) - | None -> - map in + | [] + -> map + | (Clang_ast_t.DeclRefExpr (_, _, _, drei)) :: stmts' + -> let map1 = + match drei.Clang_ast_t.drti_decl_ref with + | Some decl_ref -> ( + match decl_ref.Clang_ast_t.dr_qual_type with + | Some qual_type when decl_ref.Clang_ast_t.dr_kind = `Var + -> let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in + let procname = Procdesc.get_proc_name context.CContext.procdesc in + let pvar = sil_var_of_decl_ref context decl_ref procname in + if Pvar.is_local pvar then Exp.Map.add (Exp.Lvar pvar) typ map else map + | _ + -> map ) + | None + -> map + in do_stmts map1 stmts' - | s :: stmts' -> - let sl = snd (Clang_ast_proj.get_stmt_tuple s) in + | s :: stmts' + -> let sl = snd (Clang_ast_proj.get_stmt_tuple s) in let map1 = do_stmts map sl in - do_stmts map1 stmts' in + do_stmts map1 stmts' + in Exp.Map.bindings (do_stmts Exp.Map.empty stmts) (* Returns a list of captured variables as sil variables. *) @@ -92,17 +94,19 @@ let captured_vars_from_block_info context cvl = let procname = Procdesc.get_proc_name context.CContext.procdesc in let sil_var_of_captured_var cv vars = match cv.Clang_ast_t.bcv_variable with - | Some dr -> - (match dr.Clang_ast_t.dr_name, dr.Clang_ast_t.dr_qual_type with - | Some name_info, Some qual_type -> - let n = name_info.Clang_ast_t.ni_name in - if String.equal n CFrontend_config.self && - not (CContext.is_objc_instance context) then - vars - else - let pvar = sil_var_of_decl_ref context dr procname in - let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in - (pvar, typ) :: vars - | _ -> assert false) - | _ -> assert false in + | Some dr -> ( + match (dr.Clang_ast_t.dr_name, dr.Clang_ast_t.dr_qual_type) with + | Some name_info, Some qual_type + -> let n = name_info.Clang_ast_t.ni_name in + if String.equal n CFrontend_config.self && not (CContext.is_objc_instance context) then + vars + else + let pvar = sil_var_of_decl_ref context dr procname in + let typ = CType_decl.qual_type_to_sil_type context.CContext.tenv qual_type in + (pvar, typ) :: vars + | _ + -> assert false ) + | _ + -> assert false + in List.fold_right ~f:sil_var_of_captured_var cvl ~init:[] diff --git a/infer/src/clang/cVar_decl.mli b/infer/src/clang/cVar_decl.mli index 09f86097d..5d37ee76b 100644 --- a/infer/src/clang/cVar_decl.mli +++ b/infer/src/clang/cVar_decl.mli @@ -10,6 +10,7 @@ open! IStd (** Process variable declarations by saving them as local or global variables. *) + (** Computes the local variables of a function or method to be added to the procdesc *) val sil_var_of_decl : CContext.t -> Clang_ast_t.decl -> Typ.Procname.t -> Pvar.t @@ -20,5 +21,5 @@ val add_var_to_locals : Procdesc.t -> Clang_ast_t.decl -> Typ.t -> Pvar.t -> uni val compute_autorelease_pool_vars : CContext.t -> Clang_ast_t.stmt list -> (Exp.t * Typ.t) list -val captured_vars_from_block_info : CContext.t -> Clang_ast_t.block_captured_variable list -> - (Pvar.t * Typ.t) list +val captured_vars_from_block_info : + CContext.t -> Clang_ast_t.block_captured_variable list -> (Pvar.t * Typ.t) list diff --git a/infer/src/clang/clang_ast_extend.ml b/infer/src/clang/clang_ast_extend.ml index 4849f50f6..aad5ecbe4 100644 --- a/infer/src/clang/clang_ast_extend.ml +++ b/infer/src/clang/clang_ast_extend.ml @@ -12,8 +12,6 @@ open! IStd (* This module adds more variants to some types in AST *) (* The implementation extends default one from *) (* facebook-clang-plugins repository *) - - (* Type pointers *) type Clang_ast_types.TypePtr.t += | Builtin of Clang_ast_t.builtin_type_kind @@ -25,59 +23,88 @@ type Clang_ast_types.TypePtr.t += module TypePointerOrd = struct type t = Clang_ast_types.TypePtr.t - let rec compare a1 a2 = match a1, a2 with - | _ when phys_equal a1 a2 -> 0 - | Clang_ast_types.TypePtr.Ptr a, Clang_ast_types.TypePtr.Ptr b -> Int.compare a b - | Clang_ast_types.TypePtr.Ptr _, _ -> 1 - | _, Clang_ast_types.TypePtr.Ptr _ -> -1 - | Builtin a, Builtin b -> Polymorphic_compare.compare a b - | Builtin _, _ -> 1 - | _, Builtin _ -> -1 - | PointerOf a, PointerOf b -> compare_qual_type a b - | PointerOf _, _ -> 1 - | _, PointerOf _ -> -1 - | ReferenceOf a, ReferenceOf b -> compare_qual_type a b - | ReferenceOf _, _ -> 1 - | _, ReferenceOf _ -> -1 - | ClassType a, ClassType b -> Typ.Name.compare a b - | ClassType _, _ -> 1 - | _, ClassType _ -> -1 - | DeclPtr a, DeclPtr b -> Int.compare a b - | DeclPtr _, _ -> 1 - | _, DeclPtr _ -> -1 - | ErrorType, ErrorType -> 0 - | _ -> raise (invalid_arg ("unexpected type_ptr variants: ")) - and compare_qual_type (qt1 : Clang_ast_t.qual_type) (qt2 : Clang_ast_t.qual_type) = - if phys_equal qt1 qt2 then 0 else + + let rec compare a1 a2 = + match (a1, a2) with + | _ when phys_equal a1 a2 + -> 0 + | Clang_ast_types.TypePtr.Ptr a, Clang_ast_types.TypePtr.Ptr b + -> Int.compare a b + | Clang_ast_types.TypePtr.Ptr _, _ + -> 1 + | _, Clang_ast_types.TypePtr.Ptr _ + -> -1 + | Builtin a, Builtin b + -> Polymorphic_compare.compare a b + | Builtin _, _ + -> 1 + | _, Builtin _ + -> -1 + | PointerOf a, PointerOf b + -> compare_qual_type a b + | PointerOf _, _ + -> 1 + | _, PointerOf _ + -> -1 + | ReferenceOf a, ReferenceOf b + -> compare_qual_type a b + | ReferenceOf _, _ + -> 1 + | _, ReferenceOf _ + -> -1 + | ClassType a, ClassType b + -> Typ.Name.compare a b + | ClassType _, _ + -> 1 + | _, ClassType _ + -> -1 + | DeclPtr a, DeclPtr b + -> Int.compare a b + | DeclPtr _, _ + -> 1 + | _, DeclPtr _ + -> -1 + | ErrorType, ErrorType + -> 0 + | _ + -> raise (invalid_arg "unexpected type_ptr variants: ") + + and compare_qual_type (qt1: Clang_ast_t.qual_type) (qt2: Clang_ast_t.qual_type) = + if phys_equal qt1 qt2 then 0 + else (* enable warning here to warn and update comparison funtion when new field is added *) - let [@warning "+9"] { - Clang_ast_t.qt_type_ptr = t1; - qt_is_const = c1; - qt_is_restrict = r1; - qt_is_volatile = v1} = qt1 in - let [@warning "+9"] { - Clang_ast_t.qt_type_ptr = t2; - qt_is_const = c2; - qt_is_restrict = r2; - qt_is_volatile = v2} = qt2 in + let {Clang_ast_t.qt_type_ptr= t1; qt_is_const= c1; qt_is_restrict= r1; qt_is_volatile= v1} = + qt1 [@@warning "+9"] + in + let {Clang_ast_t.qt_type_ptr= t2; qt_is_const= c2; qt_is_restrict= r2; qt_is_volatile= v2} = + qt2 [@@warning "+9"] + in let qt_cmp = compare t1 t2 in - if qt_cmp <> 0 then qt_cmp else + if qt_cmp <> 0 then qt_cmp + else let const_cmp = Bool.compare c1 c2 in - if const_cmp <> 0 then const_cmp else + if const_cmp <> 0 then const_cmp + else let restrict_cmp = Bool.compare r1 r2 in - if restrict_cmp <> 0 then restrict_cmp else - Bool.compare v1 v2 + if restrict_cmp <> 0 then restrict_cmp else Bool.compare v1 v2 end -module TypePointerMap = Caml.Map.Make(TypePointerOrd) - +module TypePointerMap = Caml.Map.Make (TypePointerOrd) let rec type_ptr_to_string = function - | Clang_ast_types.TypePtr.Ptr raw -> "clang_ptr_" ^ (string_of_int raw) - | Builtin t -> "sil_" ^ (Clang_ast_j.string_of_builtin_type_kind t) - | PointerOf typ -> "pointer_of_" ^ type_ptr_to_string typ.Clang_ast_t.qt_type_ptr - | ReferenceOf typ -> "reference_of_" ^ type_ptr_to_string typ.Clang_ast_t.qt_type_ptr - | ClassType name -> "class_name_" ^ Typ.Name.name name - | DeclPtr raw -> "decl_ptr_" ^ (string_of_int raw) - | ErrorType -> "error_type" - | _ -> "unknown" + | Clang_ast_types.TypePtr.Ptr raw + -> "clang_ptr_" ^ string_of_int raw + | Builtin t + -> "sil_" ^ Clang_ast_j.string_of_builtin_type_kind t + | PointerOf typ + -> "pointer_of_" ^ type_ptr_to_string typ.Clang_ast_t.qt_type_ptr + | ReferenceOf typ + -> "reference_of_" ^ type_ptr_to_string typ.Clang_ast_t.qt_type_ptr + | ClassType name + -> "class_name_" ^ Typ.Name.name name + | DeclPtr raw + -> "decl_ptr_" ^ string_of_int raw + | ErrorType + -> "error_type" + | _ + -> "unknown" diff --git a/infer/src/clang/ctl_parser_types.ml b/infer/src/clang/ctl_parser_types.ml index 2dd54912a..8ac82a309 100644 --- a/infer/src/clang/ctl_parser_types.ml +++ b/infer/src/clang/ctl_parser_types.ml @@ -6,67 +6,71 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Types used by the ctl parser *) open! IStd - module L = Logging (** the kind of AST nodes where formulas are evaluated *) -type ast_node = - | Stmt of Clang_ast_t.stmt - | Decl of Clang_ast_t.decl +type ast_node = Stmt of Clang_ast_t.stmt | Decl of Clang_ast_t.decl let rec ast_node_name an = let open Clang_ast_t in match an with - | Decl dec -> - (match Clang_ast_proj.get_named_decl_tuple dec with - | Some (_, n) -> n.Clang_ast_t.ni_name - | None -> "") - | Stmt (DeclRefExpr (_, _, _, drti)) -> - (match drti.drti_decl_ref with - | Some dr -> - let ndi, _, _ = CAst_utils.get_info_from_decl_ref dr in - ndi.ni_name - | _ -> "") - | Stmt (ObjCIvarRefExpr(_, _, _, obj_c_ivar_ref_expr_info)) -> - let ndi, _, _ = CAst_utils.get_info_from_decl_ref obj_c_ivar_ref_expr_info.ovrei_decl_ref in + | Decl dec -> ( + match Clang_ast_proj.get_named_decl_tuple dec with + | Some (_, n) + -> n.Clang_ast_t.ni_name + | None + -> "" ) + | Stmt DeclRefExpr (_, _, _, drti) -> ( + match drti.drti_decl_ref with + | Some dr + -> let ndi, _, _ = CAst_utils.get_info_from_decl_ref dr in + ndi.ni_name + | _ + -> "" ) + | Stmt ObjCIvarRefExpr (_, _, _, obj_c_ivar_ref_expr_info) + -> let ndi, _, _ = CAst_utils.get_info_from_decl_ref obj_c_ivar_ref_expr_info.ovrei_decl_ref in ndi.ni_name - | Stmt (ObjCMessageExpr (_, _, _, {omei_selector})) -> - omei_selector - | Stmt (IntegerLiteral (_, _, _, integer_literal_info)) -> - integer_literal_info.ili_value - | Stmt CStyleCastExpr (_, _, _, cast_expr_info, _) -> - (match cast_expr_info.cei_cast_kind with - | `NullToPointer -> "nil" - | _ -> "") - | Stmt ObjCSubscriptRefExpr (_, [stmt; stmt_index], _, _) -> - (ast_node_name (Stmt stmt)) ^ "["^ (ast_node_name (Stmt stmt_index)) ^"]" - | Stmt OpaqueValueExpr (_, _, _, opaque_value_expr_info) -> - (match opaque_value_expr_info.ovei_source_expr with - | Some stmt -> ast_node_name (Stmt stmt) - | None -> "") + | Stmt ObjCMessageExpr (_, _, _, {omei_selector}) + -> omei_selector + | Stmt IntegerLiteral (_, _, _, integer_literal_info) + -> integer_literal_info.ili_value + | Stmt CStyleCastExpr (_, _, _, cast_expr_info, _) -> ( + match cast_expr_info.cei_cast_kind with `NullToPointer -> "nil" | _ -> "" ) + | Stmt ObjCSubscriptRefExpr (_, [stmt; stmt_index], _, _) + -> ast_node_name (Stmt stmt) ^ "[" ^ ast_node_name (Stmt stmt_index) ^ "]" + | Stmt OpaqueValueExpr (_, _, _, opaque_value_expr_info) -> ( + match opaque_value_expr_info.ovei_source_expr with + | Some stmt + -> ast_node_name (Stmt stmt) + | None + -> "" ) | Stmt ImplicitCastExpr (_, [stmt], _, _) - | Stmt PseudoObjectExpr (_, stmt::_, _) - | Stmt ParenExpr (_, [stmt], _) -> - ast_node_name (Stmt stmt) - | Stmt CallExpr (_, func::_, _) -> - let func_str = ast_node_name (Stmt func) in + | Stmt PseudoObjectExpr (_, stmt :: _, _) + | Stmt ParenExpr (_, [stmt], _) + -> ast_node_name (Stmt stmt) + | Stmt CallExpr (_, func :: _, _) + -> let func_str = ast_node_name (Stmt func) in func_str ^ "(...)" - | Stmt ObjCPropertyRefExpr (_, [stmt], _, obj_c_property_ref_expr_info) -> - let property_str = - (match obj_c_property_ref_expr_info.oprei_kind with - | `MethodRef obj_c_method_ref_info -> - (match obj_c_method_ref_info.mri_getter, obj_c_method_ref_info.mri_setter with - | Some name, _ -> name - | _, Some name -> name - | _ -> "") - | `PropertyRef decl_ref -> - match decl_ref.dr_name with Some name -> name.ni_name | None -> "") in - (ast_node_name (Stmt stmt)) ^ "." ^ property_str - | _ -> "" + | Stmt ObjCPropertyRefExpr (_, [stmt], _, obj_c_property_ref_expr_info) + -> let property_str = + match obj_c_property_ref_expr_info.oprei_kind with + | `MethodRef obj_c_method_ref_info -> ( + match (obj_c_method_ref_info.mri_getter, obj_c_method_ref_info.mri_setter) with + | Some name, _ + -> name + | _, Some name + -> name + | _ + -> "" ) + | `PropertyRef decl_ref -> + match decl_ref.dr_name with Some name -> name.ni_name | None -> "" + in + ast_node_name (Stmt stmt) ^ "." ^ property_str + | _ + -> "" let infer_prefix = "__infer_ctl_" @@ -78,33 +82,35 @@ exception ALParsingException of string https://clang.llvm.org/doxygen/Type_8cpp_source.html *) type builtin_kind = - | Void (** void *) - | Bool (** bool *) - | Char_U (** char *) + | Void (** void *) + | Bool (** bool *) + | Char_U (** char *) | UChar (** unsigned char *) - | WChar_U (** wchar_t *) + | WChar_U (** wchar_t *) | Char16 (** char16_t *) | Char32 (** char32_t *) - | UShort (** unsigned short *) + | UShort (** unsigned short *) | UInt (** unsigned int *) - | ULong (** unsigned long *) - | ULongLong (** unsigned long long *) - | Int128 (** __int128 *) - | UInt128 (** unsigned __int128 *) - | SChar (** signed char *) - | Short (** short *) - | Int (** int *) - | Long (** long *) - | LongLong (** long long *) - | Half (** half of __fp16 *) - | Float (** float *) - | Double (** double *) - | LongDouble (** long double *) - | Float128 (** __float128 *) - | NullPtr (** nullptr_t *) - | ObjCId (** id *) - | ObjCClass (** Class *) - | ObjCSel (** SEL *)[@@deriving compare] + | ULong (** unsigned long *) + | ULongLong (** unsigned long long *) + | Int128 (** __int128 *) + | UInt128 (** unsigned __int128 *) + | SChar (** signed char *) + | Short (** short *) + | Int (** int *) + | Long (** long *) + | LongLong (** long long *) + | Half (** half of __fp16 *) + | Float (** float *) + | Double (** double *) + | LongDouble (** long double *) + | Float128 (** __float128 *) + | NullPtr (** nullptr_t *) + | ObjCId (** id *) + | ObjCClass (** Class *) + | ObjCSel (** SEL *) + [@@deriving compare] + (* | OCLSampler | OCLEvent | OCLClkEvent | OCLQueue | OCLNDRange | OCLReserveID | Dependent | Overload | BoundMember | PseudoObject | UnknownAny | BuiltinFn | ARCUnbridgedCast | OMPArraySection *) @@ -113,221 +119,255 @@ let equal_builtin_kind = [%compare.equal : builtin_kind] let builtin_kind_to_string t = match t with - | Char_U -> "char" - | Char16 -> "char16_t" - | Char32 -> "char32_t" - | WChar_U -> "wchar_t" - | Bool -> "bool" - | Short -> "short" - | Int -> "int" - | Long -> "long" - | Float -> "float" - | Double -> "double" - | Void -> "void" - | SChar -> "signed char"; - | LongLong -> "long long"; - | UChar -> "unsigned char"; - | UShort -> "unsigned short"; - | UInt -> "unsigned int"; - | ULong -> "unsigned long"; - | ULongLong -> "unsigned long long"; - | LongDouble -> "long double"; - | Int128 -> "__int128" - | Float128 -> "__float128" - | UInt128 -> "unsigned __int128" - | Half -> "half" - | NullPtr -> "nullptr_t" - | ObjCId -> "id" - | ObjCClass -> "Class" - | ObjCSel -> "SEL" + | Char_U + -> "char" + | Char16 + -> "char16_t" + | Char32 + -> "char32_t" + | WChar_U + -> "wchar_t" + | Bool + -> "bool" + | Short + -> "short" + | Int + -> "int" + | Long + -> "long" + | Float + -> "float" + | Double + -> "double" + | Void + -> "void" + | SChar + -> "signed char" + | LongLong + -> "long long" + | UChar + -> "unsigned char" + | UShort + -> "unsigned short" + | UInt + -> "unsigned int" + | ULong + -> "unsigned long" + | ULongLong + -> "unsigned long long" + | LongDouble + -> "long double" + | Int128 + -> "__int128" + | Float128 + -> "__float128" + | UInt128 + -> "unsigned __int128" + | Half + -> "half" + | NullPtr + -> "nullptr_t" + | ObjCId + -> "id" + | ObjCClass + -> "Class" + | ObjCSel + -> "SEL" type abs_ctype = | BuiltIn of builtin_kind | Pointer of abs_ctype | TypeName of ALVar.alexp - | ObjCGenProt of abs_ctype * abs_ctype (* Objective-C Protocol or Generics *) + | ObjCGenProt of abs_ctype * abs_ctype + +(* Objective-C Protocol or Generics *) let display_equality_warning () = L.(debug Linters Medium) - "[WARNING:] Type Comparison failed... \ - This might indicate that the types are different or the specified type \ - is internally represented in a different way and therefore not recognized.@\n" + "[WARNING:] Type Comparison failed... This might indicate that the types are different or the specified type is internally represented in a different way and therefore not recognized.@\n" let rec abs_ctype_to_string t = match t with - | BuiltIn t' -> "BuiltIn (" ^ (builtin_kind_to_string t') ^ ")" - | Pointer t' -> "Pointer (" ^ (abs_ctype_to_string t') ^ ")" - | TypeName ae -> "TypeName (" ^ (ALVar.alexp_to_string ae) ^ ")" - | ObjCGenProt (b,p) -> - "ObjCGenProt (" ^ (abs_ctype_to_string b) ^ "," ^ (abs_ctype_to_string p) ^")" + | BuiltIn t' + -> "BuiltIn (" ^ builtin_kind_to_string t' ^ ")" + | Pointer t' + -> "Pointer (" ^ abs_ctype_to_string t' ^ ")" + | TypeName ae + -> "TypeName (" ^ ALVar.alexp_to_string ae ^ ")" + | ObjCGenProt (b, p) + -> "ObjCGenProt (" ^ abs_ctype_to_string b ^ "," ^ abs_ctype_to_string p ^ ")" let builtin_type_kind_assoc = - [ - (`Char_U, Char_U); - (`Char_S, Char_U); - (`Char16, Char16); - (`Char32, Char32); - (`WChar_U, WChar_U); - (`WChar_S, WChar_U); - (`Bool, Bool); - (`Short, Short); - (`Int, Int); - (`Long, Long); - (`Float, Float); - (`Double, Double); - (`Void, Void); - (`SChar, SChar); - (`LongLong, LongLong); - (`UChar, UChar); - (`UShort, UShort); - (`UInt, UInt); - (`ULong, ULong); - (`ULongLong, ULongLong); - (`LongDouble, LongDouble); - (`Int128, Int128); - (`UInt128, UInt128); - (`Float128, Float128); - (`NullPtr, NullPtr); - (`ObjCId, ObjCId); - (`ObjCClass, ObjCClass); - (`ObjCSel, ObjCSel); - (`Half, Half) - ] + [ (`Char_U, Char_U) + ; (`Char_S, Char_U) + ; (`Char16, Char16) + ; (`Char32, Char32) + ; (`WChar_U, WChar_U) + ; (`WChar_S, WChar_U) + ; (`Bool, Bool) + ; (`Short, Short) + ; (`Int, Int) + ; (`Long, Long) + ; (`Float, Float) + ; (`Double, Double) + ; (`Void, Void) + ; (`SChar, SChar) + ; (`LongLong, LongLong) + ; (`UChar, UChar) + ; (`UShort, UShort) + ; (`UInt, UInt) + ; (`ULong, ULong) + ; (`ULongLong, ULongLong) + ; (`LongDouble, LongDouble) + ; (`Int128, Int128) + ; (`UInt128, UInt128) + ; (`Float128, Float128) + ; (`NullPtr, NullPtr) + ; (`ObjCId, ObjCId) + ; (`ObjCClass, ObjCClass) + ; (`ObjCSel, ObjCSel) + ; (`Half, Half) ] -let builtin_equal (bi : Clang_ast_t.builtin_type_kind) (abi : builtin_kind) = - match List.Assoc.find ~equal:PVariant.(=) builtin_type_kind_assoc bi with - | Some assoc_abi when equal_builtin_kind assoc_abi abi -> true - | _ -> display_equality_warning ();false +let builtin_equal (bi: Clang_ast_t.builtin_type_kind) (abi: builtin_kind) = + match List.Assoc.find ~equal:PVariant.( = ) builtin_type_kind_assoc bi with + | Some assoc_abi when equal_builtin_kind assoc_abi abi + -> true + | _ + -> display_equality_warning () ; false let typename_to_string pointer = - match CAst_utils.get_decl pointer with - | Some decl -> - (match Clang_ast_proj.get_named_decl_tuple decl with - | Some (_, name_decl) -> Some name_decl.ni_name - | None -> None) - | _ -> None + match CAst_utils.get_decl pointer with + | Some decl -> ( + match Clang_ast_proj.get_named_decl_tuple decl with + | Some (_, name_decl) + -> Some name_decl.ni_name + | None + -> None ) + | _ + -> None let rec pointer_type_equal p ap = let open Clang_ast_t in - match p, ap with - | PointerType (_, qt), Pointer abs_ctype' - | ObjCObjectPointerType (_, qt), Pointer abs_ctype' -> - check_type_ptr qt.qt_type_ptr abs_ctype' - | _, _ -> display_equality_warning (); - false + match (p, ap) with + | PointerType (_, qt), Pointer abs_ctype' | ObjCObjectPointerType (_, qt), Pointer abs_ctype' + -> check_type_ptr qt.qt_type_ptr abs_ctype' + | _, _ + -> display_equality_warning () ; false and objc_object_type_equal c_type abs_ctype = let open Clang_ast_t in - let check_type_args abs_arg_type qt = - check_type_ptr qt.qt_type_ptr abs_arg_type in + let check_type_args abs_arg_type qt = check_type_ptr qt.qt_type_ptr abs_arg_type in let check_prot prot_name pointer = - match prot_name with - | TypeName ae -> typename_equal pointer ae - | _ -> false in - match c_type, abs_ctype with - | ObjCObjectType (_, ooti), ObjCGenProt (base, args) -> - (match (CAst_utils.get_type ooti.base_type), ooti.protocol_decls_ptr, ooti.type_args with - | Some base_type, _::_, [] -> - c_type_equal base_type base && - (List.for_all ~f:(check_prot args) ooti.protocol_decls_ptr) - | Some base_type, [], _::_ -> - c_type_equal base_type base && - (List.for_all ~f:(check_type_args args) ooti.type_args) - | _ -> false) - | _ -> false - + match prot_name with TypeName ae -> typename_equal pointer ae | _ -> false + in + match (c_type, abs_ctype) with + | ObjCObjectType (_, ooti), ObjCGenProt (base, args) -> ( + match (CAst_utils.get_type ooti.base_type, ooti.protocol_decls_ptr, ooti.type_args) with + | Some base_type, _ :: _, [] + -> c_type_equal base_type base && List.for_all ~f:(check_prot args) ooti.protocol_decls_ptr + | Some base_type, [], _ :: _ + -> c_type_equal base_type base && List.for_all ~f:(check_type_args args) ooti.type_args + | _ + -> false ) + | _ + -> false and typename_equal pointer typename = - match typename_to_string pointer with - | Some name -> - L.(debug Linters Medium) + match typename_to_string pointer with + | Some name + -> L.(debug Linters Medium) "Comparing typename '%s' and pointer '%s' for equality...@\n" - (ALVar.alexp_to_string typename) name; + (ALVar.alexp_to_string typename) name ; ALVar.compare_str_with_alexp name typename - | None -> false + | None + -> false and check_type_ptr type_ptr abs_ctype = match CAst_utils.get_type type_ptr with - | Some c_type' -> c_type_equal c_type' abs_ctype - | None -> false + | Some c_type' + -> c_type_equal c_type' abs_ctype + | None + -> false (* Temporary, partial equality function. Cover only what's covered by the types_parser. It needs to be replaced by a real comparison function for Clang_ast_t.c_type *) and c_type_equal c_type abs_ctype = L.(debug Linters Medium) - "@\nComparing c_type/abs_ctype for equality... \ - Type compared: @\nc_type = `%s` @\nabs_ctype =`%s`@\n" - (Clang_ast_j.string_of_c_type c_type) - (abs_ctype_to_string abs_ctype); + "@\nComparing c_type/abs_ctype for equality... Type compared: @\nc_type = `%s` @\nabs_ctype =`%s`@\n" + (Clang_ast_j.string_of_c_type c_type) (abs_ctype_to_string abs_ctype) ; let open Clang_ast_t in - match c_type, abs_ctype with - | BuiltinType (_ , bi), BuiltIn abi -> - builtin_equal bi abi - | PointerType _, Pointer _ - | ObjCObjectPointerType _, Pointer _ -> - pointer_type_equal c_type abs_ctype - | ObjCObjectPointerType (_, qt), ObjCGenProt _ -> - check_type_ptr qt.qt_type_ptr abs_ctype - | ObjCObjectType _, ObjCGenProt _ -> - objc_object_type_equal c_type abs_ctype - | ObjCInterfaceType (_, pointer), TypeName ae -> - typename_equal pointer ae - | TypedefType (_, tdi), TypeName ae -> - typename_equal tdi.tti_decl_ptr ae - | TypedefType (ti, _), ObjCGenProt _ -> - (match ti.ti_desugared_type with - | Some dt -> check_type_ptr dt abs_ctype - | None -> false) - | _, _ -> display_equality_warning (); - false + match (c_type, abs_ctype) with + | BuiltinType (_, bi), BuiltIn abi + -> builtin_equal bi abi + | PointerType _, Pointer _ | ObjCObjectPointerType _, Pointer _ + -> pointer_type_equal c_type abs_ctype + | ObjCObjectPointerType (_, qt), ObjCGenProt _ + -> check_type_ptr qt.qt_type_ptr abs_ctype + | ObjCObjectType _, ObjCGenProt _ + -> objc_object_type_equal c_type abs_ctype + | ObjCInterfaceType (_, pointer), TypeName ae + -> typename_equal pointer ae + | TypedefType (_, tdi), TypeName ae + -> typename_equal tdi.tti_decl_ptr ae + | TypedefType (ti, _), ObjCGenProt _ -> ( + match ti.ti_desugared_type with Some dt -> check_type_ptr dt abs_ctype | None -> false ) + | _, _ + -> display_equality_warning () ; false (* to be extended with more types *) let rec typ_string_of_type_ptr type_ptr = let open Clang_ast_t in match CAst_utils.get_type type_ptr with - | Some BuiltinType (_, bt) -> - (match List.Assoc.find ~equal:Poly.equal builtin_type_kind_assoc bt with - | Some abt -> builtin_kind_to_string abt - | None -> "") - | Some PointerType (_, qt) - | Some ObjCObjectPointerType (_, qt) -> - (typ_string_of_type_ptr qt.qt_type_ptr) ^ "*" - | Some ObjCInterfaceType (_, pointer) -> - Option.value ~default:"" (typename_to_string pointer) - | Some TypedefType (_, tdi) -> - Option.value ~default:"" (typename_to_string tdi.tti_decl_ptr) - | _ -> "" + | Some BuiltinType (_, bt) -> ( + match List.Assoc.find ~equal:Poly.equal builtin_type_kind_assoc bt with + | Some abt + -> builtin_kind_to_string abt + | None + -> "" ) + | Some PointerType (_, qt) | Some ObjCObjectPointerType (_, qt) + -> typ_string_of_type_ptr qt.qt_type_ptr ^ "*" + | Some ObjCInterfaceType (_, pointer) + -> Option.value ~default:"" (typename_to_string pointer) + | Some TypedefType (_, tdi) + -> Option.value ~default:"" (typename_to_string tdi.tti_decl_ptr) + | _ + -> "" let ast_node_type an = let typ_str = match an with - | Stmt stmt -> - (match Clang_ast_proj.get_expr_tuple stmt with - | Some (_, _, expr_info) -> - typ_string_of_type_ptr expr_info.ei_qual_type.qt_type_ptr - | _ -> "") + | Stmt stmt -> ( + match Clang_ast_proj.get_expr_tuple stmt with + | Some (_, _, expr_info) + -> typ_string_of_type_ptr expr_info.ei_qual_type.qt_type_ptr + | _ + -> "" ) | Decl decl -> - (match CAst_utils.type_of_decl decl with - | Some type_ptr -> - typ_string_of_type_ptr type_ptr - | _ -> "") in - if String.length typ_str > 0 then typ_str - else "" + match CAst_utils.type_of_decl decl with + | Some type_ptr + -> typ_string_of_type_ptr type_ptr + | _ + -> "" + in + if String.length typ_str > 0 then typ_str else "" let stmt_node_child_type an = match an with - | Stmt stmt -> - (let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in - match stmts with - | [stmt] -> ast_node_type (Stmt stmt) - | _ -> "") - | _ -> "" + | Stmt stmt + -> ( + let _, stmts = Clang_ast_proj.get_stmt_tuple stmt in + match stmts with [stmt] -> ast_node_type (Stmt stmt) | _ -> "" ) + | _ + -> "" let ast_node_cast_kind an = match an with - | Decl _ -> "" + | Decl _ + -> "" | Stmt stmt -> - match Clang_ast_proj.get_cast_kind stmt with - | Some cast_kind -> Clang_ast_proj.string_of_cast_kind cast_kind - | None -> "" + match Clang_ast_proj.get_cast_kind stmt with + | Some cast_kind + -> Clang_ast_proj.string_of_cast_kind cast_kind + | None + -> "" diff --git a/infer/src/clang/ctl_parser_types.mli b/infer/src/clang/ctl_parser_types.mli index b419dec93..a738fd9b1 100644 --- a/infer/src/clang/ctl_parser_types.mli +++ b/infer/src/clang/ctl_parser_types.mli @@ -6,15 +6,12 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) - (* Types used by the ctl parser *) open! IStd (** the kind of AST nodes where formulas are evaluated *) -type ast_node = - | Stmt of Clang_ast_t.stmt - | Decl of Clang_ast_t.decl +type ast_node = Stmt of Clang_ast_t.stmt | Decl of Clang_ast_t.decl val ast_node_name : ast_node -> string @@ -34,39 +31,41 @@ val infer_prefix : string https://clang.llvm.org/doxygen/Type_8cpp_source.html *) type builtin_kind = - | Void (** void *) - | Bool (** bool *) - | Char_U (** char *) + | Void (** void *) + | Bool (** bool *) + | Char_U (** char *) | UChar (** unsigned char *) - | WChar_U (** wchar_t *) + | WChar_U (** wchar_t *) | Char16 (** char16_t *) | Char32 (** char32_t *) - | UShort (** unsigned short *) + | UShort (** unsigned short *) | UInt (** unsigned int *) - | ULong (** unsigned long *) - | ULongLong (** unsigned long long *) - | Int128 (** __int128 *) - | UInt128 (** unsigned __int128 *) - | SChar (** signed char *) - | Short (** short *) - | Int (** int *) - | Long (** long *) - | LongLong (** long long *) - | Half (** half of __fp16 *) - | Float (** float *) - | Double (** double *) - | LongDouble (** long double *) - | Float128 (** __float128 *) - | NullPtr (** nullptr_t *) - | ObjCId (** id *) - | ObjCClass (** Class *) - | ObjCSel (** SEL *) + | ULong (** unsigned long *) + | ULongLong (** unsigned long long *) + | Int128 (** __int128 *) + | UInt128 (** unsigned __int128 *) + | SChar (** signed char *) + | Short (** short *) + | Int (** int *) + | Long (** long *) + | LongLong (** long long *) + | Half (** half of __fp16 *) + | Float (** float *) + | Double (** double *) + | LongDouble (** long double *) + | Float128 (** __float128 *) + | NullPtr (** nullptr_t *) + | ObjCId (** id *) + | ObjCClass (** Class *) + | ObjCSel (** SEL *) type abs_ctype = | BuiltIn of builtin_kind | Pointer of abs_ctype | TypeName of ALVar.alexp - | ObjCGenProt of abs_ctype * abs_ctype (* Objective-C Protocol or Generics *) + | ObjCGenProt of abs_ctype * abs_ctype + +(* Objective-C Protocol or Generics *) val c_type_equal : Clang_ast_t.c_type -> abs_ctype -> bool diff --git a/infer/src/clang/objcCategory_decl.ml b/infer/src/clang/objcCategory_decl.ml index 48496676b..d4843b955 100644 --- a/infer/src/clang/objcCategory_decl.ml +++ b/infer/src/clang/objcCategory_decl.ml @@ -8,31 +8,24 @@ *) open! IStd - module L = Logging (** In this module an ObjC category declaration or implementation is processed. The category *) + (** is saved in the tenv as a struct with the corresponding fields and methods , and the class it belongs to *) (* Name used for category with no name, i.e., "" *) -let noname_category class_name = - CFrontend_config.emtpy_name_category^class_name +let noname_category class_name = CFrontend_config.emtpy_name_category ^ class_name let cat_class_decl dr = - match dr.Clang_ast_t.dr_name with - | Some n -> CAst_utils.get_qualified_name n - | _ -> assert false + match dr.Clang_ast_t.dr_name with Some n -> CAst_utils.get_qualified_name n | _ -> assert false let get_classname decl_ref_opt = - match decl_ref_opt with - | Some dr -> cat_class_decl dr - | _ -> assert false + match decl_ref_opt with Some dr -> cat_class_decl dr | _ -> assert false -let get_classname_from_category_decl ocdi = - get_classname ocdi.Clang_ast_t.odi_class_interface +let get_classname_from_category_decl ocdi = get_classname ocdi.Clang_ast_t.odi_class_interface -let get_classname_from_category_impl ocidi = - get_classname ocidi.Clang_ast_t.ocidi_class_interface +let get_classname_from_category_impl ocidi = get_classname ocidi.Clang_ast_t.ocidi_class_interface let add_category_decl qual_type_to_sil_type tenv category_impl_info = let decl_ref_opt = category_impl_info.Clang_ast_t.ocidi_category_decl in @@ -50,18 +43,22 @@ let get_base_class_name_from_category decl = let open Clang_ast_t in let base_class_pointer_opt = match decl with - | ObjCCategoryDecl (_, _, _, _, cdi) -> - cdi.Clang_ast_t.odi_class_interface - | ObjCCategoryImplDecl (_, _, _, _, cii) -> - cii.Clang_ast_t.ocidi_class_interface - | _ -> None in + | ObjCCategoryDecl (_, _, _, _, cdi) + -> cdi.Clang_ast_t.odi_class_interface + | ObjCCategoryImplDecl (_, _, _, _, cii) + -> cii.Clang_ast_t.ocidi_class_interface + | _ + -> None + in match base_class_pointer_opt with - | Some decl_ref -> - (match CAst_utils.get_decl decl_ref.Clang_ast_t.dr_decl_pointer with - | Some ObjCInterfaceDecl (_, name_info, _, _, _) -> - Some (Typ.Name.Objc.from_qual_name (CAst_utils.get_qualified_name name_info)) - | _ -> None) - | None -> None + | Some decl_ref -> ( + match CAst_utils.get_decl decl_ref.Clang_ast_t.dr_decl_pointer with + | Some ObjCInterfaceDecl (_, name_info, _, _, _) + -> Some (Typ.Name.Objc.from_qual_name (CAst_utils.get_qualified_name name_info)) + | _ + -> None ) + | None + -> None (* Add potential extra fields defined only in the category *) (* to the corresponding class. Update the tenv accordingly.*) @@ -70,39 +67,42 @@ let process_category qual_type_to_sil_type tenv class_name decl_info decl_list = let decl_fields = CField_decl.get_fields qual_type_to_sil_type tenv class_tn_name decl_list in let class_tn_desc = Typ.Tstruct class_tn_name in let decl_key = Clang_ast_extend.DeclPtr decl_info.Clang_ast_t.di_pointer in - CAst_utils.update_sil_types_map decl_key class_tn_desc; - (match Tenv.lookup tenv class_tn_name with - | Some ({ fields } as struct_typ) -> - let new_fields = CGeneral_utils.append_no_duplicates_fields decl_fields fields in - ignore( - Tenv.mk_struct tenv - ~default:struct_typ ~fields:new_fields ~statics:[] ~methods:[] class_tn_name ); - L.(debug Capture Verbose) " Updating info for class '%a' in tenv@\n" - QualifiedCppName.pp class_name - | _ -> ()); + CAst_utils.update_sil_types_map decl_key class_tn_desc ; + ( match Tenv.lookup tenv class_tn_name with + | Some ({fields} as struct_typ) + -> let new_fields = CGeneral_utils.append_no_duplicates_fields decl_fields fields in + ignore + (Tenv.mk_struct tenv ~default:struct_typ ~fields:new_fields ~statics:[] ~methods:[] + class_tn_name) ; + L.(debug Capture Verbose) + " Updating info for class '%a' in tenv@\n" QualifiedCppName.pp class_name + | _ + -> () ) ; class_tn_desc let category_decl qual_type_to_sil_type tenv decl = let open Clang_ast_t in match decl with - | ObjCCategoryDecl (decl_info, name_info, decl_list, _, cdi) -> - let name = CAst_utils.get_qualified_name name_info in + | ObjCCategoryDecl (decl_info, name_info, decl_list, _, cdi) + -> let name = CAst_utils.get_qualified_name name_info in let class_name = get_classname_from_category_decl cdi in - L.(debug Capture Verbose) "ADDING: ObjCCategoryDecl for '%a'@\n" QualifiedCppName.pp name; + L.(debug Capture Verbose) "ADDING: ObjCCategoryDecl for '%a'@\n" QualifiedCppName.pp name ; let _ = add_class_decl qual_type_to_sil_type tenv cdi in let typ = process_category qual_type_to_sil_type tenv class_name decl_info decl_list in let _ = add_category_implementation qual_type_to_sil_type tenv cdi in typ - | _ -> assert false + | _ + -> assert false let category_impl_decl qual_type_to_sil_type tenv decl = let open Clang_ast_t in match decl with - | ObjCCategoryImplDecl (decl_info, name_info, decl_list, _, cii) -> - let name = CAst_utils.get_qualified_name name_info in + | ObjCCategoryImplDecl (decl_info, name_info, decl_list, _, cii) + -> let name = CAst_utils.get_qualified_name name_info in let class_name = get_classname_from_category_impl cii in - L.(debug Capture Verbose) "ADDING: ObjCCategoryImplDecl for '%a'@\n" QualifiedCppName.pp name; + L.(debug Capture Verbose) "ADDING: ObjCCategoryImplDecl for '%a'@\n" QualifiedCppName.pp name ; let _ = add_category_decl qual_type_to_sil_type tenv cii in let typ = process_category qual_type_to_sil_type tenv class_name decl_info decl_list in typ - | _ -> assert false + | _ + -> assert false diff --git a/infer/src/clang/objcCategory_decl.mli b/infer/src/clang/objcCategory_decl.mli index 54db1a863..9997ede2e 100644 --- a/infer/src/clang/objcCategory_decl.mli +++ b/infer/src/clang/objcCategory_decl.mli @@ -10,6 +10,7 @@ open! IStd (** In this module an ObjC category declaration or implementation is processed. The category *) + (** is saved in the tenv as a struct with the corresponding fields and methods , and the class it belongs to *) val category_decl : CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> Typ.desc diff --git a/infer/src/clang/objcInterface_decl.ml b/infer/src/clang/objcInterface_decl.ml index b69509b1b..4c8d87a51 100644 --- a/infer/src/clang/objcInterface_decl.ml +++ b/infer/src/clang/objcInterface_decl.ml @@ -10,7 +10,9 @@ open! IStd (** In this module an ObjC interface declaration or implementation is processed. The class *) + (** is saved in the tenv as a struct with the corresponding fields, potential superclass and *) + (** list of defined methods *) (* ObjectiveC doesn't have a notion of static or class fields. *) @@ -19,21 +21,26 @@ open! IStd module L = Logging let is_pointer_to_objc_class typ = - match typ.Typ.desc with - | Tptr (typ, _) when Typ.is_objc_class typ -> true - | _ -> false + match typ.Typ.desc with Tptr (typ, _) when Typ.is_objc_class typ -> true | _ -> false let get_super_interface_decl otdi_super = match otdi_super with - | Some dr -> Option.map ~f:CAst_utils.get_qualified_name dr.Clang_ast_t.dr_name - | _ -> None + | Some dr + -> Option.map ~f:CAst_utils.get_qualified_name dr.Clang_ast_t.dr_name + | _ + -> None let get_protocols protocols = - let protocol_names = List.map ~f:( - fun decl -> match decl.Clang_ast_t.dr_name with - | Some name_info -> CAst_utils.get_qualified_name name_info - | None -> assert false - ) protocols in + let protocol_names = + List.map + ~f:(fun decl -> + match decl.Clang_ast_t.dr_name with + | Some name_info + -> CAst_utils.get_qualified_name name_info + | None + -> assert false) + protocols + in protocol_names let add_class_decl qual_type_to_sil_type tenv idi = @@ -51,100 +58,106 @@ let add_categories_decl qual_type_to_sil_type tenv categories = CAst_utils.add_type_from_decl_ref_list qual_type_to_sil_type tenv categories let add_class_implementation qual_type_to_sil_type tenv idi = - let decl_ref_opt = idi.Clang_ast_t.otdi_implementation in + let decl_ref_opt = idi.Clang_ast_t.otdi_implementation in CAst_utils.add_type_from_decl_ref_opt qual_type_to_sil_type tenv decl_ref_opt false (*The superclass is the first element in the list of super classes of structs in the tenv, *) (* then come the protocols and categories. *) let get_interface_supers super_opt protocols = let super_class = - match super_opt with - | None -> [] - | Some super -> [Typ.Name.Objc.from_qual_name super] in + match super_opt with None -> [] | Some super -> [Typ.Name.Objc.from_qual_name super] + in let protocol_names = List.map ~f:Typ.Name.Objc.protocol_from_qual_name protocols in - let super_classes = super_class@protocol_names in + let super_classes = super_class @ protocol_names in super_classes -let create_supers_fields qual_type_to_sil_type tenv class_tname decl_list - otdi_super otdi_protocols = +let create_supers_fields qual_type_to_sil_type tenv class_tname decl_list otdi_super otdi_protocols = let super = get_super_interface_decl otdi_super in let protocols = get_protocols otdi_protocols in let supers = get_interface_supers super protocols in let fields = CField_decl.get_fields qual_type_to_sil_type tenv class_tname decl_list in - supers, fields + (supers, fields) (* Adds pairs (interface name, interface_type_info) to the global environment. *) let add_class_to_tenv qual_type_to_sil_type tenv decl_info name_info decl_list ocidi = let class_name = CAst_utils.get_qualified_name name_info in - L.(debug Capture Verbose) "ADDING: ObjCInterfaceDecl for '%a'@\n" QualifiedCppName.pp class_name; + L.(debug Capture Verbose) "ADDING: ObjCInterfaceDecl for '%a'@\n" QualifiedCppName.pp class_name ; let interface_name = Typ.Name.Objc.from_qual_name class_name in let interface_desc = Typ.Tstruct interface_name in let decl_key = Clang_ast_extend.DeclPtr decl_info.Clang_ast_t.di_pointer in - CAst_utils.update_sil_types_map decl_key interface_desc; + CAst_utils.update_sil_types_map decl_key interface_desc ; let decl_supers, decl_fields = create_supers_fields qual_type_to_sil_type tenv interface_name decl_list - ocidi.Clang_ast_t.otdi_super - ocidi.Clang_ast_t.otdi_protocols in + ocidi.Clang_ast_t.otdi_super ocidi.Clang_ast_t.otdi_protocols + in let fields_sc = CField_decl.fields_superclass tenv ocidi in - List.iter ~f:(fun (fn, ft, _) -> - L.(debug Capture Verbose) "----->SuperClass field: '%s' " (Typ.Fieldname.to_string fn); - L.(debug Capture Verbose) "type: '%s'@\n" (Typ.to_string ft)) fields_sc; + List.iter + ~f:(fun (fn, ft, _) -> + L.(debug Capture Verbose) "----->SuperClass field: '%s' " (Typ.Fieldname.to_string fn) ; + L.(debug Capture Verbose) "type: '%s'@\n" (Typ.to_string ft)) + fields_sc ; (*In case we found categories, or partial definition of this class earlier and they are already in the tenv *) - let fields, (supers : Typ.Name.t list) = + let fields, (supers: Typ.Name.t list) = match Tenv.lookup tenv interface_name with - | Some { fields; supers} -> - CGeneral_utils.append_no_duplicates_fields decl_fields fields, - CGeneral_utils.append_no_duplicates_csu decl_supers supers - | _ -> - decl_fields, decl_supers in + | Some {fields; supers} + -> ( CGeneral_utils.append_no_duplicates_fields decl_fields fields + , CGeneral_utils.append_no_duplicates_csu decl_supers supers ) + | _ + -> (decl_fields, decl_supers) + in let fields = CGeneral_utils.append_no_duplicates_fields fields fields_sc in (* We add the special hidden counter_field for implementing reference counting *) - let modelled_fields = Typ.Struct.objc_ref_counter_field :: CField_decl.modelled_field name_info in + let modelled_fields = + Typ.Struct.objc_ref_counter_field :: CField_decl.modelled_field name_info + in let all_fields = CGeneral_utils.append_no_duplicates_fields modelled_fields fields in - L.(debug Capture Verbose) "Class %a field:@\n" QualifiedCppName.pp class_name; - List.iter ~f:(fun (fn, _, _) -> - L.(debug Capture Verbose) "-----> field: '%s'@\n" (Typ.Fieldname.to_string fn)) all_fields; - ignore( - Tenv.mk_struct tenv - ~fields: all_fields ~supers ~methods:[] ~annots:Annot.Class.objc interface_name ); + L.(debug Capture Verbose) "Class %a field:@\n" QualifiedCppName.pp class_name ; + List.iter + ~f:(fun (fn, _, _) -> + L.(debug Capture Verbose) "-----> field: '%s'@\n" (Typ.Fieldname.to_string fn)) + all_fields ; + ignore + (Tenv.mk_struct tenv ~fields:all_fields ~supers ~methods:[] ~annots:Annot.Class.objc + interface_name) ; L.(debug Capture Verbose) - " >>>Verifying that Typename '%s' is in tenv@\n" (Typ.Name.to_string interface_name); - (match Tenv.lookup tenv interface_name with - | Some st -> - L.(debug Capture Verbose) " >>>OK. Found typ='%a'@\n" - (Typ.Struct.pp Pp.text interface_name) st - | None -> L.(debug Capture Verbose) " >>>NOT Found!!@\n"); + " >>>Verifying that Typename '%s' is in tenv@\n" (Typ.Name.to_string interface_name) ; + ( match Tenv.lookup tenv interface_name with + | Some st + -> L.(debug Capture Verbose) + " >>>OK. Found typ='%a'@\n" (Typ.Struct.pp Pp.text interface_name) st + | None + -> L.(debug Capture Verbose) " >>>NOT Found!!@\n" ) ; interface_desc (* Interface_type_info has the name of instance variables and the name of methods. *) let interface_declaration qual_type_to_sil_type tenv decl = let open Clang_ast_t in match decl with - | ObjCInterfaceDecl (decl_info, name_info, decl_list, _, ocidi) -> - let typ = add_class_to_tenv qual_type_to_sil_type tenv decl_info name_info - decl_list ocidi in + | ObjCInterfaceDecl (decl_info, name_info, decl_list, _, ocidi) + -> let typ = add_class_to_tenv qual_type_to_sil_type tenv decl_info name_info decl_list ocidi in let _ = add_class_implementation qual_type_to_sil_type tenv ocidi in let _ = add_super_class_decl qual_type_to_sil_type tenv ocidi in let _ = add_protocols_decl qual_type_to_sil_type tenv ocidi.Clang_ast_t.otdi_protocols in let known_categories = ocidi.Clang_ast_t.otdi_known_categories in let _ = add_categories_decl qual_type_to_sil_type tenv known_categories in typ - | _ -> assert false + | _ + -> assert false (* Translate the methods defined in the implementation.*) let interface_impl_declaration qual_type_to_sil_type tenv decl = let open Clang_ast_t in match decl with - | ObjCImplementationDecl (decl_info, name_info, decl_list, _, idi) -> - let class_name = CAst_utils.get_qualified_name name_info in + | ObjCImplementationDecl (decl_info, name_info, decl_list, _, idi) + -> let class_name = CAst_utils.get_qualified_name name_info in L.(debug Capture Verbose) - "ADDING: ObjCImplementationDecl for class '%a'@\n" QualifiedCppName.pp class_name; + "ADDING: ObjCImplementationDecl for class '%a'@\n" QualifiedCppName.pp class_name ; let _ = add_class_decl qual_type_to_sil_type tenv idi in let class_tn_name = Typ.Name.Objc.from_qual_name class_name in let fields = CField_decl.get_fields qual_type_to_sil_type tenv class_tn_name decl_list in - CField_decl.add_missing_fields tenv class_name fields; + CField_decl.add_missing_fields tenv class_name fields ; let decl_key = Clang_ast_extend.DeclPtr decl_info.Clang_ast_t.di_pointer in let class_desc = Typ.Tstruct class_tn_name in - CAst_utils.update_sil_types_map decl_key class_desc; - class_desc - | _ -> assert false + CAst_utils.update_sil_types_map decl_key class_desc ; class_desc + | _ + -> assert false diff --git a/infer/src/clang/objcInterface_decl.mli b/infer/src/clang/objcInterface_decl.mli index 49c4b7358..018971458 100644 --- a/infer/src/clang/objcInterface_decl.mli +++ b/infer/src/clang/objcInterface_decl.mli @@ -12,10 +12,10 @@ open! IStd (** In this module an ObjC interface declaration is processed. The class is saved in the tenv as a struct with the corresponding fields, potential superclass and list of defined methods *) -val interface_declaration : CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> - Typ.desc +val interface_declaration : + CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> Typ.desc -val interface_impl_declaration : CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> - Typ.desc +val interface_impl_declaration : + CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> Typ.desc val is_pointer_to_objc_class : Typ.t -> bool diff --git a/infer/src/clang/objcProperty_decl.ml b/infer/src/clang/objcProperty_decl.ml index aff93881f..2ff511823 100644 --- a/infer/src/clang/objcProperty_decl.ml +++ b/infer/src/clang/objcProperty_decl.ml @@ -10,7 +10,9 @@ open! IStd (** Process properties by creating their getters and setters in the case that they need to be syntethized *) + (** or in the case of dynamic. *) + (* How it works: *) (* - First, the property is defined in the interface. Then, we add the method declarations of the getter *) (* and setter to the map property_table. *) @@ -19,12 +21,8 @@ open! IStd let is_strong_property obj_c_property_decl_info = let attrs = obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes in - List.exists ~f:(fun a -> match a with - | `Strong -> true - | _ -> false) attrs + List.exists ~f:(fun a -> match a with `Strong -> true | _ -> false) attrs let is_assign_property obj_c_property_decl_info = let attrs = obj_c_property_decl_info.Clang_ast_t.opdi_property_attributes in - List.exists ~f:(fun a -> match a with - | `Assign -> true - | _ -> false) attrs + List.exists ~f:(fun a -> match a with `Assign -> true | _ -> false) attrs diff --git a/infer/src/clang/objcProperty_decl.mli b/infer/src/clang/objcProperty_decl.mli index a7eac8b75..8c79eacd0 100644 --- a/infer/src/clang/objcProperty_decl.mli +++ b/infer/src/clang/objcProperty_decl.mli @@ -10,10 +10,13 @@ open! IStd (** Process properties by creating their getters and setters in the case that they need to be syntethized *) + (** or in the case of dynamic. *) (* Given a property type returns whether the property is strong *) + val is_strong_property : Clang_ast_t.obj_c_property_decl_info -> bool (* Returns true if a property has the `assign` attribute *) + val is_assign_property : Clang_ast_t.obj_c_property_decl_info -> bool diff --git a/infer/src/clang/objcProtocol_decl.ml b/infer/src/clang/objcProtocol_decl.ml index 69db70f26..077733b16 100644 --- a/infer/src/clang/objcProtocol_decl.ml +++ b/infer/src/clang/objcProtocol_decl.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging let add_protocol_super qual_type_to_sil_type tenv obj_c_protocol_decl_info = @@ -18,24 +17,21 @@ let add_protocol_super qual_type_to_sil_type tenv obj_c_protocol_decl_info = let protocol_decl qual_type_to_sil_type tenv decl = let open Clang_ast_t in match decl with - | ObjCProtocolDecl(decl_info, name_info, _, _, obj_c_protocol_decl_info) -> - let name = CAst_utils.get_qualified_name name_info in + | ObjCProtocolDecl (decl_info, name_info, _, _, obj_c_protocol_decl_info) + -> let name = CAst_utils.get_qualified_name name_info in (* Adds pairs (protocol name, protocol_type_info) to the global environment. *) (* Protocol_type_info contains the methods composing the protocol. *) (* Here we are giving a similar treatment as interfaces (see above)*) (* It may turn out that we need a more specific treatment for protocols*) - L.(debug Capture Verbose) "ADDING: ObjCProtocolDecl for '%a'@\n" QualifiedCppName.pp name; + L.(debug Capture Verbose) "ADDING: ObjCProtocolDecl for '%a'@\n" QualifiedCppName.pp name ; let protocol_name = Typ.Name.Objc.protocol_from_qual_name name in let protocol_desc = Typ.Tstruct protocol_name in let decl_key = Clang_ast_extend.DeclPtr decl_info.Clang_ast_t.di_pointer in - CAst_utils.update_sil_types_map decl_key protocol_desc; - ignore( Tenv.mk_struct tenv ~methods:[] protocol_name ); - add_protocol_super qual_type_to_sil_type tenv obj_c_protocol_decl_info; + CAst_utils.update_sil_types_map decl_key protocol_desc ; + ignore (Tenv.mk_struct tenv ~methods:[] protocol_name) ; + add_protocol_super qual_type_to_sil_type tenv obj_c_protocol_decl_info ; protocol_desc - | _ -> assert false + | _ + -> assert false -let is_protocol decl = - let open Clang_ast_t in - match decl with - | ObjCProtocolDecl _ -> true - | _ -> false +let is_protocol decl = Clang_ast_t.(match decl with ObjCProtocolDecl _ -> true | _ -> false) diff --git a/infer/src/clang/objcProtocol_decl.mli b/infer/src/clang/objcProtocol_decl.mli index e762adf12..8f101fdcc 100644 --- a/infer/src/clang/objcProtocol_decl.mli +++ b/infer/src/clang/objcProtocol_decl.mli @@ -10,6 +10,7 @@ open! IStd (** In this module an ObjC protocol declaration or implementation is processed. The protocol *) + (** is saved in the tenv as a struct with the corresponding methods *) val protocol_decl : CAst_utils.qual_type_to_sil_type -> Tenv.t -> Clang_ast_t.decl -> Typ.desc diff --git a/infer/src/clang_stubs/ClangWrapper.mli b/infer/src/clang_stubs/ClangWrapper.mli index f2bad39d8..7d6ab5feb 100644 --- a/infer/src/clang_stubs/ClangWrapper.mli +++ b/infer/src/clang_stubs/ClangWrapper.mli @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd val exe : prog:string -> args:string list -> unit diff --git a/infer/src/eradicate/AnnotatedSignature.ml b/infer/src/eradicate/AnnotatedSignature.ml index c9908ddb9..18e7df1cd 100644 --- a/infer/src/eradicate/AnnotatedSignature.ml +++ b/infer/src/eradicate/AnnotatedSignature.ml @@ -7,134 +7,141 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module F = Format module L = Logging -type t = { - ret : Annot.Item.t * Typ.t; - params: (Mangled.t * Annot.Item.t * Typ.t) list; -} [@@deriving compare] +type t = + {ret: Annot.Item.t * Typ.t; params: (Mangled.t * Annot.Item.t * Typ.t) list} + [@@deriving compare] + +type annotation = Nullable | Present [@@deriving compare] -type annotation = +let ia_is ann ia = + match ann with | Nullable + -> Annotations.ia_is_nullable ia | Present -[@@deriving compare] - -let ia_is ann ia = match ann with - | Nullable -> Annotations.ia_is_nullable ia - | Present -> Annotations.ia_is_present ia + -> Annotations.ia_is_present ia let get proc_attributes : t = let method_annotation = proc_attributes.ProcAttributes.method_annotation in let formals = proc_attributes.ProcAttributes.formals in let ret_type = proc_attributes.ProcAttributes.ret_type in - let (ia, ial0) = method_annotation in + let ia, ial0 = method_annotation in let natl = - let rec extract ial parl = match ial, parl with - | ia :: ial', (name, typ) :: parl' -> - (name, ia, typ) :: extract ial' parl' - | [], (name, typ) :: parl' -> - (name, Annot.Item.empty, typ) :: extract [] parl' - | [], [] -> - [] - | _ :: _, [] -> - assert false in - List.rev (extract (List.rev ial0) (List.rev formals)) in - let annotated_signature = { ret = (ia, ret_type); params = natl } in + let rec extract ial parl = + match (ial, parl) with + | ia :: ial', (name, typ) :: parl' + -> (name, ia, typ) :: extract ial' parl' + | [], (name, typ) :: parl' + -> (name, Annot.Item.empty, typ) :: extract [] parl' + | [], [] + -> [] + | _ :: _, [] + -> assert false + in + List.rev (extract (List.rev ial0) (List.rev formals)) + in + let annotated_signature = {ret= (ia, ret_type); params= natl} in annotated_signature let param_is_nullable pvar ann_sig = List.exists ~f:(fun (param, annot, _) -> - Mangled.equal param (Pvar.get_name pvar) && Annotations.ia_is_nullable annot) + Mangled.equal param (Pvar.get_name pvar) && Annotations.ia_is_nullable annot) ann_sig.params let param_has_annot predicate pvar ann_sig = List.exists ~f:(fun (param, param_annot, _) -> - Mangled.equal param (Pvar.get_name pvar) && predicate param_annot) + Mangled.equal param (Pvar.get_name pvar) && predicate param_annot) ann_sig.params let pp proc_name fmt annotated_signature = let pp_ia fmt ia = if ia <> [] then F.fprintf fmt "%a " Annot.Item.pp ia in let pp_annotated_param fmt (p, ia, t) = - F.fprintf fmt " %a%a %a" pp_ia ia (Typ.pp_full Pp.text) t Mangled.pp p in + F.fprintf fmt " %a%a %a" pp_ia ia (Typ.pp_full Pp.text) t Mangled.pp p + in let ia, ret_type = annotated_signature.ret in - F.fprintf fmt "%a%a %s (%a )" - pp_ia ia - (Typ.pp_full Pp.text) ret_type - (Typ.Procname.to_simplified_string proc_name) - (Pp.comma_seq pp_annotated_param) annotated_signature.params + F.fprintf fmt "%a%a %s (%a )" pp_ia ia (Typ.pp_full Pp.text) ret_type + (Typ.Procname.to_simplified_string proc_name) (Pp.comma_seq pp_annotated_param) + annotated_signature.params let is_anonymous_inner_class_wrapper ann_sig proc_name = - let check_ret (ia, t) = - Annot.Item.is_empty ia && PatternMatch.type_is_object t in + let check_ret (ia, t) = Annot.Item.is_empty ia && PatternMatch.type_is_object t in let x_param_found = ref false in let name_is_x_number name = let name_str = Mangled.to_string name in let len = String.length name_str in - len >= 2 && - String.equal (String.sub name_str ~pos:0 ~len:1) "x" && + len >= 2 && String.equal (String.sub name_str ~pos:0 ~len:1) "x" + && let s = String.sub name_str ~pos:1 ~len:(len - 1) in let is_int = try - ignore (int_of_string s); - x_param_found := true; + ignore (int_of_string s) ; + x_param_found := true ; true - with Failure _ -> false in - is_int in + with Failure _ -> false + in + is_int + in let check_param (name, ia, t) = if String.equal (Mangled.to_string name) "this" then true - else - name_is_x_number name && - Annot.Item.is_empty ia && - PatternMatch.type_is_object t in - Typ.Procname.java_is_anonymous_inner_class proc_name - && check_ret ann_sig.ret - && List.for_all ~f:check_param ann_sig.params - && !x_param_found + else name_is_x_number name && Annot.Item.is_empty ia && PatternMatch.type_is_object t + in + Typ.Procname.java_is_anonymous_inner_class proc_name && check_ret ann_sig.ret + && List.for_all ~f:check_param ann_sig.params && !x_param_found + +let mk_ann_str s = {Annot.class_name= s; parameters= []} -let mk_ann_str s = { Annot.class_name = s; parameters = [] } let mk_ann = function - | Nullable -> mk_ann_str Annotations.nullable - | Present -> mk_ann_str Annotations.present -let mk_ia ann ia = - if ia_is ann ia then ia - else (mk_ann ann, true) :: ia -let mark_ia ann ia x = - if x then mk_ia ann ia else ia + | Nullable + -> mk_ann_str Annotations.nullable + | Present + -> mk_ann_str Annotations.present + +let mk_ia ann ia = if ia_is ann ia then ia else (mk_ann ann, true) :: ia + +let mark_ia ann ia x = if x then mk_ia ann ia else ia let method_annotation_mark_return ann method_annotation = let ia_ret, params = method_annotation in let ia_ret' = mark_ia ann ia_ret true in - ia_ret', params + (ia_ret', params) let mark proc_name ann asig (b, bs) = let ia, t = asig.ret in - let ret' = mark_ia ann ia b, t in + let ret' = (mark_ia ann ia b, t) in let mark_param (s, ia, t) x = let ia' = if x then mk_ia ann ia else ia in - (s, ia', t) in + (s, ia', t) + in let params' = let fail () = L.internal_error "INTERNAL ERROR: annotation for procedure %s has wrong number of arguments@." - (Typ.Procname.to_unique_id proc_name); - L.internal_error " ANNOTATED SIGNATURE: %a@." (pp proc_name) asig; - assert false in - let rec combine l1 l2 = match l1, l2 with - | (p, ia, t):: l1', l2' when String.equal (Mangled.to_string p) "this" -> - (p, ia, t) :: combine l1' l2' - | (s, ia, t):: l1', x:: l2' -> - mark_param (s, ia, t) x :: combine l1' l2' - | [], _:: _ -> fail () - | _:: _, [] -> fail () - | [], [] -> [] in - combine asig.params bs in - { ret = ret'; params = params'} + (Typ.Procname.to_unique_id proc_name) ; + L.internal_error " ANNOTATED SIGNATURE: %a@." (pp proc_name) asig ; + assert false + in + let rec combine l1 l2 = + match (l1, l2) with + | (p, ia, t) :: l1', l2' when String.equal (Mangled.to_string p) "this" + -> (p, ia, t) :: combine l1' l2' + | (s, ia, t) :: l1', x :: l2' + -> mark_param (s, ia, t) x :: combine l1' l2' + | [], _ :: _ + -> fail () + | _ :: _, [] + -> fail () + | [], [] + -> [] + in + combine asig.params bs + in + {ret= ret'; params= params'} let mark_return ann asig = let ia, t = asig.ret in - let ret' = mark_ia ann ia true, t in - { asig with ret = ret'} + let ret' = (mark_ia ann ia true, t) in + {asig with ret= ret'} diff --git a/infer/src/eradicate/AnnotatedSignature.mli b/infer/src/eradicate/AnnotatedSignature.mli index b3566ea8b..3339910a9 100644 --- a/infer/src/eradicate/AnnotatedSignature.mli +++ b/infer/src/eradicate/AnnotatedSignature.mli @@ -11,41 +11,38 @@ open! IStd -type t = { - ret : Annot.Item.t * Typ.t; (** Annotated return type. *) - params: (Mangled.t * Annot.Item.t * Typ.t) list; (** Annotated parameters. *) -} [@@deriving compare] +type t = + { ret: Annot.Item.t * Typ.t (** Annotated return type. *) + ; params: (Mangled.t * Annot.Item.t * Typ.t) list (** Annotated parameters. *) } + [@@deriving compare] -type annotation = - | Nullable - | Present -[@@deriving compare] +type annotation = Nullable | Present [@@deriving compare] +val is_anonymous_inner_class_wrapper : t -> Typ.Procname.t -> bool (** Check if the annotated signature is for a wrapper of an anonymous inner class method. These wrappers have the same name as the original method, every type is Object, and the parameters are called x0, x1, x2. *) -val is_anonymous_inner_class_wrapper : t -> Typ.Procname.t -> bool -(** Check if the given parameter has a Nullable annotation in the given signature *) val param_is_nullable : Pvar.t -> t -> bool +(** Check if the given parameter has a Nullable annotation in the given signature *) -(** Check if the given parameter has an annotation in the given signature *) val param_has_annot : (Annot.Item.t -> bool) -> Pvar.t -> t -> bool +(** Check if the given parameter has an annotation in the given signature *) -(** Mark the return of the method_annotation with the given annotation. *) val method_annotation_mark_return : annotation -> Annot.Method.t -> Annot.Method.t +(** Mark the return of the method_annotation with the given annotation. *) -(** Mark the annotated signature with the given annotation map. *) val mark : Typ.Procname.t -> annotation -> t -> bool * bool list -> t +(** Mark the annotated signature with the given annotation map. *) -(** Mark the return of the annotated signature with the given annotation. *) val mark_return : annotation -> t -> t +(** Mark the return of the annotated signature with the given annotation. *) -(** Get a method signature with annotations from a proc_attributes. *) val get : ProcAttributes.t -> t +(** Get a method signature with annotations from a proc_attributes. *) -(** Add the annotation to the item_annotation. *) val mk_ia : annotation -> Annot.Item.t -> Annot.Item.t +(** Add the annotation to the item_annotation. *) -(** Pretty print a method signature with annotations. *) val pp : Typ.Procname.t -> Format.formatter -> t -> unit +(** Pretty print a method signature with annotations. *) diff --git a/infer/src/eradicate/eradicate.ml b/infer/src/eradicate/eradicate.ml index 2337fd503..77ec0d293 100644 --- a/infer/src/eradicate/eradicate.ml +++ b/infer/src/eradicate/eradicate.ml @@ -17,180 +17,158 @@ open Dataflow (* ERADICATE CHECKER. TODOS:*) (* 1) add support for constructors for anonymous inner classes (currently not checked) *) - (* check that nonnullable fields are initialized in constructors *) let check_field_initialization = true type parameters = TypeState.parameters - (** Type for a module that provides a main callback function *) -module type CallBackT = -sig +module type CallBackT = sig val callback : TypeCheck.checks -> Callbacks.proc_callback_t -end (* CallBackT *) +end + +(* CallBackT *) (** Extension to the type checker. *) module type ExtensionT = sig type extension + val ext : extension TypeState.ext + val update_payload : extension TypeState.t option -> Specs.payload -> Specs.payload end (** Create a module with the toplevel callback. *) -module MkCallback - (Extension : ExtensionT) - : CallBackT = -struct +module MkCallback (Extension : ExtensionT) : CallBackT = struct (** Update the summary with stats from the checker. *) let update_summary proc_name proc_desc final_typestate_opt = match Specs.get_summary proc_name with - | Some old_summ -> - let nodes = List.map ~f:(fun n -> Procdesc.Node.get_id n) (Procdesc.get_nodes proc_desc) in + | Some old_summ + -> let nodes = List.map ~f:(fun n -> Procdesc.Node.get_id n) (Procdesc.get_nodes proc_desc) in let method_annotation = - (Specs.pdesc_resolve_attributes proc_desc).ProcAttributes.method_annotation in + (Specs.pdesc_resolve_attributes proc_desc).ProcAttributes.method_annotation + in let new_summ = - { - old_summ with - Specs.nodes = nodes; - Specs.payload = - Extension.update_payload final_typestate_opt old_summ.Specs.payload; - Specs.attributes = - { - old_summ.Specs.attributes with - ProcAttributes.loc = Procdesc.get_loc proc_desc; - method_annotation; - }; - } in + { old_summ with + Specs.nodes= nodes + ; Specs.payload= Extension.update_payload final_typestate_opt old_summ.Specs.payload + ; Specs.attributes= + { old_summ.Specs.attributes with + ProcAttributes.loc= Procdesc.get_loc proc_desc; method_annotation } } + in Specs.add_summary proc_name new_summ - | None -> () + | None + -> () - let callback1 - tenv find_canonical_duplicate calls_this checks get_proc_desc idenv curr_pname + let callback1 tenv find_canonical_duplicate calls_this checks get_proc_desc idenv curr_pname curr_pdesc annotated_signature linereader proc_loc - : bool * Extension.extension TypeState.t option = + : bool * Extension.extension TypeState.t option = let mk s = Pvar.mk s curr_pname in let add_formal typestate (s, ia, typ) = let pvar = mk s in let ta = let origin = TypeOrigin.Formal s in - TypeAnnotation.from_item_annotation ia origin in - TypeState.add pvar (typ, ta, []) typestate in + TypeAnnotation.from_item_annotation ia origin + in + TypeState.add pvar (typ, ta, []) typestate + in let get_initial_typestate () = let typestate_empty = TypeState.empty Extension.ext in - List.fold - ~f:add_formal - ~init:typestate_empty - annotated_signature.AnnotatedSignature.params in - + List.fold ~f:add_formal ~init:typestate_empty annotated_signature.AnnotatedSignature.params + in (* Check the nullable flag computed for the return value and report inconsistencies. *) - let check_return - find_canonical_duplicate exit_node final_typestate annotated_signature loc : unit = + let check_return find_canonical_duplicate exit_node final_typestate annotated_signature loc + : unit = let _, ret_type = annotated_signature.AnnotatedSignature.ret in let ret_pvar = Procdesc.get_ret_var curr_pdesc in let ret_range = TypeState.lookup_pvar ret_pvar final_typestate in - let typ_found_opt = match ret_range with - | Some (typ_found, _, _) -> Some typ_found - | None -> None in + let typ_found_opt = + match ret_range with Some (typ_found, _, _) -> Some typ_found | None -> None + in let ret_implicitly_nullable = - String.equal (PatternMatch.get_type_name ret_type) "java.lang.Void" in - State.set_node exit_node; - + String.equal (PatternMatch.get_type_name ret_type) "java.lang.Void" + in + State.set_node exit_node ; if checks.TypeCheck.check_ret_type <> [] then List.iter ~f:(fun f -> f curr_pname curr_pdesc ret_type typ_found_opt loc) - checks.TypeCheck.check_ret_type; + checks.TypeCheck.check_ret_type ; if checks.TypeCheck.eradicate then - EradicateChecks.check_return_annotation tenv - find_canonical_duplicate curr_pdesc ret_range - annotated_signature ret_implicitly_nullable loc in - + EradicateChecks.check_return_annotation tenv find_canonical_duplicate curr_pdesc ret_range + annotated_signature ret_implicitly_nullable loc + in let do_before_dataflow initial_typestate = if Config.eradicate_verbose then - L.result "Initial Typestate@\n%a@." - (TypeState.pp Extension.ext) initial_typestate in - + L.result "Initial Typestate@\n%a@." (TypeState.pp Extension.ext) initial_typestate + in let do_after_dataflow find_canonical_duplicate final_typestate = let exit_node = Procdesc.get_exit_node curr_pdesc in - check_return - find_canonical_duplicate exit_node final_typestate annotated_signature proc_loc in - - let module DFTypeCheck = MakeDF(struct - type t = Extension.extension TypeState.t - let equal = TypeState.equal - let join = TypeState.join Extension.ext - let do_node tenv node typestate = - NodePrinter.start_session node; - State.set_node node; - let typestates_succ, typestates_exn = - TypeCheck.typecheck_node - tenv Extension.ext calls_this checks idenv get_proc_desc curr_pname curr_pdesc - find_canonical_duplicate annotated_signature typestate node linereader in - - if Config.write_html then - begin - let d_typestate ts = - L.d_strln (F.asprintf "%a" (TypeState.pp Extension.ext) ts) in - L.d_strln "before:"; - d_typestate typestate; - L.d_strln "after:"; - List.iter ~f:d_typestate typestates_succ - end; - - NodePrinter.finish_session node; - typestates_succ, typestates_exn - let proc_throws _ = DontKnow - end) in + check_return find_canonical_duplicate exit_node final_typestate annotated_signature proc_loc + in + let module DFTypeCheck = MakeDF (struct + type t = Extension.extension TypeState.t + + let equal = TypeState.equal + + let join = TypeState.join Extension.ext + + let do_node tenv node typestate = + NodePrinter.start_session node ; + State.set_node node ; + let typestates_succ, typestates_exn = + TypeCheck.typecheck_node tenv Extension.ext calls_this checks idenv get_proc_desc + curr_pname curr_pdesc find_canonical_duplicate annotated_signature typestate node + linereader + in + ( if Config.write_html then + let d_typestate ts = L.d_strln (F.asprintf "%a" (TypeState.pp Extension.ext) ts) in + L.d_strln "before:" ; + d_typestate typestate ; + L.d_strln "after:" ; + List.iter ~f:d_typestate typestates_succ ) ; + NodePrinter.finish_session node ; (typestates_succ, typestates_exn) + + let proc_throws _ = DontKnow + end) in let initial_typestate = get_initial_typestate () in - do_before_dataflow initial_typestate; + do_before_dataflow initial_typestate ; let transitions = DFTypeCheck.run tenv curr_pdesc initial_typestate in match transitions (Procdesc.get_exit_node curr_pdesc) with - | DFTypeCheck.Transition (final_typestate, _, _) -> - do_after_dataflow find_canonical_duplicate final_typestate; - !calls_this, Some final_typestate - | DFTypeCheck.Dead_state -> - !calls_this, None - - let callback2 - calls_this checks - { - Callbacks.proc_desc = curr_pdesc; - summary; - get_proc_desc; - idenv; - tenv; - get_procs_in_file; - } + | DFTypeCheck.Transition (final_typestate, _, _) + -> do_after_dataflow find_canonical_duplicate final_typestate ; + (!calls_this, Some final_typestate) + | DFTypeCheck.Dead_state + -> (!calls_this, None) + + let callback2 calls_this checks + {Callbacks.proc_desc= curr_pdesc; summary; get_proc_desc; idenv; tenv; get_procs_in_file} annotated_signature linereader proc_loc : unit = let curr_pname = Specs.get_proc_name summary in - let find_duplicate_nodes = State.mk_find_duplicate_nodes curr_pdesc in let find_canonical_duplicate node = let duplicate_nodes = find_duplicate_nodes node in - try Procdesc.NodeSet.min_elt duplicate_nodes with - | Not_found -> node in - + try Procdesc.NodeSet.min_elt duplicate_nodes + with Not_found -> node + in let typecheck_proc do_checks pname pdesc proc_details_opt = - let ann_sig, loc, idenv_pn = match proc_details_opt with - | Some (ann_sig, loc, idenv_pn) -> - (ann_sig, loc, idenv_pn) - | None -> - let ann_sig = - Models.get_modelled_annotated_signature (Procdesc.get_attributes pdesc) in + let ann_sig, loc, idenv_pn = + match proc_details_opt with + | Some (ann_sig, loc, idenv_pn) + -> (ann_sig, loc, idenv_pn) + | None + -> let ann_sig = + Models.get_modelled_annotated_signature (Procdesc.get_attributes pdesc) + in let loc = Procdesc.get_loc pdesc in - (ann_sig, loc, Idenv.create pdesc) in + (ann_sig, loc, Idenv.create pdesc) + in let checks', calls_this' = - if do_checks then checks, calls_this - else - { - TypeCheck.eradicate = false; - check_extension = false; - check_ret_type = []; - }, ref false in - callback1 - tenv find_canonical_duplicate calls_this' checks' get_proc_desc idenv_pn - pname pdesc ann_sig linereader loc in - + if do_checks then (checks, calls_this) + else ({TypeCheck.eradicate= false; check_extension= false; check_ret_type= []}, ref false) + in + callback1 tenv find_canonical_duplicate calls_this' checks' get_proc_desc idenv_pn pname + pdesc ann_sig linereader loc + in let module Initializers = struct type init = Typ.Procname.t * Procdesc.t @@ -198,230 +176,214 @@ struct let final_typestates initializers_current_class = (* Get the private methods, from the same class, directly called by the initializers. *) - let get_private_called (initializers : init list) : init list = + let get_private_called (initializers: init list) : init list = let res = ref [] in let do_proc (init_pn, init_pd) = let filter callee_pn callee_attributes = let is_private = - PredSymb.equal_access callee_attributes.ProcAttributes.access PredSymb.Private in + PredSymb.equal_access callee_attributes.ProcAttributes.access PredSymb.Private + in let same_class = - let get_class_opt pn = match pn with - | Typ.Procname.Java pn_java -> - Some (Typ.Procname.java_get_class_name pn_java) - | _ -> - None in - equal_class_opt (get_class_opt init_pn) (get_class_opt callee_pn) in - is_private && same_class in - let private_called = PatternMatch.proc_calls - Specs.proc_resolve_attributes init_pd filter in + let get_class_opt pn = + match pn with + | Typ.Procname.Java pn_java + -> Some (Typ.Procname.java_get_class_name pn_java) + | _ + -> None + in + equal_class_opt (get_class_opt init_pn) (get_class_opt callee_pn) + in + is_private && same_class + in + let private_called = + PatternMatch.proc_calls Specs.proc_resolve_attributes init_pd filter + in let do_called (callee_pn, _) = match get_proc_desc callee_pn with - | Some callee_pd -> - res := (callee_pn, callee_pd) :: !res - | None -> () in - List.iter ~f:do_called private_called in - List.iter ~f:do_proc initializers; - !res in - + | Some callee_pd + -> res := (callee_pn, callee_pd) :: !res + | None + -> () + in + List.iter ~f:do_called private_called + in + List.iter ~f:do_proc initializers ; !res + in (* Get the initializers recursively called by computing a fixpoint. Start from the initializers of the current class and the current procedure. *) let initializers_recursive : init list = let initializers_base_case = initializers_current_class in - let res = ref [] in let seen = ref Typ.Procname.Set.empty in - let mark_seen (initializers : init list) : unit = - List.iter ~f:(fun (pn, _) -> seen := Typ.Procname.Set.add pn !seen) initializers; - res := !res @ initializers in - + let mark_seen (initializers: init list) : unit = + List.iter ~f:(fun (pn, _) -> seen := Typ.Procname.Set.add pn !seen) initializers ; + res := !res @ initializers + in let rec fixpoint initializers_old = let initializers_new = get_private_called initializers_old in let initializers_new' = - List.filter - ~f:(fun (pn, _) -> not (Typ.Procname.Set.mem pn !seen)) - initializers_new in - mark_seen initializers_new'; - if initializers_new' <> [] then fixpoint initializers_new' in - - mark_seen initializers_base_case; - fixpoint initializers_base_case; - !res in - + List.filter ~f:(fun (pn, _) -> not (Typ.Procname.Set.mem pn !seen)) initializers_new + in + mark_seen initializers_new' ; + if initializers_new' <> [] then fixpoint initializers_new' + in + mark_seen initializers_base_case ; fixpoint initializers_base_case ; !res + in (* Get the final typestates of all the initializers. *) let final_typestates = ref [] in let get_final_typestate (pname, pdesc) = match typecheck_proc false pname pdesc None with - | _, Some final_typestate -> - final_typestates := (pname, final_typestate) :: !final_typestates - | _, None -> () in - List.iter ~f:get_final_typestate initializers_recursive; + | _, Some final_typestate + -> final_typestates := (pname, final_typestate) :: !final_typestates + | _, None + -> () + in + List.iter ~f:get_final_typestate initializers_recursive ; List.rev !final_typestates let pname_and_pdescs_with f = let res = ref [] in - let filter pname = match Specs.proc_resolve_attributes pname with - | Some proc_attributes -> f (pname, proc_attributes) - | None -> false in + let filter pname = + match Specs.proc_resolve_attributes pname with + | Some proc_attributes + -> f (pname, proc_attributes) + | None + -> false + in let do_proc pname = if filter pname then match get_proc_desc pname with - | Some pdesc -> - res := (pname, pdesc) :: !res - | None -> () in - List.iter ~f:do_proc (get_procs_in_file curr_pname); + | Some pdesc + -> res := (pname, pdesc) :: !res + | None + -> () + in + List.iter ~f:do_proc (get_procs_in_file curr_pname) ; List.rev !res - let get_class pn = match pn with - | Typ.Procname.Java pn_java -> - Some (Typ.Procname.java_get_class_name pn_java) - | _ -> - None + let get_class pn = + match pn with + | Typ.Procname.Java pn_java + -> Some (Typ.Procname.java_get_class_name pn_java) + | _ + -> None (** Typestates after the current procedure and all initializer procedures. *) - let final_initializer_typestates_lazy = lazy - begin - let is_initializer proc_attributes = - PatternMatch.method_is_initializer tenv proc_attributes || - let ia, _ = - (Models.get_modelled_annotated_signature proc_attributes).AnnotatedSignature.ret in - Annotations.ia_is_initializer ia in - let initializers_current_class = - pname_and_pdescs_with - (function (pname, proc_attributes) -> - is_initializer proc_attributes && - equal_class_opt (get_class pname) (get_class curr_pname)) in - final_typestates - ((curr_pname, curr_pdesc) :: initializers_current_class) - end + let final_initializer_typestates_lazy = + ( lazy + (let is_initializer proc_attributes = + PatternMatch.method_is_initializer tenv proc_attributes + || + let ia, _ = + (Models.get_modelled_annotated_signature proc_attributes).AnnotatedSignature.ret + in + Annotations.ia_is_initializer ia + in + let initializers_current_class = + pname_and_pdescs_with (function + | pname, proc_attributes + -> is_initializer proc_attributes + && equal_class_opt (get_class pname) (get_class curr_pname) ) + in + final_typestates ((curr_pname, curr_pdesc) :: initializers_current_class)) ) (** Typestates after all constructors. *) - let final_constructor_typestates_lazy = lazy - begin - let constructors_current_class = - pname_and_pdescs_with - (fun (pname, _) -> - Typ.Procname.is_constructor pname && - equal_class_opt (get_class pname) (get_class curr_pname)) in - final_typestates constructors_current_class - end - - end (* Initializers *) in - + let final_constructor_typestates_lazy = + ( lazy + (let constructors_current_class = + pname_and_pdescs_with (fun (pname, _) -> + Typ.Procname.is_constructor pname + && equal_class_opt (get_class pname) (get_class curr_pname) ) + in + final_typestates constructors_current_class) ) + end + (* Initializers *) in let do_final_typestate typestate_opt calls_this = let do_typestate typestate = let start_node = Procdesc.get_start_node curr_pdesc in - if not calls_this && (* if 'this(...)' is called, no need to check initialization *) - check_field_initialization && - checks.TypeCheck.eradicate - then begin - EradicateChecks.check_constructor_initialization tenv - find_canonical_duplicate - curr_pname - curr_pdesc - start_node - Initializers.final_initializer_typestates_lazy - Initializers.final_constructor_typestates_lazy - proc_loc - end; + if not calls_this + (* if 'this(...)' is called, no need to check initialization *) + && check_field_initialization && checks.TypeCheck.eradicate + then + EradicateChecks.check_constructor_initialization tenv find_canonical_duplicate curr_pname + curr_pdesc start_node Initializers.final_initializer_typestates_lazy + Initializers.final_constructor_typestates_lazy proc_loc ; if Config.eradicate_verbose then - L.result "Final Typestate@\n%a@." - (TypeState.pp Extension.ext) typestate in - match typestate_opt with - | None -> () - | Some typestate -> do_typestate typestate in - - TypeErr.reset (); - + L.result "Final Typestate@\n%a@." (TypeState.pp Extension.ext) typestate + in + match typestate_opt with None -> () | Some typestate -> do_typestate typestate + in + TypeErr.reset () ; let calls_this, final_typestate_opt = - typecheck_proc true curr_pname curr_pdesc (Some (annotated_signature, proc_loc, idenv)) in - do_final_typestate final_typestate_opt calls_this; + typecheck_proc true curr_pname curr_pdesc (Some (annotated_signature, proc_loc, idenv)) + in + do_final_typestate final_typestate_opt calls_this ; if checks.TypeCheck.eradicate then - EradicateChecks.check_overridden_annotations - find_canonical_duplicate - tenv curr_pname curr_pdesc - annotated_signature; - - TypeErr.report_forall_checks_and_reset tenv (Checkers.ST.report_error tenv) curr_pdesc; + EradicateChecks.check_overridden_annotations find_canonical_duplicate tenv curr_pname + curr_pdesc annotated_signature ; + TypeErr.report_forall_checks_and_reset tenv (Checkers.ST.report_error tenv) curr_pdesc ; update_summary curr_pname curr_pdesc final_typestate_opt (** Entry point for the eradicate-based checker infrastructure. *) - let callback checks ({ Callbacks.proc_desc; summary } as callback_args) : Specs.summary = + let callback checks ({Callbacks.proc_desc; summary} as callback_args) : Specs.summary = let proc_name = Procdesc.get_proc_name proc_desc in let calls_this = ref false in - let filter_special_cases () = - if Typ.Procname.java_is_access_method proc_name || - (Specs.pdesc_resolve_attributes proc_desc).ProcAttributes.is_bridge_method + if Typ.Procname.java_is_access_method proc_name + || (Specs.pdesc_resolve_attributes proc_desc).ProcAttributes.is_bridge_method then None else - begin - let annotated_signature = - Models.get_modelled_annotated_signature (Specs.pdesc_resolve_attributes proc_desc) in - Some annotated_signature - end in - begin - match filter_special_cases () with - | None -> () - | Some annotated_signature -> - let loc = Procdesc.get_loc proc_desc in - let linereader = Printer.LineReader.create () in - if Config.eradicate_verbose then - L.result "%a@." (AnnotatedSignature.pp proc_name) annotated_signature; - - callback2 - calls_this checks callback_args annotated_signature linereader loc - end; + let annotated_signature = + Models.get_modelled_annotated_signature (Specs.pdesc_resolve_attributes proc_desc) + in + Some annotated_signature + in + ( match filter_special_cases () with + | None + -> () + | Some annotated_signature + -> let loc = Procdesc.get_loc proc_desc in + let linereader = Printer.LineReader.create () in + if Config.eradicate_verbose then + L.result "%a@." (AnnotatedSignature.pp proc_name) annotated_signature ; + callback2 calls_this checks callback_args annotated_signature linereader loc ) ; summary +end -end (* MkCallback *) +(* MkCallback *) (** Given an extension to the typestate with a check, call the check on each instruction. *) -module Build - (Extension : ExtensionT) - : CallBackT = -struct - module Callback = MkCallback(Extension) +module Build (Extension : ExtensionT) : CallBackT = struct + module Callback = MkCallback (Extension) + let callback = Callback.callback -end (* Build *) +end + +(* Build *) -module EmptyExtension : ExtensionT = -struct +module EmptyExtension : ExtensionT = struct type extension = unit + let ext = let empty = () in let check_instr _ _ _ _ ext _ _ = ext in let join () () = () in let pp _ () = () in - { - TypeState.empty = empty; - check_instr = check_instr; - join = join; - pp = pp; - } - let update_payload typestate_opt payload = - { payload with - Specs.typestate = typestate_opt; } + {TypeState.empty= empty; check_instr; join; pp} + + let update_payload typestate_opt payload = {payload with Specs.typestate= typestate_opt} end -module Main = - Build(EmptyExtension) +module Main = Build (EmptyExtension) (** Eradicate checker for Java @Nullable annotations. *) let callback_eradicate = - let checks = - { - TypeCheck.eradicate = true; - check_extension = false; - check_ret_type = []; - } in + let checks = {TypeCheck.eradicate= true; check_extension= false; check_ret_type= []} in Main.callback checks (** Call the given check_return_type at the end of every procedure. *) let callback_check_return_type check_return_type callback_args = let checks = - { - TypeCheck.eradicate = false; - check_extension = false; - check_ret_type = [check_return_type]; - } in + {TypeCheck.eradicate= false; check_extension= false; check_ret_type= [check_return_type]} + in Main.callback checks callback_args diff --git a/infer/src/eradicate/eradicate.mli b/infer/src/eradicate/eradicate.mli index 6caef9937..1cf4683bb 100644 --- a/infer/src/eradicate/eradicate.mli +++ b/infer/src/eradicate/eradicate.mli @@ -15,22 +15,21 @@ val callback_eradicate : Callbacks.proc_callback_t val callback_check_return_type : TypeCheck.check_return_type -> Callbacks.proc_callback_t - (** Parameters of a call. *) type parameters = (Exp.t * Typ.t) list - (** Type for a module that provides a main callback function *) -module type CallBackT = -sig +module type CallBackT = sig val callback : TypeCheck.checks -> Callbacks.proc_callback_t -end (* CallBackT *) - +end +(* CallBackT *) (** Extension to the type checker. *) module type ExtensionT = sig type extension + val ext : extension TypeState.ext + val update_payload : extension TypeState.t option -> Specs.payload -> Specs.payload end diff --git a/infer/src/eradicate/eradicateChecks.ml b/infer/src/eradicate/eradicateChecks.ml index 8f7599fec..2975fe25e 100644 --- a/infer/src/eradicate/eradicateChecks.ml +++ b/infer/src/eradicate/eradicateChecks.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging (** Module for the checks called by Eradicate. *) @@ -19,28 +18,30 @@ let return_nonnull_silent = true (* if true, check calls to libraries (i.e. not modelled and source not available) *) let check_library_calls = false - let get_field_annotation tenv fn typ = let lookup = Tenv.lookup tenv in match Typ.Struct.get_field_type_and_annotation ~lookup fn typ with - | None -> None - | Some (t, ia) -> - let ia' = + | None + -> None + | Some (t, ia) + -> let ia' = (* TODO (t4968422) eliminate not !Config.eradicate check by marking fields as nullified *) (* outside of Eradicate in some other way *) if (Models.Inference.enabled || not Config.eradicate) - && Models.Inference.field_is_marked fn + && Models.Inference.field_is_marked fn then AnnotatedSignature.mk_ia AnnotatedSignature.Nullable ia - else ia in + else ia + in Some (t, ia') -let report_error tenv = - TypeErr.report_error tenv (Checkers.ST.report_error tenv) +let report_error tenv = TypeErr.report_error tenv (Checkers.ST.report_error tenv) let explain_expr tenv node e = match Errdesc.exp_rv_dexp tenv node e with - | Some de -> Some (DecompiledExp.to_string de) - | None -> None + | Some de + -> Some (DecompiledExp.to_string de) + | None + -> None (** Classify a procedure. *) let classify_procedure proc_attributes = @@ -51,523 +52,431 @@ let classify_procedure proc_attributes = else if Specs.proc_is_library proc_attributes then "L" (* library *) else if not proc_attributes.ProcAttributes.is_defined then "S" (* skip *) else if String.is_prefix ~prefix:"com.facebook" unique_id then "F" (* FB *) - else "?" in + else "?" + in classification - let is_virtual = function - | (p, _, _):: _ when String.equal (Mangled.to_string p) "this" -> true - | _ -> false - + | (p, _, _) :: _ when String.equal (Mangled.to_string p) "this" + -> true + | _ + -> false (** Check an access (read or write) to a field. *) -let check_field_access tenv - find_canonical_duplicate curr_pname node instr_ref exp fname ta loc : unit = +let check_field_access tenv find_canonical_duplicate curr_pname node instr_ref exp fname ta loc + : unit = if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then let origin_descr = TypeAnnotation.descr_origin tenv ta in - report_error tenv - find_canonical_duplicate + report_error tenv find_canonical_duplicate (TypeErr.Null_field_access (explain_expr tenv node exp, fname, origin_descr, false)) - (Some instr_ref) - loc curr_pname + (Some instr_ref) loc curr_pname (** Check an access to an array *) -let check_array_access tenv - find_canonical_duplicate - curr_pname - node - instr_ref - array_exp - fname - ta - loc - indexed = +let check_array_access tenv find_canonical_duplicate curr_pname node instr_ref array_exp fname ta + loc indexed = if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then let origin_descr = TypeAnnotation.descr_origin tenv ta in - report_error tenv - find_canonical_duplicate + report_error tenv find_canonical_duplicate (TypeErr.Null_field_access (explain_expr tenv node array_exp, fname, origin_descr, indexed)) - (Some instr_ref) - loc - curr_pname + (Some instr_ref) loc curr_pname (** Where the condition is coming from *) type from_call = - | From_condition (** Direct condition *) - | From_instanceof (** x instanceof C *) - | From_is_false_on_null (** returns false on null *) - | From_is_true_on_null (** returns true on null *) - | From_optional_isPresent (** x.isPresent *) - | From_containsKey (** x.containsKey *) -[@@ deriving compare] + | From_condition (** Direct condition *) + | From_instanceof (** x instanceof C *) + | From_is_false_on_null (** returns false on null *) + | From_is_true_on_null (** returns true on null *) + | From_optional_isPresent (** x.isPresent *) + | From_containsKey (** x.containsKey *) + [@@deriving compare] let equal_from_call = [%compare.equal : from_call] (** Check the normalized "is zero" or "is not zero" condition of a prune instruction. *) -let check_condition tenv case_zero find_canonical_duplicate curr_pdesc - node e typ ta true_branch from_call idenv linereader loc instr_ref : unit = - let is_fun_nonnull ta = match TypeAnnotation.get_origin ta with - | TypeOrigin.Proc proc_origin -> - let (ia, _) = proc_origin.TypeOrigin.annotated_signature.AnnotatedSignature.ret in +let check_condition tenv case_zero find_canonical_duplicate curr_pdesc node e typ ta true_branch + from_call idenv linereader loc instr_ref : unit = + let is_fun_nonnull ta = + match TypeAnnotation.get_origin ta with + | TypeOrigin.Proc proc_origin + -> let ia, _ = proc_origin.TypeOrigin.annotated_signature.AnnotatedSignature.ret in Annotations.ia_is_nonnull ia - | _ -> false in - + | _ + -> false + in let contains_instanceof_throwable pdesc node = (* Check if the current procedure has a catch Throwable. *) (* That always happens in the bytecode generated by try-with-resources. *) let loc = Procdesc.Node.get_loc node in let throwable_found = ref false in - let typ_is_throwable {Typ.desc} = match desc with - | Typ.Tstruct (Typ.JavaClass _ as name) -> - String.equal (Typ.Name.name name) "java.lang.Throwable" - | _ -> false in + let typ_is_throwable {Typ.desc} = + match desc with + | Typ.Tstruct (Typ.JavaClass _ as name) + -> String.equal (Typ.Name.name name) "java.lang.Throwable" + | _ + -> false + in let do_instr = function - | Sil.Call (_, Exp.Const (Const.Cfun pn), [_; (Exp.Sizeof {typ}, _)], _, _) when - Typ.Procname.equal pn BuiltinDecl.__instanceof && typ_is_throwable typ -> - throwable_found := true - | _ -> () in + | Sil.Call (_, Exp.Const Const.Cfun pn, [_; (Exp.Sizeof {typ}, _)], _, _) + when Typ.Procname.equal pn BuiltinDecl.__instanceof && typ_is_throwable typ + -> throwable_found := true + | _ + -> () + in let do_node n = - if Location.equal loc (Procdesc.Node.get_loc n) - then List.iter ~f:do_instr (Procdesc.Node.get_instrs n) in - Procdesc.iter_nodes do_node pdesc; - !throwable_found in - + if Location.equal loc (Procdesc.Node.get_loc n) then + List.iter ~f:do_instr (Procdesc.Node.get_instrs n) + in + Procdesc.iter_nodes do_node pdesc ; !throwable_found + in let from_try_with_resources () : bool = (* heuristic to check if the condition is the translation of try-with-resources *) match Printer.LineReader.from_loc linereader loc with - | Some line -> - not (String.is_substring ~substring:"==" line || String.is_substring ~substring:"!=" line) - && (String.is_substring ~substring:"}" line) - && contains_instanceof_throwable curr_pdesc node - | None -> false in - + | Some line + -> not (String.is_substring ~substring:"==" line || String.is_substring ~substring:"!=" line) + && String.is_substring ~substring:"}" line && contains_instanceof_throwable curr_pdesc node + | None + -> false + in let is_temp = Idenv.exp_is_temp idenv e in let nonnull = is_fun_nonnull ta in let should_report = - not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta) && - (Config.eradicate_condition_redundant || nonnull) && - true_branch && - (not is_temp || nonnull) && - PatternMatch.type_is_class typ && - not (from_try_with_resources ()) && - equal_from_call from_call From_condition && - not (TypeAnnotation.origin_is_fun_library ta) in + not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta) + && (Config.eradicate_condition_redundant || nonnull) && true_branch && (not is_temp || nonnull) + && PatternMatch.type_is_class typ && not (from_try_with_resources ()) + && equal_from_call from_call From_condition && not (TypeAnnotation.origin_is_fun_library ta) + in let is_always_true = not case_zero in let nonnull = is_fun_nonnull ta in if should_report then - report_error tenv - find_canonical_duplicate + report_error tenv find_canonical_duplicate (TypeErr.Condition_redundant (is_always_true, explain_expr tenv node e, nonnull)) - (Some instr_ref) - loc curr_pdesc + (Some instr_ref) loc curr_pdesc (** Check an "is zero" condition. *) -let check_zero tenv find_canonical_duplicate = - check_condition tenv true find_canonical_duplicate +let check_zero tenv find_canonical_duplicate = check_condition tenv true find_canonical_duplicate (** Check an "is not zero" condition. *) let check_nonzero tenv find_canonical_duplicate = check_condition tenv false find_canonical_duplicate (** Check an assignment to a field. *) -let check_field_assignment tenv - find_canonical_duplicate curr_pdesc node instr_ref typestate exp_lhs - exp_rhs typ loc fname t_ia_opt typecheck_expr : unit = +let check_field_assignment tenv find_canonical_duplicate curr_pdesc node instr_ref typestate + exp_lhs exp_rhs typ loc fname t_ia_opt typecheck_expr : unit = let curr_pname = Procdesc.get_proc_name curr_pdesc in - let (t_lhs, ta_lhs, _) = + let t_lhs, ta_lhs, _ = typecheck_expr node instr_ref curr_pdesc typestate exp_lhs - (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc in - let (_, ta_rhs, _) = + (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc + in + let _, ta_rhs, _ = typecheck_expr node instr_ref curr_pdesc typestate exp_rhs - (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc in + (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc + in let should_report_nullable = - let field_is_field_injector_readwrite () = match t_ia_opt with - | Some (_, ia) -> - Annotations.ia_is_field_injector_readwrite ia - | _ -> - false in - not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta_lhs) && - TypeAnnotation.get_value AnnotatedSignature.Nullable ta_rhs && - PatternMatch.type_is_class t_lhs && - not (Typ.Fieldname.java_is_outer_instance fname) && - not (field_is_field_injector_readwrite ()) in + let field_is_field_injector_readwrite () = + match t_ia_opt with + | Some (_, ia) + -> Annotations.ia_is_field_injector_readwrite ia + | _ + -> false + in + not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta_lhs) + && TypeAnnotation.get_value AnnotatedSignature.Nullable ta_rhs + && PatternMatch.type_is_class t_lhs && not (Typ.Fieldname.java_is_outer_instance fname) + && not (field_is_field_injector_readwrite ()) + in let should_report_absent = - Config.eradicate_optional_present && - TypeAnnotation.get_value AnnotatedSignature.Present ta_lhs && - not (TypeAnnotation.get_value AnnotatedSignature.Present ta_rhs) && - not (Typ.Fieldname.java_is_outer_instance fname) in + Config.eradicate_optional_present && TypeAnnotation.get_value AnnotatedSignature.Present ta_lhs + && not (TypeAnnotation.get_value AnnotatedSignature.Present ta_rhs) + && not (Typ.Fieldname.java_is_outer_instance fname) + in let should_report_mutable = - let field_is_mutable () = match t_ia_opt with - | Some (_, ia) -> Annotations.ia_is_mutable ia - | _ -> false in - Config.eradicate_field_not_mutable && - not (Typ.Procname.is_constructor curr_pname) && - not (Typ.Procname.is_class_initializer curr_pname) && - not (field_is_mutable ()) in - if should_report_nullable || should_report_absent then - begin + let field_is_mutable () = + match t_ia_opt with Some (_, ia) -> Annotations.ia_is_mutable ia | _ -> false + in + Config.eradicate_field_not_mutable && not (Typ.Procname.is_constructor curr_pname) + && not (Typ.Procname.is_class_initializer curr_pname) && not (field_is_mutable ()) + in + ( if should_report_nullable || should_report_absent then let ann = - if should_report_nullable - then AnnotatedSignature.Nullable - else AnnotatedSignature.Present in - if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fname; + if should_report_nullable then AnnotatedSignature.Nullable else AnnotatedSignature.Present + in + if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fname ; let origin_descr = TypeAnnotation.descr_origin tenv ta_rhs in - report_error tenv - find_canonical_duplicate - (TypeErr.Field_annotation_inconsistent (ann, fname, origin_descr)) - (Some instr_ref) - loc curr_pdesc - end; + report_error tenv find_canonical_duplicate + (TypeErr.Field_annotation_inconsistent (ann, fname, origin_descr)) (Some instr_ref) loc + curr_pdesc ) ; if should_report_mutable then - begin - let origin_descr = TypeAnnotation.descr_origin tenv ta_rhs in - report_error tenv - find_canonical_duplicate - (TypeErr.Field_not_mutable (fname, origin_descr)) - (Some instr_ref) - loc curr_pdesc - end - + let origin_descr = TypeAnnotation.descr_origin tenv ta_rhs in + report_error tenv find_canonical_duplicate (TypeErr.Field_not_mutable (fname, origin_descr)) + (Some instr_ref) loc curr_pdesc (** Check that nonnullable fields are initialized in constructors. *) -let check_constructor_initialization tenv - find_canonical_duplicate - curr_pname - curr_pdesc - start_node - final_initializer_typestates - final_constructor_typestates - loc: unit = - State.set_node start_node; - if Typ.Procname.is_constructor curr_pname - then begin +let check_constructor_initialization tenv find_canonical_duplicate curr_pname curr_pdesc start_node + final_initializer_typestates final_constructor_typestates loc : unit = + State.set_node start_node ; + if Typ.Procname.is_constructor curr_pname then match PatternMatch.get_this_type (Procdesc.get_attributes curr_pdesc) with - | Some ({desc=Tptr ({desc=Tstruct name} as ts, _)}) -> ( - match Tenv.lookup tenv name with - | Some { fields } -> - let do_field (fn, ft, _) = - let annotated_with f = match get_field_annotation tenv fn ts with - | None -> false - | Some (_, ia) -> f ia in - let nullable_annotated = annotated_with Annotations.ia_is_nullable in - let nonnull_annotated = annotated_with Annotations.ia_is_nonnull in - let injector_readonly_annotated = - annotated_with Annotations.ia_is_field_injector_readonly in - - let final_type_annotation_with unknown list f = - let filter_range_opt = function - | Some (_, ta, _) -> f ta - | None -> unknown in - List.exists - ~f:(function pname, typestate -> - let pvar = Pvar.mk - (Mangled.from_string (Typ.Fieldname.to_string fn)) - pname in - filter_range_opt (TypeState.lookup_pvar pvar typestate)) - list in - - let may_be_assigned_in_final_typestate = - let origin_is_initialized = function - | TypeOrigin.Undef -> - false - | TypeOrigin.Field (TypeOrigin.Formal name, _, _) -> - let circular = String.equal (Mangled.to_string name) "this" in - not circular - | _ -> - true in - final_type_annotation_with - false - (Lazy.force final_initializer_typestates) - (fun ta -> origin_is_initialized (TypeAnnotation.get_origin ta)) in - - let may_be_nullable_in_final_typestate () = - final_type_annotation_with - true - (Lazy.force final_constructor_typestates) - (fun ta -> TypeAnnotation.get_value AnnotatedSignature.Nullable ta) in - - let should_check_field_initialization = - let in_current_class = - let fld_cname = Typ.Fieldname.java_get_class fn in - String.equal (Typ.Name.name name) fld_cname in - not injector_readonly_annotated && - PatternMatch.type_is_class ft && - in_current_class && - not (Typ.Fieldname.java_is_outer_instance fn) in - - if should_check_field_initialization then ( - if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fn; - - (* Check if field is missing annotation. *) - if not (nullable_annotated || nonnull_annotated) && - not may_be_assigned_in_final_typestate then - report_error tenv - find_canonical_duplicate - (TypeErr.Field_not_initialized (fn, curr_pname)) - None - loc - curr_pdesc; - - (* Check if field is over-annotated. *) - if Config.eradicate_field_over_annotated && - nullable_annotated && - not (may_be_nullable_in_final_typestate ()) then - report_error tenv - find_canonical_duplicate - (TypeErr.Field_over_annotated (fn, curr_pname)) - None - loc - curr_pdesc; - ) in - - List.iter ~f:do_field fields - | None -> - () - ) - | _ -> () - end + | Some {desc= Tptr (({desc= Tstruct name} as ts), _)} -> ( + match Tenv.lookup tenv name with + | Some {fields} + -> let do_field (fn, ft, _) = + let annotated_with f = + match get_field_annotation tenv fn ts with None -> false | Some (_, ia) -> f ia + in + let nullable_annotated = annotated_with Annotations.ia_is_nullable in + let nonnull_annotated = annotated_with Annotations.ia_is_nonnull in + let injector_readonly_annotated = + annotated_with Annotations.ia_is_field_injector_readonly + in + let final_type_annotation_with unknown list f = + let filter_range_opt = function Some (_, ta, _) -> f ta | None -> unknown in + List.exists + ~f:(function + | pname, typestate + -> let pvar = + Pvar.mk (Mangled.from_string (Typ.Fieldname.to_string fn)) pname + in + filter_range_opt (TypeState.lookup_pvar pvar typestate)) + list + in + let may_be_assigned_in_final_typestate = + let origin_is_initialized = function + | TypeOrigin.Undef + -> false + | TypeOrigin.Field (TypeOrigin.Formal name, _, _) + -> let circular = String.equal (Mangled.to_string name) "this" in + not circular + | _ + -> true + in + final_type_annotation_with false (Lazy.force final_initializer_typestates) (fun ta -> + origin_is_initialized (TypeAnnotation.get_origin ta) ) + in + let may_be_nullable_in_final_typestate () = + final_type_annotation_with true (Lazy.force final_constructor_typestates) (fun ta -> + TypeAnnotation.get_value AnnotatedSignature.Nullable ta ) + in + let should_check_field_initialization = + let in_current_class = + let fld_cname = Typ.Fieldname.java_get_class fn in + String.equal (Typ.Name.name name) fld_cname + in + not injector_readonly_annotated && PatternMatch.type_is_class ft && in_current_class + && not (Typ.Fieldname.java_is_outer_instance fn) + in + if should_check_field_initialization then ( + if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fn ; + (* Check if field is missing annotation. *) + if not (nullable_annotated || nonnull_annotated) + && not may_be_assigned_in_final_typestate + then + report_error tenv find_canonical_duplicate + (TypeErr.Field_not_initialized (fn, curr_pname)) None loc curr_pdesc ; + (* Check if field is over-annotated. *) + if Config.eradicate_field_over_annotated && nullable_annotated + && not (may_be_nullable_in_final_typestate ()) + then + report_error tenv find_canonical_duplicate + (TypeErr.Field_over_annotated (fn, curr_pname)) None loc curr_pdesc ) + in + List.iter ~f:do_field fields + | None + -> () ) + | _ + -> () (** Make the return type @Nullable by modifying the spec. *) let spec_make_return_nullable curr_pname = match Specs.get_summary curr_pname with - | Some summary -> - let proc_attributes = Specs.get_attributes summary in + | Some summary + -> let proc_attributes = Specs.get_attributes summary in let method_annotation = proc_attributes.ProcAttributes.method_annotation in - let method_annotation' = AnnotatedSignature.method_annotation_mark_return - AnnotatedSignature.Nullable method_annotation in + let method_annotation' = + AnnotatedSignature.method_annotation_mark_return AnnotatedSignature.Nullable + method_annotation + in let proc_attributes' = - { proc_attributes with - ProcAttributes.method_annotation = method_annotation' } in - let summary' = - { summary with - Specs.attributes = proc_attributes' } in + {proc_attributes with ProcAttributes.method_annotation= method_annotation'} + in + let summary' = {summary with Specs.attributes= proc_attributes'} in Specs.add_summary curr_pname summary' - | None -> () + | None + -> () (** Check the annotations when returning from a method. *) -let check_return_annotation tenv - find_canonical_duplicate curr_pdesc ret_range - (annotated_signature : AnnotatedSignature.t) ret_implicitly_nullable loc : unit = +let check_return_annotation tenv find_canonical_duplicate curr_pdesc ret_range + (annotated_signature: AnnotatedSignature.t) ret_implicitly_nullable loc : unit = let ret_ia, _ = annotated_signature.ret in let curr_pname = Procdesc.get_proc_name curr_pdesc in let ret_annotated_nullable = - Annotations.ia_is_nullable ret_ia || - List.exists - ~f:(fun (_, ia, _) -> Annotations.ia_is_propagates_nullable ia) - annotated_signature.params in - let ret_annotated_present = - Annotations.ia_is_present ret_ia in - let ret_annotated_nonnull = - Annotations.ia_is_nonnull ret_ia in + Annotations.ia_is_nullable ret_ia + || List.exists + ~f:(fun (_, ia, _) -> Annotations.ia_is_propagates_nullable ia) + annotated_signature.params + in + let ret_annotated_present = Annotations.ia_is_present ret_ia in + let ret_annotated_nonnull = Annotations.ia_is_nonnull ret_ia in match ret_range with (* Disables the warnings since it is not clear how to annotate the return value of lambdas *) - | Some _ when Typ.Procname.java_is_lambda curr_pname -> () - | Some (_, final_ta, _) -> - let final_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable final_ta in + | Some _ + when Typ.Procname.java_is_lambda curr_pname + -> () + | Some (_, final_ta, _) + -> let final_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable final_ta in let final_present = TypeAnnotation.get_value AnnotatedSignature.Present final_ta in let origin_descr = TypeAnnotation.descr_origin tenv final_ta in let return_not_nullable = - final_nullable && - not ret_annotated_nullable && - not ret_implicitly_nullable && - not (return_nonnull_silent && ret_annotated_nonnull) in + final_nullable && not ret_annotated_nullable && not ret_implicitly_nullable + && not (return_nonnull_silent && ret_annotated_nonnull) + in let return_value_not_present = - Config.eradicate_optional_present && - not final_present && - ret_annotated_present in + Config.eradicate_optional_present && not final_present && ret_annotated_present + in let return_over_annotated = - not final_nullable && - ret_annotated_nullable && - Config.eradicate_return_over_annotated in - + not final_nullable && ret_annotated_nullable && Config.eradicate_return_over_annotated + in if return_not_nullable && Models.Inference.enabled then - Models.Inference.proc_mark_return_nullable curr_pname; - - if return_not_nullable && - Config.eradicate_propagate_return_nullable - then - spec_make_return_nullable curr_pname; - - if return_not_nullable || return_value_not_present then - begin + Models.Inference.proc_mark_return_nullable curr_pname ; + if return_not_nullable && Config.eradicate_propagate_return_nullable then + spec_make_return_nullable curr_pname ; + ( if return_not_nullable || return_value_not_present then let ann = - if return_not_nullable - then AnnotatedSignature.Nullable - else AnnotatedSignature.Present in - report_error tenv - find_canonical_duplicate - (TypeErr.Return_annotation_inconsistent (ann, curr_pname, origin_descr)) - None - loc curr_pdesc - end; - + if return_not_nullable then AnnotatedSignature.Nullable else AnnotatedSignature.Present + in + report_error tenv find_canonical_duplicate + (TypeErr.Return_annotation_inconsistent (ann, curr_pname, origin_descr)) None loc + curr_pdesc ) ; if return_over_annotated then - begin - report_error tenv - find_canonical_duplicate - (TypeErr.Return_over_annotated curr_pname) - None - loc curr_pdesc - end - | None -> - () + report_error tenv find_canonical_duplicate (TypeErr.Return_over_annotated curr_pname) None + loc curr_pdesc + | None + -> () (** Check the receiver of a virtual call. *) -let check_call_receiver tenv - find_canonical_duplicate - curr_pdesc - node - typestate - call_params - callee_pname - (instr_ref : TypeErr.InstrRef.t) - loc - typecheck_expr - : unit = +let check_call_receiver tenv find_canonical_duplicate curr_pdesc node typestate call_params + callee_pname (instr_ref: TypeErr.InstrRef.t) loc typecheck_expr : unit = match call_params with - | ((original_this_e, this_e), typ) :: _ -> - let (_, this_ta, _) = + | ((original_this_e, this_e), typ) :: _ + -> let _, this_ta, _ = typecheck_expr tenv node instr_ref curr_pdesc typestate this_e - (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, []) loc in + (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, []) loc + in let null_method_call = TypeAnnotation.get_value AnnotatedSignature.Nullable this_ta in let optional_get_on_absent = - Config.eradicate_optional_present && - Models.is_optional_get callee_pname && - not (TypeAnnotation.get_value AnnotatedSignature.Present this_ta) in + Config.eradicate_optional_present && Models.is_optional_get callee_pname + && not (TypeAnnotation.get_value AnnotatedSignature.Present this_ta) + in if null_method_call || optional_get_on_absent then - begin - let ann = - if null_method_call - then AnnotatedSignature.Nullable - else AnnotatedSignature.Present in - let descr = explain_expr tenv node original_this_e in - let origin_descr = TypeAnnotation.descr_origin tenv this_ta in - report_error tenv - find_canonical_duplicate - (TypeErr.Call_receiver_annotation_inconsistent - (ann, descr, callee_pname, origin_descr)) - (Some instr_ref) - loc curr_pdesc - end - | [] -> () - -type resolved_param = { - num : int; - formal : Mangled.t * TypeAnnotation.t * Typ.t; - actual : Exp.t * TypeAnnotation.t; - propagates_nullable : bool; -} + let ann = + if null_method_call then AnnotatedSignature.Nullable else AnnotatedSignature.Present + in + let descr = explain_expr tenv node original_this_e in + let origin_descr = TypeAnnotation.descr_origin tenv this_ta in + report_error tenv find_canonical_duplicate + (TypeErr.Call_receiver_annotation_inconsistent (ann, descr, callee_pname, origin_descr)) + (Some instr_ref) loc curr_pdesc + | [] + -> () + +type resolved_param = + { num: int + ; formal: Mangled.t * TypeAnnotation.t * Typ.t + ; actual: Exp.t * TypeAnnotation.t + ; propagates_nullable: bool } (** Check the parameters of a call. *) -let check_call_parameters tenv - find_canonical_duplicate curr_pdesc node callee_attributes +let check_call_parameters tenv find_canonical_duplicate curr_pdesc node callee_attributes resolved_params loc instr_ref : unit = let callee_pname = callee_attributes.ProcAttributes.proc_name in - let tot_param_num = List.length resolved_params in - - let check - {num = param_num; formal = (s1, ta1, t1); actual = (orig_e2, ta2)} = + let check {num= param_num; formal= s1, ta1, t1; actual= orig_e2, ta2} = let report ann = let description = match explain_expr tenv node orig_e2 with - | Some descr -> descr - | None -> "formal parameter " ^ (Mangled.to_string s1) in + | Some descr + -> descr + | None + -> "formal parameter " ^ Mangled.to_string s1 + in let origin_descr = TypeAnnotation.descr_origin tenv ta2 in - let callee_loc = callee_attributes.ProcAttributes.loc in - report_error tenv - find_canonical_duplicate - (TypeErr.Parameter_annotation_inconsistent ( - ann, - description, - param_num, - callee_pname, - callee_loc, - origin_descr)) - (Some instr_ref) - loc curr_pdesc in - + report_error tenv find_canonical_duplicate + (TypeErr.Parameter_annotation_inconsistent + (ann, description, param_num, callee_pname, callee_loc, origin_descr)) (Some instr_ref) + loc curr_pdesc + in let check_ann ann = let b1 = TypeAnnotation.get_value ann ta1 in let b2 = TypeAnnotation.get_value ann ta2 in - match ann, b1, b2 with - | AnnotatedSignature.Nullable, false, true -> - report ann; + match (ann, b1, b2) with + | AnnotatedSignature.Nullable, false, true + -> report ann ; if Models.Inference.enabled then Models.Inference.proc_add_parameter_nullable callee_pname param_num tot_param_num - | AnnotatedSignature.Present, true, false -> - report ann - | _ -> - () in - - if PatternMatch.type_is_class t1 - then begin - check_ann AnnotatedSignature.Nullable; - if Config.eradicate_optional_present - then check_ann AnnotatedSignature.Present; - end in + | AnnotatedSignature.Present, true, false + -> report ann + | _ + -> () + in + if PatternMatch.type_is_class t1 then ( + check_ann AnnotatedSignature.Nullable ; + if Config.eradicate_optional_present then check_ann AnnotatedSignature.Present ) + in let should_check_parameters = if check_library_calls then true - else - Models.is_modelled_nullable callee_pname || - callee_attributes.ProcAttributes.is_defined || - Specs.get_summary callee_pname <> None in - + else Models.is_modelled_nullable callee_pname || callee_attributes.ProcAttributes.is_defined + || Specs.get_summary callee_pname <> None + in if should_check_parameters then (* left to right to avoid guessing the different lengths *) List.iter ~f:check resolved_params (** Checks if the annotations are consistent with the inherited class or with the implemented interfaces *) -let check_overridden_annotations - find_canonical_duplicate tenv proc_name proc_desc annotated_signature = +let check_overridden_annotations find_canonical_duplicate tenv proc_name proc_desc + annotated_signature = let start_node = Procdesc.get_start_node proc_desc in let loc = Procdesc.Node.get_loc start_node in - let check_return overriden_proc_name overriden_signature = let ret_is_nullable = let ia, _ = annotated_signature.AnnotatedSignature.ret in Annotations.ia_is_nullable ia and ret_overridden_nullable = let overriden_ia, _ = overriden_signature.AnnotatedSignature.ret in - Annotations.ia_is_nullable overriden_ia in + Annotations.ia_is_nullable overriden_ia + in if ret_is_nullable && not ret_overridden_nullable then - report_error tenv - find_canonical_duplicate - (TypeErr.Inconsistent_subclass_return_annotation (proc_name, overriden_proc_name)) - None - loc proc_desc - + report_error tenv find_canonical_duplicate + (TypeErr.Inconsistent_subclass_return_annotation (proc_name, overriden_proc_name)) None loc + proc_desc and check_params overriden_proc_name overriden_signature = let compare pos current_param overriden_param : int = let current_name, current_ia, _ = current_param in let _, overriden_ia, _ = overriden_param in let () = - if not (Annotations.ia_is_nullable current_ia) - && Annotations.ia_is_nullable overriden_ia then - report_error tenv - find_canonical_duplicate + if not (Annotations.ia_is_nullable current_ia) && Annotations.ia_is_nullable overriden_ia + then + report_error tenv find_canonical_duplicate (TypeErr.Inconsistent_subclass_parameter_annotation - (Mangled.to_string current_name, pos, proc_name, overriden_proc_name)) - None - loc proc_desc in - (pos + 1) in - + (Mangled.to_string current_name, pos, proc_name, overriden_proc_name)) None loc + proc_desc + in + pos + 1 + in (* TODO (#5280249): investigate why argument lists can be of different length *) let current_params = annotated_signature.AnnotatedSignature.params and overridden_params = overriden_signature.AnnotatedSignature.params in let initial_pos = if is_virtual current_params then 0 else 1 in if Int.equal (List.length current_params) (List.length overridden_params) then - ignore (List.fold2_exn ~f:compare ~init:initial_pos current_params overridden_params) in - + ignore (List.fold2_exn ~f:compare ~init:initial_pos current_params overridden_params) + in let check overriden_proc_name = match Specs.proc_resolve_attributes overriden_proc_name with - | Some attributes -> - let overridden_signature = Models.get_modelled_annotated_signature attributes in - check_return overriden_proc_name overridden_signature; + | Some attributes + -> let overridden_signature = Models.get_modelled_annotated_signature attributes in + check_return overriden_proc_name overridden_signature ; check_params overriden_proc_name overridden_signature - | None -> - () in - + | None + -> () + in PatternMatch.override_iter check tenv proc_name diff --git a/infer/src/eradicate/modelTables.ml b/infer/src/eradicate/modelTables.ml index ac64bad4c..dc4735192 100644 --- a/infer/src/eradicate/modelTables.ml +++ b/infer/src/eradicate/modelTables.ml @@ -14,241 +14,431 @@ module Hashtbl = Caml.Hashtbl * This file is a big bunch of tables; they read better with really long lines. * @nolint *) - (* in strict mode cannot insert null in containers *) let strict_containers = false (* in strict mode, give an error if a nullable is passed to checkNotNull *) let check_not_null_strict = false -let o = false and n = true (* o is not annotated and n is annotated with @Nullable *) -let o1 = (o, [o]) (* not annotated with one argument *) -let o2 = (o, [o; o]) (* not annotated with two arguments *) -let o3 = (o, [o; o; o;]) (* not annotated with three arguments *) -let n1 = (o, [n]) (* one argument nullable *) -let n2 = (o, [n; n]) (* two arguments nullable *) -let n3 = (o, [n; n; n]) (* three arguments nullable *) -let on = (o, [o; n]) (* the second argument is nullable *) -let ca = if strict_containers then (o, [o]) else (o, [n]) (* container add *) -let cg = if strict_containers then (n, [o]) else (n, [n]) (* container get *) -let cp = if strict_containers then (n, [o; o]) else (n, [n; n]) (* container put *) -let ng = (n, []) (* Nullable getter *) +let o = false + +and n = true + +(* o is not annotated and n is annotated with @Nullable *) +let o1 = (o, [o]) + +(* not annotated with one argument *) +let o2 = (o, [o; o]) + +(* not annotated with two arguments *) +let o3 = (o, [o; o; o]) + +(* not annotated with three arguments *) +let n1 = (o, [n]) + +(* one argument nullable *) +let n2 = (o, [n; n]) + +(* two arguments nullable *) +let n3 = (o, [n; n; n]) + +(* three arguments nullable *) +let on = (o, [o; n]) + +(* the second argument is nullable *) +let ca = if strict_containers then (o, [o]) else (o, [n]) + +(* container add *) +let cg = if strict_containers then (n, [o]) else (n, [n]) + +(* container get *) +let cp = if strict_containers then (n, [o; o]) else (n, [n; n]) + +(* container put *) +let ng = (n, []) + +(* Nullable getter *) let check_not_null_parameter_list, check_not_null_list = let x = if check_not_null_strict then o else n in let list = - [ - 1, (o, [x; n]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object"; - 1, (o, [x; n; n]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object"; - 1, (o, [x]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object):java.lang.Object"; - 1, (o, [x; n]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object"; - 1, (o, [x; n; n]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object"; - 1, (o, [x]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object):java.lang.Object"; - 1, (o, [x]), "org.junit.Assert.assertNotNull(java.lang.Object):void"; - 2, (o, [n; x]), "org.junit.Assert.assertNotNull(java.lang.String,java.lang.Object):void"; - 1, (o, [n]), "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object):java.lang.Object"; - 1, (o, [n; o]), "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object,java.lang.String):java.lang.Object"; - 1, (o, [n]), "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object):java.lang.Object"; - 1, (o, [n; o]), "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object,java.lang.String):java.lang.Object"; - ] in - List.map ~f:(fun (x, _, z) -> (x, z)) list, List.map ~f:(fun (_, y, z) -> (y, z)) list + [ ( 1 + , (o, [x; n]) + , "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object" + ) + ; ( 1 + , (o, [x; n; n]) + , "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object" + ) + ; ( 1 + , (o, [x]) + , "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object):java.lang.Object" + ) + ; ( 1 + , (o, [x; n]) + , "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object" + ) + ; ( 1 + , (o, [x; n; n]) + , "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object" + ) + ; ( 1 + , (o, [x]) + , "com.google.common.base.Preconditions.checkNotNull(java.lang.Object):java.lang.Object" ) + ; (1, (o, [x]), "org.junit.Assert.assertNotNull(java.lang.Object):void") + ; (2, (o, [n; x]), "org.junit.Assert.assertNotNull(java.lang.String,java.lang.Object):void") + ; ( 1 + , (o, [n]) + , "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object):java.lang.Object" + ) + ; ( 1 + , (o, [n; o]) + , "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object,java.lang.String):java.lang.Object" + ) + ; ( 1 + , (o, [n]) + , "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object):java.lang.Object" + ) + ; ( 1 + , (o, [n; o]) + , "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object,java.lang.String):java.lang.Object" + ) ] + in + (List.map ~f:(fun (x, _, z) -> (x, z)) list, List.map ~f:(fun (_, y, z) -> (y, z)) list) let check_state_list = - [ - (o, [n]), "Preconditions.checkState(boolean):void"; - (o, [n]), "com.facebook.common.internal.Preconditions.checkState(boolean):void"; - (o, [n; n]), "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.Object):void"; - (o, [n; n; n]), "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void"; - (o, [n]), "com.google.common.base.Preconditions.checkState(boolean):void"; - (o, [n; n]), "com.google.common.base.Preconditions.checkState(boolean,java.lang.Object):void"; - (o, [n; n; n]), "com.google.common.base.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void"; - (o, [n]), "com.facebook.infer.annotation.Assertions.assertCondition(boolean):void"; - (o, [n; o]), "com.facebook.infer.annotation.Assertions.assertCondition(boolean,java.lang.String):void"; - (o, [n]), "com.facebook.infer.annotation.Assertions.assumeCondition(boolean):void"; - (o, [n; o]), "com.facebook.infer.annotation.Assertions.assumeCondition(boolean,java.lang.String):void"; - ] + [ ((o, [n]), "Preconditions.checkState(boolean):void") + ; ((o, [n]), "com.facebook.common.internal.Preconditions.checkState(boolean):void") + ; ( (o, [n; n]) + , "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.Object):void" ) + ; ( (o, [n; n; n]) + , "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void" + ) + ; ((o, [n]), "com.google.common.base.Preconditions.checkState(boolean):void") + ; ((o, [n; n]), "com.google.common.base.Preconditions.checkState(boolean,java.lang.Object):void") + ; ( (o, [n; n; n]) + , "com.google.common.base.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void" + ) + ; ((o, [n]), "com.facebook.infer.annotation.Assertions.assertCondition(boolean):void") + ; ( (o, [n; o]) + , "com.facebook.infer.annotation.Assertions.assertCondition(boolean,java.lang.String):void" ) + ; ((o, [n]), "com.facebook.infer.annotation.Assertions.assumeCondition(boolean):void") + ; ( (o, [n; o]) + , "com.facebook.infer.annotation.Assertions.assumeCondition(boolean,java.lang.String):void" ) ] let check_argument_list = - [ - (o, [n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean):void"; - (o, [n; n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.Object):void"; - (o, [n; n; n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void"; - (o, [n]), "com.google.common.base.Preconditions.checkArgument(boolean):void"; - (o, [n; n]), "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.Object):void"; - (o, [n; n; n]), "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void"; - ] + [ ((o, [n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean):void") + ; ( (o, [n; n]) + , "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.Object):void" ) + ; ( (o, [n; n; n]) + , "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void" + ) + ; ((o, [n]), "com.google.common.base.Preconditions.checkArgument(boolean):void") + ; ( (o, [n; n]) + , "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.Object):void" ) + ; ( (o, [n; n; n]) + , "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void" + ) ] let optional_get_list : ((_ * bool list) * _) list = - [ - (o, []), "Optional.get():java.lang.Object"; - (o, []), "com.google.common.base.Optional.get():java.lang.Object"; - ] + [ ((o, []), "Optional.get():java.lang.Object") + ; ((o, []), "com.google.common.base.Optional.get():java.lang.Object") ] let optional_isPresent_list : ((_ * bool list) * _) list = - [ - (o, []), "Optional.isPresent():boolean"; - (o, []), "com.google.common.base.Optional.isPresent():boolean"; - ] + [ ((o, []), "Optional.isPresent():boolean") + ; ((o, []), "com.google.common.base.Optional.isPresent():boolean") ] (** Models for boolean functions that return true on null. *) let true_on_null_list : ((_ * bool list) * _) list = - [ - n1, "android.text.TextUtils.isEmpty(java.lang.CharSequence):boolean"; - ] - + [(n1, "android.text.TextUtils.isEmpty(java.lang.CharSequence):boolean")] (** Models for Map.containsKey *) let containsKey_list = - [ - n1, "com.google.common.collect.ImmutableMap.containsKey(java.lang.Object):boolean"; - n1, "java.util.Map.containsKey(java.lang.Object):boolean"; - ] + [ (n1, "com.google.common.collect.ImmutableMap.containsKey(java.lang.Object):boolean") + ; (n1, "java.util.Map.containsKey(java.lang.Object):boolean") ] (** Models for Map.put *) let mapPut_list = - [ - cp, "com.google.common.collect.ImmutableMap.put(java.lang.Object,java.lang.Object):java.lang.Object"; - cp, "java.util.Map.put(java.lang.Object,java.lang.Object):java.lang.Object"; - ] + [ ( cp + , "com.google.common.collect.ImmutableMap.put(java.lang.Object,java.lang.Object):java.lang.Object" + ) + ; (cp, "java.util.Map.put(java.lang.Object,java.lang.Object):java.lang.Object") ] (** Models for @Nullable annotations *) let annotated_list_nullable = - check_not_null_list @ check_state_list @ check_argument_list @ - [ - n1, "android.os.Parcel.writeList(java.util.List):void"; - n2, "android.os.Parcel.writeParcelable(android.os.Parcelable,int):void"; - n1, "android.os.Parcel.writeString(java.lang.String):void"; - (o, [o; o; n; n; n]), "com.android.sdklib.build.ApkBuilder.(java.io.File,java.io.File,java.io.File,java.lang.String,java.io.PrintStream)"; - (o, [n]), "com.android.manifmerger.ManifestMerger.xmlFileAndLine(org.w3c.dom.Node):com.android.manifmerger.IMergerLog$FileAndLine"; - on, "com.android.util.CommandLineParser$Mode.process(com.android.util.CommandLineParser$Arg,java.lang.String):java.lang.Object"; - on, "com.google.common.base.Objects$ToStringHelper.add(java.lang.String,java.lang.Object):com.google.common.base.Objects$ToStringHelper"; - n2, "com.google.common.base.Objects.equal(java.lang.Object,java.lang.Object):boolean"; - n1, "com.google.common.base.Optional.fromNullable(java.lang.Object):com.google.common.base.Optional"; - (n, []), "com.google.common.base.Optional.orNull():java.lang.Object"; - n1, "com.google.common.base.Strings.nullToEmpty(java.lang.String):java.lang.String"; - cg, "com.google.common.collect.ImmutableMap.get(java.lang.Object):java.lang.Object"; (* container get *) - o1, "com.google.common.collect.ImmutableList$Builder.add(java.lang.Object):com.google.common.collect.ImmutableList$Builder"; - o1, "com.google.common.collect.ImmutableList$Builder.addAll(java.lang.Iterable):com.google.common.collect.ImmutableList$Builder"; - o1, "com.google.common.collect.ImmutableSortedSet$Builder.add(java.lang.Object):com.google.common.collect.ImmutableSortedSet$Builder"; - on, "com.google.common.collect.Iterables.getFirst(java.lang.Iterable,java.lang.Object):java.lang.Object"; - o1, "com.google.common.util.concurrent.SettableFuture.setException(java.lang.Throwable):boolean"; - o1, "java.io.File.(java.lang.String)"; - n1, "java.io.PrintStream.print(java.lang.String):void"; - (n, [o]), "java.lang.Class.getResource(java.lang.String):java.net.URL"; - o1, "java.lang.Class.isAssignableFrom(java.lang.Class):boolean"; - n1, "java.lang.Integer.equals(java.lang.Object):boolean"; - n2, "java.lang.RuntimeException.(java.lang.String,java.lang.Throwable)"; - n1, "java.lang.String.equals(java.lang.Object):boolean"; - n1, "java.lang.StringBuilder.append(java.lang.String):java.lang.StringBuilder"; - (n, [o]), "java.lang.System.getProperty(java.lang.String):java.lang.String"; - (n, [o]), "java.lang.System.getenv(java.lang.String):java.lang.String"; - on, "java.net.URLClassLoader.newInstance(java.net.URL[],java.lang.ClassLoader):java.net.URLClassLoader"; - n1, "java.util.AbstractList.equals(java.lang.Object):boolean"; - ca, "java.util.ArrayList.add(java.lang.Object):boolean"; (* container add *) - ca, "java.util.List.add(java.lang.Object):boolean"; (* container add *) - cg, "java.util.Map.get(java.lang.Object):java.lang.Object"; (* container get *) - cp, "java.util.Map.put(java.lang.Object,java.lang.Object):java.lang.Object"; (* container put *) - (n, [o]), "javax.lang.model.element.Element.getAnnotation(java.lang.Class):java.lang.annotation.Annotation"; - ng, "javax.lang.model.element.Element.getEnclosingElement():javax.lang.model.element.Element"; - ng, "javax.lang.model.element.ExecutableElement.getDefaultValue():javax.lang.model.element.AnnotationValue"; - ng, "javax.lang.model.element.PackageElement.getEnclosingElement():javax.lang.model.element.Element"; - ng, "javax.lang.model.element.VariableElement.getConstantValue():java.lang.Object"; - ng, "javax.lang.model.type.WildcardType.getSuperBound():javax.lang.model.type.TypeMirror"; - (n, [o]), "javax.lang.model.util.Elements.getPackageElement(java.lang.CharSequence):javax.lang.model.element.PackageElement"; - (n, [o]), "javax.lang.model.util.Elements.getTypeElement(java.lang.CharSequence):javax.lang.model.element.TypeElement"; - (n, [o]), "javax.lang.model.util.Elements.getDocComment(javax.lang.model.element.Element):java.lang.String"; - o1, "javax.lang.model.util.Elements.getElementValuesWithDefaults(javax.lang.model.element.AnnotationMirror):java.util.Map"; - o1, "javax.lang.model.util.Elements.isDeprecated(javax.lang.model.element.Element):boolean"; - o1, "javax.lang.model.util.Elements.getBinaryName(javax.lang.model.element.TypeElement):javax.lang.model.element.Name"; - o1, "javax.lang.model.util.Elements.getPackageOf(javax.lang.model.element.Element):javax.lang.model.element.PackageElement"; - o1, "javax.lang.model.util.Elements.getAllMembers(javax.lang.model.element.TypeElement):java.util.List"; - o1, "javax.lang.model.util.Elements.getAllAnnotationMirrors(javax.lang.model.element.Element):java.util.List"; - o2, "javax.lang.model.util.Elements.hides(javax.lang.model.element.Element, javax.lang.model.element.Element):boolean"; - o3, "javax.lang.model.util.Elements.overrides(javax.lang.model.element.ExecutableElement, javax.lang.model.element.ExecutableElement, javax.lang.model.element.TypeElement):boolean"; - o1, "javax.lang.model.util.Types.asElement(javax.lang.model.type.TypeMirror):javax.lang.model.element.Element"; - o2, "javax.lang.model.util.Types.isSameType(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean"; - o2, "javax.lang.model.util.Types.isSubtype(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean"; - o2, "javax.lang.model.util.Types.isAssignable(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean"; - o2, "javax.lang.model.util.Types.contains(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean"; - o2, "javax.lang.model.util.Types.isSubsignature(javax.lang.model.type.ExecutableType, javax.lang.model.type.ExecutableType):boolean"; - o1, "javax.lang.model.util.Types.directSupertypes(javax.lang.model.type.TypeMirror):java.util.List"; - o1, "javax.lang.model.util.Types.erasure(javax.lang.model.type.TypeMirror):javax.lang.model.type.TypeMirror"; - o1, "javax.lang.model.util.Types.boxedClass(javax.lang.model.type.PrimitiveType):javax.lang.model.element.TypeElement"; - o1, "javax.lang.model.util.Types.unboxedType(javax.lang.model.type.TypeMirror):javax.lang.model.type.PrimitiveType"; - o1, "javax.lang.model.util.Types.capture(javax.lang.model.type.TypeMirror):javax.lang.model.type.TypeMirror"; - o1, "javax.lang.model.util.Types.getArrayType(javax.lang.model.type.TypeMirror):javax.lang.model.type.ArrayType"; - o2, "javax.lang.model.util.Types.getWildcardType(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):javax.lang.model.type.WildcardType"; - o2, "javax.lang.model.util.Types.getDeclaredType(javax.lang.model.element.TypeElement, javax.lang.model.type.TypeMirror[]):javax.lang.model.type.DeclaredType"; - o3, "javax.lang.model.util.Types.getDeclaredType(javax.lang.model.type.DeclaredType, javax.lang.model.element.TypeElement, javax.lang.model.type.TypeMirror[]):javax.lang.model.type.DeclaredType"; - o2, "javax.lang.model.util.Types.asMemberOf(javax.lang.model.type.DeclaredType, javax.lang.model.element.Element):javax.lang.model.type.TypeMirror"; - n3, "javax.tools.JavaCompiler.getStandardFileManager(javax.tools.DiagnosticListener,java.util.Locale,java.nio.charset.Charset):javax.tools.StandardJavaFileManager"; - ng, "javax.tools.JavaFileObject.getAccessLevel():javax.lang.model.element.Modifier"; - ng, "javax.tools.JavaFileObject.getNestingKind():javax.lang.model.element.NestingKind"; - o2, "com.sun.source.util.SourcePositions.getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):long"; - o2, "com.sun.source.util.SourcePositions.getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):long"; - (n, [o; o]), "com.sun.source.util.TreePath.getPath(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):com.sun.source.util.TreePath"; - (n, [o; o]), "com.sun.source.util.TreePath.getPath(com.sun.source.util.TreePath, com.sun.source.tree.Tree):com.sun.source.util.TreePath"; - (n, [o]), "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element):com.sun.source.tree.Tree"; - (n, [o]), "com.sun.source.util.Trees.getTree(javax.lang.model.element.TypeElement):com.sun.source.tree.ClassTree"; - (n, [o]), "com.sun.source.util.Trees.getTree(javax.lang.model.element.ExecutableElement):com.sun.source.tree.MethodTree"; - (n, [o; o]), "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror):com.sun.source.tree.Tree"; - (n, [o; o; o]), "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror, javax.lang.model.element.AnnotationValue):com.sun.source.tree.Tree"; - o2, "com.sun.source.util.Trees.getPath(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):com.sun.source.util.TreePath"; - (n, [o]), "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element):com.sun.source.util.TreePath"; - (n, [o; o]), "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror):com.sun.source.util.TreePath"; - (n, [o; o; o]), "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror, javax.lang.model.element.AnnotationValue):com.sun.source.util.TreePath"; - (n, [o]), "com.sun.source.util.Trees.getElement(com.sun.source.util.TreePath):javax.lang.model.element.Element"; - (n, [o]), "com.sun.source.util.Trees.getTypeMirror(com.sun.source.util.TreePath):javax.lang.model.type.TypeMirror"; - (n, [o]), "com.sun.source.util.Trees.getScope(com.sun.source.util.TreePath):com.sun.source.tree.Scope"; - (n, [o]), "com.sun.source.util.Trees.getDocComment(com.sun.source.util.TreePath):java.lang.String"; - o2, "com.sun.source.util.Trees.isAccessible(com.sun.source.tree.Scope, javax.lang.model.element.TypeElement):boolean"; - o3, "com.sun.source.util.Trees.isAccessible(com.sun.source.tree.Scope, javax.lang.model.element.Element, javax.lang.model.type.DeclaredType):boolean"; - o1, "com.sun.source.util.Trees.getOriginalType(javax.lang.model.type.ErrorType):javax.lang.model.type.TypeMirror"; - (o, [o; o; o; o]), "com.sun.source.util.Trees.printMessage(javax.tools.Diagnostic.Kind, java.lang.CharSequence, com.sun.source.tree.Tree, com.sun.source.tree.CompilationUnitTree):void"; - o1, "com.sun.source.util.Trees.getLub(com.sun.source.tree.CatchTree):javax.lang.model.type.TypeMirror"; - (n, [o; n; n]), "org.w3c.dom.Document.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object"; - (n, [o; n; n]), "org.w3c.dom.Node.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object"; - - (* References *) - ng, "java.lang.ref.Reference.get():java.lang.Object"; - ng, "java.lang.ref.PhantomReference.get():java.lang.Object"; - ng, "java.lang.ref.SoftReference.get():java.lang.Object"; - ng, "java.lang.ref.WeakReference.get():java.lang.Object"; - ng, "java.util.concurrent.atomic.AtomicReference.get():java.lang.Object"; - ] + check_not_null_list @ check_state_list @ check_argument_list + @ [ (n1, "android.os.Parcel.writeList(java.util.List):void") + ; (n2, "android.os.Parcel.writeParcelable(android.os.Parcelable,int):void") + ; (n1, "android.os.Parcel.writeString(java.lang.String):void") + ; ( (o, [o; o; n; n; n]) + , "com.android.sdklib.build.ApkBuilder.(java.io.File,java.io.File,java.io.File,java.lang.String,java.io.PrintStream)" + ) + ; ( (o, [n]) + , "com.android.manifmerger.ManifestMerger.xmlFileAndLine(org.w3c.dom.Node):com.android.manifmerger.IMergerLog$FileAndLine" + ) + ; ( on + , "com.android.util.CommandLineParser$Mode.process(com.android.util.CommandLineParser$Arg,java.lang.String):java.lang.Object" + ) + ; ( on + , "com.google.common.base.Objects$ToStringHelper.add(java.lang.String,java.lang.Object):com.google.common.base.Objects$ToStringHelper" + ) + ; (n2, "com.google.common.base.Objects.equal(java.lang.Object,java.lang.Object):boolean") + ; ( n1 + , "com.google.common.base.Optional.fromNullable(java.lang.Object):com.google.common.base.Optional" + ) + ; ((n, []), "com.google.common.base.Optional.orNull():java.lang.Object") + ; (n1, "com.google.common.base.Strings.nullToEmpty(java.lang.String):java.lang.String") + ; (cg, "com.google.common.collect.ImmutableMap.get(java.lang.Object):java.lang.Object") + ; (* container get *) + ( o1 + , "com.google.common.collect.ImmutableList$Builder.add(java.lang.Object):com.google.common.collect.ImmutableList$Builder" + ) + ; ( o1 + , "com.google.common.collect.ImmutableList$Builder.addAll(java.lang.Iterable):com.google.common.collect.ImmutableList$Builder" + ) + ; ( o1 + , "com.google.common.collect.ImmutableSortedSet$Builder.add(java.lang.Object):com.google.common.collect.ImmutableSortedSet$Builder" + ) + ; ( on + , "com.google.common.collect.Iterables.getFirst(java.lang.Iterable,java.lang.Object):java.lang.Object" + ) + ; ( o1 + , "com.google.common.util.concurrent.SettableFuture.setException(java.lang.Throwable):boolean" + ) + ; (o1, "java.io.File.(java.lang.String)") + ; (n1, "java.io.PrintStream.print(java.lang.String):void") + ; ((n, [o]), "java.lang.Class.getResource(java.lang.String):java.net.URL") + ; (o1, "java.lang.Class.isAssignableFrom(java.lang.Class):boolean") + ; (n1, "java.lang.Integer.equals(java.lang.Object):boolean") + ; (n2, "java.lang.RuntimeException.(java.lang.String,java.lang.Throwable)") + ; (n1, "java.lang.String.equals(java.lang.Object):boolean") + ; (n1, "java.lang.StringBuilder.append(java.lang.String):java.lang.StringBuilder") + ; ((n, [o]), "java.lang.System.getProperty(java.lang.String):java.lang.String") + ; ((n, [o]), "java.lang.System.getenv(java.lang.String):java.lang.String") + ; ( on + , "java.net.URLClassLoader.newInstance(java.net.URL[],java.lang.ClassLoader):java.net.URLClassLoader" + ) + ; (n1, "java.util.AbstractList.equals(java.lang.Object):boolean") + ; (ca, "java.util.ArrayList.add(java.lang.Object):boolean") + ; (* container add *) + (ca, "java.util.List.add(java.lang.Object):boolean") + ; (* container add *) + (cg, "java.util.Map.get(java.lang.Object):java.lang.Object") + ; (* container get *) + (cp, "java.util.Map.put(java.lang.Object,java.lang.Object):java.lang.Object") + ; (* container put *) + ( (n, [o]) + , "javax.lang.model.element.Element.getAnnotation(java.lang.Class):java.lang.annotation.Annotation" + ) + ; ( ng + , "javax.lang.model.element.Element.getEnclosingElement():javax.lang.model.element.Element" ) + ; ( ng + , "javax.lang.model.element.ExecutableElement.getDefaultValue():javax.lang.model.element.AnnotationValue" + ) + ; ( ng + , "javax.lang.model.element.PackageElement.getEnclosingElement():javax.lang.model.element.Element" + ) + ; (ng, "javax.lang.model.element.VariableElement.getConstantValue():java.lang.Object") + ; (ng, "javax.lang.model.type.WildcardType.getSuperBound():javax.lang.model.type.TypeMirror") + ; ( (n, [o]) + , "javax.lang.model.util.Elements.getPackageElement(java.lang.CharSequence):javax.lang.model.element.PackageElement" + ) + ; ( (n, [o]) + , "javax.lang.model.util.Elements.getTypeElement(java.lang.CharSequence):javax.lang.model.element.TypeElement" + ) + ; ( (n, [o]) + , "javax.lang.model.util.Elements.getDocComment(javax.lang.model.element.Element):java.lang.String" + ) + ; ( o1 + , "javax.lang.model.util.Elements.getElementValuesWithDefaults(javax.lang.model.element.AnnotationMirror):java.util.Map" + ) + ; (o1, "javax.lang.model.util.Elements.isDeprecated(javax.lang.model.element.Element):boolean") + ; ( o1 + , "javax.lang.model.util.Elements.getBinaryName(javax.lang.model.element.TypeElement):javax.lang.model.element.Name" + ) + ; ( o1 + , "javax.lang.model.util.Elements.getPackageOf(javax.lang.model.element.Element):javax.lang.model.element.PackageElement" + ) + ; ( o1 + , "javax.lang.model.util.Elements.getAllMembers(javax.lang.model.element.TypeElement):java.util.List" + ) + ; ( o1 + , "javax.lang.model.util.Elements.getAllAnnotationMirrors(javax.lang.model.element.Element):java.util.List" + ) + ; ( o2 + , "javax.lang.model.util.Elements.hides(javax.lang.model.element.Element, javax.lang.model.element.Element):boolean" + ) + ; ( o3 + , "javax.lang.model.util.Elements.overrides(javax.lang.model.element.ExecutableElement, javax.lang.model.element.ExecutableElement, javax.lang.model.element.TypeElement):boolean" + ) + ; ( o1 + , "javax.lang.model.util.Types.asElement(javax.lang.model.type.TypeMirror):javax.lang.model.element.Element" + ) + ; ( o2 + , "javax.lang.model.util.Types.isSameType(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean" + ) + ; ( o2 + , "javax.lang.model.util.Types.isSubtype(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean" + ) + ; ( o2 + , "javax.lang.model.util.Types.isAssignable(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean" + ) + ; ( o2 + , "javax.lang.model.util.Types.contains(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):boolean" + ) + ; ( o2 + , "javax.lang.model.util.Types.isSubsignature(javax.lang.model.type.ExecutableType, javax.lang.model.type.ExecutableType):boolean" + ) + ; ( o1 + , "javax.lang.model.util.Types.directSupertypes(javax.lang.model.type.TypeMirror):java.util.List" + ) + ; ( o1 + , "javax.lang.model.util.Types.erasure(javax.lang.model.type.TypeMirror):javax.lang.model.type.TypeMirror" + ) + ; ( o1 + , "javax.lang.model.util.Types.boxedClass(javax.lang.model.type.PrimitiveType):javax.lang.model.element.TypeElement" + ) + ; ( o1 + , "javax.lang.model.util.Types.unboxedType(javax.lang.model.type.TypeMirror):javax.lang.model.type.PrimitiveType" + ) + ; ( o1 + , "javax.lang.model.util.Types.capture(javax.lang.model.type.TypeMirror):javax.lang.model.type.TypeMirror" + ) + ; ( o1 + , "javax.lang.model.util.Types.getArrayType(javax.lang.model.type.TypeMirror):javax.lang.model.type.ArrayType" + ) + ; ( o2 + , "javax.lang.model.util.Types.getWildcardType(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror):javax.lang.model.type.WildcardType" + ) + ; ( o2 + , "javax.lang.model.util.Types.getDeclaredType(javax.lang.model.element.TypeElement, javax.lang.model.type.TypeMirror[]):javax.lang.model.type.DeclaredType" + ) + ; ( o3 + , "javax.lang.model.util.Types.getDeclaredType(javax.lang.model.type.DeclaredType, javax.lang.model.element.TypeElement, javax.lang.model.type.TypeMirror[]):javax.lang.model.type.DeclaredType" + ) + ; ( o2 + , "javax.lang.model.util.Types.asMemberOf(javax.lang.model.type.DeclaredType, javax.lang.model.element.Element):javax.lang.model.type.TypeMirror" + ) + ; ( n3 + , "javax.tools.JavaCompiler.getStandardFileManager(javax.tools.DiagnosticListener,java.util.Locale,java.nio.charset.Charset):javax.tools.StandardJavaFileManager" + ) + ; (ng, "javax.tools.JavaFileObject.getAccessLevel():javax.lang.model.element.Modifier") + ; (ng, "javax.tools.JavaFileObject.getNestingKind():javax.lang.model.element.NestingKind") + ; ( o2 + , "com.sun.source.util.SourcePositions.getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):long" + ) + ; ( o2 + , "com.sun.source.util.SourcePositions.getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):long" + ) + ; ( (n, [o; o]) + , "com.sun.source.util.TreePath.getPath(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):com.sun.source.util.TreePath" + ) + ; ( (n, [o; o]) + , "com.sun.source.util.TreePath.getPath(com.sun.source.util.TreePath, com.sun.source.tree.Tree):com.sun.source.util.TreePath" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element):com.sun.source.tree.Tree" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getTree(javax.lang.model.element.TypeElement):com.sun.source.tree.ClassTree" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getTree(javax.lang.model.element.ExecutableElement):com.sun.source.tree.MethodTree" + ) + ; ( (n, [o; o]) + , "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror):com.sun.source.tree.Tree" + ) + ; ( (n, [o; o; o]) + , "com.sun.source.util.Trees.getTree(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror, javax.lang.model.element.AnnotationValue):com.sun.source.tree.Tree" + ) + ; ( o2 + , "com.sun.source.util.Trees.getPath(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree):com.sun.source.util.TreePath" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element):com.sun.source.util.TreePath" + ) + ; ( (n, [o; o]) + , "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror):com.sun.source.util.TreePath" + ) + ; ( (n, [o; o; o]) + , "com.sun.source.util.Trees.getPath(javax.lang.model.element.Element, javax.lang.model.element.AnnotationMirror, javax.lang.model.element.AnnotationValue):com.sun.source.util.TreePath" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getElement(com.sun.source.util.TreePath):javax.lang.model.element.Element" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getTypeMirror(com.sun.source.util.TreePath):javax.lang.model.type.TypeMirror" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getScope(com.sun.source.util.TreePath):com.sun.source.tree.Scope" + ) + ; ( (n, [o]) + , "com.sun.source.util.Trees.getDocComment(com.sun.source.util.TreePath):java.lang.String" ) + ; ( o2 + , "com.sun.source.util.Trees.isAccessible(com.sun.source.tree.Scope, javax.lang.model.element.TypeElement):boolean" + ) + ; ( o3 + , "com.sun.source.util.Trees.isAccessible(com.sun.source.tree.Scope, javax.lang.model.element.Element, javax.lang.model.type.DeclaredType):boolean" + ) + ; ( o1 + , "com.sun.source.util.Trees.getOriginalType(javax.lang.model.type.ErrorType):javax.lang.model.type.TypeMirror" + ) + ; ( (o, [o; o; o; o]) + , "com.sun.source.util.Trees.printMessage(javax.tools.Diagnostic.Kind, java.lang.CharSequence, com.sun.source.tree.Tree, com.sun.source.tree.CompilationUnitTree):void" + ) + ; ( o1 + , "com.sun.source.util.Trees.getLub(com.sun.source.tree.CatchTree):javax.lang.model.type.TypeMirror" + ) + ; ( (n, [o; n; n]) + , "org.w3c.dom.Document.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object" + ) + ; ( (n, [o; n; n]) + , "org.w3c.dom.Node.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object" + ) + ; (* References *) + (ng, "java.lang.ref.Reference.get():java.lang.Object") + ; (ng, "java.lang.ref.PhantomReference.get():java.lang.Object") + ; (ng, "java.lang.ref.SoftReference.get():java.lang.Object") + ; (ng, "java.lang.ref.WeakReference.get():java.lang.Object") + ; (ng, "java.util.concurrent.atomic.AtomicReference.get():java.lang.Object") ] (** Models for @Present annotations *) let annotated_list_present = - [ - (n, [o]), "Optional.of(java.lang.Object):Optional"; - (n, [o]), "com.google.common.base.Optional.of(java.lang.Object):com.google.common.base.Optional"; - ] + [ ((n, [o]), "Optional.of(java.lang.Object):Optional") + ; ( (n, [o]) + , "com.google.common.base.Optional.of(java.lang.Object):com.google.common.base.Optional" ) ] (** Models for methods that do not return *) -let noreturn_list = - [ - (o, [o]), "java.lang.System.exit(int):void"; - ] +let noreturn_list = [((o, [o]), "java.lang.System.exit(int):void")] - -type model_table_t = (string, bool * bool list) Hashtbl.t +type model_table_t = (string, (bool * bool list)) Hashtbl.t let mk_table list = let map = Hashtbl.create 1 in - List.iter ~f:(function (v, pn_id) -> Hashtbl.replace map pn_id v) list; + List.iter ~f:(function v, pn_id -> Hashtbl.replace map pn_id v) list ; map let this_file = __FILE__ let annotated_table_nullable = mk_table annotated_list_nullable + let annotated_table_present = mk_table annotated_list_present + let check_not_null_table, check_not_null_parameter_table = - mk_table check_not_null_list, mk_table check_not_null_parameter_list + (mk_table check_not_null_list, mk_table check_not_null_parameter_list) + let check_state_table = mk_table check_state_list + let check_argument_table = mk_table check_argument_list + let containsKey_table = mk_table containsKey_list + let mapPut_table = mk_table mapPut_list + let optional_get_table = mk_table optional_get_list + let optional_isPresent_table = mk_table optional_isPresent_list + let noreturn_table = mk_table noreturn_list + let true_on_null_table = mk_table true_on_null_list diff --git a/infer/src/eradicate/modelTables.mli b/infer/src/eradicate/modelTables.mli index ce8bba760..5b149f2fd 100644 --- a/infer/src/eradicate/modelTables.mli +++ b/infer/src/eradicate/modelTables.mli @@ -9,20 +9,31 @@ open! IStd -type model_table_t = (string, bool * bool list) Caml.Hashtbl.t +type model_table_t = (string, (bool * bool list)) Caml.Hashtbl.t -(** Name of this file. *) val this_file : string +(** Name of this file. *) val annotated_table_nullable : model_table_t + val annotated_table_present : model_table_t + val check_not_null_table : model_table_t + val check_not_null_parameter_table : (string, int) Caml.Hashtbl.t + val check_state_table : model_table_t + val check_argument_table : model_table_t + val containsKey_table : model_table_t + val mapPut_table : model_table_t + val optional_get_table : model_table_t + val optional_isPresent_table : model_table_t + val noreturn_table : model_table_t + val true_on_null_table : model_table_t diff --git a/infer/src/eradicate/models.ml b/infer/src/eradicate/models.ml index 4ef6bbc1b..3946e6b32 100644 --- a/infer/src/eradicate/models.ml +++ b/infer/src/eradicate/models.ml @@ -9,7 +9,6 @@ open! IStd module Hashtbl = Caml.Hashtbl - open ModelTables module L = Logging @@ -43,22 +42,26 @@ module Inference = struct let update_count_str s_old = let n = if String.is_empty s_old then 0 - else try int_of_string s_old with - | Failure _ -> - L.internal_error "int_of_string %s@." s_old; - assert false in + else + try int_of_string s_old + with Failure _ -> + L.internal_error "int_of_string %s@." s_old ; + assert false + in string_of_int (n + 1) let update_boolvec_str _s size index bval = let s = if String.is_empty _s then String.make size '0' else _s in - String.set s index (if bval then '1' else '0'); + s.[index] <- (if bval then '1' else '0') ; s let mark_file update_str dir fname = - DB.update_file_with_lock dir fname update_str; + DB.update_file_with_lock dir fname update_str ; match DB.read_file_with_lock dir fname with - | Some buf -> L.internal_error "Read %s: %s@." fname buf - | None -> L.internal_error "Read %s: None@." fname + | Some buf + -> L.internal_error "Read %s: %s@." fname buf + | None + -> L.internal_error "Read %s: None@." fname let mark_file_count = mark_file update_count_str @@ -87,17 +90,21 @@ module Inference = struct let proc_parameters_marked pn = let dir, fname = proc_get_param_dir_fname pn in match DB.read_file_with_lock dir fname with - | None -> None - | Some buf -> - let boolvec = ref [] in - String.iter ~f:(fun c -> boolvec := (Char.equal c '1') :: !boolvec) buf; + | None + -> None + | Some buf + -> let boolvec = ref [] in + String.iter ~f:(fun c -> boolvec := Char.equal c '1' :: !boolvec) buf ; Some (List.rev !boolvec) -end (* Inference *) +end +(* Inference *) let table_has_procedure table proc_name = let proc_id = Typ.Procname.to_unique_id proc_name in - try ignore (Hashtbl.find table proc_id); true + try + ignore (Hashtbl.find table proc_id) ; + true with Not_found -> false (** Return the annotated signature of the procedure, taking into account models. *) @@ -107,55 +114,50 @@ let get_modelled_annotated_signature proc_attributes = let proc_id = Typ.Procname.to_unique_id proc_name in let infer_parameters ann_sig = let mark_par = - if Inference.enabled then Inference.proc_parameters_marked proc_name - else None in + if Inference.enabled then Inference.proc_parameters_marked proc_name else None + in match mark_par with - | None -> ann_sig - | Some bs -> - let mark = (false, bs) in - AnnotatedSignature.mark proc_name AnnotatedSignature.Nullable ann_sig mark in + | None + -> ann_sig + | Some bs + -> let mark = (false, bs) in + AnnotatedSignature.mark proc_name AnnotatedSignature.Nullable ann_sig mark + in let infer_return ann_sig = - let mark_r = - Inference.enabled && - Inference.proc_return_is_marked proc_name in - if mark_r - then AnnotatedSignature.mark_return AnnotatedSignature.Nullable ann_sig - else ann_sig in + let mark_r = Inference.enabled && Inference.proc_return_is_marked proc_name in + if mark_r then AnnotatedSignature.mark_return AnnotatedSignature.Nullable ann_sig else ann_sig + in let lookup_models_nullable ann_sig = if use_models then try let mark = Hashtbl.find annotated_table_nullable proc_id in AnnotatedSignature.mark proc_name AnnotatedSignature.Nullable ann_sig mark - with Not_found -> - ann_sig - else ann_sig in + with Not_found -> ann_sig + else ann_sig + in let lookup_models_present ann_sig = if use_models then try let mark = Hashtbl.find annotated_table_present proc_id in AnnotatedSignature.mark proc_name AnnotatedSignature.Present ann_sig mark - with Not_found -> - ann_sig - else ann_sig in - - annotated_signature - |> lookup_models_nullable - |> lookup_models_present - |> infer_return + with Not_found -> ann_sig + else ann_sig + in + annotated_signature |> lookup_models_nullable |> lookup_models_present |> infer_return |> infer_parameters - (** Return true when the procedure has been modelled for nullable. *) let is_modelled_nullable proc_name = if use_models then let proc_id = Typ.Procname.to_unique_id proc_name in - try ignore (Hashtbl.find annotated_table_nullable proc_id ); true + try + ignore (Hashtbl.find annotated_table_nullable proc_id) ; + true with Not_found -> false else false (** Check if the procedure is one of the known Preconditions.checkNotNull. *) -let is_check_not_null proc_name = - table_has_procedure check_not_null_table proc_name +let is_check_not_null proc_name = table_has_procedure check_not_null_table proc_name (** Parameter number for a procedure known to be a checkNotNull *) let get_check_not_null_parameter proc_name = @@ -164,34 +166,25 @@ let get_check_not_null_parameter proc_name = with Not_found -> 0 (** Check if the procedure is one of the known Preconditions.checkState. *) -let is_check_state proc_name = - table_has_procedure check_state_table proc_name +let is_check_state proc_name = table_has_procedure check_state_table proc_name (** Check if the procedure is one of the known Preconditions.checkArgument. *) -let is_check_argument proc_name = - table_has_procedure check_argument_table proc_name +let is_check_argument proc_name = table_has_procedure check_argument_table proc_name (** Check if the procedure does not return. *) -let is_noreturn proc_name = - table_has_procedure noreturn_table proc_name +let is_noreturn proc_name = table_has_procedure noreturn_table proc_name (** Check if the procedure is Optional.get(). *) -let is_optional_get proc_name = - table_has_procedure optional_get_table proc_name +let is_optional_get proc_name = table_has_procedure optional_get_table proc_name (** Check if the procedure is Optional.isPresent(). *) -let is_optional_isPresent proc_name = - table_has_procedure optional_isPresent_table proc_name +let is_optional_isPresent proc_name = table_has_procedure optional_isPresent_table proc_name (** Check if the procedure returns true on null. *) -let is_true_on_null proc_name = - table_has_procedure true_on_null_table proc_name - +let is_true_on_null proc_name = table_has_procedure true_on_null_table proc_name (** Check if the procedure is Map.containsKey(). *) -let is_containsKey proc_name = - table_has_procedure containsKey_table proc_name +let is_containsKey proc_name = table_has_procedure containsKey_table proc_name (** Check if the procedure is Map.put(). *) -let is_mapPut proc_name = - table_has_procedure mapPut_table proc_name +let is_mapPut proc_name = table_has_procedure mapPut_table proc_name diff --git a/infer/src/eradicate/typeAnnotation.ml b/infer/src/eradicate/typeAnnotation.ml index e35101305..28c060172 100644 --- a/infer/src/eradicate/typeAnnotation.ml +++ b/infer/src/eradicate/typeAnnotation.ml @@ -8,54 +8,42 @@ *) open! IStd - module L = Logging module F = Format module P = Printf (** Module to represent annotations on types. *) +module AnnotationsMap = Caml.Map.Make (struct + type t = AnnotatedSignature.annotation [@@deriving compare] +end) -module AnnotationsMap = Caml.Map.Make ( - struct - type t = AnnotatedSignature.annotation [@@deriving compare] - end) - -type t = { - map : bool AnnotationsMap.t; - origin : TypeOrigin.t; -} [@@deriving compare] +type t = {map: bool AnnotationsMap.t; origin: TypeOrigin.t} [@@deriving compare] let equal = [%compare.equal : t] let get_value ann ta = - try - AnnotationsMap.find ann ta.map + try AnnotationsMap.find ann ta.map with Not_found -> false let set_value ann b ta = - if Bool.equal (get_value ann ta) b then ta - else - { ta with - map = AnnotationsMap.add ann b ta.map; } + if Bool.equal (get_value ann ta) b then ta else {ta with map= AnnotationsMap.add ann b ta.map} -let get_nullable = - get_value AnnotatedSignature.Nullable +let get_nullable = get_value AnnotatedSignature.Nullable -let get_present = - get_value Present +let get_present = get_value Present -let set_nullable b = - set_value Nullable b +let set_nullable b = set_value Nullable b -let set_present b = - set_value Present b +let set_present b = set_value Present b let descr_origin tenv ta = let descr_opt = TypeOrigin.get_description tenv ta.origin in match descr_opt with - | None -> ("", None, None) - | Some (str, loc_opt, sig_opt) -> ("(Origin: " ^ str ^ ")", loc_opt, sig_opt) + | None + -> ("", None, None) + | Some (str, loc_opt, sig_opt) + -> ("(Origin: " ^ str ^ ")", loc_opt, sig_opt) let to_string ta = let nullable_s = if get_nullable ta then " @Nullable" else "" in @@ -63,44 +51,38 @@ let to_string ta = nullable_s ^ present_s let join ta1 ta2 = - let nul1, nul2 = get_nullable ta1, get_nullable ta2 in - let choose_left = match nul1, nul2 with - | false, true -> - false - | _ -> - true in - let ta_chosen, ta_other = - if choose_left then ta1, ta2 else ta2, ta1 in + let nul1, nul2 = (get_nullable ta1, get_nullable ta2) in + let choose_left = match (nul1, nul2) with false, true -> false | _ -> true in + let ta_chosen, ta_other = if choose_left then (ta1, ta2) else (ta2, ta1) in let present = get_present ta1 && get_present ta2 in let origin = - if Bool.equal nul1 nul2 - then TypeOrigin.join ta_chosen.origin ta_other.origin - else ta_chosen.origin in - let ta' = - set_present present - { ta_chosen with - origin; } in + if Bool.equal nul1 nul2 then TypeOrigin.join ta_chosen.origin ta_other.origin + else ta_chosen.origin + in + let ta' = set_present present {ta_chosen with origin} in if equal ta' ta1 then None else Some ta' let get_origin ta = ta.origin -let origin_is_fun_library ta = match get_origin ta with - | TypeOrigin.Proc proc_origin -> - proc_origin.TypeOrigin.is_library - | _ -> false +let origin_is_fun_library ta = + match get_origin ta with + | TypeOrigin.Proc proc_origin + -> proc_origin.TypeOrigin.is_library + | _ + -> false let const annotation b origin = - let nullable, present = match annotation with - | AnnotatedSignature.Nullable -> b, false - | AnnotatedSignature.Present -> false, b in - let ta = - { origin; - map = AnnotationsMap.empty; - } in + let nullable, present = + match annotation with + | AnnotatedSignature.Nullable + -> (b, false) + | AnnotatedSignature.Present + -> (false, b) + in + let ta = {origin; map= AnnotationsMap.empty} in set_present present (set_nullable nullable ta) -let with_origin ta o = - { ta with origin = o } +let with_origin ta o = {ta with origin= o} let from_item_annotation ia origin = let ta = const Nullable (Annotations.ia_is_nullable ia) origin in diff --git a/infer/src/eradicate/typeAnnotation.mli b/infer/src/eradicate/typeAnnotation.mli index 86d1bd21d..17c4848e3 100644 --- a/infer/src/eradicate/typeAnnotation.mli +++ b/infer/src/eradicate/typeAnnotation.mli @@ -17,14 +17,21 @@ val equal : t -> t -> bool val const : AnnotatedSignature.annotation -> bool -> TypeOrigin.t -> t -(** Human-readable description of the origin of a nullable value. *) val descr_origin : Tenv.t -> t -> TypeErr.origin_descr +(** Human-readable description of the origin of a nullable value. *) val from_item_annotation : Annot.Item.t -> TypeOrigin.t -> t + val get_origin : t -> TypeOrigin.t + val get_value : AnnotatedSignature.annotation -> t -> bool + val join : t -> t -> t option + val origin_is_fun_library : t -> bool + val set_value : AnnotatedSignature.annotation -> bool -> t -> t + val to_string : t -> string + val with_origin : t -> TypeOrigin.t -> t diff --git a/infer/src/eradicate/typeCheck.ml b/infer/src/eradicate/typeCheck.ml index 4e806f256..c9d0f92bc 100644 --- a/infer/src/eradicate/typeCheck.ml +++ b/infer/src/eradicate/typeCheck.ml @@ -8,15 +8,15 @@ *) open! IStd - module L = Logging module F = Format module DExp = DecompiledExp (** Module for type checking. *) -let remove_temps = true (* remove temp ids from typestates *) +let remove_temps = true +(* remove temp ids from typestates *) (** Module to treat selected complex expressions as constants. *) module ComplexExpressions = struct @@ -24,57 +24,53 @@ module ComplexExpressions = struct Boolean checks (e.g. null check) and assignments on expressions considered constant are retained across the control flow assuming there are no modifications in between. *) type expressions_constant = - | FL_PARAMETER_STATIC (* parameter.field and static fields *) - | FL_ALL_NESTED_FIELDS (* all forms of var.field1. ... .fieldn *) - | FUNCTIONS_IDEMPOTENT (* the above plus function calls are considered idempotent *) + | FL_PARAMETER_STATIC + (* parameter.field and static fields *) + | FL_ALL_NESTED_FIELDS + (* all forms of var.field1. ... .fieldn *) + | FUNCTIONS_IDEMPOTENT - let complex_expressions_flag = FUNCTIONS_IDEMPOTENT + (* the above plus function calls are considered idempotent *) - let parameter_and_static_field () = - complex_expressions_flag >= FL_PARAMETER_STATIC + let complex_expressions_flag = FUNCTIONS_IDEMPOTENT - let all_nested_fields () = - complex_expressions_flag >= FL_ALL_NESTED_FIELDS + let parameter_and_static_field () = complex_expressions_flag >= FL_PARAMETER_STATIC - let functions_idempotent () = - complex_expressions_flag >= FUNCTIONS_IDEMPOTENT + let all_nested_fields () = complex_expressions_flag >= FL_ALL_NESTED_FIELDS + let functions_idempotent () = complex_expressions_flag >= FUNCTIONS_IDEMPOTENT let procname_optional_isPresent = Models.is_optional_isPresent + let procname_instanceof = Typ.Procname.equal BuiltinDecl.__instanceof let procname_is_false_on_null pn = match Specs.proc_resolve_attributes pn with - | Some proc_attributes -> - let annotated_signature = - Models.get_modelled_annotated_signature proc_attributes in + | Some proc_attributes + -> let annotated_signature = Models.get_modelled_annotated_signature proc_attributes in let ret_ann, _ = annotated_signature.AnnotatedSignature.ret in Annotations.ia_is_false_on_null ret_ann - | None -> - false + | None + -> false let procname_is_true_on_null pn = let annotated_true_on_null () = match Specs.proc_resolve_attributes pn with - | Some proc_attributes -> - let annotated_signature = - Models.get_modelled_annotated_signature proc_attributes in + | Some proc_attributes + -> let annotated_signature = Models.get_modelled_annotated_signature proc_attributes in let ret_ann, _ = annotated_signature.AnnotatedSignature.ret in Annotations.ia_is_true_on_null ret_ann - | None -> - false in - Models.is_true_on_null pn || - annotated_true_on_null () + | None + -> false + in + Models.is_true_on_null pn || annotated_true_on_null () let procname_containsKey = Models.is_containsKey (** Recognize *all* the procedures treated specially in conditionals *) let procname_used_in_condition pn = - procname_optional_isPresent pn || - procname_instanceof pn || - procname_containsKey pn || - BuiltinDecl.is_declared pn - + procname_optional_isPresent pn || procname_instanceof pn || procname_containsKey pn + || BuiltinDecl.is_declared pn exception Not_handled @@ -83,63 +79,57 @@ module ComplexExpressions = struct (* Arbitrary function parameters and field access are allowed *) (* when the relevant options are active. *) let exp_to_string_map_dexp tenv map_dexp node' exp = - let rec dexp_to_string dexp = - let case_not_handled () = - raise Not_handled in - + let case_not_handled () = raise Not_handled in match dexp with - | DExp.Darray (de1, de2) -> - dexp_to_string de1 ^ "[" ^ dexp_to_string de2 ^ "]" - | DExp.Darrow (de, f) - | DExp.Ddot (de, f) -> - dexp_to_string de ^ "." ^ Typ.Fieldname.to_string f - | DExp.Dbinop (op, de1, de2) -> - "(" ^ dexp_to_string de1 ^ (Binop.str Pp.text op) ^ dexp_to_string de2 ^ ")" - | DExp.Dconst (Const.Cfun pn) -> - Typ.Procname.to_unique_id pn - | DExp.Dconst c -> - F.asprintf "%a" (Const.pp Pp.text) c - | DExp.Dderef de -> - dexp_to_string de - | DExp.Dfcall (fun_dexp, args, _, { CallFlags.cf_virtual = isvirtual }) - | DExp.Dretcall (fun_dexp, args, _, { CallFlags.cf_virtual = isvirtual }) - when functions_idempotent () -> - let pp_arg fmt de = F.fprintf fmt "%s" (dexp_to_string de) in + | DExp.Darray (de1, de2) + -> dexp_to_string de1 ^ "[" ^ dexp_to_string de2 ^ "]" + | DExp.Darrow (de, f) | DExp.Ddot (de, f) + -> dexp_to_string de ^ "." ^ Typ.Fieldname.to_string f + | DExp.Dbinop (op, de1, de2) + -> "(" ^ dexp_to_string de1 ^ Binop.str Pp.text op ^ dexp_to_string de2 ^ ")" + | DExp.Dconst Const.Cfun pn + -> Typ.Procname.to_unique_id pn + | DExp.Dconst c + -> F.asprintf "%a" (Const.pp Pp.text) c + | DExp.Dderef de + -> dexp_to_string de + | DExp.Dfcall (fun_dexp, args, _, {CallFlags.cf_virtual= isvirtual}) + | DExp.Dretcall (fun_dexp, args, _, {CallFlags.cf_virtual= isvirtual}) + when functions_idempotent () + -> let pp_arg fmt de = F.fprintf fmt "%s" (dexp_to_string de) in let pp_args fmt des = Pp.comma_seq pp_arg fmt des in let pp fmt = let virt = if isvirtual then "V" else "" in - F.fprintf fmt "%a(%a)%s" pp_arg fun_dexp pp_args args virt in + F.fprintf fmt "%a(%a)%s" pp_arg fun_dexp pp_args args virt + in F.asprintf "%t" pp - | DExp.Dfcall _ - | DExp.Dretcall _ -> - case_not_handled () - | DExp.Dpvar pv - | DExp.Dpvaraddr pv when not (Pvar.is_frontend_tmp pv) -> - Pvar.to_string pv - | DExp.Dpvar _ - | DExp.Dpvaraddr _ (* front-end variable -- this should not happen) *) -> - case_not_handled () - | DExp.Dunop (op, de) -> - Unop.str op ^ dexp_to_string de - | DExp.Dsizeof _ -> - case_not_handled () - | DExp.Dunknown -> - case_not_handled () in - + | DExp.Dfcall _ | DExp.Dretcall _ + -> case_not_handled () + | (DExp.Dpvar pv | DExp.Dpvaraddr pv) when not (Pvar.is_frontend_tmp pv) + -> Pvar.to_string pv + | DExp.Dpvar _ | DExp.Dpvaraddr _ (* front-end variable -- this should not happen) *) + -> case_not_handled () + | DExp.Dunop (op, de) + -> Unop.str op ^ dexp_to_string de + | DExp.Dsizeof _ + -> case_not_handled () + | DExp.Dunknown + -> case_not_handled () + in match map_dexp (Errdesc.exp_rv_dexp tenv node' exp) with - | Some de -> - begin - try Some (dexp_to_string de) - with Not_handled -> None - end - | None -> None + | Some de -> ( + try Some (dexp_to_string de) + with Not_handled -> None ) + | None + -> None let exp_to_string tenv node' exp = let map_dexp de_opt = de_opt in exp_to_string_map_dexp tenv map_dexp node' exp +end -end (* ComplexExpressions *) +(* ComplexExpressions *) type check_return_type = Typ.Procname.t -> Procdesc.t -> Typ.t -> Typ.t option -> Location.t -> unit @@ -148,608 +138,558 @@ type find_canonical_duplicate = Procdesc.Node.t -> Procdesc.Node.t type get_proc_desc = TypeState.get_proc_desc -type checks = - { - eradicate : bool; - check_extension : bool; - check_ret_type : check_return_type list; - } +type checks = {eradicate: bool; check_extension: bool; check_ret_type: check_return_type list} (** Typecheck an expression. *) -let rec typecheck_expr - find_canonical_duplicate visited checks tenv node instr_ref (curr_pdesc : Procdesc.t) - typestate e tr_default loc : TypeState.range = match e with - | Exp.Lvar pvar -> - (match TypeState.lookup_pvar pvar typestate with - | Some tr -> TypeState.range_add_locs tr [loc] - | None -> tr_default) - | Exp.Var id -> - (match TypeState.lookup_id id typestate with - | Some tr -> TypeState.range_add_locs tr [loc] - | None -> tr_default) - | Exp.Const (Const.Cint i) when IntLit.iszero i -> - let (typ, _, locs) = tr_default in - if PatternMatch.type_is_class typ - then (typ, TypeAnnotation.const AnnotatedSignature.Nullable true (TypeOrigin.Const loc), locs) +let rec typecheck_expr find_canonical_duplicate visited checks tenv node instr_ref + (curr_pdesc: Procdesc.t) typestate e tr_default loc : TypeState.range = + match e with + | Exp.Lvar pvar -> ( + match TypeState.lookup_pvar pvar typestate with + | Some tr + -> TypeState.range_add_locs tr [loc] + | None + -> tr_default ) + | Exp.Var id -> ( + match TypeState.lookup_id id typestate with + | Some tr + -> TypeState.range_add_locs tr [loc] + | None + -> tr_default ) + | Exp.Const Const.Cint i when IntLit.iszero i + -> let typ, _, locs = tr_default in + if PatternMatch.type_is_class typ then + (typ, TypeAnnotation.const AnnotatedSignature.Nullable true (TypeOrigin.Const loc), locs) else let t, ta, ll = tr_default in (t, TypeAnnotation.with_origin ta (TypeOrigin.Const loc), ll) - | Exp.Exn e1 -> - typecheck_expr - find_canonical_duplicate visited checks tenv - node instr_ref curr_pdesc + | Exp.Exn e1 + -> typecheck_expr find_canonical_duplicate visited checks tenv node instr_ref curr_pdesc typestate e1 tr_default loc - | Exp.Const _ -> - let (typ, _, locs) = tr_default in + | Exp.Const _ + -> let typ, _, locs = tr_default in (typ, TypeAnnotation.const AnnotatedSignature.Nullable false (TypeOrigin.Const loc), locs) - | Exp.Lfield (exp, fn, typ) -> - let _, _, locs = tr_default in - let (_, ta, locs') = - typecheck_expr - find_canonical_duplicate visited checks tenv node instr_ref curr_pdesc typestate exp - (typ, - TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, - locs) - loc in + | Exp.Lfield (exp, fn, typ) + -> let _, _, locs = tr_default in + let _, ta, locs' = + typecheck_expr find_canonical_duplicate visited checks tenv node instr_ref curr_pdesc + typestate exp + (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, locs) loc + in let exp_origin = TypeAnnotation.get_origin ta in - let tr_new = match EradicateChecks.get_field_annotation tenv fn typ with - | Some (t, ia) -> - ( - t, - TypeAnnotation.from_item_annotation ia (TypeOrigin.Field (exp_origin, fn, loc)), - locs' - ) - | None -> tr_default in + let tr_new = + match EradicateChecks.get_field_annotation tenv fn typ with + | Some (t, ia) + -> ( t + , TypeAnnotation.from_item_annotation ia (TypeOrigin.Field (exp_origin, fn, loc)) + , locs' ) + | None + -> tr_default + in if checks.eradicate then - EradicateChecks.check_field_access tenv - find_canonical_duplicate curr_pdesc node instr_ref exp fn ta loc; + EradicateChecks.check_field_access tenv find_canonical_duplicate curr_pdesc node instr_ref + exp fn ta loc ; tr_new - | Exp.Lindex (array_exp, index_exp) -> - let (_, ta, _) = - typecheck_expr - find_canonical_duplicate - visited - checks tenv - node - instr_ref - curr_pdesc - typestate - array_exp - tr_default - loc in + | Exp.Lindex (array_exp, index_exp) + -> let _, ta, _ = + typecheck_expr find_canonical_duplicate visited checks tenv node instr_ref curr_pdesc + typestate array_exp tr_default loc + in let index = - match EradicateChecks.explain_expr tenv node index_exp with - | Some s -> s - | None -> "?" in + match EradicateChecks.explain_expr tenv node index_exp with Some s -> s | None -> "?" + in let fname = Typ.Fieldname.Java.from_string index in if checks.eradicate then - EradicateChecks.check_array_access tenv - find_canonical_duplicate - curr_pdesc - node - instr_ref - array_exp - fname - ta - loc - true; + EradicateChecks.check_array_access tenv find_canonical_duplicate curr_pdesc node instr_ref + array_exp fname ta loc true ; tr_default - | _ -> tr_default + | _ + -> tr_default (** Typecheck an instruction. *) -let typecheck_instr - tenv ext calls_this checks (node: Procdesc.Node.t) idenv get_proc_desc curr_pname - curr_pdesc find_canonical_duplicate annotated_signature instr_ref linereader typestate instr = +let typecheck_instr tenv ext calls_this checks (node: Procdesc.Node.t) idenv get_proc_desc + curr_pname curr_pdesc find_canonical_duplicate annotated_signature instr_ref linereader + typestate instr = (* let print_current_state () = *) (* L.stdout "Current Typestate in node %a@\n%a@." *) (* Procdesc.Node.pp (TypeErr.InstrRef.get_node instr_ref) *) (* (TypeState.pp ext) typestate; *) (* L.stdout " %a@." (Sil.pp_instr pe_text) instr in *) - (* Handle the case where a field access X.f happens via a temporary variable $Txxx. This has been observed in assignments this.f = exp when exp contains an ifthenelse. Reconstuct the original expression knowing: the origin of $Txxx is 'this'. *) let handle_field_access_via_temporary typestate exp = let name_is_temporary name = let prefix = "$T" in - String.is_prefix ~prefix:prefix name in + String.is_prefix ~prefix name + in let pvar_get_origin pvar = match TypeState.lookup_pvar pvar typestate with - | Some (_, ta, _) -> - Some (TypeAnnotation.get_origin ta) - | None -> None in - let handle_temporary e = match Idenv.expand_expr idenv e with - | Exp.Lvar pvar when name_is_temporary (Pvar.to_string pvar) -> - begin - match pvar_get_origin pvar with - | Some (TypeOrigin.Formal s) -> - let pvar' = Pvar.mk s curr_pname in - Some (Exp.Lvar pvar') - | _ -> None - end - | _ -> None in + | Some (_, ta, _) + -> Some (TypeAnnotation.get_origin ta) + | None + -> None + in + let handle_temporary e = + match Idenv.expand_expr idenv e with + | Exp.Lvar pvar when name_is_temporary (Pvar.to_string pvar) -> ( + match pvar_get_origin pvar with + | Some TypeOrigin.Formal s + -> let pvar' = Pvar.mk s curr_pname in + Some (Exp.Lvar pvar') + | _ + -> None ) + | _ + -> None + in match exp with - | Exp.Lfield (e, fn, typ) -> - let exp' = match handle_temporary e with - | Some e' -> - Exp.Lfield (e', fn, typ) - | None -> exp in + | Exp.Lfield (e, fn, typ) + -> let exp' = + match handle_temporary e with Some e' -> Exp.Lfield (e', fn, typ) | None -> exp + in exp' - | _ -> exp in - + | _ + -> exp + in (* Convert a complex expressions into a pvar. When [is_assigment] is true, update the relevant annotations for the pvar. *) let convert_complex_exp_to_pvar node' is_assignment _exp typestate loc = let exp = handle_field_access_via_temporary typestate (Idenv.expand_expr idenv _exp) in - let default = exp, typestate in - + let default = (exp, typestate) in (* If this is an assignment, update the typestate for a field access pvar. *) let update_typestate_fld pvar origin fn typ = match TypeState.lookup_pvar pvar typestate with - | Some _ when not is_assignment -> typestate + | Some _ when not is_assignment + -> typestate | _ -> - (match EradicateChecks.get_field_annotation tenv fn typ with - | Some (t, ia) -> - let range = - ( - t, - TypeAnnotation.from_item_annotation - ia (TypeOrigin.Field (origin, fn, loc)), - [loc] - ) in - TypeState.add pvar range typestate - | None -> typestate) in - + match EradicateChecks.get_field_annotation tenv fn typ with + | Some (t, ia) + -> let range = + ( t + , TypeAnnotation.from_item_annotation ia (TypeOrigin.Field (origin, fn, loc)) + , [loc] ) + in + TypeState.add pvar range typestate + | None + -> typestate + in (* Convert a function call to a pvar. *) let handle_function_call call_node id = match Errdesc.find_normal_variable_funcall call_node id with - | Some (Exp.Const (Const.Cfun pn), _, _, _) - when not (ComplexExpressions.procname_used_in_condition pn) -> - begin - match ComplexExpressions.exp_to_string tenv node' exp with - | None -> default - | Some exp_str -> - let pvar = Pvar.mk (Mangled.from_string exp_str) curr_pname in - let already_defined_in_typestate = - match TypeState.lookup_pvar pvar typestate with - | Some (_, ta, _) -> - not (TypeOrigin.equal TypeOrigin.Undef (TypeAnnotation.get_origin ta)) - | None -> - false in - - if is_assignment && already_defined_in_typestate - then default (* Don't overwrite pvar representing result of function call. *) - else Exp.Lvar pvar, typestate - end - | _ -> default in - + | Some (Exp.Const Const.Cfun pn, _, _, _) + when not (ComplexExpressions.procname_used_in_condition pn) -> ( + match ComplexExpressions.exp_to_string tenv node' exp with + | None + -> default + | Some exp_str + -> let pvar = Pvar.mk (Mangled.from_string exp_str) curr_pname in + let already_defined_in_typestate = + match TypeState.lookup_pvar pvar typestate with + | Some (_, ta, _) + -> not (TypeOrigin.equal TypeOrigin.Undef (TypeAnnotation.get_origin ta)) + | None + -> false + in + if is_assignment && already_defined_in_typestate then default + (* Don't overwrite pvar representing result of function call. *) + else (Exp.Lvar pvar, typestate) ) + | _ + -> default + in match exp with - | Exp.Var id when - ComplexExpressions.functions_idempotent () && - Errdesc.find_normal_variable_funcall node' id <> None -> - handle_function_call node' id - | Exp.Lvar pvar when - ComplexExpressions.functions_idempotent () && Pvar.is_frontend_tmp pvar -> - let frontend_variable_assignment = - Errdesc.find_program_variable_assignment node pvar in - begin - match frontend_variable_assignment with - | Some (call_node, id) -> - handle_function_call call_node id - - | _ -> default - end - - | Exp.Lvar _ -> - default - | Exp.Lfield (exp_, fn, typ) when ComplexExpressions.parameter_and_static_field () -> - let inner_origin = - (match exp_ with - | Exp.Lvar pvar -> TypeState.lookup_pvar pvar typestate - | Exp.Var id -> TypeState.lookup_id id typestate - | _ -> None) - |> - Option.value_map - ~f:(fun (_, ta, _) -> TypeAnnotation.get_origin ta) - ~default:TypeOrigin.ONone in + | Exp.Var id + when ComplexExpressions.functions_idempotent () + && Errdesc.find_normal_variable_funcall node' id <> None + -> handle_function_call node' id + | Exp.Lvar pvar when ComplexExpressions.functions_idempotent () && Pvar.is_frontend_tmp pvar + -> ( + let frontend_variable_assignment = Errdesc.find_program_variable_assignment node pvar in + match frontend_variable_assignment with + | Some (call_node, id) + -> handle_function_call call_node id + | _ + -> default ) + | Exp.Lvar _ + -> default + | Exp.Lfield (exp_, fn, typ) when ComplexExpressions.parameter_and_static_field () + -> let inner_origin = + ( match exp_ with + | Exp.Lvar pvar + -> TypeState.lookup_pvar pvar typestate + | Exp.Var id + -> TypeState.lookup_id id typestate + | _ + -> None ) + |> Option.value_map + ~f:(fun (_, ta, _) -> TypeAnnotation.get_origin ta) + ~default:TypeOrigin.ONone + in let exp' = Idenv.expand_expr_temps idenv node exp_ in - - let is_parameter_field pvar = (* parameter.field *) + let is_parameter_field pvar = + (* parameter.field *) let name = Pvar.get_name pvar in let filter (s, _, _) = Mangled.equal s name in - List.exists ~f:filter annotated_signature.AnnotatedSignature.params in - - let is_static_field pvar = (* static field *) - Pvar.is_global pvar in - + List.exists ~f:filter annotated_signature.AnnotatedSignature.params + in + let is_static_field pvar = + (* static field *) + Pvar.is_global pvar + in let pvar_to_str pvar = - if Exp.is_this (Exp.Lvar pvar) then "" - else Pvar.to_string pvar ^ "_" in - - let res = match exp' with - | Exp.Lvar pv when is_parameter_field pv || is_static_field pv -> - let fld_name = pvar_to_str pv ^ Typ.Fieldname.to_string fn in + if Exp.is_this (Exp.Lvar pvar) then "" else Pvar.to_string pvar ^ "_" + in + let res = + match exp' with + | Exp.Lvar pv when is_parameter_field pv || is_static_field pv + -> let fld_name = pvar_to_str pv ^ Typ.Fieldname.to_string fn in let pvar = Pvar.mk (Mangled.from_string fld_name) curr_pname in let typestate' = update_typestate_fld pvar inner_origin fn typ in (Exp.Lvar pvar, typestate') - | Exp.Lfield (_exp', fn', _) when Typ.Fieldname.java_is_outer_instance fn' -> - (* handle double dereference when accessing a field from an outer class *) + | Exp.Lfield (_exp', fn', _) when Typ.Fieldname.java_is_outer_instance fn' + -> (* handle double dereference when accessing a field from an outer class *) let fld_name = Typ.Fieldname.to_string fn' ^ "_" ^ Typ.Fieldname.to_string fn in let pvar = Pvar.mk (Mangled.from_string fld_name) curr_pname in let typestate' = update_typestate_fld pvar inner_origin fn typ in (Exp.Lvar pvar, typestate') - | Exp.Lvar _ | Exp.Lfield _ when ComplexExpressions.all_nested_fields () -> + | (Exp.Lvar _ | Exp.Lfield _) when ComplexExpressions.all_nested_fields () -> ( + match (* treat var.field1. ... .fieldn as a constant *) - begin - match ComplexExpressions.exp_to_string tenv node' exp with - | Some exp_str -> - let pvar = Pvar.mk (Mangled.from_string exp_str) curr_pname in - let typestate' = update_typestate_fld pvar inner_origin fn typ in - (Exp.Lvar pvar, typestate') - | None -> - default - end - | _ -> - default in + ComplexExpressions.exp_to_string tenv node' exp + with + | Some exp_str + -> let pvar = Pvar.mk (Mangled.from_string exp_str) curr_pname in + let typestate' = update_typestate_fld pvar inner_origin fn typ in + (Exp.Lvar pvar, typestate') + | None + -> default ) + | _ + -> default + in res - | _ -> default in - + | _ + -> default + in let constructor_check_calls_this calls_this pn = - match curr_pname, pn with - | Typ.Procname.Java curr_pname_java, Typ.Procname.Java pn_java -> - if String.equal - (Typ.Procname.java_get_class_name curr_pname_java) - (Typ.Procname.java_get_class_name pn_java) + match (curr_pname, pn) with + | Typ.Procname.Java curr_pname_java, Typ.Procname.Java pn_java + -> if String.equal (Typ.Procname.java_get_class_name curr_pname_java) + (Typ.Procname.java_get_class_name pn_java) then calls_this := true - | _ -> - () in - + | _ + -> () + in (* Drops hidden and synthetic parameters which we do not check in a call. *) let drop_unchecked_params calls_this proc_attributes params = let pname = proc_attributes.ProcAttributes.proc_name in if Typ.Procname.is_constructor pname then match PatternMatch.get_this_type proc_attributes with - | Some _ -> - begin - constructor_check_calls_this calls_this pname; - - (* Drop reference parameters to this and outer objects. *) - let is_hidden_parameter (n, _) = - let n_str = Mangled.to_string n in - String.equal n_str "this" || - Str.string_match (Str.regexp "$bcvar[0-9]+") n_str 0 in - let rec drop_n_args ntl = match ntl with - | fp:: tail when is_hidden_parameter fp -> 1 + drop_n_args tail - | _ -> 0 in - let n = drop_n_args proc_attributes.ProcAttributes.formals in - let visible_params = List.drop params n in - - (* Drop the trailing hidden parameter if the constructor is synthetic. *) - if proc_attributes.ProcAttributes.is_synthetic_method then - List.take visible_params (List.length visible_params - 1) - else - visible_params - end - | None -> params - else - params in - + | Some _ + -> constructor_check_calls_this calls_this pname ; + (* Drop reference parameters to this and outer objects. *) + let is_hidden_parameter (n, _) = + let n_str = Mangled.to_string n in + String.equal n_str "this" || Str.string_match (Str.regexp "$bcvar[0-9]+") n_str 0 + in + let rec drop_n_args ntl = + match ntl with + | fp :: tail when is_hidden_parameter fp + -> 1 + drop_n_args tail + | _ + -> 0 + in + let n = drop_n_args proc_attributes.ProcAttributes.formals in + let visible_params = List.drop params n in + (* Drop the trailing hidden parameter if the constructor is synthetic. *) + if proc_attributes.ProcAttributes.is_synthetic_method then + List.take visible_params (List.length visible_params - 1) + else visible_params + | None + -> params + else params + in (* Drop parameters from the signature which we do not check in a call. *) let drop_unchecked_signature_params proc_attributes annotated_signature = - if Typ.Procname.is_constructor (proc_attributes.ProcAttributes.proc_name) && - proc_attributes.ProcAttributes.is_synthetic_method then - List.take - annotated_signature.AnnotatedSignature.params + if Typ.Procname.is_constructor proc_attributes.ProcAttributes.proc_name + && proc_attributes.ProcAttributes.is_synthetic_method + then + List.take annotated_signature.AnnotatedSignature.params (List.length annotated_signature.AnnotatedSignature.params - 1) - else - annotated_signature.AnnotatedSignature.params in - + else annotated_signature.AnnotatedSignature.params + in let is_return pvar = let ret_pvar = Procdesc.get_ret_var curr_pdesc in - Pvar.equal pvar ret_pvar in - + Pvar.equal pvar ret_pvar + in (* Apply a function to a pvar and its associated content if front-end generated. *) let pvar_apply loc handle_pvar typestate pvar = let typestate' = handle_pvar typestate pvar in let curr_node = TypeErr.InstrRef.get_node instr_ref in let frontent_variable_assignment = - if Pvar.is_frontend_tmp pvar - then Errdesc.find_program_variable_assignment curr_node pvar - else None in + if Pvar.is_frontend_tmp pvar then Errdesc.find_program_variable_assignment curr_node pvar + else None + in match frontent_variable_assignment with - | None -> - typestate' - | Some (node', id) -> - (* handle the case where pvar is a frontend-generated program variable *) + | None + -> typestate' + | Some (node', id) + -> (* handle the case where pvar is a frontend-generated program variable *) let exp = Idenv.expand_expr idenv (Exp.Var id) in - begin - match convert_complex_exp_to_pvar node' false exp typestate' loc with - | Exp.Lvar pvar', _ -> handle_pvar typestate' pvar' - | _ -> typestate' - end in - - + match convert_complex_exp_to_pvar node' false exp typestate' loc with + | Exp.Lvar pvar', _ + -> handle_pvar typestate' pvar' + | _ + -> typestate' + in (* typecheck_expr with fewer parameters, using a common template for typestate range *) let typecheck_expr_simple typestate1 exp1 typ1 origin1 loc1 = - typecheck_expr - find_canonical_duplicate calls_this checks tenv node instr_ref - curr_pdesc typestate1 exp1 - (typ1, TypeAnnotation.const AnnotatedSignature.Nullable false origin1, [loc1]) - loc1 in - + typecheck_expr find_canonical_duplicate calls_this checks tenv node instr_ref curr_pdesc + typestate1 exp1 + (typ1, TypeAnnotation.const AnnotatedSignature.Nullable false origin1, [loc1]) loc1 + in (* check if there are errors in exp1 *) let typecheck_expr_for_errors typestate1 exp1 loc1 : unit = - ignore (typecheck_expr_simple typestate1 exp1 (Typ.mk Tvoid) TypeOrigin.Undef loc1) in - + ignore (typecheck_expr_simple typestate1 exp1 (Typ.mk Tvoid) TypeOrigin.Undef loc1) + in match instr with - | Sil.Remove_temps (idl, _) -> - if remove_temps then List.fold_right ~f:TypeState.remove_id idl ~init:typestate + | Sil.Remove_temps (idl, _) + -> if remove_temps then List.fold_right ~f:TypeState.remove_id idl ~init:typestate else typestate - | Sil.Declare_locals _ - | Sil.Abstract _ - | Sil.Nullify _ -> typestate - | Sil.Load (id, e, typ, loc) -> - typecheck_expr_for_errors typestate e loc; + | Sil.Declare_locals _ | Sil.Abstract _ | Sil.Nullify _ + -> typestate + | Sil.Load (id, e, typ, loc) + -> typecheck_expr_for_errors typestate e loc ; let e', typestate' = convert_complex_exp_to_pvar node false e typestate loc in - TypeState.add_id id - (typecheck_expr_simple typestate' e' typ TypeOrigin.Undef loc) - typestate' - | Sil.Store (Exp.Lvar pvar, _, Exp.Exn _, _) when is_return pvar -> - (* skip assignment to return variable where it is an artifact of a throw instruction *) + TypeState.add_id id (typecheck_expr_simple typestate' e' typ TypeOrigin.Undef loc) typestate' + | Sil.Store (Exp.Lvar pvar, _, Exp.Exn _, _) when is_return pvar + -> (* skip assignment to return variable where it is an artifact of a throw instruction *) typestate - | Sil.Store (e1, typ, e2, loc) -> - typecheck_expr_for_errors typestate e1 loc; + | Sil.Store (e1, typ, e2, loc) + -> typecheck_expr_for_errors typestate e1 loc ; let e1', typestate1 = convert_complex_exp_to_pvar node true e1 typestate loc in - let check_field_assign () = match e1 with - | Exp.Lfield (_, fn, f_typ) -> - let t_ia_opt = EradicateChecks.get_field_annotation tenv fn f_typ in + let check_field_assign () = + match e1 with + | Exp.Lfield (_, fn, f_typ) + -> let t_ia_opt = EradicateChecks.get_field_annotation tenv fn f_typ in if checks.eradicate then - EradicateChecks.check_field_assignment tenv - find_canonical_duplicate curr_pdesc node + EradicateChecks.check_field_assignment tenv find_canonical_duplicate curr_pdesc node instr_ref typestate1 e1' e2 typ loc fn t_ia_opt (typecheck_expr find_canonical_duplicate calls_this checks tenv) - | _ -> () in + | _ + -> () + in let typestate2 = match e1' with - | Exp.Lvar pvar -> - TypeState.add - pvar - (typecheck_expr_simple typestate1 e2 typ TypeOrigin.Undef loc) + | Exp.Lvar pvar + -> TypeState.add pvar (typecheck_expr_simple typestate1 e2 typ TypeOrigin.Undef loc) typestate1 - | Exp.Lfield _ -> - typestate1 - | _ -> - typestate1 in - check_field_assign (); - typestate2 - | Sil.Call (Some (id, _), Exp.Const (Const.Cfun pn), [(_, typ)], loc, _) - when Typ.Procname.equal pn BuiltinDecl.__new || - Typ.Procname.equal pn BuiltinDecl.__new_array -> - TypeState.add_id - id + | Exp.Lfield _ + -> typestate1 + | _ + -> typestate1 + in + check_field_assign () ; typestate2 + | Sil.Call (Some (id, _), Exp.Const Const.Cfun pn, [(_, typ)], loc, _) + when Typ.Procname.equal pn BuiltinDecl.__new || Typ.Procname.equal pn BuiltinDecl.__new_array + -> TypeState.add_id id (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.New, [loc]) - typestate (* new never returns null *) - | Sil.Call (Some (id, _), Exp.Const (Const.Cfun pn), (e, typ):: _, loc, _) - when Typ.Procname.equal pn BuiltinDecl.__cast -> - typecheck_expr_for_errors typestate e loc; - let e', typestate' = - convert_complex_exp_to_pvar node false e typestate loc in + typestate + (* new never returns null *) + | Sil.Call (Some (id, _), Exp.Const Const.Cfun pn, (e, typ) :: _, loc, _) + when Typ.Procname.equal pn BuiltinDecl.__cast + -> typecheck_expr_for_errors typestate e loc ; + let e', typestate' = convert_complex_exp_to_pvar node false e typestate loc in (* cast copies the type of the first argument *) - TypeState.add_id id - (typecheck_expr_simple typestate' e' typ TypeOrigin.ONone loc) - typestate' - | Sil.Call (Some (id, _), Exp.Const (Const.Cfun pn), [(array_exp, t)], loc, _) - when Typ.Procname.equal pn BuiltinDecl.__get_array_length -> - let (_, ta, _) = typecheck_expr - find_canonical_duplicate - calls_this - checks tenv - node - instr_ref - curr_pdesc - typestate - array_exp - (t, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) - loc in + TypeState.add_id id (typecheck_expr_simple typestate' e' typ TypeOrigin.ONone loc) typestate' + | Sil.Call (Some (id, _), Exp.Const Const.Cfun pn, [(array_exp, t)], loc, _) + when Typ.Procname.equal pn BuiltinDecl.__get_array_length + -> let _, ta, _ = + typecheck_expr find_canonical_duplicate calls_this checks tenv node instr_ref curr_pdesc + typestate array_exp + (t, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc + in if checks.eradicate then - EradicateChecks.check_array_access tenv - find_canonical_duplicate - curr_pdesc - node - instr_ref - array_exp - (Typ.Fieldname.Java.from_string "length") - ta - loc - false; - TypeState.add_id - id - ( - Typ.mk (Tint (Typ.IInt)), - TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.New, - [loc] - ) - typestate - | Sil.Call (_, Exp.Const (Const.Cfun pn), _, _, _) when BuiltinDecl.is_declared pn -> - typestate (* skip othe builtins *) + EradicateChecks.check_array_access tenv find_canonical_duplicate curr_pdesc node instr_ref + array_exp (Typ.Fieldname.Java.from_string "length") ta loc false ; + TypeState.add_id id + ( Typ.mk (Tint Typ.IInt) + , TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.New + , [loc] ) typestate + | Sil.Call (_, Exp.Const Const.Cfun pn, _, _, _) when BuiltinDecl.is_declared pn + -> typestate (* skip othe builtins *) | Sil.Call - (ret_id, - Exp.Const (Const.Cfun ((Typ.Procname.Java callee_pname_java) as callee_pname)), - etl_, - loc, - cflags) - -> - ignore (Ondemand.analyze_proc_name ~propagate_exceptions:true curr_pdesc callee_pname); + ( ret_id + , Exp.Const Const.Cfun (Typ.Procname.Java callee_pname_java as callee_pname) + , etl_ + , loc + , cflags ) + -> ignore (Ondemand.analyze_proc_name ~propagate_exceptions:true curr_pdesc callee_pname) ; let callee_attributes = match Specs.proc_resolve_attributes (* AttributesTable.load_attributes *) callee_pname with - | Some proc_attributes -> - proc_attributes - | None -> - let formals = + | Some proc_attributes + -> proc_attributes + | None + -> let formals = List.mapi ~f:(fun i (_, typ) -> - let arg = - if Int.equal i 0 && - not (Typ.Procname.java_is_static callee_pname) - then "this" - else Printf.sprintf "arg%d" i in - (Mangled.from_string arg, typ)) - etl_ in + let arg = + if Int.equal i 0 && not (Typ.Procname.java_is_static callee_pname) then "this" + else Printf.sprintf "arg%d" i + in + (Mangled.from_string arg, typ)) + etl_ + in let ret_type = Typ.java_proc_return_typ callee_pname_java in let proc_attributes = { (ProcAttributes.default callee_pname Config.Java) with - ProcAttributes.formals; - ret_type; - } in - proc_attributes in - + ProcAttributes.formals= formals; ret_type } + in + proc_attributes + in let etl = drop_unchecked_params calls_this callee_attributes etl_ in let call_params, typestate1 = let handle_et (e1, t1) (etl1, typestate1) = - typecheck_expr_for_errors typestate e1 loc; + typecheck_expr_for_errors typestate e1 loc ; let e2, typestate2 = convert_complex_exp_to_pvar node false e1 typestate1 loc in - (((e1, e2), t1) :: etl1), typestate2 in - List.fold_right ~f:handle_et etl ~init:([], typestate) in - - let annotated_signature = - Models.get_modelled_annotated_signature callee_attributes in + (((e1, e2), t1) :: etl1, typestate2) + in + List.fold_right ~f:handle_et etl ~init:([], typestate) + in + let annotated_signature = Models.get_modelled_annotated_signature callee_attributes in let signature_params = - drop_unchecked_signature_params callee_attributes annotated_signature in - + drop_unchecked_signature_params callee_attributes annotated_signature + in let is_anonymous_inner_class_constructor = - Typ.Procname.java_is_anonymous_inner_class_constructor callee_pname in - + Typ.Procname.java_is_anonymous_inner_class_constructor callee_pname + in let do_return (ret_ta, ret_typ) loc' typestate' = - let mk_return_range () = - ( - ret_typ, - ret_ta, - [loc'] - ) in - + let mk_return_range () = (ret_typ, ret_ta, [loc']) in match ret_id with - | None -> - typestate' - | Some (id, _) -> - TypeState.add_id - id - (mk_return_range ()) - typestate' in - + | None + -> typestate' + | Some (id, _) + -> TypeState.add_id id (mk_return_range ()) typestate' + in (* Handle Preconditions.checkNotNull. *) let do_preconditions_check_not_null parameter_num ~is_vararg typestate' = (* clear the nullable flag of the first parameter of the procedure *) let clear_nullable_flag typestate'' pvar = (* remove the nullable flag for the given pvar *) match TypeState.lookup_pvar pvar typestate'' with - | Some (t, ta, _) -> - let should_report = - Config.eradicate_condition_redundant && - not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta) && - not (TypeAnnotation.origin_is_fun_library ta) in - if checks.eradicate && should_report then - begin + | Some (t, ta, _) + -> let should_report = + Config.eradicate_condition_redundant + && not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta) + && not (TypeAnnotation.origin_is_fun_library ta) + in + ( if checks.eradicate && should_report then let cond = Exp.BinOp (Binop.Ne, Exp.Lvar pvar, Exp.null) in - EradicateChecks.report_error tenv - find_canonical_duplicate + EradicateChecks.report_error tenv find_canonical_duplicate (TypeErr.Condition_redundant - (true, EradicateChecks.explain_expr tenv node cond, false)) - (Some instr_ref) - loc curr_pdesc - end; - TypeState.add - pvar + (true, EradicateChecks.explain_expr tenv node cond, false)) (Some instr_ref) + loc curr_pdesc ) ; + TypeState.add pvar (t, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) typestate'' - | None -> - typestate' in - let rec find_parameter n eetl1 = match n, eetl1 with - | n, _ :: eetl2 when n > 1 -> find_parameter (n -1) eetl2 - | 1, ((_, Exp.Lvar pvar), typ):: _ -> Some (pvar, typ) - | _ -> None in - + | None + -> typestate' + in + let rec find_parameter n eetl1 = + match (n, eetl1) with + | n, _ :: eetl2 when n > 1 + -> find_parameter (n - 1) eetl2 + | 1, ((_, Exp.Lvar pvar), typ) :: _ + -> Some (pvar, typ) + | _ + -> None + in match find_parameter parameter_num call_params with - | Some (pvar, _) -> - if is_vararg - then - let do_vararg_value e ts = match Idenv.expand_expr idenv e with - | Exp.Lvar pvar1 -> - pvar_apply loc clear_nullable_flag ts pvar1 - | _ -> ts in + | Some (pvar, _) + -> if is_vararg then + let do_vararg_value e ts = + match Idenv.expand_expr idenv e with + | Exp.Lvar pvar1 + -> pvar_apply loc clear_nullable_flag ts pvar1 + | _ + -> ts + in let vararg_values = PatternMatch.java_get_vararg_values node pvar idenv in List.fold_right ~f:do_vararg_value vararg_values ~init:typestate' - else - pvar_apply loc clear_nullable_flag typestate' pvar - | None -> typestate' in - - + else pvar_apply loc clear_nullable_flag typestate' pvar + | None + -> typestate' + in (* Handle Preconditions.checkState for &&-separated conditions x!=null. *) let do_preconditions_check_state typestate' = - let handle_pvar ann b typestate1 pvar = (* handle the annotation flag for pvar *) + let handle_pvar ann b typestate1 pvar = + (* handle the annotation flag for pvar *) match TypeState.lookup_pvar pvar typestate1 with - | Some (t, _, _) -> - TypeState.add - pvar - (t, TypeAnnotation.const ann b TypeOrigin.ONone, [loc]) - typestate1 - | None -> - typestate1 in - + | Some (t, _, _) + -> TypeState.add pvar (t, TypeAnnotation.const ann b TypeOrigin.ONone, [loc]) typestate1 + | None + -> typestate1 + in let res_typestate = ref typestate' in - - let set_flag pvar ann b = (* set the annotation flag for pvar *) - res_typestate := pvar_apply loc (handle_pvar ann b) !res_typestate pvar in - + let set_flag pvar ann b = + (* set the annotation flag for pvar *) + res_typestate := pvar_apply loc (handle_pvar ann b) !res_typestate pvar + in let handle_negated_condition cond_node = - let do_instr = (function - | Sil.Prune (Exp.BinOp (Binop.Eq, _cond_e, Exp.Const (Const.Cint i)), _, _, _) - | Sil.Prune (Exp.BinOp (Binop.Eq, Exp.Const (Const.Cint i), _cond_e), _, _, _) - when IntLit.iszero i -> + let do_instr = + function[@warning "-57"] + | Sil.Prune (Exp.BinOp (Binop.Eq, _cond_e, Exp.Const Const.Cint i), _, _, _) + | Sil.Prune (Exp.BinOp (Binop.Eq, Exp.Const Const.Cint i, _cond_e), _, _, _) + when IntLit.iszero i + -> ( let cond_e = Idenv.expand_expr_temps idenv cond_node _cond_e in - begin - match convert_complex_exp_to_pvar cond_node false cond_e typestate' loc with - | Exp.Lvar pvar', _ -> - set_flag pvar' AnnotatedSignature.Nullable false - | _ -> () - end - | _ -> ()) [@warning "-57"] (* FIXME: silenced warning may be legit *) in - List.iter ~f:do_instr (Procdesc.Node.get_instrs cond_node) in + match convert_complex_exp_to_pvar cond_node false cond_e typestate' loc with + | Exp.Lvar pvar', _ + -> set_flag pvar' AnnotatedSignature.Nullable false + | _ + -> () ) + | _ + -> () + (* FIXME: silenced warning may be legit *) + in + List.iter ~f:do_instr (Procdesc.Node.get_instrs cond_node) + in let handle_optional_isPresent node' e = match convert_complex_exp_to_pvar node' false e typestate' loc with - | Exp.Lvar pvar', _ -> - set_flag pvar' AnnotatedSignature.Present true - | _ -> () in + | Exp.Lvar pvar', _ + -> set_flag pvar' AnnotatedSignature.Present true + | _ + -> () + in match call_params with - | ((_, Exp.Lvar pvar), _):: _ -> + | ((_, Exp.Lvar pvar), _) :: _ + -> ( (* temporary variable for the value of the boolean condition *) - begin - let curr_node = TypeErr.InstrRef.get_node instr_ref in - let branch = false in - match Errdesc.find_boolean_assignment curr_node pvar branch with - (* In foo(cond1 && cond2), the node that sets the result to false + let curr_node = TypeErr.InstrRef.get_node instr_ref in + let branch = false in + match Errdesc.find_boolean_assignment curr_node pvar branch with + (* In foo(cond1 && cond2), the node that sets the result to false has all the negated conditions as parents. *) - | Some boolean_assignment_node -> - List.iter - ~f:handle_negated_condition - (Procdesc.Node.get_preds boolean_assignment_node); - !res_typestate - | None -> - begin - match Errdesc.find_program_variable_assignment curr_node pvar with - | None -> - () - | Some (node', id) -> - let () = match Errdesc.find_normal_variable_funcall node' id with - | Some (Exp.Const (Const.Cfun pn), [e], _, _) - when ComplexExpressions.procname_optional_isPresent pn -> - handle_optional_isPresent node' e - | _ -> () in - () - end; - !res_typestate - end - | _ -> typestate' in - + | Some boolean_assignment_node + -> List.iter ~f:handle_negated_condition + (Procdesc.Node.get_preds boolean_assignment_node) ; + !res_typestate + | None + -> ( match Errdesc.find_program_variable_assignment curr_node pvar with + | None + -> () + | Some (node', id) + -> let () = + match Errdesc.find_normal_variable_funcall node' id with + | Some (Exp.Const Const.Cfun pn, [e], _, _) + when ComplexExpressions.procname_optional_isPresent pn + -> handle_optional_isPresent node' e + | _ + -> () + in + () ) ; + !res_typestate ) + | _ + -> typestate' + in (* Handle m.put(k,v) as assignment pvar = v for the pvar associated to m.get(k) *) let do_map_put typestate' = (* Get the proc name for map.get() from map.put() *) @@ -758,457 +698,428 @@ let typecheck_instr let parameters = [object_t] in Typ.Procname.java_replace_parameters (Typ.Procname.java_replace_return_type - (Typ.Procname.java_replace_method pname_put "get") - object_t) - parameters in + (Typ.Procname.java_replace_method pname_put "get") object_t) + parameters + in match call_params with - | ((_, Exp.Lvar pv_map), _) :: - ((_, exp_key), _) :: - ((_, exp_value), typ_value) :: _ -> + | ((_, Exp.Lvar pv_map), _) :: ((_, exp_key), _) :: ((_, exp_value), typ_value) :: _ + -> ( (* Convert the dexp for k to the dexp for m.get(k) *) - let convert_dexp_key_to_dexp_get dopt = match dopt, callee_pname with - | Some dexp_key, Typ.Procname.Java callee_pname_java -> - let pname_get = - Typ.Procname.Java (pname_get_from_pname_put callee_pname_java) in + let convert_dexp_key_to_dexp_get dopt = + match (dopt, callee_pname) with + | Some dexp_key, Typ.Procname.Java callee_pname_java + -> let pname_get = Typ.Procname.Java (pname_get_from_pname_put callee_pname_java) in let dexp_get = DExp.Dconst (Const.Cfun pname_get) in let dexp_map = DExp.Dpvar pv_map in let args = [dexp_map; dexp_key] in - let call_flags = { CallFlags.default with CallFlags.cf_virtual = true } in + let call_flags = {CallFlags.default with CallFlags.cf_virtual= true} in Some (DExp.Dretcall (dexp_get, args, loc, call_flags)) - | _ -> None in - begin - match ComplexExpressions.exp_to_string_map_dexp tenv - convert_dexp_key_to_dexp_get node exp_key with - | Some map_get_str -> - let pvar_map_get = Pvar.mk (Mangled.from_string map_get_str) curr_pname in - TypeState.add - pvar_map_get - (typecheck_expr_simple typestate' exp_value typ_value TypeOrigin.Undef loc) - typestate' - | None -> + | _ + -> None + in + match + ComplexExpressions.exp_to_string_map_dexp tenv convert_dexp_key_to_dexp_get node + exp_key + with + | Some map_get_str + -> let pvar_map_get = Pvar.mk (Mangled.from_string map_get_str) curr_pname in + TypeState.add pvar_map_get + (typecheck_expr_simple typestate' exp_value typ_value TypeOrigin.Undef loc) typestate' - end - | _ -> - typestate' in - + | None + -> typestate' ) + | _ + -> typestate' + in let typestate_after_call, resolved_ret = let resolve_param i (sparam, cparam) = - let (s1, ia1, t1) = sparam in - let ((orig_e2, e2), t2) = cparam in + let s1, ia1, t1 = sparam in + let (orig_e2, e2), t2 = cparam in let ta1 = TypeAnnotation.from_item_annotation ia1 (TypeOrigin.Formal s1) in - let (_, ta2, _) = - typecheck_expr - find_canonical_duplicate calls_this checks - tenv node instr_ref curr_pdesc typestate e2 - (t2, - TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, - []) - loc in + let _, ta2, _ = + typecheck_expr find_canonical_duplicate calls_this checks tenv node instr_ref + curr_pdesc typestate e2 + (t2, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, []) loc + in let formal = (s1, ta1, t1) in let actual = (orig_e2, ta2) in - let num = i+1 in + let num = i + 1 in let formal_is_propagates_nullable = Annotations.ia_is_propagates_nullable ia1 in let actual_is_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable ta2 in let propagates_nullable = formal_is_propagates_nullable && actual_is_nullable in - EradicateChecks.{num; formal; actual; propagates_nullable} in - + EradicateChecks.{num; formal; actual; propagates_nullable} + in (* Apply a function that operates on annotations *) - let apply_annotation_transformer - resolved_ret (resolved_params : EradicateChecks.resolved_param list) = + let apply_annotation_transformer resolved_ret + (resolved_params: EradicateChecks.resolved_param list) = let rec handle_params resolved_ret params = match (params : EradicateChecks.resolved_param list) with - | param :: params' - when param.propagates_nullable -> - let (_, actual_ta) = param.actual in + | param :: params' when param.propagates_nullable + -> let _, actual_ta = param.actual in let resolved_ret' = - let (ret_ta, ret_typ) = resolved_ret in + let ret_ta, ret_typ = resolved_ret in let ret_ta' = let actual_nullable = - TypeAnnotation.get_value AnnotatedSignature.Nullable actual_ta in + TypeAnnotation.get_value AnnotatedSignature.Nullable actual_ta + in let old_nullable = - TypeAnnotation.get_value AnnotatedSignature.Nullable ret_ta in - let new_nullable = - old_nullable || actual_nullable in - TypeAnnotation.set_value - AnnotatedSignature.Nullable - new_nullable - ret_ta in - (ret_ta', ret_typ) in + TypeAnnotation.get_value AnnotatedSignature.Nullable ret_ta + in + let new_nullable = old_nullable || actual_nullable in + TypeAnnotation.set_value AnnotatedSignature.Nullable new_nullable ret_ta + in + (ret_ta', ret_typ) + in handle_params resolved_ret' params' - | _ :: params' -> - handle_params resolved_ret params' - | [] -> - resolved_ret in - handle_params resolved_ret resolved_params in - + | _ :: params' + -> handle_params resolved_ret params' + | [] + -> resolved_ret + in + handle_params resolved_ret resolved_params + in let resolved_ret_ = - let (ret_ia, ret_typ) = annotated_signature.AnnotatedSignature.ret in + let ret_ia, ret_typ = annotated_signature.AnnotatedSignature.ret in let is_library = Specs.proc_is_library callee_attributes in - let origin = TypeOrigin.Proc - { - TypeOrigin.pname = callee_pname; - loc; - annotated_signature; - is_library; - } in + let origin = + TypeOrigin.Proc {TypeOrigin.pname= callee_pname; loc; annotated_signature; is_library} + in let ret_ta = TypeAnnotation.from_item_annotation ret_ia origin in - (ret_ta, ret_typ) in - + (ret_ta, ret_typ) + in let sig_len = List.length signature_params in let call_len = List.length call_params in let min_len = min sig_len call_len in let slice l = let len = List.length l in - if len > min_len - then List.slice l (len - min_len) 0 - else l in + if len > min_len then List.slice l (len - min_len) 0 else l + in let sig_slice = slice signature_params in let call_slice = slice call_params in let sig_call_params = List.filter ~f:(fun (sparam, _) -> - let (s, _, _) = sparam in - let param_is_this = - String.equal (Mangled.to_string s) "this" || - String.is_prefix ~prefix:"this$" (Mangled.to_string s) in - not param_is_this) - (List.zip_exn sig_slice call_slice) in - + let s, _, _ = sparam in + let param_is_this = + String.equal (Mangled.to_string s) "this" + || String.is_prefix ~prefix:"this$" (Mangled.to_string s) + in + not param_is_this) + (List.zip_exn sig_slice call_slice) + in let resolved_params = List.mapi ~f:resolve_param sig_call_params in - let resolved_ret = - apply_annotation_transformer resolved_ret_ resolved_params in - + let resolved_ret = apply_annotation_transformer resolved_ret_ resolved_params in let typestate_after_call = - if not is_anonymous_inner_class_constructor then - begin - if Config.eradicate_debug then - begin - let unique_id = Typ.Procname.to_unique_id callee_pname in - let classification = - EradicateChecks.classify_procedure callee_attributes in - L.result " %s unique id: %s@." classification unique_id - end; - if cflags.CallFlags.cf_virtual && checks.eradicate then - EradicateChecks.check_call_receiver tenv - find_canonical_duplicate - curr_pdesc - node - typestate1 - call_params - callee_pname - instr_ref - loc - (typecheck_expr find_canonical_duplicate calls_this checks); - if checks.eradicate then - EradicateChecks.check_call_parameters tenv - find_canonical_duplicate - curr_pdesc - node - callee_attributes - resolved_params - loc - instr_ref; - let typestate2 = - if checks.check_extension then - let etl' = List.map ~f:(fun ((_, e), t) -> (e, t)) call_params in - let extension = TypeState.get_extension typestate1 in - let extension' = - ext.TypeState.check_instr - tenv get_proc_desc curr_pname curr_pdesc extension instr etl' in - TypeState.set_extension typestate1 extension' - else typestate1 in - let has_method pn name = match pn with - | Typ.Procname.Java pn_java -> - String.equal (Typ.Procname.java_get_method pn_java) name - | _ -> - false in - if Models.is_check_not_null callee_pname then - do_preconditions_check_not_null - (Models.get_check_not_null_parameter callee_pname) - ~is_vararg:false - typestate2 - else - if has_method callee_pname "checkNotNull" - && Typ.Procname.java_is_vararg callee_pname - then - let last_parameter = List.length call_params in - do_preconditions_check_not_null - last_parameter - ~is_vararg:true - typestate2 - else if Models.is_check_state callee_pname || - Models.is_check_argument callee_pname then - do_preconditions_check_state typestate2 - else if Models.is_mapPut callee_pname then - do_map_put typestate2 - else typestate2 - end - else typestate1 in - typestate_after_call, resolved_ret in + if not is_anonymous_inner_class_constructor then ( + ( if Config.eradicate_debug then + let unique_id = Typ.Procname.to_unique_id callee_pname in + let classification = EradicateChecks.classify_procedure callee_attributes in + L.result " %s unique id: %s@." classification unique_id ) ; + if cflags.CallFlags.cf_virtual && checks.eradicate then + EradicateChecks.check_call_receiver tenv find_canonical_duplicate curr_pdesc node + typestate1 call_params callee_pname instr_ref loc + (typecheck_expr find_canonical_duplicate calls_this checks) ; + if checks.eradicate then + EradicateChecks.check_call_parameters tenv find_canonical_duplicate curr_pdesc node + callee_attributes resolved_params loc instr_ref ; + let typestate2 = + if checks.check_extension then + let etl' = List.map ~f:(fun ((_, e), t) -> (e, t)) call_params in + let extension = TypeState.get_extension typestate1 in + let extension' = + ext.TypeState.check_instr tenv get_proc_desc curr_pname curr_pdesc extension + instr etl' + in + TypeState.set_extension typestate1 extension' + else typestate1 + in + let has_method pn name = + match pn with + | Typ.Procname.Java pn_java + -> String.equal (Typ.Procname.java_get_method pn_java) name + | _ + -> false + in + if Models.is_check_not_null callee_pname then + do_preconditions_check_not_null (Models.get_check_not_null_parameter callee_pname) + ~is_vararg:false typestate2 + else if has_method callee_pname "checkNotNull" + && Typ.Procname.java_is_vararg callee_pname + then + let last_parameter = List.length call_params in + do_preconditions_check_not_null last_parameter ~is_vararg:true typestate2 + else if Models.is_check_state callee_pname || Models.is_check_argument callee_pname + then do_preconditions_check_state typestate2 + else if Models.is_mapPut callee_pname then do_map_put typestate2 + else typestate2 ) + else typestate1 + in + (typestate_after_call, resolved_ret) + in do_return resolved_ret loc typestate_after_call - | Sil.Call _ -> - typestate - | Sil.Prune (cond, loc, true_branch, _) -> - let rec check_condition node' c : _ TypeState.t = + | Sil.Call _ + -> typestate + | Sil.Prune (cond, loc, true_branch, _) + -> let rec check_condition node' c : _ TypeState.t = (* check if the expression is coming from a call, and return the argument *) let from_call filter_callee e : Exp.t option = match e with - | Exp.Var id -> - begin - match Errdesc.find_normal_variable_funcall node' id with - | Some (Exp.Const (Const.Cfun pn), e1:: _, _, _) when - filter_callee pn -> - Some e1 - | _ -> None - end - | _ -> None in - + | Exp.Var id -> ( + match Errdesc.find_normal_variable_funcall node' id with + | Some (Exp.Const Const.Cfun pn, e1 :: _, _, _) when filter_callee pn + -> Some e1 + | _ + -> None ) + | _ + -> None + in (* check if the expression is coming from instanceof *) let from_instanceof e : Exp.t option = - from_call ComplexExpressions.procname_instanceof e in - + from_call ComplexExpressions.procname_instanceof e + in (* check if the expression is coming from Optional.isPresent *) let from_optional_isPresent e : Exp.t option = - from_call ComplexExpressions.procname_optional_isPresent e in - + from_call ComplexExpressions.procname_optional_isPresent e + in (* check if the expression is coming from a procedure returning false on null *) let from_is_false_on_null e : Exp.t option = - from_call ComplexExpressions.procname_is_false_on_null e in - + from_call ComplexExpressions.procname_is_false_on_null e + in (* check if the expression is coming from a procedure returning true on null *) let from_is_true_on_null e : Exp.t option = - from_call ComplexExpressions.procname_is_true_on_null e in - + from_call ComplexExpressions.procname_is_true_on_null e + in (* check if the expression is coming from Map.containsKey *) let from_containsKey e : Exp.t option = - from_call ComplexExpressions.procname_containsKey e in - + from_call ComplexExpressions.procname_containsKey e + in (* Turn x.containsKey(e) into the pvar for x.get(e) *) (* which is then treated as a normal condition != null. *) let handle_containsKey e = let map_dexp = function | Some - (DExp.Dretcall - (DExp.Dconst - (Const.Cfun (Typ.Procname.Java pname_java)), args, loc, call_flags)) -> - let pname_java' = + DExp.Dretcall + (DExp.Dconst Const.Cfun Typ.Procname.Java pname_java, args, loc, call_flags) + -> let pname_java' = let object_t = (Some "java.lang", "Object") in Typ.Procname.java_replace_return_type - (Typ.Procname.java_replace_method pname_java "get") - object_t in + (Typ.Procname.java_replace_method pname_java "get") object_t + in let fun_dexp = DExp.Dconst (Const.Cfun (Typ.Procname.Java pname_java')) in Some (DExp.Dretcall (fun_dexp, args, loc, call_flags)) - | _ -> None in - begin - match ComplexExpressions.exp_to_string_map_dexp tenv map_dexp node' e with - | Some e_str -> - let pvar = - Pvar.mk (Mangled.from_string e_str) curr_pname in - let e1 = Exp.Lvar pvar in - let (typ, ta, _) = - typecheck_expr_simple typestate e1 (Typ.mk Tvoid) TypeOrigin.ONone loc in - let range = (typ, ta, [loc]) in - let typestate1 = TypeState.add pvar range typestate in - typestate1, e1, EradicateChecks.From_containsKey - | None -> - typestate, e, EradicateChecks.From_condition - end in - - let set_flag e' ann b typestate2 = (* add constraint on e' for annotation ann *) + | _ + -> None + in + match ComplexExpressions.exp_to_string_map_dexp tenv map_dexp node' e with + | Some e_str + -> let pvar = Pvar.mk (Mangled.from_string e_str) curr_pname in + let e1 = Exp.Lvar pvar in + let typ, ta, _ = + typecheck_expr_simple typestate e1 (Typ.mk Tvoid) TypeOrigin.ONone loc + in + let range = (typ, ta, [loc]) in + let typestate1 = TypeState.add pvar range typestate in + (typestate1, e1, EradicateChecks.From_containsKey) + | None + -> (typestate, e, EradicateChecks.From_condition) + in + let set_flag e' ann b typestate2 = + (* add constraint on e' for annotation ann *) let handle_pvar typestate' pvar = match TypeState.lookup_pvar pvar typestate' with - | Some (t, ta1, locs) -> - if TypeAnnotation.get_value ann ta1 <> b then + | Some (t, ta1, locs) + -> if TypeAnnotation.get_value ann ta1 <> b then let ta2 = TypeAnnotation.set_value ann b ta1 in TypeState.add pvar (t, ta2, locs) typestate' else typestate' - | None -> typestate' in + | None + -> typestate' + in match e' with - | Exp.Lvar pvar -> - pvar_apply loc handle_pvar typestate2 pvar - | _ -> typestate2 in - begin match c with - | Exp.BinOp (Binop.Eq, Exp.Const (Const.Cint i), e) - | Exp.BinOp (Binop.Eq, e, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - typecheck_expr_for_errors typestate e loc; - let typestate1, e1, from_call = match from_is_true_on_null e with - | Some e1 -> - typestate, e1, EradicateChecks.From_is_true_on_null - | None -> - typestate, e, EradicateChecks.From_condition in - let e', typestate2 = convert_complex_exp_to_pvar node' false e1 typestate1 loc in - let (typ, ta, _) = - typecheck_expr_simple typestate2 e' (Typ.mk Tvoid) TypeOrigin.ONone loc in - - if checks.eradicate then - EradicateChecks.check_zero tenv - find_canonical_duplicate curr_pdesc - node' e' typ - ta true_branch EradicateChecks.From_condition - idenv linereader loc instr_ref; - begin - match from_call with - | EradicateChecks.From_is_true_on_null -> - (* if f returns true on null, then false branch implies != null *) - if TypeAnnotation.get_value AnnotatedSignature.Nullable ta - then set_flag e' AnnotatedSignature.Nullable false typestate2 - else typestate2 - | _ -> - typestate2 - end - - | Exp.BinOp (Binop.Ne, Exp.Const (Const.Cint i), e) - | Exp.BinOp (Binop.Ne, e, Exp.Const (Const.Cint i)) when IntLit.iszero i -> - typecheck_expr_for_errors typestate e loc; - let typestate1, e1, from_call = match from_instanceof e with - | Some e1 -> (* (e1 instanceof C) implies (e1 != null) *) - typestate, e1, EradicateChecks.From_instanceof + | Exp.Lvar pvar + -> pvar_apply loc handle_pvar typestate2 pvar + | _ + -> typestate2 + in + match[@warning "-57"] c with + | Exp.BinOp (Binop.Eq, Exp.Const Const.Cint i, e) + | Exp.BinOp (Binop.Eq, e, Exp.Const Const.Cint i) + when IntLit.iszero i + -> ( + typecheck_expr_for_errors typestate e loc ; + let typestate1, e1, from_call = + match from_is_true_on_null e with + | Some e1 + -> (typestate, e1, EradicateChecks.From_is_true_on_null) + | None + -> (typestate, e, EradicateChecks.From_condition) + in + let e', typestate2 = convert_complex_exp_to_pvar node' false e1 typestate1 loc in + let typ, ta, _ = + typecheck_expr_simple typestate2 e' (Typ.mk Tvoid) TypeOrigin.ONone loc + in + if checks.eradicate then + EradicateChecks.check_zero tenv find_canonical_duplicate curr_pdesc node' e' typ ta + true_branch EradicateChecks.From_condition idenv linereader loc instr_ref ; + match from_call with + | EradicateChecks.From_is_true_on_null + -> (* if f returns true on null, then false branch implies != null *) + if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then + set_flag e' AnnotatedSignature.Nullable false typestate2 + else typestate2 + | _ + -> typestate2 ) + | Exp.BinOp (Binop.Ne, Exp.Const Const.Cint i, e) + | Exp.BinOp (Binop.Ne, e, Exp.Const Const.Cint i) + when IntLit.iszero i + -> ( + typecheck_expr_for_errors typestate e loc ; + let typestate1, e1, from_call = + match from_instanceof e with + | Some e1 + -> (* (e1 instanceof C) implies (e1 != null) *) + (typestate, e1, EradicateChecks.From_instanceof) + | None -> + match from_optional_isPresent e with + | Some e1 + -> (typestate, e1, EradicateChecks.From_optional_isPresent) | None -> - begin - match from_optional_isPresent e with - | Some e1 -> - typestate, e1, EradicateChecks.From_optional_isPresent - | None -> - begin - match from_is_false_on_null e with - | Some e1 -> - typestate, e1, EradicateChecks.From_is_false_on_null - | None -> - begin - match from_containsKey e with - | Some _ when ComplexExpressions.functions_idempotent () -> - handle_containsKey e - | _ -> - typestate, e, EradicateChecks.From_condition - end - end - end in - let e', typestate2 = convert_complex_exp_to_pvar node' false e1 typestate1 loc in - let (typ, ta, _) = - typecheck_expr_simple typestate2 e' (Typ.mk Tvoid) TypeOrigin.ONone loc in - - if checks.eradicate then - EradicateChecks.check_nonzero tenv find_canonical_duplicate curr_pdesc - node e' typ ta true_branch from_call idenv linereader loc instr_ref; - begin - match from_call with - | EradicateChecks.From_optional_isPresent -> - if not (TypeAnnotation.get_value AnnotatedSignature.Present ta) - then set_flag e' AnnotatedSignature.Present true typestate2 - else typestate2 - | EradicateChecks.From_is_true_on_null -> - typestate2 - | EradicateChecks.From_condition - | EradicateChecks.From_containsKey - | EradicateChecks.From_instanceof - | EradicateChecks.From_is_false_on_null -> - if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then - set_flag e' AnnotatedSignature.Nullable false typestate2 - else typestate2 - end - - | Exp.UnOp (Unop.LNot, (Exp.BinOp (Binop.Eq, e1, e2)), _) -> - check_condition node' (Exp.BinOp (Binop.Ne, e1, e2)) - | Exp.UnOp (Unop.LNot, (Exp.BinOp (Binop.Ne, e1, e2)), _) -> - check_condition node' (Exp.BinOp (Binop.Eq, e1, e2)) - | _ -> typestate - end [@warning "-57"] (* FIXME: silenced warning may be legit *) in - + match from_is_false_on_null e with + | Some e1 + -> (typestate, e1, EradicateChecks.From_is_false_on_null) + | None -> + match from_containsKey e with + | Some _ when ComplexExpressions.functions_idempotent () + -> handle_containsKey e + | _ + -> (typestate, e, EradicateChecks.From_condition) + in + let e', typestate2 = convert_complex_exp_to_pvar node' false e1 typestate1 loc in + let typ, ta, _ = + typecheck_expr_simple typestate2 e' (Typ.mk Tvoid) TypeOrigin.ONone loc + in + if checks.eradicate then + EradicateChecks.check_nonzero tenv find_canonical_duplicate curr_pdesc node e' typ ta + true_branch from_call idenv linereader loc instr_ref ; + match from_call with + | EradicateChecks.From_optional_isPresent + -> if not (TypeAnnotation.get_value AnnotatedSignature.Present ta) then + set_flag e' AnnotatedSignature.Present true typestate2 + else typestate2 + | EradicateChecks.From_is_true_on_null + -> typestate2 + | EradicateChecks.From_condition + | EradicateChecks.From_containsKey + | EradicateChecks.From_instanceof + | EradicateChecks.From_is_false_on_null + -> if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then + set_flag e' AnnotatedSignature.Nullable false typestate2 + else typestate2 ) + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Eq, e1, e2), _) + -> check_condition node' (Exp.BinOp (Binop.Ne, e1, e2)) + | Exp.UnOp (Unop.LNot, Exp.BinOp (Binop.Ne, e1, e2), _) + -> check_condition node' (Exp.BinOp (Binop.Eq, e1, e2)) + | _ + -> typestate + (* FIXME: silenced warning may be legit *) + in (* Handle assigment fron a temp pvar in a condition. This recognizes the handling of temp variables in ((x = ...) != null) *) let handle_assignment_in_condition pvar = match Procdesc.Node.get_preds node with - | [prev_node] -> - let found = ref None in - let do_instr i = match i with - | Sil.Store (e, _, e', _) - when Exp.equal (Exp.Lvar pvar) (Idenv.expand_expr idenv e') -> - found := Some e - | _ -> () in - List.iter ~f:do_instr (Procdesc.Node.get_instrs prev_node); + | [prev_node] + -> let found = ref None in + let do_instr i = + match i with + | Sil.Store (e, _, e', _) when Exp.equal (Exp.Lvar pvar) (Idenv.expand_expr idenv e') + -> found := Some e + | _ + -> () + in + List.iter ~f:do_instr (Procdesc.Node.get_instrs prev_node) ; !found - | _ -> None in - + | _ + -> None + in (* Normalize the condition by resolving temp variables. *) - let rec normalize_cond _node _cond = match _cond with - | Exp.UnOp (Unop.LNot, c, top) -> - let node', c' = normalize_cond _node c in - node', Exp.UnOp (Unop.LNot, c', top) - | Exp.BinOp (bop, c1, c2) -> - let node', c1' = normalize_cond _node c1 in + let rec normalize_cond _node _cond = + match _cond with + | Exp.UnOp (Unop.LNot, c, top) + -> let node', c' = normalize_cond _node c in + (node', Exp.UnOp (Unop.LNot, c', top)) + | Exp.BinOp (bop, c1, c2) + -> let node', c1' = normalize_cond _node c1 in let node'', c2' = normalize_cond node' c2 in - node'', Exp.BinOp (bop, c1', c2') - | Exp.Var _ -> - let c' = Idenv.expand_expr idenv _cond in - if not (Exp.equal c' _cond) then normalize_cond _node c' - else _node, c' - | Exp.Lvar pvar when Pvar.is_frontend_tmp pvar -> - (match handle_assignment_in_condition pvar with - | None -> - (match Errdesc.find_program_variable_assignment _node pvar with - | Some (node', id) -> node', Exp.Var id - | None -> _node, _cond) - | Some e2 -> _node, e2) - | c -> _node, c in - + (node'', Exp.BinOp (bop, c1', c2')) + | Exp.Var _ + -> let c' = Idenv.expand_expr idenv _cond in + if not (Exp.equal c' _cond) then normalize_cond _node c' else (_node, c') + | Exp.Lvar pvar when Pvar.is_frontend_tmp pvar -> ( + match handle_assignment_in_condition pvar with + | None -> ( + match Errdesc.find_program_variable_assignment _node pvar with + | Some (node', id) + -> (node', Exp.Var id) + | None + -> (_node, _cond) ) + | Some e2 + -> (_node, e2) ) + | c + -> (_node, c) + in let node', ncond = normalize_cond node cond in check_condition node' ncond (** Typecheck the instructions in a cfg node. *) -let typecheck_node - tenv ext calls_this checks idenv get_proc_desc curr_pname curr_pdesc +let typecheck_node tenv ext calls_this checks idenv get_proc_desc curr_pname curr_pdesc find_canonical_duplicate annotated_signature typestate node linereader = - let instrs = Procdesc.Node.get_instrs node in let instr_ref_gen = TypeErr.InstrRef.create_generator node in - let typestates_exn = ref [] in let noreturn = ref false in - - let handle_exceptions typestate instr = match instr with - | Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) - when Models.is_noreturn callee_pname -> - noreturn := true - | Sil.Call (_, Exp.Const (Const.Cfun callee_pname), _, _, _) -> - let callee_attributes_opt = - Specs.proc_resolve_attributes callee_pname in + let handle_exceptions typestate instr = + match instr with + | Sil.Call (_, Exp.Const Const.Cfun callee_pname, _, _, _) when Models.is_noreturn callee_pname + -> noreturn := true + | Sil.Call (_, Exp.Const Const.Cfun callee_pname, _, _, _) + -> let callee_attributes_opt = Specs.proc_resolve_attributes callee_pname in (* check if the call might throw an exception *) - let has_exceptions = match callee_attributes_opt with - | Some callee_attributes -> - callee_attributes.ProcAttributes.exceptions <> [] - | None -> false in - if has_exceptions then - typestates_exn := typestate :: !typestates_exn - | Sil.Store (Exp.Lvar pv, _, _, _) when - Pvar.is_return pv && - Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.throw_kind -> - (* throw instruction *) + let has_exceptions = + match callee_attributes_opt with + | Some callee_attributes + -> callee_attributes.ProcAttributes.exceptions <> [] + | None + -> false + in + if has_exceptions then typestates_exn := typestate :: !typestates_exn + | Sil.Store (Exp.Lvar pv, _, _, _) + when Pvar.is_return pv + && Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.throw_kind + -> (* throw instruction *) typestates_exn := typestate :: !typestates_exn - | _ -> () in - + | _ + -> () + in let canonical_node = find_canonical_duplicate node in - let do_instruction ext typestate instr = - let instr_ref = (* keep unique instruction reference per-node *) - TypeErr.InstrRef.gen instr_ref_gen in + let instr_ref = + (* keep unique instruction reference per-node *) + TypeErr.InstrRef.gen instr_ref_gen + in let instr' = - typecheck_instr - tenv ext calls_this checks node idenv get_proc_desc curr_pname curr_pdesc - find_canonical_duplicate annotated_signature instr_ref linereader typestate instr in - handle_exceptions typestate instr; - instr' in - + typecheck_instr tenv ext calls_this checks node idenv get_proc_desc curr_pname curr_pdesc + find_canonical_duplicate annotated_signature instr_ref linereader typestate instr + in + handle_exceptions typestate instr ; instr' + in (* Reset 'always' field for forall errors to false. *) (* This is used to track if it is set to true for all visit to the node. *) - TypeErr.node_reset_forall canonical_node; - + TypeErr.node_reset_forall canonical_node ; let typestate_succ = List.fold ~f:(do_instruction ext) ~init:typestate instrs in let dont_propagate = - Procdesc.Node.equal_nodekind - (Procdesc.Node.get_kind node) - Procdesc.Node.exn_sink_kind (* don't propagate exceptions *) - || - !noreturn in - if dont_propagate - then [], [] (* don't propagate to exit node *) - else [typestate_succ], !typestates_exn + Procdesc.Node.equal_nodekind (Procdesc.Node.get_kind node) Procdesc.Node.exn_sink_kind + (* don't propagate exceptions *) + || !noreturn + in + if dont_propagate then ([], []) (* don't propagate to exit node *) + else ([typestate_succ], !typestates_exn) diff --git a/infer/src/eradicate/typeCheck.mli b/infer/src/eradicate/typeCheck.mli index 5407460a0..b7d81cfc9 100644 --- a/infer/src/eradicate/typeCheck.mli +++ b/infer/src/eradicate/typeCheck.mli @@ -9,7 +9,6 @@ open! IStd - (** Module type for the type checking functions. *) type check_return_type = @@ -19,16 +18,9 @@ type find_canonical_duplicate = Procdesc.Node.t -> Procdesc.Node.t type get_proc_desc = TypeState.get_proc_desc -type checks = - { - eradicate : bool; - check_extension : bool; - check_ret_type : check_return_type list; - } +type checks = {eradicate: bool; check_extension: bool; check_ret_type: check_return_type list} val typecheck_node : - Tenv.t -> 'a TypeState.ext -> - bool ref -> checks -> Idenv.t -> - get_proc_desc -> Typ.Procname.t -> Procdesc.t -> - find_canonical_duplicate -> AnnotatedSignature.t -> 'a TypeState.t -> - Procdesc.Node.t -> Printer.LineReader.t -> 'a TypeState.t list * 'a TypeState.t list + Tenv.t -> 'a TypeState.ext -> bool ref -> checks -> Idenv.t -> get_proc_desc -> Typ.Procname.t + -> Procdesc.t -> find_canonical_duplicate -> AnnotatedSignature.t -> 'a TypeState.t + -> Procdesc.Node.t -> Printer.LineReader.t -> 'a TypeState.t list * 'a TypeState.t list diff --git a/infer/src/eradicate/typeErr.ml b/infer/src/eradicate/typeErr.ml index 8a995a3d0..6ccf5f4fb 100644 --- a/infer/src/eradicate/typeErr.ml +++ b/infer/src/eradicate/typeErr.ml @@ -9,65 +9,77 @@ open! IStd module Hashtbl = Caml.Hashtbl - module L = Logging module MF = MarkupFormatter module P = Printf (** Module for Type Error messages. *) - (** Describe the origin of values propagated by the checker. *) -module type InstrRefT = -sig +module type InstrRefT = sig type t [@@deriving compare] + val equal : t -> t -> bool + type generator + val create_generator : Procdesc.Node.t -> generator + val gen : generator -> t + val get_node : t -> Procdesc.Node.t + val hash : t -> int + val replace_node : t -> Procdesc.Node.t -> t -end (* InstrRefT *) +end +(* InstrRefT *) (** Per-node instruction reference. *) -module InstrRef : InstrRefT = -struct +module InstrRef : InstrRefT = struct type t = Procdesc.Node.t * int [@@deriving compare] + let equal = [%compare.equal : t] + type generator = Procdesc.Node.t * int ref + let hash (n, i) = Hashtbl.hash (Procdesc.Node.hash n, i) + let get_node (n, _) = n + let replace_node (_, i) n' = (n', i) + let create_generator n = (n, ref 0) + let gen instr_ref_gen = - let (node, ir) = instr_ref_gen in - incr ir; - (node, !ir) -end (* InstrRef *) + let node, ir = instr_ref_gen in + incr ir ; (node, !ir) +end +(* InstrRef *) -type origin_descr = - string * - Location.t option * - AnnotatedSignature.t option (* callee signature *) +type origin_descr = string * Location.t option * AnnotatedSignature.t option +(* callee signature *) (* ignore origin descr *) let compare_origin_descr _ _ = 0 type parameter_not_nullable = - AnnotatedSignature.annotation * - string * (* description *) - int * (* parameter number *) - Typ.Procname.t * - Location.t * (* callee location *) - origin_descr -[@@deriving compare] + AnnotatedSignature.annotation + * string + * (* description *) + int + * (* parameter number *) + Typ.Procname.t + * Location.t + * (* callee location *) + origin_descr + [@@deriving compare] (** Instance of an error *) type err_instance = - | Condition_redundant of (bool * (string option) * bool) + | Condition_redundant of (bool * string option * bool) | Inconsistent_subclass_return_annotation of Typ.Procname.t * Typ.Procname.t | Inconsistent_subclass_parameter_annotation of string * int * Typ.Procname.t * Typ.Procname.t | Field_not_initialized of Typ.Fieldname.t * Typ.Procname.t @@ -75,63 +87,62 @@ type err_instance = | Field_annotation_inconsistent of AnnotatedSignature.annotation * Typ.Fieldname.t * origin_descr | Field_over_annotated of Typ.Fieldname.t * Typ.Procname.t | Null_field_access of string option * Typ.Fieldname.t * origin_descr * bool - | Call_receiver_annotation_inconsistent - of AnnotatedSignature.annotation * string option * Typ.Procname.t * origin_descr + | Call_receiver_annotation_inconsistent of + AnnotatedSignature.annotation * string option * Typ.Procname.t * origin_descr | Parameter_annotation_inconsistent of parameter_not_nullable | Return_annotation_inconsistent of AnnotatedSignature.annotation * Typ.Procname.t * origin_descr | Return_over_annotated of Typ.Procname.t -[@@deriving compare] - -module H = Hashtbl.Make(struct - type t = err_instance * InstrRef.t option [@@deriving compare] - - let equal = [%compare.equal : t] - - let err_instance_hash x = - let string_hash s = Hashtbl.hash s in - let string_opt_hash so = Hashtbl.hash so in - match x with - | Condition_redundant (b, so, nn) -> - Hashtbl.hash (1, b, string_opt_hash so, nn) - | Field_not_initialized (fn, pn) -> - Hashtbl.hash (2, string_hash ((Typ.Fieldname.to_string fn) ^ (Typ.Procname.to_string pn))) - | Field_not_mutable (fn, _) -> - Hashtbl.hash (3, string_hash (Typ.Fieldname.to_string fn)) - | Field_annotation_inconsistent (ann, fn, _) -> - Hashtbl.hash (4, ann, string_hash (Typ.Fieldname.to_string fn)) - | Field_over_annotated (fn, pn) -> - Hashtbl.hash (5, string_hash ((Typ.Fieldname.to_string fn) ^ (Typ.Procname.to_string pn))) - | Null_field_access (so, fn, _, _) -> - Hashtbl.hash (6, string_opt_hash so, string_hash (Typ.Fieldname.to_string fn)) - | Call_receiver_annotation_inconsistent (ann, so, pn, _) -> - Hashtbl.hash (7, ann, string_opt_hash so, Typ.Procname.hash_pname pn) - | Parameter_annotation_inconsistent (ann, s, n, pn, _, _) -> - Hashtbl.hash (8, ann, string_hash s, n, Typ.Procname.hash_pname pn) - | Return_annotation_inconsistent (ann, pn, _) -> - Hashtbl.hash (9, ann, Typ.Procname.hash_pname pn) - | Return_over_annotated pn -> - Hashtbl.hash (10, Typ.Procname.hash_pname pn) - | Inconsistent_subclass_return_annotation (pn, opn) -> - Hashtbl.hash (11, Typ.Procname.hash_pname pn, Typ.Procname.hash_pname opn) - | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) -> - let pn_hash = string_hash param_name in - Hashtbl.hash (12, pn_hash, pos, Typ.Procname.hash_pname pn, Typ.Procname.hash_pname opn) - - let hash (err_inst, instr_ref_opt) = - let x = match instr_ref_opt with - | None -> None - | Some instr_ref -> Some (InstrRef.hash instr_ref) in - let y = err_instance_hash err_inst in - Hashtbl.hash (x, y) - end (* H *)) - -type err_state = { - loc: Location.t; (** location of the error *) - mutable always: bool; (** always fires on its associated node *) -} - -let err_tbl : err_state H.t = - H.create 1 + [@@deriving compare] + +module H = Hashtbl.Make (struct + type t = err_instance * InstrRef.t option [@@deriving compare] + + let equal = [%compare.equal : t] + + let err_instance_hash x = + let string_hash s = Hashtbl.hash s in + let string_opt_hash so = Hashtbl.hash so in + match x with + | Condition_redundant (b, so, nn) + -> Hashtbl.hash (1, b, string_opt_hash so, nn) + | Field_not_initialized (fn, pn) + -> Hashtbl.hash (2, string_hash (Typ.Fieldname.to_string fn ^ Typ.Procname.to_string pn)) + | Field_not_mutable (fn, _) + -> Hashtbl.hash (3, string_hash (Typ.Fieldname.to_string fn)) + | Field_annotation_inconsistent (ann, fn, _) + -> Hashtbl.hash (4, ann, string_hash (Typ.Fieldname.to_string fn)) + | Field_over_annotated (fn, pn) + -> Hashtbl.hash (5, string_hash (Typ.Fieldname.to_string fn ^ Typ.Procname.to_string pn)) + | Null_field_access (so, fn, _, _) + -> Hashtbl.hash (6, string_opt_hash so, string_hash (Typ.Fieldname.to_string fn)) + | Call_receiver_annotation_inconsistent (ann, so, pn, _) + -> Hashtbl.hash (7, ann, string_opt_hash so, Typ.Procname.hash_pname pn) + | Parameter_annotation_inconsistent (ann, s, n, pn, _, _) + -> Hashtbl.hash (8, ann, string_hash s, n, Typ.Procname.hash_pname pn) + | Return_annotation_inconsistent (ann, pn, _) + -> Hashtbl.hash (9, ann, Typ.Procname.hash_pname pn) + | Return_over_annotated pn + -> Hashtbl.hash (10, Typ.Procname.hash_pname pn) + | Inconsistent_subclass_return_annotation (pn, opn) + -> Hashtbl.hash (11, Typ.Procname.hash_pname pn, Typ.Procname.hash_pname opn) + | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) + -> let pn_hash = string_hash param_name in + Hashtbl.hash (12, pn_hash, pos, Typ.Procname.hash_pname pn, Typ.Procname.hash_pname opn) + + let hash (err_inst, instr_ref_opt) = + let x = + match instr_ref_opt with None -> None | Some instr_ref -> Some (InstrRef.hash instr_ref) + in + let y = err_instance_hash err_inst in + Hashtbl.hash (x, y) +end +(* H *)) + +type err_state = + { loc: Location.t (** location of the error *) + ; mutable always: bool (** always fires on its associated node *) } + +let err_tbl : err_state H.t = H.create 1 (** Reset the error table. *) let reset () = H.reset err_tbl @@ -140,74 +151,94 @@ let reset () = H.reset err_tbl The forall status indicates that the error should be printed only if it occurs on every path. *) let get_forall = function - | Condition_redundant _ -> true - | Field_not_initialized _ -> false - | Field_not_mutable _ -> false - | Field_annotation_inconsistent _ -> false - | Field_over_annotated _ -> false - | Inconsistent_subclass_return_annotation _ -> false - | Inconsistent_subclass_parameter_annotation _ -> false - | Null_field_access _ -> false - | Call_receiver_annotation_inconsistent _ -> false - | Parameter_annotation_inconsistent _ -> false - | Return_annotation_inconsistent _ -> false - | Return_over_annotated _ -> false - + | Condition_redundant _ + -> true + | Field_not_initialized _ + -> false + | Field_not_mutable _ + -> false + | Field_annotation_inconsistent _ + -> false + | Field_over_annotated _ + -> false + | Inconsistent_subclass_return_annotation _ + -> false + | Inconsistent_subclass_parameter_annotation _ + -> false + | Null_field_access _ + -> false + | Call_receiver_annotation_inconsistent _ + -> false + | Parameter_annotation_inconsistent _ + -> false + | Return_annotation_inconsistent _ + -> false + | Return_over_annotated _ + -> false (** Reset the always field of the forall erros in the node, so if they are not set again we know that they don't fire on every path. *) let node_reset_forall node = let iter (err_instance, instr_ref_opt) err_state = - match instr_ref_opt, get_forall err_instance with - | Some instr_ref, is_forall -> - let node' = InstrRef.get_node instr_ref in + match (instr_ref_opt, get_forall err_instance) with + | Some instr_ref, is_forall + -> let node' = InstrRef.get_node instr_ref in if is_forall && Procdesc.Node.equal node node' then err_state.always <- false - | None, _ -> () in + | None, _ + -> () + in H.iter iter err_tbl (** Add an error to the error table and return whether it should be printed now. *) let add_err find_canonical_duplicate err_instance instr_ref_opt loc = let is_forall = get_forall err_instance in - if H.mem err_tbl (err_instance, instr_ref_opt) - then false (* don't print now *) - else begin + if H.mem err_tbl (err_instance, instr_ref_opt) then false (* don't print now *) + else let instr_ref_opt_deduplicate = - match is_forall, instr_ref_opt with - | true, Some instr_ref -> (* use canonical duplicate for forall checks *) + match (is_forall, instr_ref_opt) with + | true, Some instr_ref + -> (* use canonical duplicate for forall checks *) let node = InstrRef.get_node instr_ref in let canonical_node = find_canonical_duplicate node in let instr_ref' = InstrRef.replace_node instr_ref canonical_node in Some instr_ref' - | _ -> instr_ref_opt in - H.add err_tbl (err_instance, instr_ref_opt_deduplicate) { loc = loc; always = true }; - not is_forall (* print now if it's not a forall check *) - end + | _ + -> instr_ref_opt + in + H.add err_tbl (err_instance, instr_ref_opt_deduplicate) {loc; always= true} ; + not is_forall + +(* print now if it's not a forall check *) module Strict = struct - let method_get_strict (signature : AnnotatedSignature.t) = - let (ia, _) = signature.ret in + let method_get_strict (signature: AnnotatedSignature.t) = + let ia, _ = signature.ret in Annotations.ia_get_strict ia - let this_type_get_strict tenv (signature : AnnotatedSignature.t) = + let this_type_get_strict tenv (signature: AnnotatedSignature.t) = match signature.params with - | (p, _, this_type):: _ when String.equal (Mangled.to_string p) "this" -> - begin - match PatternMatch.type_get_annotation tenv this_type with - | Some ia -> Annotations.ia_get_strict ia - | None -> None - end - | _ -> None + | (p, _, this_type) :: _ when String.equal (Mangled.to_string p) "this" -> ( + match PatternMatch.type_get_annotation tenv this_type with + | Some ia + -> Annotations.ia_get_strict ia + | None + -> None ) + | _ + -> None let signature_get_strict tenv signature = match method_get_strict signature with - | None -> this_type_get_strict tenv signature - | Some x -> Some x - - let origin_descr_get_strict tenv origin_descr = match origin_descr with - | _, _, Some signature -> - signature_get_strict tenv signature - | _, _, None -> - None + | None + -> this_type_get_strict tenv signature + | Some x + -> Some x + + let origin_descr_get_strict tenv origin_descr = + match origin_descr with + | _, _, Some signature + -> signature_get_strict tenv signature + | _, _, None + -> None let report_on_method_arguments = false @@ -217,287 +248,237 @@ module Strict = struct let err_instance_get_strict tenv err_instance : Annot.t option = match err_instance with | Call_receiver_annotation_inconsistent (AnnotatedSignature.Nullable, _, _, origin_descr) - | Null_field_access (_, _, origin_descr, _) -> - origin_descr_get_strict tenv origin_descr + | Null_field_access (_, _, origin_descr, _) + -> origin_descr_get_strict tenv origin_descr | Parameter_annotation_inconsistent (AnnotatedSignature.Nullable, _, _, _, _, origin_descr) - when report_on_method_arguments -> - origin_descr_get_strict tenv origin_descr - | _ -> None -end (* Strict *) + when report_on_method_arguments + -> origin_descr_get_strict tenv origin_descr + | _ + -> None +end + +(* Strict *) type st_report_error = - Typ.Procname.t -> - Procdesc.t -> - Localise.t -> - Location.t -> - ?advice: string option -> - ?field_name: Typ.Fieldname.t option -> - ?origin_loc: Location.t option -> - ?exception_kind: (string -> Localise.error_desc -> exn) -> - ?always_report: bool -> - string -> - unit + Typ.Procname.t -> Procdesc.t -> Localise.t -> Location.t -> ?advice:string option + -> ?field_name:Typ.Fieldname.t option -> ?origin_loc:Location.t option + -> ?exception_kind:(string -> Localise.error_desc -> exn) -> ?always_report:bool -> string + -> unit (** Report an error right now. *) -let report_error_now tenv - (st_report_error : st_report_error) err_instance loc pdesc : unit = +let report_error_now tenv (st_report_error: st_report_error) err_instance loc pdesc : unit = let pname = Procdesc.get_proc_name pdesc in let do_print ew_string kind s = - L.progress "%a:%d " SourceFile.pp loc.Location.file loc.Location.line; - let mname = match pname with - | Typ.Procname.Java pname_java -> - Typ.Procname.java_get_method pname_java - | _ -> - Typ.Procname.to_simplified_string pname in - L.progress "%s %s in %s %s@." ew_string (Localise.to_issue_id kind) mname s in - - let is_err, kind, description, advice, field_name, origin_loc = match err_instance with - | Condition_redundant (b, s_opt, nonnull) -> - let name = - if nonnull - then Localise.eradicate_condition_redundant_nonnull - else Localise.eradicate_condition_redundant in - false, - name, - P.sprintf - "The condition %s is always %b according to the existing annotations." - (Option.value s_opt ~default:"") - b, - Some ("Consider adding a " ^ - MF.monospaced_to_string "@Nullable" ^ - " annotation or removing the redundant check."), - None, - None - | Field_not_initialized (fn, pn) -> - let constructor_name = - if Typ.Procname.is_constructor pn - then "the constructor" + L.progress "%a:%d " SourceFile.pp loc.Location.file loc.Location.line ; + let mname = + match pname with + | Typ.Procname.Java pname_java + -> Typ.Procname.java_get_method pname_java + | _ + -> Typ.Procname.to_simplified_string pname + in + L.progress "%s %s in %s %s@." ew_string (Localise.to_issue_id kind) mname s + in + let is_err, kind, description, advice, field_name, origin_loc = + match err_instance with + | Condition_redundant (b, s_opt, nonnull) + -> let name = + if nonnull then Localise.eradicate_condition_redundant_nonnull + else Localise.eradicate_condition_redundant + in + ( false + , name + , P.sprintf "The condition %s is always %b according to the existing annotations." + (Option.value s_opt ~default:"") b + , Some + ( "Consider adding a " ^ MF.monospaced_to_string "@Nullable" + ^ " annotation or removing the redundant check." ) + , None + , None ) + | Field_not_initialized (fn, pn) + -> let constructor_name = + if Typ.Procname.is_constructor pn then "the constructor" else match pn with - | Typ.Procname.Java pn_java -> - MF.monospaced_to_string (Typ.Procname.java_get_method pn_java) - | _ -> - MF.monospaced_to_string (Typ.Procname.to_simplified_string pn) in - true, - Localise.eradicate_field_not_initialized, - Format.asprintf - "Field %a is not initialized in %s and is not declared %a" - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - constructor_name - MF.pp_monospaced "@Nullable", - None, - Some fn, - None - | Field_not_mutable (fn, (origin_description, origin_loc, _)) -> - true, - Localise.eradicate_field_not_mutable, - Format.asprintf - "Field %a is modified but is not declared %a. %s" - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - MF.pp_monospaced "@Mutable" - origin_description, - None, - None, - origin_loc - | Field_annotation_inconsistent (ann, fn, (origin_description, origin_loc, _)) -> - let kind_s, description = match ann with - | AnnotatedSignature.Nullable -> - Localise.eradicate_field_not_nullable, - Format.asprintf - "Field %a can be null but is not declared %a. %s" - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - MF.pp_monospaced "@Nullable" - origin_description - | AnnotatedSignature.Present -> - Localise.eradicate_field_value_absent, - Format.asprintf - "Field %a is assigned a possibly absent value but is declared %a. %s" - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - MF.pp_monospaced "@Present" - origin_description in - true, - kind_s, - description, - None, - None, - origin_loc - | Field_over_annotated (fn, pn) -> - let constructor_name = - if Typ.Procname.is_constructor pn - then "the constructor" + | Typ.Procname.Java pn_java + -> MF.monospaced_to_string (Typ.Procname.java_get_method pn_java) + | _ + -> MF.monospaced_to_string (Typ.Procname.to_simplified_string pn) + in + ( true + , Localise.eradicate_field_not_initialized + , Format.asprintf "Field %a is not initialized in %s and is not declared %a" + MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) constructor_name + MF.pp_monospaced "@Nullable" + , None + , Some fn + , None ) + | Field_not_mutable (fn, (origin_description, origin_loc, _)) + -> ( true + , Localise.eradicate_field_not_mutable + , Format.asprintf "Field %a is modified but is not declared %a. %s" MF.pp_monospaced + (Typ.Fieldname.to_simplified_string fn) MF.pp_monospaced "@Mutable" origin_description + , None + , None + , origin_loc ) + | Field_annotation_inconsistent (ann, fn, (origin_description, origin_loc, _)) + -> let kind_s, description = + match ann with + | AnnotatedSignature.Nullable + -> ( Localise.eradicate_field_not_nullable + , Format.asprintf "Field %a can be null but is not declared %a. %s" MF.pp_monospaced + (Typ.Fieldname.to_simplified_string fn) MF.pp_monospaced "@Nullable" + origin_description ) + | AnnotatedSignature.Present + -> ( Localise.eradicate_field_value_absent + , Format.asprintf + "Field %a is assigned a possibly absent value but is declared %a. %s" + MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) MF.pp_monospaced + "@Present" origin_description ) + in + (true, kind_s, description, None, None, origin_loc) + | Field_over_annotated (fn, pn) + -> let constructor_name = + if Typ.Procname.is_constructor pn then "the constructor" else match pn with - | Typ.Procname.Java pn_java -> - Typ.Procname.java_get_method pn_java - | _ -> - Typ.Procname.to_simplified_string pn in - true, - Localise.eradicate_field_over_annotated, - Format.asprintf - "Field %a is always initialized in %s but is declared %a" - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - constructor_name - MF.pp_monospaced "@Nullable", - None, - Some fn, - None - | Null_field_access (s_opt, fn, (origin_description, origin_loc, _), indexed) -> - let at_index = if indexed then "element at index" else "field" in - true, - Localise.eradicate_null_field_access, - Format.asprintf - "Object %a could be null when accessing %s %a. %s" - MF.pp_monospaced (Option.value s_opt ~default:"") - at_index - MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) - origin_description, - None, - None, - origin_loc - | Call_receiver_annotation_inconsistent (ann, s_opt, pn, (origin_description, origin_loc, _)) -> - let kind_s, description = match ann with - | AnnotatedSignature.Nullable -> - Localise.eradicate_null_method_call, - Format.asprintf - "The value of %a in the call to %a could be null. %s" - MF.pp_monospaced (Option.value s_opt ~default:"") - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - origin_description - | AnnotatedSignature.Present -> - Localise.eradicate_value_not_present, - Format.asprintf - "The value of %a in the call to %a is not %a. %s" - MF.pp_monospaced (Option.value s_opt ~default:"") - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - MF.pp_monospaced "@Present" - origin_description in - true, - kind_s, - description, - None, - None, - origin_loc - | Parameter_annotation_inconsistent (ann, s, n, pn, _, (origin_desc, origin_loc, _)) -> - let kind_s, description = match ann with - | AnnotatedSignature.Nullable -> - Localise.eradicate_parameter_not_nullable, - Format.asprintf - "%a needs a non-null value in parameter %d but argument %a can be null. %s" - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - n - MF.pp_monospaced s - origin_desc - | AnnotatedSignature.Present -> - Localise.eradicate_parameter_value_absent, - Format.asprintf - "%a needs a present value in parameter %d but argument %a can be absent. %s" - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - n - MF.pp_monospaced s - origin_desc in - true, - kind_s, - description, - None, - None, - origin_loc - | Return_annotation_inconsistent (ann, pn, (origin_description, origin_loc, _)) -> - let kind_s, description = match ann with - | AnnotatedSignature.Nullable -> - Localise.eradicate_return_not_nullable, - Format.asprintf - "Method %a may return null but it is not annotated with %a. %s" - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - MF.pp_monospaced "@Nullable" - origin_description - | AnnotatedSignature.Present -> - Localise.eradicate_return_value_not_present, - Format.asprintf - "Method %a may return an absent value but it is annotated with %a. %s" - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - MF.pp_monospaced "@Present" - origin_description in - true, - kind_s, - description, - None, - None, - origin_loc - | Return_over_annotated pn -> - false, - Localise.eradicate_return_over_annotated, - Format.asprintf - "Method %a is annotated with %a but never returns null." - MF.pp_monospaced (Typ.Procname.to_simplified_string pn) - MF.pp_monospaced "@Nullable", - None, - None, - None - | Inconsistent_subclass_return_annotation (pn, opn) -> - false, - Localise.eradicate_inconsistent_subclass_return_annotation, - Format.asprintf - "Method %a is annotated with %a but overrides unannotated method %a." - MF.pp_monospaced (Typ.Procname.to_simplified_string ~withclass: true pn) - MF.pp_monospaced "@Nullable" - MF.pp_monospaced (Typ.Procname.to_simplified_string ~withclass: true opn), - None, - None, - None - | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) -> - let translate_position = function - | 1 -> "First" - | 2 -> "Second" - | 3 -> "Third" - | n -> (string_of_int n)^"th" in - false, - Localise.eradicate_inconsistent_subclass_parameter_annotation, - Format.asprintf - "%s parameter %a of method %a is not %a but is declared %a\ - in the parent class method %a." - (translate_position pos) - MF.pp_monospaced param_name - MF.pp_monospaced (Typ.Procname.to_simplified_string ~withclass: true pn) - MF.pp_monospaced "@Nullable" - MF.pp_monospaced "@Nullable" - MF.pp_monospaced (Typ.Procname.to_simplified_string ~withclass: true opn), - None, - None, - None in + | Typ.Procname.Java pn_java + -> Typ.Procname.java_get_method pn_java + | _ + -> Typ.Procname.to_simplified_string pn + in + ( true + , Localise.eradicate_field_over_annotated + , Format.asprintf "Field %a is always initialized in %s but is declared %a" + MF.pp_monospaced (Typ.Fieldname.to_simplified_string fn) constructor_name + MF.pp_monospaced "@Nullable" + , None + , Some fn + , None ) + | Null_field_access (s_opt, fn, (origin_description, origin_loc, _), indexed) + -> let at_index = if indexed then "element at index" else "field" in + ( true + , Localise.eradicate_null_field_access + , Format.asprintf "Object %a could be null when accessing %s %a. %s" MF.pp_monospaced + (Option.value s_opt ~default:"") at_index MF.pp_monospaced + (Typ.Fieldname.to_simplified_string fn) origin_description + , None + , None + , origin_loc ) + | Call_receiver_annotation_inconsistent (ann, s_opt, pn, (origin_description, origin_loc, _)) + -> let kind_s, description = + match ann with + | AnnotatedSignature.Nullable + -> ( Localise.eradicate_null_method_call + , Format.asprintf "The value of %a in the call to %a could be null. %s" + MF.pp_monospaced (Option.value s_opt ~default:"") MF.pp_monospaced + (Typ.Procname.to_simplified_string pn) origin_description ) + | AnnotatedSignature.Present + -> ( Localise.eradicate_value_not_present + , Format.asprintf "The value of %a in the call to %a is not %a. %s" MF.pp_monospaced + (Option.value s_opt ~default:"") MF.pp_monospaced + (Typ.Procname.to_simplified_string pn) MF.pp_monospaced "@Present" + origin_description ) + in + (true, kind_s, description, None, None, origin_loc) + | Parameter_annotation_inconsistent (ann, s, n, pn, _, (origin_desc, origin_loc, _)) + -> let kind_s, description = + match ann with + | AnnotatedSignature.Nullable + -> ( Localise.eradicate_parameter_not_nullable + , Format.asprintf + "%a needs a non-null value in parameter %d but argument %a can be null. %s" + MF.pp_monospaced (Typ.Procname.to_simplified_string pn) n MF.pp_monospaced s + origin_desc ) + | AnnotatedSignature.Present + -> ( Localise.eradicate_parameter_value_absent + , Format.asprintf + "%a needs a present value in parameter %d but argument %a can be absent. %s" + MF.pp_monospaced (Typ.Procname.to_simplified_string pn) n MF.pp_monospaced s + origin_desc ) + in + (true, kind_s, description, None, None, origin_loc) + | Return_annotation_inconsistent (ann, pn, (origin_description, origin_loc, _)) + -> let kind_s, description = + match ann with + | AnnotatedSignature.Nullable + -> ( Localise.eradicate_return_not_nullable + , Format.asprintf "Method %a may return null but it is not annotated with %a. %s" + MF.pp_monospaced (Typ.Procname.to_simplified_string pn) MF.pp_monospaced + "@Nullable" origin_description ) + | AnnotatedSignature.Present + -> ( Localise.eradicate_return_value_not_present + , Format.asprintf + "Method %a may return an absent value but it is annotated with %a. %s" + MF.pp_monospaced (Typ.Procname.to_simplified_string pn) MF.pp_monospaced + "@Present" origin_description ) + in + (true, kind_s, description, None, None, origin_loc) + | Return_over_annotated pn + -> ( false + , Localise.eradicate_return_over_annotated + , Format.asprintf "Method %a is annotated with %a but never returns null." MF.pp_monospaced + (Typ.Procname.to_simplified_string pn) MF.pp_monospaced "@Nullable" + , None + , None + , None ) + | Inconsistent_subclass_return_annotation (pn, opn) + -> ( false + , Localise.eradicate_inconsistent_subclass_return_annotation + , Format.asprintf "Method %a is annotated with %a but overrides unannotated method %a." + MF.pp_monospaced (Typ.Procname.to_simplified_string ~withclass:true pn) + MF.pp_monospaced "@Nullable" MF.pp_monospaced + (Typ.Procname.to_simplified_string ~withclass:true opn) + , None + , None + , None ) + | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) + -> let translate_position = function + | 1 + -> "First" + | 2 + -> "Second" + | 3 + -> "Third" + | n + -> string_of_int n ^ "th" + in + ( false + , Localise.eradicate_inconsistent_subclass_parameter_annotation + , Format.asprintf + "%s parameter %a of method %a is not %a but is declared %ain the parent class method %a." + (translate_position pos) MF.pp_monospaced param_name MF.pp_monospaced + (Typ.Procname.to_simplified_string ~withclass:true pn) MF.pp_monospaced "@Nullable" + MF.pp_monospaced "@Nullable" MF.pp_monospaced + (Typ.Procname.to_simplified_string ~withclass:true opn) + , None + , None + , None ) + in let ew_string = if is_err then "Error" else "Warning" in - do_print ew_string kind description; + do_print ew_string kind description ; let always_report = Strict.err_instance_get_strict tenv err_instance <> None in - st_report_error - pname - pdesc - kind - loc - ~advice - ~field_name - ~origin_loc - ~exception_kind: (fun k d -> Exceptions.Eradicate (k, d)) - ~always_report - description - + st_report_error pname pdesc kind loc ~advice ~field_name ~origin_loc + ~exception_kind:(fun k d -> Exceptions.Eradicate (k, d)) + ~always_report description (** Report an error unless is has been reported already, or unless it's a forall error since it requires waiting until the end of the analysis and be printed by flush. *) -let report_error tenv (st_report_error : st_report_error) find_canonical_duplicate - err_instance instr_ref_opt loc pdesc = - let should_report_now = - add_err find_canonical_duplicate err_instance instr_ref_opt loc in - if should_report_now then - report_error_now tenv st_report_error err_instance loc pdesc +let report_error tenv (st_report_error: st_report_error) find_canonical_duplicate err_instance + instr_ref_opt loc pdesc = + let should_report_now = add_err find_canonical_duplicate err_instance instr_ref_opt loc in + if should_report_now then report_error_now tenv st_report_error err_instance loc pdesc (** Report the forall checks at the end of the analysis and reset the error table *) let report_forall_checks_and_reset tenv st_report_error proc_desc = let iter (err_instance, instr_ref_opt) err_state = - match instr_ref_opt, get_forall err_instance with - | Some instr_ref, is_forall -> - let node = InstrRef.get_node instr_ref in - State.set_node node; - if is_forall && err_state.always - then report_error_now tenv st_report_error err_instance err_state.loc proc_desc - | None, _ -> () in - H.iter iter err_tbl; - reset () + match (instr_ref_opt, get_forall err_instance) with + | Some instr_ref, is_forall + -> let node = InstrRef.get_node instr_ref in + State.set_node node ; + if is_forall && err_state.always then + report_error_now tenv st_report_error err_instance err_state.loc proc_desc + | None, _ + -> () + in + H.iter iter err_tbl ; reset () diff --git a/infer/src/eradicate/typeErr.mli b/infer/src/eradicate/typeErr.mli index 2137063de..a24b4f949 100644 --- a/infer/src/eradicate/typeErr.mli +++ b/infer/src/eradicate/typeErr.mli @@ -9,47 +9,54 @@ open! IStd - (** Module for Type Error messages. *) - -module type InstrRefT = -sig +module type InstrRefT = sig type t [@@deriving compare] + val equal : t -> t -> bool + type generator + val create_generator : Procdesc.Node.t -> generator + val gen : generator -> t + val get_node : t -> Procdesc.Node.t + val hash : t -> int + val replace_node : t -> Procdesc.Node.t -> t -end (* InstrRefT *) +end + +(* InstrRefT *) module InstrRef : InstrRefT -module Strict : -sig - val signature_get_strict : - Tenv.t -> AnnotatedSignature.t -> Annot.t option -end (* Strict *) +module Strict : sig + val signature_get_strict : Tenv.t -> AnnotatedSignature.t -> Annot.t option +end + +(* Strict *) +type origin_descr = string * Location.t option * AnnotatedSignature.t option -type origin_descr = - string * - Location.t option * - AnnotatedSignature.t option (* callee signature *) +(* callee signature *) type parameter_not_nullable = - AnnotatedSignature.annotation * - string * (* description *) - int * (* parameter number *) - Typ.Procname.t * - Location.t * (* callee location *) - origin_descr + AnnotatedSignature.annotation + * string + * (* description *) + int + * (* parameter number *) + Typ.Procname.t + * Location.t + * (* callee location *) + origin_descr (** Instance of an error *) type err_instance = - | Condition_redundant of (bool * (string option) * bool) + | Condition_redundant of (bool * string option * bool) | Inconsistent_subclass_return_annotation of Typ.Procname.t * Typ.Procname.t | Inconsistent_subclass_parameter_annotation of string * int * Typ.Procname.t * Typ.Procname.t | Field_not_initialized of Typ.Fieldname.t * Typ.Procname.t @@ -57,33 +64,23 @@ type err_instance = | Field_annotation_inconsistent of AnnotatedSignature.annotation * Typ.Fieldname.t * origin_descr | Field_over_annotated of Typ.Fieldname.t * Typ.Procname.t | Null_field_access of string option * Typ.Fieldname.t * origin_descr * bool - | Call_receiver_annotation_inconsistent - of AnnotatedSignature.annotation * string option * Typ.Procname.t * origin_descr + | Call_receiver_annotation_inconsistent of + AnnotatedSignature.annotation * string option * Typ.Procname.t * origin_descr | Parameter_annotation_inconsistent of parameter_not_nullable | Return_annotation_inconsistent of AnnotatedSignature.annotation * Typ.Procname.t * origin_descr | Return_over_annotated of Typ.Procname.t - val node_reset_forall : Procdesc.Node.t -> unit type st_report_error = - Typ.Procname.t -> - Procdesc.t -> - Localise.t -> - Location.t -> - ?advice: string option -> - ?field_name: Typ.Fieldname.t option -> - ?origin_loc: Location.t option -> - ?exception_kind: (string -> Localise.error_desc -> exn) -> - ?always_report: bool -> - string -> - unit + Typ.Procname.t -> Procdesc.t -> Localise.t -> Location.t -> ?advice:string option + -> ?field_name:Typ.Fieldname.t option -> ?origin_loc:Location.t option + -> ?exception_kind:(string -> Localise.error_desc -> exn) -> ?always_report:bool -> string + -> unit val report_error : - Tenv.t -> st_report_error -> - (Procdesc.Node.t -> Procdesc.Node.t) -> - err_instance -> InstrRef.t option -> Location.t -> - Procdesc.t -> unit + Tenv.t -> st_report_error -> (Procdesc.Node.t -> Procdesc.Node.t) -> err_instance + -> InstrRef.t option -> Location.t -> Procdesc.t -> unit val report_forall_checks_and_reset : Tenv.t -> st_report_error -> Procdesc.t -> unit diff --git a/infer/src/eradicate/typeOrigin.ml b/infer/src/eradicate/typeOrigin.ml index 8f9ba956d..c81d38e6c 100644 --- a/infer/src/eradicate/typeOrigin.ml +++ b/infer/src/eradicate/typeOrigin.ml @@ -8,21 +8,17 @@ *) open! IStd - module L = Logging module P = Printf - (** Describe the origin of values propagated by the checker. *) - type proc_origin = - { - pname : Typ.Procname.t; - loc: Location.t; - annotated_signature : AnnotatedSignature.t; - is_library : bool; - } [@@deriving compare] + { pname: Typ.Procname.t + ; loc: Location.t + ; annotated_signature: AnnotatedSignature.t + ; is_library: bool } + [@@deriving compare] type t = | Const of Location.t @@ -32,68 +28,68 @@ type t = | New | ONone | Undef -[@@deriving compare] + [@@deriving compare] let equal = [%compare.equal : t] let rec to_string = function - | Const _ -> - "Const" - | Field (o, fn, _) -> - "Field " ^ Typ.Fieldname.to_simplified_string fn ^ (" (inner: " ^ to_string o ^ ")") - | Formal s -> - "Formal " ^ Mangled.to_string s - | Proc po -> - Printf.sprintf - "Fun %s" - (Typ.Procname.to_simplified_string po.pname) - | New -> - "New" - | ONone -> - "ONone" - | Undef -> - "Undef" + | Const _ + -> "Const" + | Field (o, fn, _) + -> "Field " ^ Typ.Fieldname.to_simplified_string fn ^ " (inner: " ^ to_string o ^ ")" + | Formal s + -> "Formal " ^ Mangled.to_string s + | Proc po + -> Printf.sprintf "Fun %s" (Typ.Procname.to_simplified_string po.pname) + | New + -> "New" + | ONone + -> "ONone" + | Undef + -> "Undef" let get_description tenv origin = - let atline loc = - " at line " ^ (string_of_int loc.Location.line) in + let atline loc = " at line " ^ string_of_int loc.Location.line in match origin with - | Const loc -> - Some ("null constant" ^ atline loc, Some loc, None) - | Field (_, fn, loc) -> - Some ("field " ^ Typ.Fieldname.to_simplified_string fn ^ atline loc, Some loc, None) - | Formal s -> - Some ("method parameter " ^ Mangled.to_string s, None, None) - | Proc po -> - let strict = match TypeErr.Strict.signature_get_strict tenv po.annotated_signature with - | Some ann -> + | Const loc + -> Some ("null constant" ^ atline loc, Some loc, None) + | Field (_, fn, loc) + -> Some ("field " ^ Typ.Fieldname.to_simplified_string fn ^ atline loc, Some loc, None) + | Formal s + -> Some ("method parameter " ^ Mangled.to_string s, None, None) + | Proc po + -> let strict = + match TypeErr.Strict.signature_get_strict tenv po.annotated_signature with + | Some ann + -> ( let str = "@Strict" in - (match ann.Annot.parameters with - | par1 :: _ -> Printf.sprintf "%s(%s) " str par1 - | [] -> Printf.sprintf "%s " str) - | None -> "" in + match ann.Annot.parameters with + | par1 :: _ + -> Printf.sprintf "%s(%s) " str par1 + | [] + -> Printf.sprintf "%s " str ) + | None + -> "" + in let modelled_in = - if Models.is_modelled_nullable po.pname - then " modelled in " ^ ModelTables.this_file - else "" in - let description = Printf.sprintf - "call to %s%s%s%s" - strict - (Typ.Procname.to_simplified_string po.pname) - modelled_in - (atline po.loc) in + if Models.is_modelled_nullable po.pname then " modelled in " ^ ModelTables.this_file + else "" + in + let description = + Printf.sprintf "call to %s%s%s%s" strict (Typ.Procname.to_simplified_string po.pname) + modelled_in (atline po.loc) + in Some (description, Some po.loc, Some po.annotated_signature) - | New - | ONone - | Undef -> None - + | New | ONone | Undef + -> None -let join o1 o2 = match o1, o2 with (* left priority *) - | Undef, _ - | _, Undef -> - Undef - | Field _, (Const _ | Formal _ | Proc _ | New) -> - (* low priority to Field, to support field initialization patterns *) +let join o1 o2 = + match (o1, o2) with + (* left priority *) + | Undef, _ | _, Undef + -> Undef + | Field _, (Const _ | Formal _ | Proc _ | New) + -> (* low priority to Field, to support field initialization patterns *) o2 - | _ -> - o1 + | _ + -> o1 diff --git a/infer/src/eradicate/typeOrigin.mli b/infer/src/eradicate/typeOrigin.mli index 364a2a874..197d36e92 100644 --- a/infer/src/eradicate/typeOrigin.mli +++ b/infer/src/eradicate/typeOrigin.mli @@ -11,30 +11,29 @@ open! IStd (** Case Proc *) type proc_origin = - { - pname : Typ.Procname.t; - loc: Location.t; - annotated_signature : AnnotatedSignature.t; - is_library : bool; - } [@@deriving compare] + { pname: Typ.Procname.t + ; loc: Location.t + ; annotated_signature: AnnotatedSignature.t + ; is_library: bool } + [@@deriving compare] type t = - | Const of Location.t (** A constant in the source *) - | Field of t * Typ.Fieldname.t * Location.t (** A field access *) - | Formal of Mangled.t (** A formal parameter *) - | Proc of proc_origin (** A procedure call *) - | New (** A new object creation *) - | ONone (** No origin is known *) - | Undef (** Undefined value before initialization *) -[@@deriving compare] + | Const of Location.t (** A constant in the source *) + | Field of t * Typ.Fieldname.t * Location.t (** A field access *) + | Formal of Mangled.t (** A formal parameter *) + | Proc of proc_origin (** A procedure call *) + | New (** A new object creation *) + | ONone (** No origin is known *) + | Undef (** Undefined value before initialization *) + [@@deriving compare] val equal : t -> t -> bool -(** Get a description to be used for error messages. *) val get_description : Tenv.t -> t -> TypeErr.origin_descr option +(** Get a description to be used for error messages. *) -(** Join with left priority *) val join : t -> t -> t +(** Join with left priority *) -(** Raw string representation. *) val to_string : t -> string +(** Raw string representation. *) diff --git a/infer/src/eradicate/typeState.ml b/infer/src/eradicate/typeState.ml index 2d9dee836..e0c512348 100644 --- a/infer/src/eradicate/typeState.ml +++ b/infer/src/eradicate/typeState.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging module F = Format module P = Printf @@ -22,65 +21,51 @@ type get_proc_desc = Typ.Procname.t -> Procdesc.t option (** Extension to a typestate with values of type 'a. *) type 'a ext = - { - empty : 'a; (** empty extension *) - check_instr : - Tenv.t -> get_proc_desc -> Typ.Procname.t -> - Procdesc.t -> 'a -> Sil.instr -> parameters -> - 'a; (** check the extension for an instruction *) - join : 'a -> 'a -> 'a; (** join two extensions *) - pp : Format.formatter -> 'a -> unit (** pretty print an extension *) - } - -let unit_ext : unit ext = { - empty = (); - check_instr = (fun _ _ _ _ () _ _ -> ()); - join = (fun () () -> ()); - pp = (fun _ () -> ()); -} - + { empty: 'a (** empty extension *) + ; check_instr: + Tenv.t -> get_proc_desc -> Typ.Procname.t -> Procdesc.t -> 'a -> Sil.instr -> parameters + -> 'a + (** check the extension for an instruction *) + ; join: 'a -> 'a -> 'a (** join two extensions *) + ; pp: Format.formatter -> 'a -> unit (** pretty print an extension *) } + +let unit_ext : unit ext = + { empty= () + ; check_instr= (fun _ _ _ _ () _ _ -> ()) + ; join= (fun () () -> ()) + ; pp= (fun _ () -> ()) } module M = Caml.Map.Make (struct - type t = Exp.t - let compare = Exp.compare end) + type t = Exp.t + + let compare = Exp.compare +end) -type range = Typ.t * TypeAnnotation.t * (Location.t list) [@@deriving compare] +type range = Typ.t * TypeAnnotation.t * Location.t list [@@deriving compare] -type 'a t = - { - map: range M.t; - extension : 'a; - } [@@deriving compare] +type 'a t = {map: range M.t; extension: 'a} [@@deriving compare] (* Ignore the extension field, which is a pure instrumentation *) let compare t1 t2 = compare (fun _ _ -> 0) t1 t2 let equal t1 t2 = Int.equal (compare t1 t2) 0 -let empty ext = - { - map = M.empty; - extension = ext.empty; - } +let empty ext = {map= M.empty; extension= ext.empty} let pp ext fmt typestate = let pp_loc fmt loc = F.fprintf fmt "%d" loc.Location.line in let pp_locs fmt locs = F.fprintf fmt " [%a]" (Pp.seq pp_loc) locs in let pp_one exp (typ, ta, locs) = - F.fprintf fmt " %a -> [%s] %s %a%a@\n" - Exp.pp exp - (TypeOrigin.to_string (TypeAnnotation.get_origin ta)) (TypeAnnotation.to_string ta) - (Typ.pp_full Pp.text) typ - pp_locs locs in + F.fprintf fmt " %a -> [%s] %s %a%a@\n" Exp.pp exp + (TypeOrigin.to_string (TypeAnnotation.get_origin ta)) + (TypeAnnotation.to_string ta) (Typ.pp_full Pp.text) typ pp_locs locs + in let pp_map map = M.iter pp_one map in + pp_map typestate.map ; ext.pp fmt typestate.extension - pp_map typestate.map; - ext.pp fmt typestate.extension +let type_join typ1 typ2 = if PatternMatch.type_is_object typ1 then typ2 else typ1 -let type_join typ1 typ2 = - if PatternMatch.type_is_object typ1 then typ2 else typ1 -let locs_join locs1 locs2 = - IList.merge_sorted_nodup Location.compare [] locs1 locs2 +let locs_join locs1 locs2 = IList.merge_sorted_nodup Location.compare [] locs1 locs2 (** Add a list of locations to a range. *) let range_add_locs (typ, ta, locs1) locs2 = @@ -96,50 +81,45 @@ let map_join m1 m2 = let tjoined = ref (if only_keep_intersection then M.empty else m1) in let range_join (typ1, ta1, locs1) (typ2, ta2, locs2) = match TypeAnnotation.join ta1 ta2 with - | None -> None - | Some ta' -> - let typ' = type_join typ1 typ2 in + | None + -> None + | Some ta' + -> let typ' = type_join typ1 typ2 in let locs' = locs_join locs1 locs2 in - Some (typ', ta', locs') in - let extend_lhs exp2 range2 = (* extend lhs if possible, otherwise return false *) + Some (typ', ta', locs') + in + let extend_lhs exp2 range2 = + (* extend lhs if possible, otherwise return false *) try let range1 = M.find exp2 m1 in - (match range_join range1 range2 with - | None -> - if only_keep_intersection then tjoined := M.add exp2 range1 !tjoined - | Some range' -> tjoined := M.add exp2 range' !tjoined) - with Not_found -> - if not only_keep_intersection then tjoined := M.add exp2 range2 !tjoined in - let missing_rhs exp1 range1 = (* handle elements missing in the rhs *) - try - ignore (M.find exp1 m2) + match range_join range1 range2 with + | None + -> if only_keep_intersection then tjoined := M.add exp2 range1 !tjoined + | Some range' + -> tjoined := M.add exp2 range' !tjoined + with Not_found -> if not only_keep_intersection then tjoined := M.add exp2 range2 !tjoined + in + let missing_rhs exp1 range1 = + (* handle elements missing in the rhs *) + try ignore (M.find exp1 m2) with Not_found -> - let (t1, ta1, locs1) = range1 in + let t1, ta1, locs1 = range1 in let range1' = let ta1' = TypeAnnotation.with_origin ta1 TypeOrigin.Undef in - (t1, ta1', locs1) in - if not only_keep_intersection then tjoined := M.add exp1 range1' !tjoined in - if phys_equal m1 m2 then m1 - else ( - M.iter extend_lhs m2; - M.iter missing_rhs m1; - !tjoined - ) + (t1, ta1', locs1) + in + if not only_keep_intersection then tjoined := M.add exp1 range1' !tjoined + in + if phys_equal m1 m2 then m1 else ( M.iter extend_lhs m2 ; M.iter missing_rhs m1 ; !tjoined ) let join ext t1 t2 = - let tjoin = - { - map = map_join t1.map t2.map; - extension = ext.join t1.extension t2.extension; - } in - if Config.write_html then - begin - let s = F.asprintf "State 1:@.%a@.State 2:@.%a@.After Join:@.%a@." - (pp ext) t1 - (pp ext) t2 - (pp ext) tjoin in - L.d_strln s; - end; + let tjoin = {map= map_join t1.map t2.map; extension= ext.join t1.extension t2.extension} in + ( if Config.write_html then + let s = + F.asprintf "State 1:@.%a@.State 2:@.%a@.After Join:@.%a@." (pp ext) t1 (pp ext) t2 (pp ext) + tjoin + in + L.d_strln s ) ; tjoin let lookup_id id typestate = @@ -152,20 +132,16 @@ let lookup_pvar pvar typestate = let add_id id range typestate = let map' = M.add (Exp.Var id) range typestate.map in - if phys_equal map' typestate.map then typestate - else { typestate with map = map' } + if phys_equal map' typestate.map then typestate else {typestate with map= map'} let add pvar range typestate = let map' = M.add (Exp.Lvar pvar) range typestate.map in - if phys_equal map' typestate.map then typestate - else { typestate with map = map' } + if phys_equal map' typestate.map then typestate else {typestate with map= map'} let remove_id id typestate = let map' = M.remove (Exp.Var id) typestate.map in - if phys_equal map' typestate.map then typestate - else { typestate with map = map' } + if phys_equal map' typestate.map then typestate else {typestate with map= map'} let get_extension typestate = typestate.extension -let set_extension typestate extension = - { typestate with extension = extension } +let set_extension typestate extension = {typestate with extension} diff --git a/infer/src/eradicate/typeState.mli b/infer/src/eradicate/typeState.mli index 1a05d0019..0d62faa75 100644 --- a/infer/src/eradicate/typeState.mli +++ b/infer/src/eradicate/typeState.mli @@ -18,31 +18,41 @@ type get_proc_desc = Typ.Procname.t -> Procdesc.t option (** Extension to a typestate with values of type 'a. *) type 'a ext = - { - empty : 'a; (** empty extension *) - check_instr : - Tenv.t -> get_proc_desc -> Typ.Procname.t -> - Procdesc.t ->'a -> Sil.instr -> parameters -> - 'a; (** check the extension for an instruction *) - join : 'a -> 'a -> 'a; (** join two extensions *) - pp : Format.formatter -> 'a -> unit (** pretty print an extension *) - } + { empty: 'a (** empty extension *) + ; check_instr: + Tenv.t -> get_proc_desc -> Typ.Procname.t -> Procdesc.t -> 'a -> Sil.instr -> parameters + -> 'a + (** check the extension for an instruction *) + ; join: 'a -> 'a -> 'a (** join two extensions *) + ; pp: Format.formatter -> 'a -> unit (** pretty print an extension *) } (** Typestate extended with elements of type 'a. *) type 'a t -type range = Typ.t * TypeAnnotation.t * (Location.t list) +type range = Typ.t * TypeAnnotation.t * Location.t list val add_id : Ident.t -> range -> 'a t -> 'a t + val add : Pvar.t -> range -> 'a t -> 'a t + val empty : 'a ext -> 'a t + val equal : 'a t -> 'a t -> bool + val get_extension : 'a t -> 'a + val join : 'a ext -> 'a t -> 'a t -> 'a t + val lookup_id : Ident.t -> 'a t -> range option + val lookup_pvar : Pvar.t -> 'a t -> range option + val pp : 'a ext -> Format.formatter -> 'a t -> unit -val range_add_locs : range -> (Location.t list) -> range + +val range_add_locs : range -> Location.t list -> range + val remove_id : Ident.t -> 'a t -> 'a t + val set_extension : 'a t -> 'a -> 'a t + val unit_ext : unit ext diff --git a/infer/src/harness/androidFramework.ml b/infer/src/harness/androidFramework.ml index c586bdb6c..a57d6eb04 100644 --- a/infer/src/harness/androidFramework.ml +++ b/infer/src/harness/androidFramework.ml @@ -8,73 +8,80 @@ *) open! IStd - module L = Logging module F = Format (** Android lifecycle types and their lifecycle methods that are called by the framework *) let on_destroy = "onDestroy" + let on_destroy_view = "onDestroyView" (** return true if [pname] is a special lifecycle cleanup method *) let is_destroy_method pname = match pname with - | Typ.Procname.Java pname_java -> - let method_name = Typ.Procname.java_get_method pname_java in - String.equal method_name on_destroy - || String.equal method_name on_destroy_view - | _ -> - false + | Typ.Procname.Java pname_java + -> let method_name = Typ.Procname.java_get_method pname_java in + String.equal method_name on_destroy || String.equal method_name on_destroy_view + | _ + -> false let android_lifecycles = let android_content = "android.content" in let android_app = "android.app" in let fragment_lifecycle = - ["onInflate"; "onAttach"; "onCreate"; "onCreateView"; "onViewCreated"; "onActivityCreated"; - "onViewStateRestored"; "onStart"; "onResume"; "onPause"; "onSaveInstanceState"; "onStop"; - on_destroy_view; on_destroy; "onDetach"] in - [ (android_content, - "ContentProvider", - ["onCreate"]); - (android_app, - "Activity", - ["onCreate"; "onStart"; "onRestoreInstanceState"; "onPostCreate"; "onResume"; "onPostResume"; - "onCreateDescription"; "onSaveInstanceState"; "onPause"; "onStop"; on_destroy]); - (android_app, - "Service", - ["onCreate"; "onStart"; "onStartCommand"; "onBind"; "onUnbind"; on_destroy]); - (android_content, - "BroadcastReceiever", - ["onReceive"]); - (android_app, - "Fragment", - fragment_lifecycle); - (* this is the pre-Android 3.0 Fragment type (can also be used post-3.0) *) - ("android.support.v4.app", - "Fragment", - fragment_lifecycle); - ] + [ "onInflate" + ; "onAttach" + ; "onCreate" + ; "onCreateView" + ; "onViewCreated" + ; "onActivityCreated" + ; "onViewStateRestored" + ; "onStart" + ; "onResume" + ; "onPause" + ; "onSaveInstanceState" + ; "onStop" + ; on_destroy_view + ; on_destroy + ; "onDetach" ] + in + [ (android_content, "ContentProvider", ["onCreate"]) + ; ( android_app + , "Activity" + , [ "onCreate" + ; "onStart" + ; "onRestoreInstanceState" + ; "onPostCreate" + ; "onResume" + ; "onPostResume" + ; "onCreateDescription" + ; "onSaveInstanceState" + ; "onPause" + ; "onStop" + ; on_destroy ] ) + ; ( android_app + , "Service" + , ["onCreate"; "onStart"; "onStartCommand"; "onBind"; "onUnbind"; on_destroy] ) + ; (android_content, "BroadcastReceiever", ["onReceive"]) + ; (android_app, "Fragment", fragment_lifecycle) + ; (* this is the pre-Android 3.0 Fragment type (can also be used post-3.0) *) + ("android.support.v4.app", "Fragment", fragment_lifecycle) ] let is_subtype_package_class tenv tname package classname = - PatternMatch.is_subtype tenv - tname (Typ.Name.Java.from_package_class package classname) + PatternMatch.is_subtype tenv tname (Typ.Name.Java.from_package_class package classname) -let is_context tenv tname = - is_subtype_package_class tenv tname "android.content" "Context" +let is_context tenv tname = is_subtype_package_class tenv tname "android.content" "Context" -let is_application tenv tname = - is_subtype_package_class tenv tname "android.app" "Application" +let is_application tenv tname = is_subtype_package_class tenv tname "android.app" "Application" -let is_activity tenv tname = - is_subtype_package_class tenv tname "android.app" "Activity" +let is_activity tenv tname = is_subtype_package_class tenv tname "android.app" "Activity" -let is_view tenv tname = - is_subtype_package_class tenv tname "android.view" "View" +let is_view tenv tname = is_subtype_package_class tenv tname "android.view" "View" let is_fragment tenv tname = - is_subtype_package_class tenv tname "android.app" "Fragment" || - is_subtype_package_class tenv tname "android.support.v4.app" "Fragment" + is_subtype_package_class tenv tname "android.app" "Fragment" + || is_subtype_package_class tenv tname "android.support.v4.app" "Fragment" (** return true if [class_name] is the name of a class that belong to the Android framework *) let is_android_lib_class class_name = @@ -85,24 +92,29 @@ let is_android_lib_class class_name = a list of method names [lifecycle_procs_strs], get the appropriate typ and procnames *) let get_lifecycle_for_framework_typ_opt tenv lifecycle_typ lifecycle_proc_strs = match Tenv.lookup tenv lifecycle_typ with - | Some { methods } -> - (* TODO (t4645631): collect the procedures for which is_java is returning false *) + | Some {methods} + -> (* TODO (t4645631): collect the procedures for which is_java is returning false *) let lookup_proc lifecycle_proc = - List.find_exn ~f:(fun decl_proc -> + List.find_exn + ~f:(fun decl_proc -> match decl_proc with - | Typ.Procname.Java decl_proc_java -> - String.equal lifecycle_proc (Typ.Procname.java_get_method decl_proc_java) - | _ -> - false - ) methods in + | Typ.Procname.Java decl_proc_java + -> String.equal lifecycle_proc (Typ.Procname.java_get_method decl_proc_java) + | _ + -> false) + methods + in (* convert each of the framework lifecycle proc strings to a lifecycle method procname *) let lifecycle_procs = - List.fold ~f:(fun lifecycle_procs lifecycle_proc_str -> - try (lookup_proc lifecycle_proc_str) :: lifecycle_procs + List.fold + ~f:(fun lifecycle_procs lifecycle_proc_str -> + try lookup_proc lifecycle_proc_str :: lifecycle_procs with Not_found -> lifecycle_procs) - ~init:[] lifecycle_proc_strs in + ~init:[] lifecycle_proc_strs + in lifecycle_procs - | _ -> [] + | _ + -> [] (** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *) let get_lifecycles = android_lifecycles diff --git a/infer/src/harness/androidFramework.mli b/infer/src/harness/androidFramework.mli index bc28f868b..ea76c9f28 100644 --- a/infer/src/harness/androidFramework.mli +++ b/infer/src/harness/androidFramework.mli @@ -11,29 +11,30 @@ open! IStd (** Android lifecycle types and their lifecycle methods that are called by the framework *) -(** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *) val get_lifecycles : (string * string * string list) list +(** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *) -(** return true if [typename] <: android.content.Context *) val is_context : Tenv.t -> Typ.Name.t -> bool +(** return true if [typename] <: android.content.Context *) -(** return true if [typename] <: android.app.Application *) val is_application : Tenv.t -> Typ.Name.t -> bool +(** return true if [typename] <: android.app.Application *) -(** return true if [typename] <: android.app.Activity *) val is_activity : Tenv.t -> Typ.Name.t -> bool +(** return true if [typename] <: android.app.Activity *) -(** return true if [typename] <: android.view.View *) val is_view : Tenv.t -> Typ.Name.t -> bool +(** return true if [typename] <: android.view.View *) val is_fragment : Tenv.t -> Typ.Name.t -> bool -(** return true if [procname] is a special lifecycle cleanup method *) val is_destroy_method : Typ.Procname.t -> bool +(** return true if [procname] is a special lifecycle cleanup method *) +val get_lifecycle_for_framework_typ_opt : + Tenv.t -> Typ.Name.t -> string list -> Typ.Procname.t list (** given an Android framework type mangled string [lifecycle_typ] (e.g., android.app.Activity) and a list of method names [lifecycle_procs_strs], get the appropriate typ and procnames *) -val get_lifecycle_for_framework_typ_opt : Tenv.t -> Typ.Name.t -> string list -> Typ.Procname.t list -(** return true if [class_name] is the name of a class that belong to the Android framework *) val is_android_lib_class : Typ.Name.t -> bool +(** return true if [class_name] is the name of a class that belong to the Android framework *) diff --git a/infer/src/harness/harness.ml b/infer/src/harness/harness.ml index 56af19652..2a4757831 100644 --- a/infer/src/harness/harness.ml +++ b/infer/src/harness/harness.ml @@ -8,7 +8,6 @@ *) open! IStd - module L = Logging module F = Format @@ -18,46 +17,53 @@ module F = Format constituting a lifecycle trace *) let try_create_lifecycle_trace name lifecycle_name lifecycle_procs tenv = match name with - | Typ.JavaClass _ -> - if PatternMatch.is_subtype tenv name lifecycle_name && - not (AndroidFramework.is_android_lib_class name) then + | Typ.JavaClass _ + -> if PatternMatch.is_subtype tenv name lifecycle_name + && not (AndroidFramework.is_android_lib_class name) + then let ptr_to_struct_typ = Some (Typ.mk (Tptr (Typ.mk (Tstruct name), Pk_pointer))) in List.fold ~f:(fun trace lifecycle_proc -> - (* given a lifecycle subclass T, resolve the call T.lifecycle_proc() to the procname + (* given a lifecycle subclass T, resolve the call T.lifecycle_proc() to the procname * that will actually be called at runtime *) - let resolved_proc = SymExec.resolve_method tenv name lifecycle_proc in - (resolved_proc, ptr_to_struct_typ) :: trace) - ~init:[] - lifecycle_procs - else - [] - | _ -> [] + let resolved_proc = SymExec.resolve_method tenv name lifecycle_proc in + (resolved_proc, ptr_to_struct_typ) :: trace) + ~init:[] lifecycle_procs + else [] + | _ + -> [] (** generate a harness for a lifecycle type in an Android application *) let create_harness cfg cg tenv = - List.iter ~f:(fun (pkg, clazz, lifecycle_methods) -> + List.iter + ~f:(fun (pkg, clazz, lifecycle_methods) -> let typname = Typ.Name.Java.from_package_class pkg clazz in let framework_procs = - AndroidFramework.get_lifecycle_for_framework_typ_opt tenv typname lifecycle_methods in + AndroidFramework.get_lifecycle_for_framework_typ_opt tenv typname lifecycle_methods + in (* iterate through the type environment and generate a lifecycle harness for each subclass of [lifecycle_typ] *) (* TODO: instead of iterating through the type environment, interate through the types declared in [cfg] *) - Tenv.iter (fun name _ -> + Tenv.iter + (fun name _ -> match try_create_lifecycle_trace name typname framework_procs tenv with - | [] -> () - | lifecycle_trace -> - let harness_procname = + | [] + -> () + | lifecycle_trace + -> let harness_procname = let harness_cls_name = Typ.Name.name name in let pname = Typ.Procname.Java - (Typ.Procname.java - (Typ.Name.Java.from_string harness_cls_name) None - "InferGeneratedHarness" [] Typ.Procname.Static) in + (Typ.Procname.java (Typ.Name.Java.from_string harness_cls_name) None + "InferGeneratedHarness" [] Typ.Procname.Static) + in match pname with - | Typ.Procname.Java harness_procname -> harness_procname - | _ -> assert false in - Inhabit.inhabit_trace tenv lifecycle_trace harness_procname cg cfg - ) tenv - ) AndroidFramework.get_lifecycles + | Typ.Procname.Java harness_procname + -> harness_procname + | _ + -> assert false + in + Inhabit.inhabit_trace tenv lifecycle_trace harness_procname cg cfg) + tenv) + AndroidFramework.get_lifecycles diff --git a/infer/src/harness/harness.mli b/infer/src/harness/harness.mli index 562bbe24f..bddf06cf8 100644 --- a/infer/src/harness/harness.mli +++ b/infer/src/harness/harness.mli @@ -11,5 +11,5 @@ open! IStd (** Automatically create a harness method to exercise code under test *) -(** Generate a harness method for exe_env and add it to the execution environment *) val create_harness : Cfg.cfg -> Cg.t -> Tenv.t -> unit +(** Generate a harness method for exe_env and add it to the execution environment *) diff --git a/infer/src/harness/inhabit.ml b/infer/src/harness/inhabit.ml index 411af409d..482330819 100644 --- a/infer/src/harness/inhabit.ml +++ b/infer/src/harness/inhabit.ml @@ -23,41 +23,37 @@ type lifecycle_trace = (Typ.Procname.t * Typ.t option) list (** list of instrs and temporary variables created during inhabitation and a cache of types that * have already been inhabited *) -type env = { instrs : Sil.instr list; - cache : Exp.t TypMap.t; - (* set of types currently being inhabited. consult to prevent infinite recursion *) - cur_inhabiting : TypSet.t; - pc : Location.t; - harness_name : Typ.Procname.java } +type env = + { instrs: Sil.instr list + ; cache: Exp.t TypMap.t + ; (* set of types currently being inhabited. consult to prevent infinite recursion *) + cur_inhabiting: TypSet.t + ; pc: Location.t + ; harness_name: Typ.Procname.java } let procdesc_from_name cfg pname = let pdesc_ref = ref None in - Cfg.iter_proc_desc cfg - (fun cfg_pname pdesc -> - if Typ.Procname.equal cfg_pname pname then - pdesc_ref := Some pdesc - ); + Cfg.iter_proc_desc cfg (fun cfg_pname pdesc -> + if Typ.Procname.equal cfg_pname pname then pdesc_ref := Some pdesc ) ; !pdesc_ref let formals_from_name cfg pname = - match procdesc_from_name cfg pname with - | Some pdesc -> Procdesc.get_formals pdesc - | None -> [] + match procdesc_from_name cfg pname with Some pdesc -> Procdesc.get_formals pdesc | None -> [] (** add an instruction to the env, update tmp_vars, and bump the pc *) let env_add_instr instr env = - let incr_pc pc = { pc with Location.line = pc.Location.line + 1 } in - { env with instrs = instr :: env.instrs; pc = incr_pc env.pc } + let incr_pc pc = {pc with Location.line= pc.Location.line + 1} in + {env with instrs= instr :: env.instrs; pc= incr_pc env.pc} (** call flags for an allocation or call to a constructor *) let cf_alloc = CallFlags.default -let fun_exp_from_name proc_name = Exp.Const (Const.Cfun (proc_name)) +let fun_exp_from_name proc_name = Exp.Const (Const.Cfun proc_name) let local_name_cntr = ref 0 let create_fresh_local_name () = - incr local_name_cntr; + incr local_name_cntr ; "dummy_local" ^ string_of_int !local_name_cntr (** more forgiving variation of List.tl that won't raise an exception on the empty list *) @@ -73,87 +69,103 @@ let inhabit_alloc sizeof_typ sizeof_len ret_typ alloc_kind env = let inhabited_exp = Exp.Var retval in let call_instr = let fun_new = fun_exp_from_name alloc_kind in - let sizeof_exp = Exp.Sizeof {typ=sizeof_typ; nbytes=None; - dynamic_length=sizeof_len; subtype=Subtype.exact} in + let sizeof_exp = + Exp.Sizeof {typ= sizeof_typ; nbytes= None; dynamic_length= sizeof_len; subtype= Subtype.exact} + in let args = [(sizeof_exp, Typ.mk (Tptr (ret_typ, Typ.Pk_pointer)))] in - Sil.Call (Some (retval, ret_typ), fun_new, args, env.pc, cf_alloc) in + Sil.Call (Some (retval, ret_typ), fun_new, args, env.pc, cf_alloc) + in (inhabited_exp, env_add_instr call_instr env) -(** find or create a Sil expression with type typ *) (* TODO: this should be done in a differnt way: just make typ a param of the harness procedure *) + +(** find or create a Sil expression with type typ *) let rec inhabit_typ tenv typ cfg env = try (TypMap.find typ env.cache, env) with Not_found -> - let inhabit_internal typ env = match typ.Typ.desc with - | Typ.Tptr ({desc=Tarray (inner_typ, Some _, _)}, Typ.Pk_pointer) -> - let len = Exp.Const (Const.Cint (IntLit.one)) in + let inhabit_internal typ env = + match typ.Typ.desc with + | Typ.Tptr ({desc= Tarray (inner_typ, Some _, _)}, Typ.Pk_pointer) + -> let len = Exp.Const (Const.Cint IntLit.one) in let arr_typ = Typ.mk (Tarray (inner_typ, Some IntLit.one, None)) in inhabit_alloc arr_typ (Some len) typ BuiltinDecl.__new_array env - | Typ.Tptr (typ, Typ.Pk_pointer) -> - (* TODO (t4575417): this case does not work correctly for enums, but they are currently + | Typ.Tptr (typ, Typ.Pk_pointer) + -> (* TODO (t4575417): this case does not work correctly for enums, but they are currently * broken in Infer anyway (see t4592290) *) - let (allocated_obj_exp, env) = inhabit_alloc typ None typ BuiltinDecl.__new env in + let allocated_obj_exp, env = inhabit_alloc typ None typ BuiltinDecl.__new env in (* select methods that are constructors and won't force us into infinite recursion because * we are already inhabiting one of their argument types *) let get_all_suitable_constructors (typ: Typ.t) = match typ.desc with | Tstruct name when Typ.Name.is_class name -> ( - match Tenv.lookup tenv name with - | Some { methods } -> - let is_suitable_constructor p = - let try_get_non_receiver_formals p = - get_non_receiver_formals (formals_from_name cfg p) in - Typ.Procname.is_constructor p - && List.for_all ~f:(fun (_, typ) -> - not (TypSet.mem typ env.cur_inhabiting) - ) (try_get_non_receiver_formals p) in - List.filter ~f:(fun p -> is_suitable_constructor p) methods - | _ -> [] - ) - | _ -> [] + match Tenv.lookup tenv name with + | Some {methods} + -> let is_suitable_constructor p = + let try_get_non_receiver_formals p = + get_non_receiver_formals (formals_from_name cfg p) + in + Typ.Procname.is_constructor p + && List.for_all + ~f:(fun (_, typ) -> not (TypSet.mem typ env.cur_inhabiting)) + (try_get_non_receiver_formals p) + in + List.filter ~f:(fun p -> is_suitable_constructor p) methods + | _ + -> [] ) + | _ + -> [] in - let (env, typ_class_name) = match get_all_suitable_constructors typ with - | constructor :: _ -> - (* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to + let env, typ_class_name = + match get_all_suitable_constructors typ with + | constructor :: _ + -> (* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to * nondeterministically call all possible constructors instead *) - let env = - inhabit_constructor tenv constructor (allocated_obj_exp, typ) cfg env in + let env = inhabit_constructor tenv constructor (allocated_obj_exp, typ) cfg env in (* try to get the unqualified name as a class (e.g., Object for java.lang.Object so we * we can use it as a descriptive local variable name in the harness *) let typ_class_name = match constructor with - | Typ.Procname.Java pname_java -> - Typ.Procname.java_get_simple_class_name pname_java - | _ -> - create_fresh_local_name () in + | Typ.Procname.Java pname_java + -> Typ.Procname.java_get_simple_class_name pname_java + | _ + -> create_fresh_local_name () + in (env, Mangled.from_string typ_class_name) - | [] -> (env, Mangled.from_string (create_fresh_local_name ())) in + | [] + -> (env, Mangled.from_string (create_fresh_local_name ())) + in (* add the instructions *& local = [allocated_obj_exp]; id = *& local, where local and id are * both fresh. the only point of this is to add a descriptive local name that makes error * reports from the harness look nicer -- it's not necessary to make symbolic execution work *) let fresh_local_exp = - Exp.Lvar (Pvar.mk typ_class_name (Typ.Procname.Java env.harness_name)) in - let write_to_local_instr = - Sil.Store (fresh_local_exp, typ, allocated_obj_exp, env.pc) in + Exp.Lvar (Pvar.mk typ_class_name (Typ.Procname.Java env.harness_name)) + in + let write_to_local_instr = Sil.Store (fresh_local_exp, typ, allocated_obj_exp, env.pc) in let env' = env_add_instr write_to_local_instr env in let fresh_id = Ident.create_fresh Ident.knormal in let read_from_local_instr = Sil.Load (fresh_id, fresh_local_exp, typ, env'.pc) in (Exp.Var fresh_id, env_add_instr read_from_local_instr env') - | Typ.Tint (_) -> (Exp.Const (Const.Cint (IntLit.zero)), env) - | Typ.Tfloat (_) -> (Exp.Const (Const.Cfloat 0.0), env) - | _ -> - L.internal_error "Couldn't inhabit typ: %a@." (Typ.pp Pp.text) typ; - assert false in - let (inhabited_exp, env') = - inhabit_internal typ { env with cur_inhabiting = TypSet.add typ env.cur_inhabiting } in - (inhabited_exp, { env' with cache = TypMap.add typ inhabited_exp env.cache; - cur_inhabiting = env.cur_inhabiting }) + | Typ.Tint _ + -> (Exp.Const (Const.Cint IntLit.zero), env) + | Typ.Tfloat _ + -> (Exp.Const (Const.Cfloat 0.0), env) + | _ + -> L.internal_error "Couldn't inhabit typ: %a@." (Typ.pp Pp.text) typ ; + assert false + in + let inhabited_exp, env' = + inhabit_internal typ {env with cur_inhabiting= TypSet.add typ env.cur_inhabiting} + in + ( inhabited_exp + , {env' with cache= TypMap.add typ inhabited_exp env.cache; cur_inhabiting= env.cur_inhabiting} + ) (** inhabit each of the types in the formals list *) and inhabit_args tenv formals cfg env = let inhabit_arg (_, formal_typ) (args, env) = - let (exp, env) = inhabit_typ tenv formal_typ cfg env in - ((exp, formal_typ) :: args, env) in + let exp, env = inhabit_typ tenv formal_typ cfg env in + ((exp, formal_typ) :: args, env) + in List.fold_right ~f:inhabit_arg formals ~init:([], env) (** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the @@ -162,12 +174,14 @@ and inhabit_constructor tenv constr_name (allocated_obj, obj_type) cfg env = try (* this lookup can fail when we try to get the procdesc of a procedure from a different * module. this could be solved with a whole - program class hierarchy analysis *) - let (args, env) = + let args, env = let non_receiver_formals = tl_or_empty (formals_from_name cfg constr_name) in - inhabit_args tenv non_receiver_formals cfg env in + inhabit_args tenv non_receiver_formals cfg env + in let constr_instr = let fun_exp = fun_exp_from_name constr_name in - Sil.Call (None, fun_exp, (allocated_obj, obj_type) :: args, env.pc, CallFlags.default) in + Sil.Call (None, fun_exp, (allocated_obj, obj_type) :: args, env.pc, CallFlags.default) + in env_add_instr constr_instr env with Not_found -> env @@ -175,54 +189,63 @@ let inhabit_call_with_args procname procdesc args env = let retval = let ret_typ = Procdesc.get_ret_type procdesc in let is_void = Typ.equal_desc ret_typ.Typ.desc Typ.Tvoid in - if is_void then None else Some (Ident.create_fresh Ident.knormal, ret_typ) in + if is_void then None else Some (Ident.create_fresh Ident.knormal, ret_typ) + in let call_instr = let fun_exp = fun_exp_from_name procname in let flags = - { CallFlags.default with CallFlags.cf_virtual = not (Typ.Procname.java_is_static procname); } in - Sil.Call (retval, fun_exp, args, env.pc, flags) in + {CallFlags.default with CallFlags.cf_virtual= not (Typ.Procname.java_is_static procname)} + in + Sil.Call (retval, fun_exp, args, env.pc, flags) + in env_add_instr call_instr env (** create Sil that inhabits args to and calls proc_name *) let inhabit_call tenv (procname, receiver) cfg env = try match procdesc_from_name cfg procname with - | Some procdesc -> - (* swap the type of the 'this' formal with the receiver type, if there is one *) - let formals = match (Procdesc.get_formals procdesc, receiver) with - | ((name, _) :: formals, Some receiver) -> (name, receiver) :: formals - | (formals, None) -> formals - | ([], Some _) -> - failwithf - "Expected at least one formal to bind receiver to in method %a" - Typ.Procname.pp procname in - let (args, env) = inhabit_args tenv formals cfg env in + | Some procdesc + -> (* swap the type of the 'this' formal with the receiver type, if there is one *) + let formals = + match (Procdesc.get_formals procdesc, receiver) with + | (name, _) :: formals, Some receiver + -> (name, receiver) :: formals + | formals, None + -> formals + | [], Some _ + -> failwithf "Expected at least one formal to bind receiver to in method %a" + Typ.Procname.pp procname + in + let args, env = inhabit_args tenv formals cfg env in inhabit_call_with_args procname procdesc args env - | None -> env + | None + -> env with Not_found -> env (** create a dummy file for the harness and associate them in the exe_env *) let create_dummy_harness_filename harness_name = - let dummy_file_dir = - Filename.temp_dir_name in + let dummy_file_dir = Filename.temp_dir_name in let file_str = - Typ.Procname.java_get_class_name - harness_name ^ "_" ^ Typ.Procname.java_get_method harness_name ^ ".java" in + Typ.Procname.java_get_class_name harness_name ^ "_" ^ Typ.Procname.java_get_method harness_name + ^ ".java" + in Filename.concat dummy_file_dir file_str -(** write the SIL for the harness to a file *) (* TODO (t3040429): fill this file up with Java-like code that matches the SIL *) + +(** write the SIL for the harness to a file *) let write_harness_to_file harness_instrs harness_file_name = let harness_file = Utils.create_outfile harness_file_name in - let pp_harness fmt = List.iter ~f:(fun instr -> - Format.fprintf fmt "%a@\n" (Sil.pp_instr Pp.text) instr) harness_instrs in - Utils.do_outf harness_file (fun outf -> - pp_harness outf.fmt; - Utils.close_outf outf) + let pp_harness fmt = + List.iter + ~f:(fun instr -> Format.fprintf fmt "%a@\n" (Sil.pp_instr Pp.text) instr) + harness_instrs + in + Utils.do_outf harness_file (fun outf -> pp_harness outf.fmt ; Utils.close_outf outf) (** add the harness proc to the cg and make sure its callees can be looked up by sym execution *) let add_harness_to_cg harness_name harness_node cg = - Cg.add_defined_node cg (Typ.Procname.Java harness_name); + Cg.add_defined_node cg (Typ.Procname.Java harness_name) ; List.iter ~f:(fun p -> Cg.add_edge cg (Typ.Procname.Java harness_name) p) (Procdesc.Node.get_callees harness_node) @@ -231,30 +254,30 @@ let add_harness_to_cg harness_name harness_node cg = * proc to the cg *) let setup_harness_cfg harness_name env cg cfg = (* each procedure has different scope: start names from id 0 *) - Ident.NameGenerator.reset (); + Ident.NameGenerator.reset () ; let procname = Typ.Procname.Java harness_name in let proc_attributes = { (ProcAttributes.default procname Config.Java) with - ProcAttributes.is_defined = true; - loc = env.pc; - } in - let procdesc = - Cfg.create_proc_desc cfg proc_attributes in + ProcAttributes.is_defined= true; loc= env.pc } + in + let procdesc = Cfg.create_proc_desc cfg proc_attributes in let harness_node = (* important to reverse the list or there will be scoping issues! *) - let instrs = (List.rev env.instrs) in + let instrs = List.rev env.instrs in let nodekind = Procdesc.Node.Stmt_node "method_body" in - Procdesc.create_node procdesc env.pc nodekind instrs in - let (start_node, exit_node) = + Procdesc.create_node procdesc env.pc nodekind instrs + in + let start_node, exit_node = let create_node kind = Procdesc.create_node procdesc env.pc kind [] in let start_kind = Procdesc.Node.Start_node procname in let exit_kind = Procdesc.Node.Exit_node procname in - (create_node start_kind, create_node exit_kind) in - Procdesc.set_start_node procdesc start_node; - Procdesc.set_exit_node procdesc exit_node; - Procdesc.Node.add_locals_ret_declaration start_node proc_attributes []; - Procdesc.node_set_succs_exn procdesc start_node [harness_node] [exit_node]; - Procdesc.node_set_succs_exn procdesc harness_node [exit_node] [exit_node]; + (create_node start_kind, create_node exit_kind) + in + Procdesc.set_start_node procdesc start_node ; + Procdesc.set_exit_node procdesc exit_node ; + Procdesc.Node.add_locals_ret_declaration start_node proc_attributes [] ; + Procdesc.node_set_succs_exn procdesc start_node [harness_node] [exit_node] ; + Procdesc.node_set_succs_exn procdesc harness_node [exit_node] [exit_node] ; add_harness_to_cg harness_name harness_node cg (** create a procedure named harness_name that calls each of the methods in trace in the specified @@ -265,19 +288,14 @@ let inhabit_trace tenv trace harness_name cg cfg = let harness_filename = create_dummy_harness_filename harness_name in let start_line = 1 in let empty_env = - let pc = { Location.line = start_line; col = 1; file = source_file; } in - { instrs = []; - cache = TypMap.empty; - pc = pc; - cur_inhabiting = TypSet.empty; - harness_name = harness_name; } in + let pc = {Location.line= start_line; col= 1; file= source_file} in + {instrs= []; cache= TypMap.empty; pc; cur_inhabiting= TypSet.empty; harness_name} + in (* invoke lifecycle methods *) let env'' = - List.fold - ~f:(fun env to_call -> inhabit_call tenv to_call cfg env) - ~init:empty_env - trace in + List.fold ~f:(fun env to_call -> inhabit_call tenv to_call cfg env) ~init:empty_env trace + in try - setup_harness_cfg harness_name env'' cg cfg; + setup_harness_cfg harness_name env'' cg cfg ; write_harness_to_file (List.rev env''.instrs) harness_filename with Not_found -> () diff --git a/infer/src/harness/inhabit.mli b/infer/src/harness/inhabit.mli index 80bfb0573..1f692e5b0 100644 --- a/infer/src/harness/inhabit.mli +++ b/infer/src/harness/inhabit.mli @@ -13,7 +13,6 @@ open! IStd type lifecycle_trace = (Typ.Procname.t * Typ.t option) list +val inhabit_trace : Tenv.t -> lifecycle_trace -> Typ.Procname.java -> Cg.t -> Cfg.cfg -> unit (** create a procedure named harness_name that calls each of the methods in trace add it to the cg/cfg *) -val inhabit_trace : Tenv.t -> lifecycle_trace -> Typ.Procname.java -> Cg.t -> Cfg.cfg -> unit - diff --git a/infer/src/integration/Buck.ml b/infer/src/integration/Buck.ml index 2abdf4579..1fd63d1e1 100644 --- a/infer/src/integration/Buck.ml +++ b/infer/src/integration/Buck.ml @@ -9,30 +9,29 @@ open! IStd -type target = { name: string; flavors: string list; } +type target = {name: string; flavors: string list} let target_of_string target = match String.split target ~on:'#' with - | name::flavors_string::[] -> - let flavors = String.split flavors_string ~on:',' in + | [name; flavors_string] + -> let flavors = String.split flavors_string ~on:',' in {name; flavors} - | name::[] -> { name; flavors=[] } - | _ -> failwithf "cannot parse target %s" target + | [name] + -> {name; flavors= []} + | _ + -> failwithf "cannot parse target %s" target -let string_of_target { name; flavors } = +let string_of_target {name; flavors} = let pp_string fmt s = Format.fprintf fmt "%s" s in Format.asprintf "%s#%a" name (Pp.comma_seq pp_string) flavors - let is_target_string = let target_regexp = Str.regexp "[^/]*//[^/]+.*:.*" in - fun s -> - Str.string_match target_regexp s 0 + fun s -> Str.string_match target_regexp s 0 let no_targets_found_error_and_exit buck_cmd = Process.print_error_and_exit - "No targets found in Buck command %s.@\nOnly fully qualified Buck targets are supported. \ - In particular, aliases are not allowed.@." + "No targets found in Buck command %s.@\nOnly fully qualified Buck targets are supported. In particular, aliases are not allowed.@." (String.concat ~sep:" " buck_cmd) let add_flavor_to_target target = @@ -40,28 +39,30 @@ let add_flavor_to_target target = if List.mem ~equal:String.equal target.flavors flavor then (* there's already an infer flavor associated to the target, do nothing *) target - else - { target with flavors = flavor::target.flavors } in - match Config.buck_compilation_database, Config.analyzer with - | Some `Deps, _ -> - add "uber-compilation-database" - | Some `NoDeps, _ -> - add "compilation-database" - | None, CompileOnly -> - target - | None, (Linters | CaptureOnly) -> - add "infer-capture-all" - | None, (BiAbduction | Checkers) -> - add "infer" - | None, (Eradicate | Crashcontext) -> - failwithf "Analyzer %s is Java-only; not supported with Buck flavors" + else {target with flavors= flavor :: target.flavors} + in + match (Config.buck_compilation_database, Config.analyzer) with + | Some `Deps, _ + -> add "uber-compilation-database" + | Some `NoDeps, _ + -> add "compilation-database" + | None, CompileOnly + -> target + | None, (Linters | CaptureOnly) + -> add "infer-capture-all" + | None, (BiAbduction | Checkers) + -> add "infer" + | None, (Eradicate | Crashcontext) + -> failwithf "Analyzer %s is Java-only; not supported with Buck flavors" (Config.string_of_analyzer Config.analyzer) let add_flavors_to_buck_command build_cmd = let add_infer_if_target s (cmd, found_one_target) = - if not (is_target_string s) then (s::cmd, found_one_target) - else (string_of_target (add_flavor_to_target (target_of_string s))::cmd, true) in - let (cmd', found_one_target) = - List.fold_right build_cmd ~f:add_infer_if_target ~init:([], false) in - if not found_one_target then no_targets_found_error_and_exit build_cmd; + if not (is_target_string s) then (s :: cmd, found_one_target) + else (string_of_target (add_flavor_to_target (target_of_string s)) :: cmd, true) + in + let cmd', found_one_target = + List.fold_right build_cmd ~f:add_infer_if_target ~init:([], false) + in + if not found_one_target then no_targets_found_error_and_exit build_cmd ; cmd' diff --git a/infer/src/integration/Buck.mli b/infer/src/integration/Buck.mli index 34d03dccc..8544ca0e1 100644 --- a/infer/src/integration/Buck.mli +++ b/infer/src/integration/Buck.mli @@ -9,16 +9,16 @@ open! IStd -(** is this a Buck target string, eg //foo/bar:baz or boo//foo/bar:baz *) val is_target_string : string -> bool +(** is this a Buck target string, eg //foo/bar:baz or boo//foo/bar:baz *) -(** prints an error that no Buck targets were identified in the given list, and exits *) val no_targets_found_error_and_exit : string list -> unit +(** prints an error that no Buck targets were identified in the given list, and exits *) +val add_flavors_to_buck_command : string list -> string list (** Add infer flavors to the targets in the given buck command, depending on the infer analyzer. For instance, in capture mode, the buck command: buck build //foo/bar:baz#some,flavor becomes: buck build //foo/bar:baz#infer-capture-all,some,flavor *) -val add_flavors_to_buck_command : string list -> string list diff --git a/infer/src/integration/CaptureCompilationDatabase.ml b/infer/src/integration/CaptureCompilationDatabase.ml index 8fc3b525a..81e1afcd9 100644 --- a/infer/src/integration/CaptureCompilationDatabase.ml +++ b/infer/src/integration/CaptureCompilationDatabase.ml @@ -8,133 +8,135 @@ *) open! IStd - module F = Format - module CLOpt = CommandLineOption module L = Logging let capture_text = - if Config.equal_analyzer Config.analyzer Config.Linters then "linting" - else "translating" + if Config.equal_analyzer Config.analyzer Config.Linters then "linting" else "translating" let create_files_stack compilation_database should_capture_file = let stack = Stack.create () in - let add_to_stack file _ = if should_capture_file file then - Stack.push stack file in - CompilationDatabase.iter compilation_database add_to_stack; - stack + let add_to_stack file _ = if should_capture_file file then Stack.push stack file in + CompilationDatabase.iter compilation_database add_to_stack ; stack let swap_command cmd = let plusplus = "++" in let clang = "clang" in let clangplusplus = "clang++" in - if String.is_suffix ~suffix:plusplus cmd then - Config.wrappers_dir ^/ clangplusplus - else - Config.wrappers_dir ^/ clang + if String.is_suffix ~suffix:plusplus cmd then Config.wrappers_dir ^/ clangplusplus + else Config.wrappers_dir ^/ clang let run_compilation_file compilation_database file = try let compilation_data = CompilationDatabase.find compilation_database file in let wrapper_cmd = swap_command compilation_data.command in let arg_file = - ClangQuotes.mk_arg_file - "cdb_clang_args_" ClangQuotes.EscapedNoQuotes [compilation_data.args] in - let args = ["@" ^ arg_file] in + ClangQuotes.mk_arg_file "cdb_clang_args_" ClangQuotes.EscapedNoQuotes [compilation_data.args] + in + let args = [("@" ^ arg_file)] in let env = - `Extend [ - (CLOpt.args_env_var, - String.concat ~sep:(String.of_char CLOpt.env_var_sep) - (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--fcp-syntax-only"]))] in + `Extend + [ ( CLOpt.args_env_var + , String.concat ~sep:(String.of_char CLOpt.env_var_sep) + (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--fcp-syntax-only"]) ) ] + in (Some compilation_data.dir, wrapper_cmd, args, env) with Not_found -> - Process.print_error_and_exit "Failed to find compilation data for %a@\n%!" - SourceFile.pp file + Process.print_error_and_exit "Failed to find compilation data for %a@\n%!" SourceFile.pp file let run_compilation_database compilation_database should_capture_file = let number_of_files = CompilationDatabase.get_size compilation_database in - L.(debug Capture Quiet) "Starting %s %d files@\n%!" capture_text number_of_files; - L.progress "Starting %s %d files@\n%!" capture_text number_of_files; + L.(debug Capture Quiet) "Starting %s %d files@\n%!" capture_text number_of_files ; + L.progress "Starting %s %d files@\n%!" capture_text number_of_files ; let jobs_stack = create_files_stack compilation_database should_capture_file in let capture_text_upper = String.capitalize capture_text in - let job_to_string = - fun file -> Format.asprintf "%s %a" capture_text_upper SourceFile.pp file in + let job_to_string file = Format.asprintf "%s %a" capture_text_upper SourceFile.pp file in let fail_on_failed_job = if Config.linters_ignore_clang_failures then false else match Config.buck_compilation_database with - | Some `NoDeps -> Config.clang_frontend_do_lint - | _ -> false in + | Some `NoDeps + -> Config.clang_frontend_do_lint + | _ + -> false + in Process.run_jobs_in_parallel ~fail_on_failed_job jobs_stack (run_compilation_file compilation_database) job_to_string (** Computes the compilation database files. *) let get_compilation_database_files_buck ~prog ~args = match Buck.add_flavors_to_buck_command args with - | build :: args_with_flavor -> ( + | build :: args_with_flavor + -> ( let build_args = build :: "--config" :: "*//cxx.pch_enabled=false" :: args_with_flavor in - Process.create_process_and_wait ~prog ~args:build_args; + Process.create_process_and_wait ~prog ~args:build_args ; (* The option --keep-going is not accepted in the command buck targets *) let args_with_flavor_no_keep_going = - List.filter ~f:(fun s -> not (String.equal s "--keep-going")) args_with_flavor in + List.filter ~f:(fun s -> not (String.equal s "--keep-going")) args_with_flavor + in let buck_targets_shell = prog :: "targets" :: "--show-output" :: args_with_flavor_no_keep_going - |> Utils.shell_escape_command in - let (output, exit_or_signal) = - Utils.with_process_in buck_targets_shell In_channel.input_lines in + |> Utils.shell_escape_command + in + let output, exit_or_signal = + Utils.with_process_in buck_targets_shell In_channel.input_lines + in match exit_or_signal with - | Error _ as status -> - failwithf "*** command failed:@\n*** %s@\n*** %s@." - buck_targets_shell + | Error _ as status + -> failwithf "*** command failed:@\n*** %s@\n*** %s@." buck_targets_shell (Unix.Exit_or_signal.to_string_hum status) | Ok () -> - match output with - | [] -> L.external_error "There are no files to process, exiting@."; exit 0 - | lines -> - L.(debug Capture Quiet) "Reading compilation database from:@\n%s@\n" - (String.concat ~sep:"\n" lines); - (* this assumes that flavors do not contain spaces *) - let split_regex = Str.regexp "#[^ ]* " in - let scan_output compilation_database_files line = - match Str.bounded_split split_regex line 2 with - | _::filename::[] -> - `Raw filename::compilation_database_files - | _ -> - failwithf - "Failed to parse `buck targets --show-output ...` line of output:@\n%s" - line in - List.fold ~f:scan_output ~init:[] lines - ) - | _ -> - let cmd = String.concat ~sep:" " (prog :: args) in - Process.print_error_and_exit "Incorrect buck command: %s. Please use buck build " cmd + match output with + | [] + -> L.external_error "There are no files to process, exiting@." ; exit 0 + | lines + -> L.(debug Capture Quiet) + "Reading compilation database from:@\n%s@\n" (String.concat ~sep:"\n" lines) ; + (* this assumes that flavors do not contain spaces *) + let split_regex = Str.regexp "#[^ ]* " in + let scan_output compilation_database_files line = + match Str.bounded_split split_regex line 2 with + | [_; filename] + -> `Raw filename :: compilation_database_files + | _ + -> failwithf "Failed to parse `buck targets --show-output ...` line of output:@\n%s" + line + in + List.fold ~f:scan_output ~init:[] lines ) + | _ + -> let cmd = String.concat ~sep:" " (prog :: args) in + Process.print_error_and_exit "Incorrect buck command: %s. Please use buck build " + cmd (** Compute the compilation database files. *) let get_compilation_database_files_xcodebuild ~prog ~args = let tmp_file = Filename.temp_file "cdb" ".json" in - let xcodebuild_prog, xcodebuild_args = prog, prog::args in + let xcodebuild_prog, xcodebuild_args = (prog, prog :: args) in let xcpretty_prog = "xcpretty" in let xcpretty_args = - [xcpretty_prog; "--report"; "json-compilation-database"; "--output"; tmp_file] in - L.(debug Capture Quiet) "Running %s | %s@\n@." (List.to_string ~f:Fn.id xcodebuild_args) - (List.to_string ~f:Fn.id xcpretty_args); + [xcpretty_prog; "--report"; "json-compilation-database"; "--output"; tmp_file] + in + L.(debug Capture Quiet) + "Running %s | %s@\n@." (List.to_string ~f:Fn.id xcodebuild_args) + (List.to_string ~f:Fn.id xcpretty_args) ; let producer_status, consumer_status = - Process.pipeline - ~producer_prog:xcodebuild_prog ~producer_args:xcodebuild_args - ~consumer_prog:xcpretty_prog ~consumer_args:xcpretty_args in - match producer_status, consumer_status with - | Ok (), Ok () -> [`Escaped tmp_file] - | _ -> - L.external_error "There was an error executing the build command"; - exit 1 + Process.pipeline ~producer_prog:xcodebuild_prog ~producer_args:xcodebuild_args + ~consumer_prog:xcpretty_prog ~consumer_args:xcpretty_args + in + match (producer_status, consumer_status) with + | Ok (), Ok () + -> [`Escaped tmp_file] + | _ + -> L.external_error "There was an error executing the build command" ; exit 1 let capture_files_in_database ~changed_files compilation_database = - let filter_changed = match changed_files with - | None -> - fun _ -> true - | Some changed_files_set -> - fun source_file -> SourceFile.Set.mem source_file changed_files_set + let filter_changed = + match changed_files with + | None + -> fun _ -> true + | Some changed_files_set + -> fun source_file -> SourceFile.Set.mem source_file changed_files_set in run_compilation_database compilation_database filter_changed diff --git a/infer/src/integration/CaptureCompilationDatabase.mli b/infer/src/integration/CaptureCompilationDatabase.mli index f7b5ee69b..f57178ffe 100644 --- a/infer/src/integration/CaptureCompilationDatabase.mli +++ b/infer/src/integration/CaptureCompilationDatabase.mli @@ -9,20 +9,20 @@ open! IStd +val capture_files_in_database : + changed_files:SourceFile.Set.t option -> CompilationDatabase.t -> unit (** Run the capture of the files for which we have compilation commands in the database and in [changed_files], if specified. *) -val capture_files_in_database : changed_files:SourceFile.Set.t option -> - CompilationDatabase.t -> unit val capture_file_in_database : CompilationDatabase.t -> SourceFile.t -> unit +val get_compilation_database_files_buck : + prog:string -> args:string list -> [> `Raw of string] list (** Get the compilation database files that contain the compilation given by the buck command. It will be the compilation of the passed targets only or also the dependencies according to the flag --buck-compilation-database deps | no-deps *) -val get_compilation_database_files_buck : prog:string -> args:string list -> - [> `Raw of string ] list +val get_compilation_database_files_xcodebuild : + prog:string -> args:string list -> [> `Escaped of string] list (** Get the compilation database files that contain the compilation given by the xcodebuild command, using xcpretty. *) -val get_compilation_database_files_xcodebuild : prog:string -> args:string list -> - [> `Escaped of string ] list diff --git a/infer/src/integration/Clang.ml b/infer/src/integration/Clang.ml index c85b10564..a9a14a2bf 100644 --- a/infer/src/integration/Clang.ml +++ b/infer/src/integration/Clang.ml @@ -7,55 +7,56 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module F = Format - module L = Logging -type compiler = - | Clang - | Make [@@deriving compare] +type compiler = Clang | Make [@@deriving compare] let rec pp_list pp fmt = function - | [] -> () - | x::[] -> pp fmt x - | x::tl -> F.fprintf fmt "%a@\n%a" pp x (pp_list pp) tl + | [] + -> () + | [x] + -> pp fmt x + | x :: tl + -> F.fprintf fmt "%a@\n%a" pp x (pp_list pp) tl -let pp_env fmt env = - pp_list (fun fmt s -> F.fprintf fmt "%s" s) fmt env +let pp_env fmt env = pp_list (fun fmt s -> F.fprintf fmt "%s" s) fmt env -let pp_extended_env fmt (env : Unix.env) = - let pp_pair fmt (var, value) = - F.fprintf fmt "%s=%s" var value in +let pp_extended_env fmt (env: Unix.env) = + let pp_pair fmt (var, value) = F.fprintf fmt "%s=%s" var value in let pp_pair_list = pp_list pp_pair in match env with - | `Replace values -> - pp_pair_list fmt values - | `Extend values -> - let is_extended s = + | `Replace values + -> pp_pair_list fmt values + | `Extend values + -> let is_extended s = match String.lsplit2 s ~on:'=' with - | Some (var, _) -> List.exists ~f:(fun (var', _) -> String.equal var var') values - | None -> false in - let env_not_extended = Unix.environment () |> Array.to_list - |> List.filter ~f:(Fn.non is_extended) in + | Some (var, _) + -> List.exists ~f:(fun (var', _) -> String.equal var var') values + | None + -> false + in + let env_not_extended = + Unix.environment () |> Array.to_list |> List.filter ~f:(Fn.non is_extended) + in F.fprintf fmt "%a@\n%a" pp_env env_not_extended pp_pair_list values - | `Replace_raw values -> - pp_env fmt values + | `Replace_raw values + -> pp_env fmt values let capture compiler ~prog ~args = match compiler with - | Clang -> - ClangWrapper.exe ~prog ~args - | Make -> - let path_var = "PATH" in - let new_path = Config.wrappers_dir ^ ":" ^ (Sys.getenv_exn path_var) in - let extended_env = `Extend [path_var, new_path] in - L.environment_info "Running command %s with env:@\n%a@\n@." prog pp_extended_env extended_env; - Unix.fork_exec ~prog:prog ~argv:(prog::args) ~env:extended_env () - |> Unix.waitpid + | Clang + -> ClangWrapper.exe ~prog ~args + | Make + -> let path_var = "PATH" in + let new_path = Config.wrappers_dir ^ ":" ^ Sys.getenv_exn path_var in + let extended_env = `Extend [(path_var, new_path)] in + L.environment_info "Running command %s with env:@\n%a@\n@." prog pp_extended_env extended_env ; + Unix.fork_exec ~prog ~argv:(prog :: args) ~env:extended_env () |> Unix.waitpid |> function - | Ok () -> () - | Error _ as status -> - failwithf "*** capture command failed:@\n*** %s@\n*** %s@." - (String.concat ~sep:" " (prog::args)) - (Unix.Exit_or_signal.to_string_hum status) + | Ok () + -> () + | Error _ as status + -> failwithf "*** capture command failed:@\n*** %s@\n*** %s@." + (String.concat ~sep:" " (prog :: args)) + (Unix.Exit_or_signal.to_string_hum status) diff --git a/infer/src/integration/Clang.mli b/infer/src/integration/Clang.mli index 269d29418..887e27eb6 100644 --- a/infer/src/integration/Clang.mli +++ b/infer/src/integration/Clang.mli @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd -type compiler = - | Clang - | Make [@@deriving compare] +type compiler = Clang | Make [@@deriving compare] val capture : compiler -> prog:string -> args:string list -> unit diff --git a/infer/src/integration/ClangQuotes.ml b/infer/src/integration/ClangQuotes.ml new file mode 100644 index 000000000..41fac518f --- /dev/null +++ b/infer/src/integration/ClangQuotes.ml @@ -0,0 +1,37 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) +(* module for escaping clang arguments on the command line and put them into files *) +open! IStd +module L = Logging + +(** quoting style of the arguments *) +type style = + | EscapedDoubleQuotes + (** the arguments should be enclosed in "double quotes" and are already escaped *) + | SingleQuotes (** the arguments should be enclosed in 'single quotes' and have to be escaped *) + | EscapedNoQuotes (** the arguments should not be enclosed in quotes and are already escaped *) + +let quote style = + match style with + | EscapedNoQuotes + -> fun s -> s + | EscapedDoubleQuotes + -> fun s -> "\"" ^ s ^ "\"" + | SingleQuotes + -> let map = function '\'' -> Some "\\'" | '\\' -> Some "\\\\" | _ -> None in + fun s -> "'" ^ Escape.escape_map map s ^ "'" + +let mk_arg_file prefix style args = + let file = Filename.temp_file prefix ".txt" in + let write_args outc = + Out_channel.output_string outc (List.map ~f:(quote style) args |> String.concat ~sep:" ") + in + Utils.with_file_out file ~f:write_args |> ignore ; + L.(debug Capture Medium) "Clang options stored in file %s@\n" file ; + file diff --git a/infer/src/integration/ClangQuotes.mli b/infer/src/integration/ClangQuotes.mli new file mode 100644 index 000000000..54a204942 --- /dev/null +++ b/infer/src/integration/ClangQuotes.mli @@ -0,0 +1,24 @@ +(* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +open! IStd + +(* module for escaping clang arguments on the command line and put them into files *) + +(** quoting style of the arguments *) + +type style = + | EscapedDoubleQuotes + (** the arguments should be enclosed in "double quotes" and are already escaped *) + | SingleQuotes (** the arguments should be enclosed in 'single quotes' and have to be escaped *) + | EscapedNoQuotes (** the arguments should not be enclosed in quotes and are already escaped *) + +val quote : style -> string -> string + +val mk_arg_file : string -> style -> string list -> string diff --git a/infer/src/integration/ClangQuotes.re b/infer/src/integration/ClangQuotes.re deleted file mode 100644 index 6944c0e5a..000000000 --- a/infer/src/integration/ClangQuotes.re +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -/* module for escaping clang arguments on the command line and put them into files */ -open! IStd; - -module L = Logging; - - -/** quoting style of the arguments */ -type style = - | EscapedDoubleQuotes /** the arguments should be enclosed in "double quotes" and are already escaped */ - | SingleQuotes /** the arguments should be enclosed in 'single quotes' and have to be escaped */ - | EscapedNoQuotes /** the arguments should not be enclosed in quotes and are already escaped */; - -let quote style => - switch style { - | EscapedNoQuotes => (fun s => s) - | EscapedDoubleQuotes => (fun s => "\"" ^ s ^ "\"") - | SingleQuotes => - let map = ( - fun - | '\'' => Some "\\'" - | '\\' => Some "\\\\" - | _ => None - ); - (fun s => "'" ^ Escape.escape_map map s ^ "'") - }; - -let mk_arg_file prefix style args => { - let file = Filename.temp_file prefix ".txt"; - let write_args outc => - Out_channel.output_string outc (List.map f::(quote style) args |> String.concat sep::" "); - Utils.with_file_out file f::write_args |> ignore; - L.(debug Capture Medium) "Clang options stored in file %s@\n" file; - file -}; diff --git a/infer/src/integration/ClangQuotes.rei b/infer/src/integration/ClangQuotes.rei deleted file mode 100644 index 998ef0677..000000000 --- a/infer/src/integration/ClangQuotes.rei +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2016 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -open! IStd; - -/* module for escaping clang arguments on the command line and put them into files */ - -/** quoting style of the arguments */ -type style = - | EscapedDoubleQuotes /** the arguments should be enclosed in "double quotes" and are already escaped */ - | SingleQuotes /** the arguments should be enclosed in 'single quotes' and have to be escaped */ - | EscapedNoQuotes /** the arguments should not be enclosed in quotes and are already escaped */; - -let quote: style => string => string; - -let mk_arg_file: string => style => list string => string; diff --git a/infer/src/integration/CompilationDatabase.ml b/infer/src/integration/CompilationDatabase.ml index 2e656b0b1..cdd19e03b 100644 --- a/infer/src/integration/CompilationDatabase.ml +++ b/infer/src/integration/CompilationDatabase.ml @@ -8,16 +8,12 @@ *) open! IStd - module L = Logging -type compilation_data = { - dir : string; - command : string; - args : string; -} +type compilation_data = {dir: string; command: string; args: string} type t = compilation_data SourceFile.Map.t ref + let empty () = ref SourceFile.Map.empty let get_size database = SourceFile.Map.cardinal !database @@ -29,63 +25,61 @@ let find database key = SourceFile.Map.find key !database let parse_command_and_arguments command_and_arguments = let regexp = Str.regexp "[^\\][ ]" in let index = Str.search_forward regexp command_and_arguments 0 in - let command = Str.string_before command_and_arguments (index+1) in - let arguments = Str.string_after command_and_arguments (index+1) in - command, arguments + let command = Str.string_before command_and_arguments (index + 1) in + let arguments = Str.string_after command_and_arguments (index + 1) in + (command, arguments) (** Parse the compilation database json file into the compilationDatabase map. The json file consists of an array of json objects that contain the file to be compiled, the directory to be compiled in, and the compilation command as a list and as a string. We pack this information into the compilationDatabase map, and remove the clang invocation part, because we will use a clang wrapper. *) -let decode_json_file (database : t) json_format = - let json_path = match json_format with | `Raw x | `Escaped x -> x in - let to_string s = match json_format with - | `Raw _ -> - s - | `Escaped _ -> - Utils.with_process_in (Printf.sprintf "/bin/sh -c 'printf \"%%s\" %s'" s) In_channel.input_line_exn - |> fst in - L.(debug Capture Quiet) "parsing compilation database from %s@\n" json_path; - let exit_format_error () = - failwith ("Json file doesn't have the expected format") in +let decode_json_file (database: t) json_format = + let json_path = + match json_format + with `Raw x | `Escaped x -> x + in + let to_string s = + match json_format with + | `Raw _ + -> s + | `Escaped _ + -> Utils.with_process_in (Printf.sprintf "/bin/sh -c 'printf \"%%s\" %s'" s) + In_channel.input_line_exn + |> fst + in + L.(debug Capture Quiet) "parsing compilation database from %s@\n" json_path ; + let exit_format_error () = failwith "Json file doesn't have the expected format" in let json = Yojson.Basic.from_file json_path in - let get_dir el = - match el with - | ("directory", `String dir) -> Some (to_string dir) - | _ -> None in - let get_file el = - match el with - | ("file", `String file) -> Some (to_string file) - | _ -> None in - let get_cmd el = - match el with - | ("command", `String cmd) -> Some cmd - | _ -> None in + let get_dir el = match el with "directory", `String dir -> Some (to_string dir) | _ -> None in + let get_file el = match el with "file", `String file -> Some (to_string file) | _ -> None in + let get_cmd el = match el with "command", `String cmd -> Some cmd | _ -> None in let rec parse_json json = match json with - | `List arguments -> - List.iter ~f:parse_json arguments - | `Assoc l -> - let dir = match List.find_map ~f:get_dir l with - | Some dir -> dir - | None -> exit_format_error () in - let file = match List.find_map ~f:get_file l with - | Some file -> file - | None -> exit_format_error () in - let cmd = match List.find_map ~f:get_cmd l with - | Some cmd -> cmd - | None -> exit_format_error () in + | `List arguments + -> List.iter ~f:parse_json arguments + | `Assoc l + -> let dir = + match List.find_map ~f:get_dir l with Some dir -> dir | None -> exit_format_error () + in + let file = + match List.find_map ~f:get_file l with Some file -> file | None -> exit_format_error () + in + let cmd = + match List.find_map ~f:get_cmd l with Some cmd -> cmd | None -> exit_format_error () + in let command, args = parse_command_and_arguments cmd in - let compilation_data = { dir; command; args;} in + let compilation_data = {dir; command; args} in let abs_file = if Filename.is_relative file then dir ^/ file else file in let source_file = SourceFile.from_abs_path abs_file in database := SourceFile.Map.add source_file compilation_data !database - | _ -> exit_format_error () in + | _ + -> exit_format_error () + in parse_json json let from_json_files db_json_files = let db = empty () in - List.iter ~f:(decode_json_file db) db_json_files; - L.(debug Capture Quiet) "created database with %d entries@\n" (get_size db); + List.iter ~f:(decode_json_file db) db_json_files ; + L.(debug Capture Quiet) "created database with %d entries@\n" (get_size db) ; db diff --git a/infer/src/integration/CompilationDatabase.mli b/infer/src/integration/CompilationDatabase.mli index 00a32f0d9..2dbf866d1 100644 --- a/infer/src/integration/CompilationDatabase.mli +++ b/infer/src/integration/CompilationDatabase.mli @@ -11,11 +11,7 @@ open! IStd type t -type compilation_data = { - dir : string; - command : string; - args : string; -} +type compilation_data = {dir: string; command: string; args: string} val empty : unit -> t @@ -25,6 +21,6 @@ val iter : t -> (SourceFile.t -> compilation_data -> unit) -> unit val find : t -> SourceFile.t -> compilation_data -val decode_json_file : t -> [< `Escaped of string | `Raw of string ] -> unit +val decode_json_file : t -> [< `Escaped of string | `Raw of string] -> unit -val from_json_files : [< `Escaped of string | `Raw of string ] list -> t +val from_json_files : [< `Escaped of string | `Raw of string] list -> t diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml index 7d060b0b3..19c5a2bcf 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -16,28 +16,50 @@ module L = Logging module F = Format type build_system = - | BAnalyze | BAnt | BBuck | BClang | BGradle | BJava | BJavac | BMake | BMvn - | BNdk | BXcode -[@@deriving compare] + | BAnalyze + | BAnt + | BBuck + | BClang + | BGradle + | BJava + | BJavac + | BMake + | BMvn + | BNdk + | BXcode + [@@deriving compare] let equal_build_system = [%compare.equal : build_system] (* List of ([build system], [executable name]). Several executables may map to the same build system. In that case, the first one in the list will be used for printing, eg, in which mode infer is running. *) -let build_system_exe_assoc = [ - BAnalyze, "analyze"; BAnt, "ant"; BBuck, "buck"; BGradle, "gradle"; BGradle, "gradlew"; - BJava, "java"; BJavac, "javac"; - BClang, "cc"; BClang, "clang"; BClang, "gcc"; BClang, "clang++"; BClang, "c++"; BClang, "g++"; - BMake, "make"; BMake, "configure"; BMake, "cmake"; BMake, "waf"; - BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild"; -] +let build_system_exe_assoc = + [ (BAnalyze, "analyze") + ; (BAnt, "ant") + ; (BBuck, "buck") + ; (BGradle, "gradle") + ; (BGradle, "gradlew") + ; (BJava, "java") + ; (BJavac, "javac") + ; (BClang, "cc") + ; (BClang, "clang") + ; (BClang, "gcc") + ; (BClang, "clang++") + ; (BClang, "c++") + ; (BClang, "g++") + ; (BMake, "make") + ; (BMake, "configure") + ; (BMake, "cmake") + ; (BMake, "waf") + ; (BMvn, "mvn") + ; (BMvn, "mvnw") + ; (BNdk, "ndk-build") + ; (BXcode, "xcodebuild") ] let build_system_of_exe_name name = - try - List.Assoc.find_exn ~equal:String.equal (List.Assoc.inverse build_system_exe_assoc) name - with Not_found -> - invalid_argf "Unsupported build command %s" name + try List.Assoc.find_exn ~equal:String.equal (List.Assoc.inverse build_system_exe_assoc) name + with Not_found -> invalid_argf "Unsupported build command %s" name let string_of_build_system build_system = List.Assoc.find_exn ~equal:equal_build_system build_system_exe_assoc build_system @@ -48,57 +70,61 @@ type mode = | BuckGenrule of string | BuckCompilationDB of string * string list | Clang of Clang.compiler * string * string list - | ClangCompilationDB of [ `Escaped of string | `Raw of string ] list + | ClangCompilationDB of [`Escaped of string | `Raw of string] list | Javac of Javac.compiler * string * string list | Maven of string * string list | PythonCapture of build_system * string list | XcodeXcpretty of string * string list -[@@deriving compare] + [@@deriving compare] let equal_driver_mode = [%compare.equal : mode] let pp_driver_mode fmt driver_mode = let log_argfile_arg fname = try - F.fprintf fmt "-- Contents of '%s'@\n" fname; - In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname); - F.fprintf fmt "-- /Contents of '%s'@." fname; - with exn -> - F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn in + F.fprintf fmt "-- Contents of '%s'@\n" fname ; + In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname) ; + F.fprintf fmt "-- /Contents of '%s'@." fname + with exn -> F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn + in match driver_mode with - | Analyze | BuckGenrule _ | BuckCompilationDB _ | ClangCompilationDB _ | PythonCapture (_,_) - | XcodeXcpretty _ -> - (* these are pretty boring, do not log anything *) + | Analyze + | BuckGenrule _ + | BuckCompilationDB _ + | ClangCompilationDB _ + | PythonCapture (_, _) + | XcodeXcpretty _ + -> (* these are pretty boring, do not log anything *) () - | Javac (_, prog, args) -> - F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog; + | Javac (_, prog, args) + -> F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog ; let log_arg arg = - F.fprintf fmt "Arg: %s@\n" arg; + F.fprintf fmt "Arg: %s@\n" arg ; (* "@fname" means that fname is an arg file containing additional arguments to pass to javac. *) String.chop_prefix ~prefix:"@" arg - |> - (* Sometimes these argfiles go away at the end of the build and we cannot inspect them after + |> (* Sometimes these argfiles go away at the end of the build and we cannot inspect them after the fact, so log them now. *) - Option.iter ~f:log_argfile_arg in + Option.iter ~f:log_argfile_arg + in List.iter ~f:log_arg args - | Maven (prog, args) -> - F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog; + | Maven (prog, args) + -> F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog ; List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args - | Clang (_, prog, args) -> - F.fprintf fmt "Clang driver mode:@\nprog = %s@\n" prog; + | Clang (_, prog, args) + -> F.fprintf fmt "Clang driver mode:@\nprog = %s@\n" prog ; List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args (* A clean command for each driver mode to be suggested to the user in case nothing got captured. *) let clean_compilation_command driver_mode = match driver_mode with - | BuckCompilationDB (prog, _) - | Clang (_, prog, _) -> - Some (prog ^ " clean") - | XcodeXcpretty (prog, args) -> - Some (String.concat ~sep:" " (List.append (prog::args) ["clean"])) - | _ -> None + | BuckCompilationDB (prog, _) | Clang (_, prog, _) + -> Some (prog ^ " clean") + | XcodeXcpretty (prog, args) + -> Some (String.concat ~sep:" " (List.append (prog :: args) ["clean"])) + | _ + -> None (* Clean up the results dir to select only what's relevant to go in the Buck cache. In particular, get rid of non-deterministic outputs.*) @@ -106,57 +132,62 @@ let clean_results_dir () = (* In Buck flavors mode we keep all capture data, but in Java mode we keep only the tenv *) let should_delete_dir = let dirs_to_delete = - "backend_stats" :: "classnames" :: "filelists" :: "frontend_stats" :: "multicore" :: - "reporting_stats" :: "sources" :: - if Config.flavors then [] - else ["attributes"] in - List.mem ~equal:String.equal dirs_to_delete in + "backend_stats" + :: "classnames" + :: "filelists" + :: "frontend_stats" + :: "multicore" + :: "reporting_stats" + :: "sources" :: (if Config.flavors then [] else ["attributes"]) + in + List.mem ~equal:String.equal dirs_to_delete + in let should_delete_file = let suffixes_to_delete = - ".txt" :: ".csv" :: ".json" :: - if Config.flavors then [] - else [ ".cfg"; ".cg" ] in + ".txt" :: ".csv" :: ".json" :: (if Config.flavors then [] else [".cfg"; ".cg"]) + in fun name -> (* Keep the JSON report *) not (String.equal (Filename.basename name) "report.json") - && List.exists ~f:(Filename.check_suffix name) suffixes_to_delete in + && List.exists ~f:(Filename.check_suffix name) suffixes_to_delete + in let rec clean name = - let rec cleandir dir = match Unix.readdir_opt dir with - | Some entry -> - if should_delete_dir entry then ( - Utils.rmtree (name ^/ entry) - ) else if not (String.equal entry Filename.current_dir_name - || String.equal entry Filename.parent_dir_name) then ( - clean (name ^/ entry) - ); - cleandir dir (* next entry *) - | None -> - Unix.closedir dir in + let rec cleandir dir = + match Unix.readdir_opt dir with + | Some entry + -> if should_delete_dir entry then Utils.rmtree (name ^/ entry) + else if not + ( String.equal entry Filename.current_dir_name + || String.equal entry Filename.parent_dir_name ) + then clean (name ^/ entry) ; + cleandir dir + (* next entry *) + | None + -> Unix.closedir dir + in match Unix.opendir name with - | dir -> - cleandir dir - | exception Unix.Unix_error (Unix.ENOTDIR, _, _) -> - if should_delete_file name then - Unix.unlink name; + | dir + -> cleandir dir + | exception Unix.Unix_error (Unix.ENOTDIR, _, _) + -> if should_delete_file name then Unix.unlink name ; () - | exception Unix.Unix_error (Unix.ENOENT, _, _) -> - () in + | exception Unix.Unix_error (Unix.ENOENT, _, _) + -> () + in clean Config.results_dir let check_captured_empty driver_mode = let clean_command_opt = clean_compilation_command driver_mode in (* if merge is passed, the captured folder will be empty at this point, but will be filled later on. *) - if Utils.dir_is_empty Config.captured_dir && not Config.merge then (( - match clean_command_opt with - | Some clean_command -> - L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command - | None -> - L.user_warning "@\nNothing to compile. Try cleaning the build first.@." - ); - true - ) else - false + if Utils.dir_is_empty Config.captured_dir && not Config.merge then ( + ( match clean_command_opt with + | Some clean_command + -> L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command + | None + -> L.user_warning "@\nNothing to compile. Try cleaning the build first.@." ) ; + true ) + else false let register_perf_stats_report () = let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name in @@ -166,132 +197,137 @@ let register_perf_stats_report () = let reset_duplicates_file () = let start = Config.results_dir ^/ Config.duplicates_filename in - let delete () = - Unix.unlink start in + let delete () = Unix.unlink start in let create () = - Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in - if Sys.file_exists start = `Yes then delete (); + Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) + in + if Sys.file_exists start = `Yes then delete () ; create () (* Create the .start file, and update the timestamp unless in continue mode *) let touch_start_file_unless_continue () = let start = Config.results_dir ^/ Config.start_filename in - let delete () = - Unix.unlink start in + let delete () = Unix.unlink start in let create () = - Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in + Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) + in if not (Sys.file_exists start = `Yes) then create () - else if not Config.continue_capture then (delete (); create ()) - + else if not Config.continue_capture then ( delete () ; create () ) let run_command ~prog ~args cleanup = Unix.waitpid (Unix.fork_exec ~prog ~argv:(prog :: args) ()) - |> fun status - -> cleanup status - ; ok_exn (Unix.Exit_or_signal.or_error status) + |> fun status -> + cleanup status ; + ok_exn (Unix.Exit_or_signal.or_error status) let check_xcpretty () = match Unix.system "xcpretty --version" with - | Ok () -> () - | Error _ -> - L.user_error - "@\nxcpretty not found in the path. Please consider installing xcpretty \ - for a more robust integration with xcodebuild. Otherwise use the option \ - --no-xcpretty.@\n@." + | Ok () + -> () + | Error _ + -> L.user_error + "@\nxcpretty not found in the path. Please consider installing xcpretty for a more robust integration with xcodebuild. Otherwise use the option --no-xcpretty.@\n@." let capture_with_compilation_database db_files = let root = Unix.getcwd () in - Config.clang_compilation_dbs := List.map db_files ~f:(function - | `Escaped fname -> `Escaped (Utils.filename_to_absolute ~root fname) - | `Raw fname -> `Raw (Utils.filename_to_absolute ~root fname) - ); + Config.clang_compilation_dbs + := List.map db_files ~f:(function + | `Escaped fname + -> `Escaped (Utils.filename_to_absolute ~root fname) + | `Raw fname + -> `Raw (Utils.filename_to_absolute ~root fname) ) ; let compilation_database = CompilationDatabase.from_json_files db_files in CaptureCompilationDatabase.capture_files_in_database compilation_database let capture ~changed_files = function - | Analyze -> - () - | BuckCompilationDB (prog, args) -> - L.progress "Capturing using Buck's compilation database...@."; + | Analyze + -> () + | BuckCompilationDB (prog, args) + -> L.progress "Capturing using Buck's compilation database...@." ; let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck ~prog ~args in capture_with_compilation_database ~changed_files json_cdb - | BuckGenrule path -> - L.progress "Capturing for Buck genrule compatibility...@."; - JMain.from_arguments path - | Clang (compiler, prog, args) -> - L.progress "Capturing in make/cc mode...@."; - Clang.capture compiler ~prog ~args - | ClangCompilationDB db_files -> - L.progress "Capturing using compilation database...@."; + | BuckGenrule path + -> L.progress "Capturing for Buck genrule compatibility...@." ; JMain.from_arguments path + | Clang (compiler, prog, args) + -> L.progress "Capturing in make/cc mode...@." ; Clang.capture compiler ~prog ~args + | ClangCompilationDB db_files + -> L.progress "Capturing using compilation database...@." ; capture_with_compilation_database ~changed_files db_files - | Javac (compiler, prog, args) -> - L.progress "Capturing in javac mode...@."; - Javac.capture compiler ~prog ~args - | Maven (prog, args) -> - L.progress "Capturing in maven mode...@."; - Maven.capture ~prog ~args - | PythonCapture (build_system, build_cmd) -> - L.progress "Capturing in %s mode...@." (string_of_build_system build_system); + | Javac (compiler, prog, args) + -> L.progress "Capturing in javac mode...@." ; Javac.capture compiler ~prog ~args + | Maven (prog, args) + -> L.progress "Capturing in maven mode...@." ; Maven.capture ~prog ~args + | PythonCapture (build_system, build_cmd) + -> L.progress "Capturing in %s mode...@." (string_of_build_system build_system) ; let in_buck_mode = equal_build_system build_system BBuck in let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in let args = - List.rev_append Config.anon_args ( - ["--analyzer"; - List.Assoc.find_exn ~equal:Config.equal_analyzer - (List.map ~f:(fun (n,a) -> (a,n)) Config.string_to_analyzer) Config.analyzer] @ - (match Config.blacklist with - | Some s when in_buck_mode -> ["--blacklist-regex"; s] - | _ -> []) @ - (if not Config.create_harness then [] else - ["--android-harness"]) @ - (match Config.java_jar_compiler with None -> [] | Some p -> - ["--java-jar-compiler"; p]) @ - (match List.rev Config.buck_build_args with - | args when in_buck_mode -> - List.map ~f:(fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> List.concat - | _ -> []) @ - (if not Config.debug_mode then [] else - ["--debug"]) @ - (if not Config.debug_exceptions then [] else - ["--debug-exceptions"]) @ - (if Config.filtering then [] else - ["--no-filtering"]) @ - (if not Config.flavors || not in_buck_mode then [] else - ["--use-flavors"]) @ - "-j" :: (string_of_int Config.jobs) :: - (match Config.load_average with None -> [] | Some l -> - ["-l"; string_of_float l]) @ - (if not Config.pmd_xml then [] else - ["--pmd-xml"]) @ - ["--project-root"; Config.project_root] @ - (if not Config.reactive_mode then [] else - ["--reactive"]) @ - "--out" :: Config.results_dir :: - (match Config.xcode_developer_dir with None -> [] | Some d -> - ["--xcode-developer-dir"; d]) @ - "--" :: - if in_buck_mode && Config.flavors then - (* let children infer processes know that they are inside Buck *) - let infer_args_with_buck = String.concat ~sep:(String.of_char CLOpt.env_var_sep) - (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) in - Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck; - Buck.add_flavors_to_buck_command build_cmd - else build_cmd - ) in - run_command ~prog:infer_py ~args - (function - | Result.Error (`Exit_non_zero exit_code) -> - if Int.equal exit_code Config.infer_py_argparse_error_exit_code then - (* swallow infer.py argument parsing error *) - Config.print_usage_exit () - | _ -> - () - ) - | XcodeXcpretty (prog, args) -> - L.progress "Capturing using xcodebuild and xcpretty...@."; - check_xcpretty (); + List.rev_append Config.anon_args + ( [ "--analyzer" + ; List.Assoc.find_exn ~equal:Config.equal_analyzer + (List.map ~f:(fun (n, a) -> (a, n)) Config.string_to_analyzer) + Config.analyzer ] + @ ( match Config.blacklist with + | Some s when in_buck_mode + -> ["--blacklist-regex"; s] + | _ + -> [] ) + @ (if not Config.create_harness then [] else ["--android-harness"]) + @ ( match Config.java_jar_compiler with + | None + -> [] + | Some p + -> ["--java-jar-compiler"; p] ) + @ ( match List.rev Config.buck_build_args with + | args when in_buck_mode + -> List.map ~f:(fun arg -> ["--Xbuck"; ("'" ^ arg ^ "'")]) args |> List.concat + | _ + -> [] ) + @ (if not Config.debug_mode then [] else ["--debug"]) + @ (if not Config.debug_exceptions then [] else ["--debug-exceptions"]) + @ (if Config.filtering then [] else ["--no-filtering"]) + @ (if not Config.flavors || not in_buck_mode then [] else ["--use-flavors"]) + @ "-j" + :: string_of_int Config.jobs + :: ( match Config.load_average with + | None + -> [] + | Some l + -> ["-l"; string_of_float l] ) + @ (if not Config.pmd_xml then [] else ["--pmd-xml"]) + @ ["--project-root"; Config.project_root] + @ (if not Config.reactive_mode then [] else ["--reactive"]) + @ "--out" + :: Config.results_dir + :: ( match Config.xcode_developer_dir with + | None + -> [] + | Some d + -> ["--xcode-developer-dir"; d] ) + @ "--" + :: ( if in_buck_mode && Config.flavors then + (* let children infer processes know that they are inside Buck *) + let infer_args_with_buck = + String.concat ~sep:(String.of_char CLOpt.env_var_sep) + (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) + in + Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck ; + Buck.add_flavors_to_buck_command build_cmd + else build_cmd ) ) + in + run_command ~prog:infer_py ~args (function + | Result.Error `Exit_non_zero exit_code + -> if Int.equal exit_code Config.infer_py_argparse_error_exit_code then + (* swallow infer.py argument parsing error *) + Config.print_usage_exit () + | _ + -> () ) + | XcodeXcpretty (prog, args) + -> L.progress "Capturing using xcodebuild and xcpretty...@." ; + check_xcpretty () ; let json_cdb = - CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args in + CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args + in capture_with_compilation_database ~changed_files json_cdb let run_parallel_analysis ~changed_files = @@ -299,220 +335,217 @@ let run_parallel_analysis ~changed_files = Utils.rmtree multicore_dir ; Unix.mkdir_p multicore_dir ; InferAnalyze.main ~changed_files ~makefile:(multicore_dir ^/ "Makefile") ; - run_command - ~prog:"make" ~args:( - "-C" :: multicore_dir :: - "-k" :: - "-j" :: (string_of_int Config.jobs) :: - (Option.value_map ~f:(fun l -> ["-l"; string_of_float l]) ~default:[] Config.load_average) @ - (if Config.debug_mode then [] else ["-s"]) - ) (fun _ -> ()) + run_command ~prog:"make" + ~args: + ( "-C" + :: multicore_dir + :: "-k" + :: "-j" + :: string_of_int Config.jobs + :: Option.value_map + ~f:(fun l -> ["-l"; string_of_float l]) + ~default:[] Config.load_average + @ if Config.debug_mode then [] else ["-s"] ) (fun _ -> () ) let execute_analyze ~changed_files = if Int.equal Config.jobs 1 || Config.cluster_cmdline <> None then InferAnalyze.main ~changed_files ~makefile:"" - else - run_parallel_analysis ~changed_files + else run_parallel_analysis ~changed_files let report () = let report_csv = - if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") in + if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") + in let report_json = Some (Config.results_dir ^/ "report.json") in InferPrint.main ~report_csv ~report_json ; (* Post-process the report according to the user config. By default, calls report.py to create a human-readable report. Do not bother calling the report hook when called from within Buck or in quiet mode. *) - match Config.quiet || Config.buck_cache_mode, Config.report_hook with - | true, _ - | false, None -> - () - | false, Some prog -> - let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in + match (Config.quiet || Config.buck_cache_mode, Config.report_hook) with + | true, _ | false, None + -> () + | false, Some prog + -> let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in let if_true key opt args = if not opt then args else key :: args in let args = - if_some "--issues-csv" report_csv @@ - if_some "--issues-json" report_json @@ - if_some "--issues-txt" Config.bugs_txt @@ - if_true "--pmd-xml" Config.pmd_xml [ - "--project-root"; Config.project_root; - "--results-dir"; Config.results_dir - ] in + if_some "--issues-csv" report_csv @@ if_some "--issues-json" report_json + @@ if_some "--issues-txt" Config.bugs_txt + @@ if_true "--pmd-xml" Config.pmd_xml + ["--project-root"; Config.project_root; "--results-dir"; Config.results_dir] + in if is_error (Unix.waitpid (Unix.fork_exec ~prog ~argv:(prog :: args) ())) then L.external_error - "** Error running the reporting script:@\n** %s %s@\n** See error above@." - prog (String.concat ~sep:" " args) + "** Error running the reporting script:@\n** %s %s@\n** See error above@." prog + (String.concat ~sep:" " args) let analyze_and_report ~changed_files driver_mode = - let should_analyze, should_report = match driver_mode, Config.analyzer with - | PythonCapture (BBuck, _), _ -> - (* In Buck mode when compilation db is not used, analysis is invoked either from capture or + let should_analyze, should_report = + match (driver_mode, Config.analyzer) with + | PythonCapture (BBuck, _), _ + -> (* In Buck mode when compilation db is not used, analysis is invoked either from capture or a separate Analyze invocation is necessary, depending on the buck flavor used. *) - false, false - | _ when Config.maven -> - (* Called from Maven, only do capture. *) - false, false - | _, (CaptureOnly | CompileOnly) -> - false, false - | _, (BiAbduction | Checkers | Crashcontext | Eradicate) -> - true, true - | _, Linters -> - false, true in - if (should_analyze || should_report) && - (((Sys.file_exists Config.captured_dir) <> `Yes) || - check_captured_empty driver_mode) then ( - L.user_error "There was nothing to analyze.@\n@." ; - ) else if should_analyze then - execute_analyze ~changed_files; + (false, false) + | _ when Config.maven + -> (* Called from Maven, only do capture. *) + (false, false) + | _, (CaptureOnly | CompileOnly) + -> (false, false) + | _, (BiAbduction | Checkers | Crashcontext | Eradicate) + -> (true, true) + | _, Linters + -> (false, true) + in + if (should_analyze || should_report) + && (Sys.file_exists Config.captured_dir <> `Yes || check_captured_empty driver_mode) + then L.user_error "There was nothing to analyze.@\n@." + else if should_analyze then execute_analyze ~changed_files ; if should_report && Config.report then report () (** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *) let fail_on_issue_epilogue () = - let issues_json = DB.Results_dir.(path_to_filename Abs_root ["report.json"]) - |> DB.filename_to_string in + let issues_json = + DB.Results_dir.(path_to_filename Abs_root ["report.json"]) |> DB.filename_to_string + in match Utils.read_file issues_json with - | Ok lines -> - let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in + | Ok lines + -> let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in if issues <> [] then exit Config.fail_on_issue_exit_code - | Error error -> - L.internal_error "Failed to read report file '%s': %s@." issues_json error ; - () + | Error error + -> L.internal_error "Failed to read report file '%s': %s@." issues_json error ; () let log_infer_args driver_mode = L.environment_info "INFER_ARGS = %s@\n" - (Option.value (Sys.getenv CLOpt.args_env_var) ~default:""); - List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args; - List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest; - L.environment_info "Project root = %s@\n" Config.project_root; - L.environment_info "CWD = %s@\n" (Sys.getcwd ()); + (Option.value (Sys.getenv CLOpt.args_env_var) ~default:"") ; + List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args ; + List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest ; + L.environment_info "Project root = %s@\n" Config.project_root ; + L.environment_info "CWD = %s@\n" (Sys.getcwd ()) ; L.environment_info "Driver mode:@\n%a@." pp_driver_mode driver_mode let assert_supported_mode required_analyzer requested_mode_string = - let analyzer_enabled = match required_analyzer with - | `Clang -> Version.clang_enabled - | `Java -> Version.java_enabled - | `Xcode -> Version.clang_enabled && Version.xcode_enabled in + let analyzer_enabled = + match required_analyzer with + | `Clang + -> Version.clang_enabled + | `Java + -> Version.java_enabled + | `Xcode + -> Version.clang_enabled && Version.xcode_enabled + in if not analyzer_enabled then - let analyzer_string = match required_analyzer with - | `Clang -> "clang" - | `Java -> "java" - | `Xcode -> "clang and xcode" in + let analyzer_string = + match required_analyzer with + | `Clang + -> "clang" + | `Java + -> "java" + | `Xcode + -> "clang and xcode" + in failwithf - "Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild \ - infer with %s enabled.@." + "Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild infer with %s enabled.@." requested_mode_string analyzer_string analyzer_string -let assert_supported_build_system build_system = match build_system with - | BAnt | BGradle | BJava | BJavac | BMvn -> - string_of_build_system build_system - |> assert_supported_mode `Java - | BClang | BMake | BNdk -> - string_of_build_system build_system - |> assert_supported_mode `Clang - | BXcode -> - string_of_build_system build_system - |> assert_supported_mode `Xcode - | BBuck -> - let (analyzer, build_string) = - if Config.flavors then - (`Clang, "buck with flavors") +let assert_supported_build_system build_system = + match build_system with + | BAnt | BGradle | BJava | BJavac | BMvn + -> string_of_build_system build_system |> assert_supported_mode `Java + | BClang | BMake | BNdk + -> string_of_build_system build_system |> assert_supported_mode `Clang + | BXcode + -> string_of_build_system build_system |> assert_supported_mode `Xcode + | BBuck + -> let analyzer, build_string = + if Config.flavors then (`Clang, "buck with flavors") else if Option.is_some Config.buck_compilation_database then (`Clang, "buck compilation database") else ( if Config.reactive_mode then L.user_error - "WARNING: The reactive analysis mode is not compatible with the Buck integration for \ - Java"; - (`Java, string_of_build_system build_system) - ) in + "WARNING: The reactive analysis mode is not compatible with the Buck integration for Java" ; + (`Java, string_of_build_system build_system) ) + in assert_supported_mode analyzer build_string - | BAnalyze -> - () + | BAnalyze + -> () let driver_mode_of_build_cmd build_cmd = match build_cmd with - | [] -> - if not (List.is_empty !Config.clang_compilation_dbs) then ( - assert_supported_mode `Clang "clang compilation database"; - ClangCompilationDB !Config.clang_compilation_dbs - ) else - Analyze - | prog :: args -> - let build_system = build_system_of_exe_name (Filename.basename prog) in - assert_supported_build_system build_system; + | [] + -> if not (List.is_empty !Config.clang_compilation_dbs) then ( + assert_supported_mode `Clang "clang compilation database" ; + ClangCompilationDB !Config.clang_compilation_dbs ) + else Analyze + | prog :: args + -> let build_system = build_system_of_exe_name (Filename.basename prog) in + assert_supported_build_system build_system ; match build_system_of_exe_name (Filename.basename prog) with - | BAnalyze -> - CLOpt.warnf - "WARNING: `infer -- analyze` is deprecated; \ - use the `infer analyze` subcommand instead@."; + | BAnalyze + -> CLOpt.warnf + "WARNING: `infer -- analyze` is deprecated; use the `infer analyze` subcommand instead@." ; Analyze - | BBuck when Option.is_some Config.buck_compilation_database -> - BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args)) - | BClang -> - Clang (Clang.Clang, prog, args) - | BMake -> - Clang (Clang.Make, prog, args) - | BJava -> - Javac (Javac.Java, prog, args) - | BJavac -> - Javac (Javac.Javac, prog, args) - | BMvn -> - Maven (prog, args) - | BXcode when Config.xcpretty -> - XcodeXcpretty (prog, args) - | BAnt | BBuck | BGradle | BNdk | BXcode as build_system -> - PythonCapture (build_system, build_cmd) + | BBuck when Option.is_some Config.buck_compilation_database + -> BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args)) + | BClang + -> Clang (Clang.Clang, prog, args) + | BMake + -> Clang (Clang.Make, prog, args) + | BJava + -> Javac (Javac.Java, prog, args) + | BJavac + -> Javac (Javac.Javac, prog, args) + | BMvn + -> Maven (prog, args) + | BXcode when Config.xcpretty + -> XcodeXcpretty (prog, args) + | BAnt | BBuck | BGradle | BNdk | BXcode as build_system + -> PythonCapture (build_system, build_cmd) let get_driver_mode () = match Config.generated_classes with - | _ when Config.maven -> - (* infer is pretending to be javac in the Maven integration *) - let build_args = match Array.to_list Sys.argv with - | _::args -> args - | [] -> [] in + | _ when Config.maven + -> (* infer is pretending to be javac in the Maven integration *) + let build_args = match Array.to_list Sys.argv with _ :: args -> args | [] -> [] in Javac (Javac.Javac, "javac", build_args) - | Some path -> - assert_supported_mode `Java "Buck genrule"; + | Some path + -> assert_supported_mode `Java "Buck genrule" ; BuckGenrule path - | None -> - driver_mode_of_build_cmd (List.rev Config.rest) - -let mode_from_command_line = lazy ( - match Config.generated_classes with - | _ when Config.maven -> - (* infer is pretending to be javac in the Maven integration *) - let build_args = match Array.to_list Sys.argv with - | _::args -> args - | [] -> [] in + | None + -> driver_mode_of_build_cmd (List.rev Config.rest) + +let mode_from_command_line = + ( lazy + ( match Config.generated_classes with + | _ when Config.maven + -> (* infer is pretending to be javac in the Maven integration *) + let build_args = match Array.to_list Sys.argv with _ :: args -> args | [] -> [] in Javac (Javac.Javac, "javac", build_args) - | Some path -> - assert_supported_mode `Java "Buck genrule"; + | Some path + -> assert_supported_mode `Java "Buck genrule" ; BuckGenrule path - | None -> - driver_mode_of_build_cmd (List.rev Config.rest) -) + | None + -> driver_mode_of_build_cmd (List.rev Config.rest) ) ) let run_prologue driver_mode = if CLOpt.is_originator then L.environment_info "%a@\n" Config.pp_version () ; if Config.debug_mode || Config.stats_mode then log_infer_args driver_mode ; - if Config.dump_duplicate_symbols then reset_duplicates_file (); + if Config.dump_duplicate_symbols then reset_duplicates_file () ; (* infer might be called from a Makefile and itself uses `make` to run the analysis in parallel, but cannot communicate with the parent make command. Since infer won't interfere with them anyway, pretend that we are not called from another make to prevent make falling back to a mono-threaded execution. *) - Unix.unsetenv "MAKEFLAGS"; + Unix.unsetenv "MAKEFLAGS" ; register_perf_stats_report () ; if not Config.buck_cache_mode then touch_start_file_unless_continue () ; () let run_epilogue driver_mode = - if CLOpt.is_originator then ( - let in_buck_mode = match driver_mode with | PythonCapture (BBuck, _) -> true | _ -> false in - StatsAggregator.generate_files () ; - if Config.equal_analyzer Config.analyzer Config.Crashcontext then - Crashcontext.crashcontext_epilogue ~in_buck_mode; - if Config.fail_on_bug then - fail_on_issue_epilogue () ; - ); - if Config.buck_cache_mode then clean_results_dir (); + ( if CLOpt.is_originator then + let in_buck_mode = match driver_mode with PythonCapture (BBuck, _) -> true | _ -> false in + StatsAggregator.generate_files () ; + if Config.equal_analyzer Config.analyzer Config.Crashcontext then + Crashcontext.crashcontext_epilogue ~in_buck_mode ; + if Config.fail_on_bug then fail_on_issue_epilogue () ) ; + if Config.buck_cache_mode then clean_results_dir () ; () diff --git a/infer/src/integration/Driver.mli b/infer/src/integration/Driver.mli index 892a27c30..d98157280 100644 --- a/infer/src/integration/Driver.mli +++ b/infer/src/integration/Driver.mli @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd (** entry points for top-level functionalities such as capture under various build systems, @@ -19,26 +20,26 @@ type mode = | BuckGenrule of string | BuckCompilationDB of string * string list | Clang of Clang.compiler * string * string list - | ClangCompilationDB of [ `Escaped of string | `Raw of string ] list + | ClangCompilationDB of [`Escaped of string | `Raw of string] list | Javac of Javac.compiler * string * string list | Maven of string * string list | PythonCapture of build_system * string list | XcodeXcpretty of string * string list -[@@deriving compare] + [@@deriving compare] val equal_driver_mode : mode -> mode -> bool -(** driver mode computed from the command-line arguments and settings in Config *) val mode_from_command_line : mode Lazy.t +(** driver mode computed from the command-line arguments and settings in Config *) -(** prepare the environment for running the given mode *) val run_prologue : mode -> unit +(** prepare the environment for running the given mode *) -(** run the capture for the given mode *) val capture : changed_files:SourceFile.Set.t option -> mode -> unit +(** run the capture for the given mode *) -(** run the analysis for the given mode *) val analyze_and_report : changed_files:SourceFile.Set.t option -> mode -> unit +(** run the analysis for the given mode *) -(** cleanup infer-out/ for Buck, generate stats, and generally post-process the results of a run *) val run_epilogue : mode -> unit +(** cleanup infer-out/ for Buck, generate stats, and generally post-process the results of a run *) diff --git a/infer/src/integration/Javac.ml b/infer/src/integration/Javac.ml index 766296c3d..25d0d4c97 100644 --- a/infer/src/integration/Javac.ml +++ b/infer/src/integration/Javac.ml @@ -8,81 +8,86 @@ *) open! IStd - module L = Logging module F = Format - module CLOpt = CommandLineOption -type compiler = Java | Javac [@@ deriving compare] +type compiler = Java | Javac [@@deriving compare] let compile compiler build_prog build_args = let prog, prog_args = - match compiler, Config.java_jar_compiler with - | _, None -> (build_prog, ["-J-Duser.language=en"]) - | Java, Some jar -> (build_prog, ["-jar"; jar]) - | _, Some jar -> (* fall back to java in PATH to avoid passing -jar to javac *) - ("java", ["-jar"; jar]) in + match (compiler, Config.java_jar_compiler) with + | _, None + -> (build_prog, ["-J-Duser.language=en"]) + | Java, Some jar + -> (build_prog, ["-jar"; jar]) + | _, Some jar + -> (* fall back to java in PATH to avoid passing -jar to javac *) + ("java", ["-jar"; jar]) + in let cli_args, file_args = let args = - "-verbose" :: "-g" :: - (* Ensure that some form of "-d ..." is passed to javac. It's unclear whether this is strictly + "-verbose" + :: "-g" + :: (* Ensure that some form of "-d ..." is passed to javac. It's unclear whether this is strictly needed but the tests break without this for now. See discussion in D4397716. *) - match Config.javac_classes_out with - | Some _ -> - build_args - | None -> - "-d" :: CLOpt.init_work_dir :: build_args in + ( match Config.javac_classes_out with + | Some _ + -> build_args + | None + -> "-d" :: CLOpt.init_work_dir :: build_args ) + in List.partition_tf args ~f:(fun arg -> (* As mandated by javac, argument files must not contain certain arguments. *) - String.is_prefix ~prefix:"-J" arg || String.is_prefix ~prefix:"@" arg) in + String.is_prefix ~prefix:"-J" arg || String.is_prefix ~prefix:"@" arg ) + in (* Pass non-special args via a file to avoid exceeding the command line size limit. *) let args_file = let file = Filename.temp_file "args_" "" in let quoted_file_args = List.map file_args ~f:(fun arg -> - if String.contains arg '\'' then arg else F.sprintf "'%s'" arg) in + if String.contains arg '\'' then arg else F.sprintf "'%s'" arg ) + in Out_channel.with_file file ~f:(fun oc -> Out_channel.output_lines oc quoted_file_args) ; - file in - let cli_file_args = cli_args @ ["@" ^ args_file] in + file + in + let cli_file_args = cli_args @ [("@" ^ args_file)] in let args = prog_args @ cli_file_args in - L.(debug Capture Quiet) "Current working directory: '%s'@." (Sys.getcwd ()); + L.(debug Capture Quiet) "Current working directory: '%s'@." (Sys.getcwd ()) ; let verbose_out_file = Filename.temp_file "javac_" ".out" in let try_run cmd error_k = let shell_cmd = Utils.shell_escape_command cmd in - let shell_cmd_redirected = - Printf.sprintf "%s 2>'%s'" shell_cmd verbose_out_file in - L.(debug Capture Quiet) "Trying to execute: %s@." shell_cmd_redirected; + let shell_cmd_redirected = Printf.sprintf "%s 2>'%s'" shell_cmd verbose_out_file in + L.(debug Capture Quiet) "Trying to execute: %s@." shell_cmd_redirected ; let error_k_or_fail err_msg = - match error_k, err_msg with - | Some k, (`UnixError (err, log)) -> - L.(debug Capture Quiet) "*** Failed: %s!@\n%s@." - (Unix.Exit_or_signal.to_string_hum (Error err)) log; + match (error_k, err_msg) with + | Some k, `UnixError (err, log) + -> L.(debug Capture Quiet) + "*** Failed: %s!@\n%s@." (Unix.Exit_or_signal.to_string_hum (Error err)) log ; k () - | Some k, (`ExceptionError exn) -> - L.(debug Capture Quiet) "*** Failed: %a!@\n" Exn.pp exn; - k () - | None, (`UnixError (err, log)) -> - let verbose_errlog = Utils.with_file_in verbose_out_file ~f:In_channel.input_all in - failwithf "@\n*** Failed to execute compilation command: %s@\n*** Command: %s@\n\ - *** Output:@\n%s%s@\n*** Infer needs a working compilation command to run.@." - (Unix.Exit_or_signal.to_string_hum (Error err)) shell_cmd log verbose_errlog; - | None, (`ExceptionError exn) -> - raise exn in + | Some k, `ExceptionError exn + -> L.(debug Capture Quiet) "*** Failed: %a!@\n" Exn.pp exn ; k () + | None, `UnixError (err, log) + -> let verbose_errlog = Utils.with_file_in verbose_out_file ~f:In_channel.input_all in + failwithf + "@\n*** Failed to execute compilation command: %s@\n*** Command: %s@\n*** Output:@\n%s%s@\n*** Infer needs a working compilation command to run.@." + (Unix.Exit_or_signal.to_string_hum (Error err)) shell_cmd log verbose_errlog + | None, `ExceptionError exn + -> raise exn + in match Utils.with_process_in shell_cmd_redirected In_channel.input_all with - | (log, Error err) -> - error_k_or_fail (`UnixError (err, log)) - | exception exn -> - error_k_or_fail (`ExceptionError exn) - | (log, Ok ()) -> - L.(debug Capture Quiet) "*** Success. Logs:@\n%s" log in - let fallback () = try_run ("javac"::cli_file_args) None in - try_run (prog::args) (Some fallback); + | log, Error err + -> error_k_or_fail (`UnixError (err, log)) + | exception exn + -> error_k_or_fail (`ExceptionError exn) + | log, Ok () + -> L.(debug Capture Quiet) "*** Success. Logs:@\n%s" log + in + let fallback () = try_run ("javac" :: cli_file_args) None in + try_run (prog :: args) (Some fallback) ; verbose_out_file - let capture compiler ~prog ~args = let verbose_out_file = compile compiler prog args in - if Config.analyzer <> Config.CompileOnly then - JMain.from_verbose_out verbose_out_file; + if Config.analyzer <> Config.CompileOnly then JMain.from_verbose_out verbose_out_file ; if not (Config.debug_mode || Config.stats_mode) then Unix.unlink verbose_out_file diff --git a/infer/src/integration/Maven.ml b/infer/src/integration/Maven.ml index de53a12a8..c7f905d1c 100644 --- a/infer/src/integration/Maven.ml +++ b/infer/src/integration/Maven.ml @@ -7,15 +7,16 @@ * of patent rights can be found in the PATENTS file in the same directory. *) open! IStd - module CLOpt = CommandLineOption module L = Logging let infer_profile_name = "infer-capture" -let infer_profile = lazy +let infer_profile = + ( lazy (* indented so that users may copy it into their projects if they want to *) - (Printf.sprintf {| + (Printf.sprintf + {| %s @@ -33,7 +34,7 @@ let infer_profile = lazy |} - infer_profile_name (Config.bin_dir ^/ CommandDoc.infer_exe_name)) + infer_profile_name (Config.bin_dir ^/ CommandDoc.infer_exe_name)) ) let pom_worklist = ref [CLOpt.init_work_dir] @@ -44,87 +45,94 @@ let add_infer_profile_to_xml dir maven_xml infer_xml = (* whether there already is an infer profile --this will always be true at the end *) let found_infer_profile = ref false in (* Process an xml document from the root. Assume the Dtd has already been handled. *) - let rec process_root xml_in xml_out = - process xml_in xml_out [] + let rec process_root xml_in xml_out = process xml_in xml_out [] and insert_infer_profile xml_out = let infer_xml = Xmlm.make_input ~strip:false (`String (0, Lazy.force infer_profile)) in - Xmlm.input infer_xml |> ignore; (* skip dummy DTD *) + Xmlm.input infer_xml |> ignore ; + (* skip dummy DTD *) process_root infer_xml xml_out and process xml_in xml_out tag_stack = let elt_in = Xmlm.input xml_in in match elt_in with - | `El_start tag -> - Xmlm.output xml_out elt_in; + | `El_start tag + -> Xmlm.output xml_out elt_in ; let tag_name = snd (fst tag) in - if String.equal tag_name "profiles" then ( - found_profiles_tag := true - ); - process xml_in xml_out (tag_name::tag_stack) - | `El_end -> - (match tag_stack with - | "profiles"::_ when not !found_infer_profile -> - (* found the tag but no infer profile found, add one *) - insert_infer_profile xml_out - | _::[] when not !found_profiles_tag-> - (* closing the root tag but no tag found, add + if String.equal tag_name "profiles" then found_profiles_tag := true ; + process xml_in xml_out (tag_name :: tag_stack) + | `El_end + -> ( + ( match tag_stack with + | "profiles" :: _ when not !found_infer_profile + -> (* found the tag but no infer profile found, add one *) + insert_infer_profile xml_out + | [_] when not !found_profiles_tag + -> (* closing the root tag but no tag found, add [infer profile] *) - Xmlm.output xml_out (`El_start (("", "profiles"), [])); - found_profiles_tag := true; (* do not add again *) - insert_infer_profile xml_out; - Xmlm.output xml_out `El_end - | _ -> () - ); - Xmlm.output xml_out elt_in; - (match tag_stack with - | _::parent::tl -> - process xml_in xml_out (parent::tl) - | _::[] -> - (* closing the first tag, we're done *) - () - | [] -> - invalid_arg "ill-formed xml") - | `Data data -> - Xmlm.output xml_out elt_in; - (match tag_stack with - | "id"::"profile"::"profiles"::_ when String.equal data infer_profile_name -> - L.(debug Capture Quiet) "Found infer profile, not adding one@."; - found_infer_profile := true - | "module"::"modules"::_ -> - let abs_data = dir ^/ data in - L.(debug Capture Quiet) "Adding maven module %s@." abs_data; - pom_worklist := abs_data::!pom_worklist - | _ -> () - ); + Xmlm.output xml_out (`El_start (("", "profiles"), [])) ; + found_profiles_tag := true ; + (* do not add again *) + insert_infer_profile xml_out ; + Xmlm.output xml_out `El_end + | _ + -> () ) ; + Xmlm.output xml_out elt_in ; + match tag_stack with + | _ :: parent :: tl + -> process xml_in xml_out (parent :: tl) + | [_] + -> (* closing the first tag, we're done *) + () + | [] + -> invalid_arg "ill-formed xml" ) + | `Data data + -> Xmlm.output xml_out elt_in ; + ( match tag_stack with + | "id" :: "profile" :: "profiles" :: _ when String.equal data infer_profile_name + -> L.(debug Capture Quiet) "Found infer profile, not adding one@." ; + found_infer_profile := true + | "module" :: "modules" :: _ + -> let abs_data = dir ^/ data in + L.(debug Capture Quiet) "Adding maven module %s@." abs_data ; + pom_worklist := abs_data :: !pom_worklist + | _ + -> () ) ; process xml_in xml_out tag_stack - | `Dtd _ -> - (* already processed the Dtd node *) - assert false in + | `Dtd _ + -> (* already processed the Dtd node *) + assert false + in let process_document () = (* process `Dtd; if present, it is always the first node *) - (match Xmlm.peek maven_xml with - | `Dtd _ -> - copy maven_xml infer_xml - | _ -> - Xmlm.output infer_xml (`Dtd None) - ); - process_root maven_xml infer_xml; - Xmlm.eoi maven_xml |> ignore; - if not (Xmlm.eoi maven_xml) then invalid_arg "More than one document" in + ( match Xmlm.peek maven_xml with + | `Dtd _ + -> copy maven_xml infer_xml + | _ + -> Xmlm.output infer_xml (`Dtd None) ) ; + process_root maven_xml infer_xml ; + Xmlm.eoi maven_xml |> ignore ; + if not (Xmlm.eoi maven_xml) then invalid_arg "More than one document" + in process_document () let add_infer_profile mvn_pom infer_pom = let ic = In_channel.create mvn_pom in let with_oc out_chan = let with_ic () = - let xml_in = Xmlm.make_input ~strip:false (`Channel ic) - ~ns:(fun ns -> Some ns) (* be generous with namespaces *) in - let xml_out = Xmlm.make_output ~nl:true (`Channel out_chan) - ~ns_prefix:(fun prefix -> Some prefix) (* be generous with namespaces *) in - add_infer_profile_to_xml (Filename.dirname mvn_pom) xml_in xml_out in - protect ~f:with_ic ~finally:(fun () -> In_channel.close ic) in + let xml_in = + Xmlm.make_input ~strip:false (`Channel ic) ~ns:(fun ns -> Some ns) + (* be generous with namespaces *) + in + let xml_out = + Xmlm.make_output ~nl:true (`Channel out_chan) ~ns_prefix:(fun prefix -> Some prefix) + (* be generous with namespaces *) + in + add_infer_profile_to_xml (Filename.dirname mvn_pom) xml_in xml_out + in + protect ~f:with_ic ~finally:(fun () -> In_channel.close ic) + in try Utils.with_file_out infer_pom ~f:with_oc with Xmlm.Error ((line, col), error) as exn -> - L.external_error "%s:%d:%d: ERROR: %s@." mvn_pom line col (Xmlm.error_message error); + L.external_error "%s:%d:%d: ERROR: %s@." mvn_pom line col (Xmlm.error_message error) ; raise exn let add_profile_to_pom_in_directory dir = @@ -135,33 +143,34 @@ let add_profile_to_pom_in_directory dir = let maven_pom_path = dir ^/ "pom.xml" in let saved_pom_path = dir ^/ "pom.xml.infer-orig" in let infer_pom_path = dir ^/ "pom.xml.infer" in - add_infer_profile maven_pom_path infer_pom_path; - Unix.rename ~src:maven_pom_path ~dst:saved_pom_path; + add_infer_profile maven_pom_path infer_pom_path ; + Unix.rename ~src:maven_pom_path ~dst:saved_pom_path ; Epilogues.register ~f:(fun () -> Unix.rename ~src:saved_pom_path ~dst:maven_pom_path) - "restoring Maven's pom.xml to its original state"; - Unix.rename ~src:infer_pom_path ~dst:maven_pom_path; + "restoring Maven's pom.xml to its original state" ; + Unix.rename ~src:infer_pom_path ~dst:maven_pom_path ; if Config.debug_mode || Config.stats_mode then Epilogues.register ~f:(fun () -> Unix.rename ~src:maven_pom_path ~dst:infer_pom_path) "saving infer's pom.xml" let capture ~prog ~args = - while not (List.is_empty !pom_worklist); do + while not (List.is_empty !pom_worklist) do let pom = List.hd_exn !pom_worklist in - pom_worklist := List.tl_exn !pom_worklist; + pom_worklist := List.tl_exn !pom_worklist ; add_profile_to_pom_in_directory pom - done; - let extra_args = "-P"::infer_profile_name::[] in + done ; + let extra_args = ["-P"; infer_profile_name] in let capture_args = args @ extra_args in - L.(debug Capture Quiet) "Running maven capture:@\n%s %s@." prog - (String.concat ~sep:" " (List.map ~f:(Printf.sprintf "'%s'") capture_args)); + L.(debug Capture Quiet) + "Running maven capture:@\n%s %s@." prog + (String.concat ~sep:" " (List.map ~f:(Printf.sprintf "'%s'") capture_args)) ; (* let children infer processes know that they are spawned by Maven *) - Unix.fork_exec ~prog ~argv:(prog::capture_args) ~env:Config.env_inside_maven () - |> Unix.waitpid + Unix.fork_exec ~prog ~argv:(prog :: capture_args) ~env:Config.env_inside_maven () |> Unix.waitpid |> function - | Ok () -> () - | Error _ as status -> - failwithf "*** Maven command failed:@\n*** %s@\n*** %s@\n" - (String.concat ~sep:" " (prog::capture_args)) - (Unix.Exit_or_signal.to_string_hum status) + | Ok () + -> () + | Error _ as status + -> failwithf "*** Maven command failed:@\n*** %s@\n*** %s@\n" + (String.concat ~sep:" " (prog :: capture_args)) + (Unix.Exit_or_signal.to_string_hum status) diff --git a/infer/src/integration/ReportDiff.ml b/infer/src/integration/ReportDiff.ml index 79be54431..1fe3aacc7 100644 --- a/infer/src/integration/ReportDiff.ml +++ b/infer/src/integration/ReportDiff.ml @@ -13,24 +13,29 @@ let reportdiff ~current_report:current_report_fname ~previous_report:previous_re let empty_report = [] in Option.value_map ~f:(fun filename -> Jsonbug_j.report_of_string (In_channel.read_all filename)) - ~default:empty_report filename_opt in + ~default:empty_report filename_opt + in let current_report = load_report current_report_fname in let previous_report = load_report previous_report_fname in let diff = let unfiltered_diff = Differential.of_reports ~current_report ~previous_report in if Config.filtering then - let file_renamings = match Config.file_renamings with - | Some f -> DifferentialFilters.FileRenamings.from_json_file f - | None -> DifferentialFilters.FileRenamings.empty in - let interesting_paths = Option.map ~f:(fun fname -> - List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname)) - Config.differential_filter_files in - DifferentialFilters.do_filter - unfiltered_diff - file_renamings - ~skip_duplicated_types:Config.skip_duplicated_types - ~interesting_paths - else unfiltered_diff in + let file_renamings = + match Config.file_renamings with + | Some f + -> DifferentialFilters.FileRenamings.from_json_file f + | None + -> DifferentialFilters.FileRenamings.empty + in + let interesting_paths = + Option.map + ~f:(fun fname -> + List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname)) + Config.differential_filter_files + in + DifferentialFilters.do_filter unfiltered_diff file_renamings + ~skip_duplicated_types:Config.skip_duplicated_types ~interesting_paths + else unfiltered_diff + in let out_path = Config.results_dir ^/ "differential" in - Unix.mkdir_p out_path; - Differential.to_files diff out_path + Unix.mkdir_p out_path ; Differential.to_files diff out_path diff --git a/infer/src/integration/ReportDiff.mli b/infer/src/integration/ReportDiff.mli index 636a438a0..2ed5dff19 100644 --- a/infer/src/integration/ReportDiff.mli +++ b/infer/src/integration/ReportDiff.mli @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd val reportdiff : current_report:string option -> previous_report:string option -> unit diff --git a/infer/src/java/jAnnotation.ml b/infer/src/java/jAnnotation.ml index 0b4535a5a..8f4b4e585 100644 --- a/infer/src/java/jAnnotation.ml +++ b/infer/src/java/jAnnotation.ml @@ -9,45 +9,42 @@ *) open! IStd - open Javalib_pack - (** Translate an annotation. *) let translate a : Annot.t = let class_name = JBasics.cn_name a.JBasics.kind in let rec translate_value_pair acc (x, value) = match value with - | JBasics.EVArray (JBasics.EVCstString s :: l) -> - translate_value_pair (s::acc) (x, JBasics.EVArray l) - | JBasics.EVCstString s -> - s :: acc - | JBasics.EVCstBoolean 0 -> - (* just translate bools as strings. means we can't distinguish between a boolean false + | JBasics.EVArray ((JBasics.EVCstString s) :: l) + -> translate_value_pair (s :: acc) (x, JBasics.EVArray l) + | JBasics.EVCstString s + -> s :: acc + | JBasics.EVCstBoolean 0 + -> (* just translate bools as strings. means we can't distinguish between a boolean false literal parameter and string literal "false" parameter, but that's ok. *) - "false" :: acc - | JBasics.EVCstBoolean 1 -> - "true" :: acc - | _ -> - acc in + "false" + :: acc + | JBasics.EVCstBoolean 1 + -> "true" :: acc + | _ + -> acc + in let parameters = - List.fold ~f:translate_value_pair ~init:[] a.JBasics.element_value_pairs - |> List.rev in - { Annot.class_name; parameters; } + List.fold ~f:translate_value_pair ~init:[] a.JBasics.element_value_pairs |> List.rev + in + {Annot.class_name= class_name; parameters} (** Translate an item annotation. *) let translate_item avlist : Annot.Item.t = - let trans_vis = function - | Javalib.RTVisible -> true - | Javalib.RTInvisible -> false in - let trans (a, v) = translate a, trans_vis v in + let trans_vis = function Javalib.RTVisible -> true | Javalib.RTInvisible -> false in + let trans (a, v) = (translate a, trans_vis v) in List.map ~f:trans avlist - (** Translate a method annotation. *) let translate_method ann : Annot.Method.t = let global_ann = ann.Javalib.ma_global in let param_ann = ann.Javalib.ma_parameters in let ret_item = translate_item global_ann in let param_items = List.map ~f:translate_item param_ann in - ret_item, param_items + (ret_item, param_items) diff --git a/infer/src/java/jAnnotation.mli b/infer/src/java/jAnnotation.mli index b811264f1..6af897098 100644 --- a/infer/src/java/jAnnotation.mli +++ b/infer/src/java/jAnnotation.mli @@ -9,12 +9,10 @@ *) open! IStd - open Javalib_pack - -(** Translate an item annotation. *) val translate_item : (JBasics.annotation * Javalib.visibility) list -> Annot.Item.t +(** Translate an item annotation. *) -(** Translate a method annotation. *) val translate_method : Javalib.method_annotations -> Annot.Method.t +(** Translate a method annotation. *) diff --git a/infer/src/java/jClasspath.ml b/infer/src/java/jClasspath.ml index 81fa32da8..083ac8061 100644 --- a/infer/src/java/jClasspath.ml +++ b/infer/src/java/jClasspath.ml @@ -10,9 +10,7 @@ open! IStd open! PVariant - open Javalib_pack - module L = Logging (** version of Javalib.get_class that does not spam stderr *) @@ -22,30 +20,33 @@ let models_specs_filenames = ref String.Set.empty let models_jar = ref "" - let models_tenv = ref (Tenv.create ()) - let load_models_tenv zip_channel = let models_tenv_filename_in_jar = let root = Filename.concat Config.default_in_zip_results_dir Config.captured_dir_name in - Filename.concat root Config.global_tenv_filename in + Filename.concat root Config.global_tenv_filename + in let temp_tenv_filename = - DB.filename_from_string (Filename.temp_file "tmp_" Config.global_tenv_filename) in + DB.filename_from_string (Filename.temp_file "tmp_" Config.global_tenv_filename) + in let entry = Zip.find_entry zip_channel models_tenv_filename_in_jar in let temp_tenv_file = DB.filename_to_string temp_tenv_filename in let models_tenv = try - Zip.copy_entry_to_file zip_channel entry temp_tenv_file; + Zip.copy_entry_to_file zip_channel entry temp_tenv_file ; match Tenv.load_from_file temp_tenv_filename with - | None -> failwith "Models tenv file could not be loaded" - | Some tenv -> tenv + | None + -> failwith "Models tenv file could not be loaded" + | Some tenv + -> tenv with - | Not_found -> failwith "Models tenv not found in jar file" - | Sys_error msg -> failwith ("Models jar could not be opened "^msg) in - DB.file_remove temp_tenv_filename; - models_tenv - + | Not_found + -> failwith "Models tenv not found in jar file" + | Sys_error msg + -> failwith ("Models jar could not be opened " ^ msg) + in + DB.file_remove temp_tenv_filename ; models_tenv let collect_specs_filenames jar_filename = let zip_channel = Zip.open_in jar_filename in @@ -53,123 +54,100 @@ let collect_specs_filenames jar_filename = let filename = e.Zip.filename in if not (Filename.check_suffix filename Config.specs_files_suffix) then set else - let proc_filename = (Filename.chop_extension (Filename.basename filename)) in - String.Set.add set proc_filename in - models_specs_filenames := - List.fold ~f:collect ~init:!models_specs_filenames (Zip.entries zip_channel); - models_tenv := load_models_tenv zip_channel; + let proc_filename = Filename.chop_extension (Filename.basename filename) in + String.Set.add set proc_filename + in + models_specs_filenames + := List.fold ~f:collect ~init:!models_specs_filenames (Zip.entries zip_channel) ; + models_tenv := load_models_tenv zip_channel ; Zip.close_in zip_channel - let add_models jar_filename = - models_jar := jar_filename; - if Sys.file_exists !models_jar = `Yes then - collect_specs_filenames jar_filename - else - failwith "Java model file not found" - - -let is_model procname = - String.Set.mem !models_specs_filenames (Typ.Procname.to_filename procname) + models_jar := jar_filename ; + if Sys.file_exists !models_jar = `Yes then collect_specs_filenames jar_filename + else failwith "Java model file not found" +let is_model procname = String.Set.mem !models_specs_filenames (Typ.Procname.to_filename procname) let split_classpath cp = Str.split (Str.regexp JFile.sep) cp - let append_path classpath path = if Sys.file_exists path = `Yes then let root = Unix.getcwd () in let full_path = Utils.filename_to_absolute ~root path in - if Int.equal (String.length classpath) 0 then - full_path - else - classpath^JFile.sep^full_path - else - classpath + if Int.equal (String.length classpath) 0 then full_path else classpath ^ JFile.sep ^ full_path + else classpath - -type file_entry = - | Singleton of SourceFile.t - | Duplicate of (string * SourceFile.t) list +type file_entry = Singleton of SourceFile.t | Duplicate of (string * SourceFile.t) list type t = string * file_entry String.Map.t * JBasics.ClassSet.t - (* Open the source file and search for the package declaration. Only the case where the package is declared in a single line is supported *) let read_package_declaration source_file = let path = SourceFile.to_abs_path source_file in let file_in = In_channel.create path in - let remove_trailing_semicolon = - Str.replace_first (Str.regexp ";") "" in + let remove_trailing_semicolon = Str.replace_first (Str.regexp ";") "" in let empty_package = "" in let rec loop () = try let line = remove_trailing_semicolon (In_channel.input_line_exn file_in) in match Str.split (Str.regexp "[ \t]+") line with - | [] -> loop () - | hd::package::[] when String.equal hd "package" -> package - | _ -> loop () - with End_of_file -> - In_channel.close file_in; - empty_package in + | [] + -> loop () + | [hd; package] when String.equal hd "package" + -> package + | _ + -> loop () + with End_of_file -> In_channel.close file_in ; empty_package + in loop () - let add_source_file path map = let convert_to_absolute p = - if Filename.is_relative p then - Filename.concat (Sys.getcwd ()) p - else - p in + if Filename.is_relative p then Filename.concat (Sys.getcwd ()) p else p + in let basename = Filename.basename path in let entry = - let current_source_file = - SourceFile.from_abs_path (convert_to_absolute path) in + let current_source_file = SourceFile.from_abs_path (convert_to_absolute path) in try match String.Map.find_exn map basename with - | Singleton previous_source_file -> - (* Another source file with the same base name has been found. + | Singleton previous_source_file + -> (* Another source file with the same base name has been found. Reading the package from the source file to resolve the ambiguity only happens in this case *) let previous_package = read_package_declaration previous_source_file and current_package = read_package_declaration current_source_file in - let source_list = [ - (current_package, current_source_file); - (previous_package, previous_source_file)] in + let source_list = + [(current_package, current_source_file); (previous_package, previous_source_file)] + in Duplicate source_list - | Duplicate previous_source_files -> - (* Two or more source file with the same base name have been found *) + | Duplicate previous_source_files + -> (* Two or more source file with the same base name have been found *) let current_package = read_package_declaration current_source_file in Duplicate ((current_package, current_source_file) :: previous_source_files) with Not_found -> (* Most common case: there is no conflict with the base name of the source file *) - Singleton current_source_file in + Singleton current_source_file + in String.Map.add ~key:basename ~data:entry map - -let add_root_path path roots = - String.Set.add roots path - +let add_root_path path roots = String.Set.add roots path let load_from_verbose_output javac_verbose_out = let file_in = In_channel.create javac_verbose_out in - let class_filename_re = - Str.regexp - "\\[wrote RegularFileObject\\[\\(.*\\)\\]\\]" in - let source_filename_re = - Str.regexp - "\\[parsing started RegularFileObject\\[\\(.*\\)\\]\\]" in - let classpath_re = - Str.regexp - "\\[search path for class files: \\(.*\\)\\]" in + let class_filename_re = Str.regexp "\\[wrote RegularFileObject\\[\\(.*\\)\\]\\]" in + let source_filename_re = Str.regexp "\\[parsing started RegularFileObject\\[\\(.*\\)\\]\\]" in + let classpath_re = Str.regexp "\\[search path for class files: \\(.*\\)\\]" in let rec loop paths roots sources classes = try let line = In_channel.input_line_exn file_in in if Str.string_match class_filename_re line 0 then let path = Str.matched_group 1 line in let cn, root_info = Javalib.extract_class_name_from_file path in - let root_dir = if String.equal root_info "" then Filename.current_dir_name else root_info in + let root_dir = + if String.equal root_info "" then Filename.current_dir_name else root_info + in loop paths (add_root_path root_dir roots) sources (JBasics.ClassSet.add cn classes) else if Str.string_match source_filename_re line 0 then let path = Str.matched_group 1 line in @@ -178,165 +156,118 @@ let load_from_verbose_output javac_verbose_out = let classpath = Str.matched_group 1 line in let parsed_paths = Str.split (Str.regexp_string ",") classpath in loop parsed_paths roots sources classes - else - (* skip this line *) + else (* skip this line *) loop paths roots sources classes with - | JBasics.Class_structure_error _ - | Invalid_argument _ -> loop paths roots sources classes - | End_of_file -> - In_channel.close file_in; - let classpath = - List.fold - ~f:append_path - ~init:"" - ((String.Set.elements roots) @ paths) in - (classpath, sources, classes) in + | JBasics.Class_structure_error _ | Invalid_argument _ + -> loop paths roots sources classes + | End_of_file + -> In_channel.close file_in ; + let classpath = List.fold ~f:append_path ~init:"" (String.Set.elements roots @ paths) in + (classpath, sources, classes) + in loop [] String.Set.empty String.Map.empty JBasics.ClassSet.empty - let classname_of_class_filename class_filename = JBasics.make_cn (String.map ~f:(function '/' -> '.' | c -> c) class_filename) - let extract_classnames classnames jar_filename = let file_in = Zip.open_in jar_filename in let collect classes entry = let class_filename = entry.Zip.filename in match Filename.split_extension class_filename with - | basename, Some "class" -> - (classname_of_class_filename basename) :: classes - | _ -> classes in + | basename, Some "class" + -> classname_of_class_filename basename :: classes + | _ + -> classes + in let classnames_after = List.fold ~f:collect ~init:classnames (Zip.entries file_in) in - Zip.close_in file_in; - classnames_after - + Zip.close_in file_in ; classnames_after let collect_classnames start_classmap jar_filename = List.fold ~f:(fun map cn -> JBasics.ClassSet.add cn map) - ~init:start_classmap - (extract_classnames [] jar_filename) - + ~init:start_classmap (extract_classnames [] jar_filename) let search_classes path = let add_class roots classes class_filename = - let cn, root_dir = - Javalib.extract_class_name_from_file class_filename in - (add_root_path root_dir roots, JBasics.ClassSet.add cn classes) in + let cn, root_dir = Javalib.extract_class_name_from_file class_filename in + (add_root_path root_dir roots, JBasics.ClassSet.add cn classes) + in Utils.directory_fold (fun accu p -> - let paths, classes = accu in - if Filename.check_suffix p "class" then - add_class paths classes p - else if Filename.check_suffix p "jar" then - (add_root_path p paths, collect_classnames classes p) - else accu) - (String.Set.empty, JBasics.ClassSet.empty) - path - + let paths, classes = accu in + if Filename.check_suffix p "class" then add_class paths classes p + else if Filename.check_suffix p "jar" then + (add_root_path p paths, collect_classnames classes p) + else accu) + (String.Set.empty, JBasics.ClassSet.empty) path let search_sources () = let initial_map = - List.fold - ~f:(fun map path -> add_source_file path map) - ~init:String.Map.empty - Config.sources in + List.fold ~f:(fun map path -> add_source_file path map) ~init:String.Map.empty Config.sources + in match Config.sourcepath with - | None -> initial_map - | Some sourcepath -> - Utils.directory_fold - (fun map p -> - if Filename.check_suffix p "java" - then add_source_file p map - else map) - initial_map - sourcepath - + | None + -> initial_map + | Some sourcepath + -> Utils.directory_fold + (fun map p -> if Filename.check_suffix p "java" then add_source_file p map else map) + initial_map sourcepath let load_from_arguments classes_out_path = let roots, classes = search_classes classes_out_path in - let split cp_option = - Option.value_map ~f:split_classpath ~default:[] cp_option in + let split cp_option = Option.value_map ~f:split_classpath ~default:[] cp_option in let combine path_list classpath = - List.fold ~f:append_path ~init:classpath (List.rev path_list) in + List.fold ~f:append_path ~init:classpath (List.rev path_list) + in let classpath = - combine (split Config.classpath) "" - |> combine (String.Set.elements roots) - |> combine (split Config.bootclasspath) in + combine (split Config.classpath) "" |> combine (String.Set.elements roots) + |> combine (split Config.bootclasspath) + in (classpath, search_sources (), classes) - type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t +type program = {classpath: Javalib.class_path; models: classmap; mutable classmap: classmap} -type program = { - classpath: Javalib.class_path; - models: classmap; - mutable classmap: classmap -} - +let get_classmap program = program.classmap -let get_classmap program = - program.classmap - - -let get_classpath program = - program.classpath - - -let get_models program = - program.models +let get_classpath program = program.classpath +let get_models program = program.models let add_class cn jclass program = program.classmap <- JBasics.ClassMap.add cn jclass program.classmap -let cleanup program = - Javalib.close_class_path program.classpath +let cleanup program = Javalib.close_class_path program.classpath let lookup_node cn program = - try - Some (JBasics.ClassMap.find cn (get_classmap program)) + try Some (JBasics.ClassMap.find cn (get_classmap program)) with Not_found -> - try - let jclass = javalib_get_class (get_classpath program) cn in - add_class cn jclass program; - Some jclass - with - | JBasics.No_class_found _ - | JBasics.Class_structure_error _ - | Invalid_argument _ -> None - + try + let jclass = javalib_get_class (get_classpath program) cn in + add_class cn jclass program ; Some jclass + with JBasics.No_class_found _ | JBasics.Class_structure_error _ | Invalid_argument _ -> None let collect_classes start_classmap jar_filename = let classpath = Javalib.class_path jar_filename in let collect classmap cn = - try - JBasics.ClassMap.add cn (javalib_get_class classpath cn) classmap - with JBasics.Class_structure_error _ -> - classmap in - let classmap = - List.fold - ~f:collect - ~init:start_classmap - (extract_classnames [] jar_filename) in - Javalib.close_class_path classpath; - classmap - + try JBasics.ClassMap.add cn (javalib_get_class classpath cn) classmap + with JBasics.Class_structure_error _ -> classmap + in + let classmap = List.fold ~f:collect ~init:start_classmap (extract_classnames [] jar_filename) in + Javalib.close_class_path classpath ; classmap let load_program classpath classes = - L.(debug Capture Medium) "loading program ... %!"; + L.(debug Capture Medium) "loading program ... %!" ; let models = if String.equal !models_jar "" then JBasics.ClassMap.empty - else collect_classes JBasics.ClassMap.empty !models_jar in - let program = { - classpath = Javalib.class_path classpath; - models = models; - classmap = JBasics.ClassMap.empty - } in - JBasics.ClassSet.iter - (fun cn -> ignore (lookup_node cn program)) - classes; - L.(debug Capture Medium) "done@."; + else collect_classes JBasics.ClassMap.empty !models_jar + in + let program = + {classpath= Javalib.class_path classpath; models; classmap= JBasics.ClassMap.empty} + in + JBasics.ClassSet.iter (fun cn -> ignore (lookup_node cn program)) classes ; + L.(debug Capture Medium) "done@." ; program diff --git a/infer/src/java/jClasspath.mli b/infer/src/java/jClasspath.mli index f59abb292..bbfa7a55f 100644 --- a/infer/src/java/jClasspath.mli +++ b/infer/src/java/jClasspath.mli @@ -9,36 +9,33 @@ *) open! IStd - open Javalib_pack -(** Jar file containing the models *) val models_jar : string ref +(** Jar file containing the models *) -(** Type environment of the models *) val models_tenv : Tenv.t ref +(** Type environment of the models *) +val add_models : string -> unit (** Adds the set of procnames for the models of Java libraries so that methods with similar names are skipped during the capture *) -val add_models : string -> unit -(** Check if there is a model for the given procname *) val is_model : Typ.Procname.t -> bool +(** Check if there is a model for the given procname *) val split_classpath : string -> string list (** map entry for source files with potential basename collision within the same compiler call *) -type file_entry = - | Singleton of SourceFile.t - | Duplicate of (string * SourceFile.t) list +type file_entry = Singleton of SourceFile.t | Duplicate of (string * SourceFile.t) list type t = string * file_entry String.Map.t * JBasics.ClassSet.t -(** load the list of source files and the list of classes from the javac verbose file *) val load_from_verbose_output : string -> t +(** load the list of source files and the list of classes from the javac verbose file *) -(** load the list of source files and the list of classes from Config.generated_classes *) val load_from_arguments : string -> t +(** load the list of source files and the list of classes from Config.generated_classes *) type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t @@ -50,11 +47,11 @@ val get_models : program -> classmap val cleanup : program -> unit -(** load a java program *) val load_program : string -> JBasics.ClassSet.t -> program +(** load a java program *) -(** retrive a Java node from the classname *) val lookup_node : JBasics.class_name -> program -> JCode.jcode Javalib.interface_or_class option +(** retrive a Java node from the classname *) -(** [collect_classes cmap filename] adds to [cmap] the classes found in the jar file [filename] *) val collect_classes : classmap -> string -> classmap +(** [collect_classes cmap filename] adds to [cmap] the classes found in the jar file [filename] *) diff --git a/infer/src/java/jConfig.ml b/infer/src/java/jConfig.ml index 83cba8d17..33c9e463f 100644 --- a/infer/src/java/jConfig.ml +++ b/infer/src/java/jConfig.ml @@ -9,7 +9,6 @@ *) open! IStd - open Javalib_pack (** {2 Class names and types} *) @@ -22,7 +21,7 @@ let infer_array_cl = builtins_package ^ ".InferArray" let infer_undefined_cl = builtins_package ^ ".InferUndefined" -let obj_type = (JBasics.TObject (JBasics.TClass JBasics.java_lang_object)) +let obj_type = JBasics.TObject (JBasics.TClass JBasics.java_lang_object) let bool_type = JBasics.TBasic `Bool @@ -42,9 +41,9 @@ let lock_cl = "java.util.concurrent.locks.Lock" let reentrant_rwlock_cl = "java.util.concurrent.locks.ReentrantReadWriteLock" -let reentrant_rlock_cl = reentrant_rwlock_cl^"$ReadLock" +let reentrant_rlock_cl = reentrant_rwlock_cl ^ "$ReadLock" -let reentrant_wlock_cl = reentrant_rwlock_cl^"$WriteLock" +let reentrant_wlock_cl = reentrant_rwlock_cl ^ "$WriteLock" let thread_class = "java.lang.Thread" @@ -110,9 +109,10 @@ let long_code = "J" let short_code = "S" -let class_code cl = "L"^cl +let class_code cl = "L" ^ cl let errors_db_file = "errors.db" + let main_errors_file = "Java_frontend.errors" (** {2 Flags } *) diff --git a/infer/src/java/jContext.ml b/infer/src/java/jContext.ml index 7c2927cbd..14c11824f 100644 --- a/infer/src/java/jContext.ml +++ b/infer/src/java/jContext.ml @@ -10,68 +10,60 @@ open! IStd module Hashtbl = Caml.Hashtbl - open Javalib_pack open Sawja_pack - module NodeTbl = Procdesc.NodeHash -type jump_kind = - | Next - | Jump of int - | Exit +type jump_kind = Next | Jump of int | Exit (** Translation data *) -type icfg = { - tenv : Tenv.t; - cg : Cg.t; - cfg : Cfg.cfg; -} +type icfg = {tenv: Tenv.t; cg: Cg.t; cfg: Cfg.cfg} type t = - { icfg : icfg; - procdesc : Procdesc.t; - impl : JBir.t; - mutable var_map : (Pvar.t * Typ.t * Typ.t) JBir.VarMap.t; - if_jumps : int NodeTbl.t; - goto_jumps : (int, jump_kind) Hashtbl.t; - cn : JBasics.class_name; - source_file : SourceFile.t; - program : JClasspath.program; - } + { icfg: icfg + ; procdesc: Procdesc.t + ; impl: JBir.t + ; mutable var_map: (Pvar.t * Typ.t * Typ.t) JBir.VarMap.t + ; if_jumps: int NodeTbl.t + ; goto_jumps: (int, jump_kind) Hashtbl.t + ; cn: JBasics.class_name + ; source_file: SourceFile.t + ; program: JClasspath.program } let create_context icfg procdesc impl cn source_file program = - { icfg; - procdesc; - impl; - var_map = JBir.VarMap.empty; - if_jumps = NodeTbl.create 10; - goto_jumps = Hashtbl.create 10; - cn; - source_file; - program; - } + { icfg + ; procdesc + ; impl + ; var_map= JBir.VarMap.empty + ; if_jumps= NodeTbl.create 10 + ; goto_jumps= Hashtbl.create 10 + ; cn + ; source_file + ; program } let get_cfg context = context.icfg.cfg + let get_cg context = context.icfg.cg + let get_tenv context = context.icfg.tenv + let set_var_map context var_map = context.var_map <- var_map let get_or_set_pvar_type context var typ = let var_map = context.var_map in try - let (pvar, otyp, _) = (JBir.VarMap.find var var_map) in + let pvar, otyp, _ = JBir.VarMap.find var var_map in let tenv = get_tenv context in - if Prover.Subtyping_check.check_subtype tenv typ otyp || - Prover.Subtyping_check.check_subtype tenv otyp typ then - set_var_map context (JBir.VarMap.add var (pvar, otyp, typ) var_map) - else set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map); + if Prover.Subtyping_check.check_subtype tenv typ otyp + || Prover.Subtyping_check.check_subtype tenv otyp typ + then set_var_map context (JBir.VarMap.add var (pvar, otyp, typ) var_map) + else set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map) ; (pvar, typ) with Not_found -> - let procname = (Procdesc.get_proc_name context.procdesc) in + let procname = Procdesc.get_proc_name context.procdesc in let varname = Mangled.from_string (JBir.var_name_g var) in let pvar = Pvar.mk varname procname in - set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map); + set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map) ; (pvar, typ) let set_pvar context var typ = fst (get_or_set_pvar_type context var typ) @@ -79,51 +71,44 @@ let set_pvar context var typ = fst (get_or_set_pvar_type context var typ) let reset_pvar_type context = let var_map = context.var_map in let aux var item = - match item with (pvar, otyp, _) -> - set_var_map context (JBir.VarMap.add var (pvar, otyp, otyp) var_map) in + match item + with pvar, otyp, _ -> set_var_map context (JBir.VarMap.add var (pvar, otyp, otyp) var_map) + in JBir.VarMap.iter aux var_map let get_var_type context var = try - let (_, _, otyp) = JBir.VarMap.find var context.var_map in + let _, _, otyp = JBir.VarMap.find var context.var_map in Some otyp with Not_found -> None let get_if_jumps context = context.if_jumps + let get_goto_jumps context = context.goto_jumps -let add_if_jump context node pc = - NodeTbl.add (get_if_jumps context) node pc +let add_if_jump context node pc = NodeTbl.add (get_if_jumps context) node pc let get_if_jump context node = - try - Some (NodeTbl.find (get_if_jumps context) node) + try Some (NodeTbl.find (get_if_jumps context) node) with Not_found -> None -let add_goto_jump context pc jump = - Hashtbl.add (get_goto_jumps context) pc jump +let add_goto_jump context pc jump = Hashtbl.add (get_goto_jumps context) pc jump let get_goto_jump context pc = - try - Hashtbl.find (get_goto_jumps context) pc + try Hashtbl.find (get_goto_jumps context) pc with Not_found -> Next let is_goto_jump context pc = - try - match Hashtbl.find (get_goto_jumps context) pc with - | Jump _ -> true - | _ -> false + try match Hashtbl.find (get_goto_jumps context) pc with Jump _ -> true | _ -> false with Not_found -> false let exn_node_table = Typ.Procname.Hash.create 100 -let reset_exn_node_table () = - Typ.Procname.Hash.clear exn_node_table +let reset_exn_node_table () = Typ.Procname.Hash.clear exn_node_table -let add_exn_node procname (exn_node : Procdesc.Node.t) = +let add_exn_node procname (exn_node: Procdesc.Node.t) = Typ.Procname.Hash.add exn_node_table procname exn_node let get_exn_node procdesc = - try - Some (Typ.Procname.Hash.find exn_node_table (Procdesc.get_proc_name procdesc)) + try Some (Typ.Procname.Hash.find exn_node_table (Procdesc.get_proc_name procdesc)) with Not_found -> None diff --git a/infer/src/java/jContext.mli b/infer/src/java/jContext.mli index 7aabcf2bf..7299c0a05 100644 --- a/infer/src/java/jContext.mli +++ b/infer/src/java/jContext.mli @@ -9,94 +9,76 @@ *) open! IStd - open Javalib_pack open Sawja_pack (** data structure for representing whether an instruction is a goto, a return or a standard instruction. *) -type jump_kind = - | Next - | Jump of int - | Exit - +type jump_kind = Next | Jump of int | Exit (** Hastable for storing nodes that correspond to if-instructions. These are used when adding the edges in the contrl flow graph. *) module NodeTbl : Caml.Hashtbl.S with type key = Procdesc.Node.t - (** data structure for saving the three structures tht contain the intermediate representation of a file: the type environment, the control graph and the control flow graph *) -type icfg = { - tenv : Tenv.t; - cg : Cg.t; - cfg : Cfg.cfg; -} +type icfg = {tenv: Tenv.t; cg: Cg.t; cfg: Cfg.cfg} (** data structure for storing the context elements. *) type t = private - { icfg : icfg; - procdesc : Procdesc.t; - impl : JBir.t; - mutable var_map : (Pvar.t * Typ.t * Typ.t) JBir.VarMap.t; - if_jumps : int NodeTbl.t; - goto_jumps : (int, jump_kind) Caml.Hashtbl.t; - cn : JBasics.class_name; - source_file : SourceFile.t; - program : JClasspath.program; - } + { icfg: icfg + ; procdesc: Procdesc.t + ; impl: JBir.t + ; mutable var_map: (Pvar.t * Typ.t * Typ.t) JBir.VarMap.t + ; if_jumps: int NodeTbl.t + ; goto_jumps: (int, jump_kind) Caml.Hashtbl.t + ; cn: JBasics.class_name + ; source_file: SourceFile.t + ; program: JClasspath.program } - -(** cretes a context for a given method. *) val create_context : - icfg -> - Procdesc.t -> - JBir.t -> - JBasics.class_name -> - SourceFile.t -> - JClasspath.program -> - t + icfg -> Procdesc.t -> JBir.t -> JBasics.class_name -> SourceFile.t -> JClasspath.program -> t +(** cretes a context for a given method. *) -(** returns the type environment that corresponds to the current file. *) val get_tenv : t -> Tenv.t +(** returns the type environment that corresponds to the current file. *) -(** returns the control graph that corresponds to the current file. *) val get_cg : t -> Cg.t +(** returns the control graph that corresponds to the current file. *) -(** returns the control flow graph that corresponds to the current file. *) val get_cfg : t -> Cfg.cfg +(** returns the control flow graph that corresponds to the current file. *) -(** adds to the context the line that an if-node will jump to *) val add_if_jump : t -> Procdesc.Node.t -> int -> unit +(** adds to the context the line that an if-node will jump to *) -(** returns whether the given node corresponds to an if-instruction *) val get_if_jump : t -> Procdesc.Node.t -> int option +(** returns whether the given node corresponds to an if-instruction *) -(** adds to the context the line that the node in the given line will jump to. *) val add_goto_jump : t -> int -> jump_kind -> unit +(** adds to the context the line that the node in the given line will jump to. *) +val get_goto_jump : t -> int -> jump_kind (** if the given line corresponds to a goto instruction, then returns the line where it jumps to, otherwise returns the next line. *) -val get_goto_jump : t -> int -> jump_kind -(** returns whether the given line corresponds to a goto instruction. *) val is_goto_jump : t -> int -> bool +(** returns whether the given line corresponds to a goto instruction. *) -(** [set_pvar context var type] adds a variable with a type to the context *) val set_pvar : t -> JBir.var -> Typ.t -> Pvar.t +(** [set_pvar context var type] adds a variable with a type to the context *) -(** [get_var_type context var] returns the type of the variable, if the variable is in the context *) val get_var_type : t -> JBir.var -> Typ.t option +(** [get_var_type context var] returns the type of the variable, if the variable is in the context *) -(** resets the dynamic type of the variables in the context. *) val reset_pvar_type : t -> unit +(** resets the dynamic type of the variables in the context. *) -(** resets the hashtable mapping methods to their exception nodes *) val reset_exn_node_table : unit -> unit +(** resets the hashtable mapping methods to their exception nodes *) -(** adds the exception node for a given method *) val add_exn_node : Typ.Procname.t -> Procdesc.Node.t -> unit +(** adds the exception node for a given method *) -(** returns the exception node of a given method *) val get_exn_node : Procdesc.t -> Procdesc.Node.t option +(** returns the exception node of a given method *) diff --git a/infer/src/java/jFrontend.ml b/infer/src/java/jFrontend.ml index 951813ec9..869c38521 100644 --- a/infer/src/java/jFrontend.ml +++ b/infer/src/java/jFrontend.ml @@ -10,222 +10,209 @@ open! IStd open! PVariant - open Javalib_pack open Sawja_pack - module L = Logging - -let add_edges - (context : JContext.t) start_node exn_node exit_nodes method_body_nodes impl super_call = +let add_edges (context: JContext.t) start_node exn_node exit_nodes method_body_nodes impl + super_call = let pc_nb = Array.length method_body_nodes in let last_pc = pc_nb - 1 in let is_last pc = Int.equal pc last_pc in let rec get_body_nodes_ pc visited = let current_nodes = method_body_nodes.(pc) in match current_nodes with - | JTrans.Skip when (is_last pc) && not (JContext.is_goto_jump context pc) -> exit_nodes - | JTrans.Skip -> direct_successors pc (Int.Set.add visited pc) - | JTrans.Instr node -> [node] - | JTrans.Prune (node_true, node_false) -> [node_true; node_false] - | JTrans.Loop (join_node, _, _) -> [join_node] + | JTrans.Skip when is_last pc && not (JContext.is_goto_jump context pc) + -> exit_nodes + | JTrans.Skip + -> direct_successors pc (Int.Set.add visited pc) + | JTrans.Instr node + -> [node] + | JTrans.Prune (node_true, node_false) + -> [node_true; node_false] + | JTrans.Loop (join_node, _, _) + -> [join_node] and direct_successors pc visited = - if is_last pc && not (JContext.is_goto_jump context pc) then - exit_nodes + if is_last pc && not (JContext.is_goto_jump context pc) then exit_nodes else match JContext.get_goto_jump context pc with - | JContext.Next -> - let next_pc = pc + 1 in - if Int.Set.mem visited next_pc - then [] - else get_body_nodes_ next_pc visited - | JContext.Jump goto_pc when Int.Set.mem visited goto_pc -> - [] (* loop in goto *) - | JContext.Jump goto_pc -> - get_body_nodes_ goto_pc visited - | JContext.Exit -> - exit_nodes in - let get_body_nodes pc = - get_body_nodes_ pc Int.Set.empty in + | JContext.Next + -> let next_pc = pc + 1 in + if Int.Set.mem visited next_pc then [] else get_body_nodes_ next_pc visited + | JContext.Jump goto_pc when Int.Set.mem visited goto_pc + -> [] (* loop in goto *) + | JContext.Jump goto_pc + -> get_body_nodes_ goto_pc visited + | JContext.Exit + -> exit_nodes + in + let get_body_nodes pc = get_body_nodes_ pc Int.Set.empty in let get_succ_nodes node pc = match JContext.get_if_jump context node with - | None -> direct_successors pc Int.Set.empty - | Some jump_pc -> get_body_nodes jump_pc in + | None + -> direct_successors pc Int.Set.empty + | Some jump_pc + -> get_body_nodes jump_pc + in let get_exn_nodes = - if super_call then (fun _ -> exit_nodes) - else JTransExn.create_exception_handlers context [exn_node] get_body_nodes impl in + if super_call then fun _ -> exit_nodes + else JTransExn.create_exception_handlers context [exn_node] get_body_nodes impl + in let connect node pc = - Procdesc.node_set_succs_exn - context.procdesc node (get_succ_nodes node pc) (get_exn_nodes pc) in + Procdesc.node_set_succs_exn context.procdesc node (get_succ_nodes node pc) (get_exn_nodes pc) + in let connect_nodes pc translated_instruction = match translated_instruction with - | JTrans.Skip -> () - | JTrans.Instr node -> connect node pc - | JTrans.Prune (node_true, node_false) -> - connect node_true pc; + | JTrans.Skip + -> () + | JTrans.Instr node + -> connect node pc + | JTrans.Prune (node_true, node_false) + -> connect node_true pc ; connect node_false pc + | JTrans.Loop (join_node, node_true, node_false) + -> Procdesc.node_set_succs_exn context.procdesc join_node [node_true; node_false] [] ; + connect node_true pc ; connect node_false pc - | JTrans.Loop (join_node, node_true, node_false) -> - Procdesc.node_set_succs_exn context.procdesc join_node [node_true; node_false] []; - connect node_true pc; - connect node_false pc in + in let first_nodes = (* deals with the case of an empty array *) - direct_successors (-1) Int.Set.empty in - + direct_successors (-1) Int.Set.empty + in (* the exceptions edges here are going directly to the exit node *) - Procdesc.node_set_succs_exn context.procdesc start_node first_nodes exit_nodes; - + Procdesc.node_set_succs_exn context.procdesc start_node first_nodes exit_nodes ; if not super_call then (* the exceptions node is just before the exit node *) - Procdesc.node_set_succs_exn context.procdesc exn_node exit_nodes exit_nodes; + Procdesc.node_set_succs_exn context.procdesc exn_node exit_nodes exit_nodes ; Array.iteri ~f:connect_nodes method_body_nodes - (** Add a concrete method. *) let add_cmethod source_file program linereader icfg cm proc_name = let cn, _ = JBasics.cms_split cm.Javalib.cm_class_method_signature in match JTrans.create_cm_procdesc source_file program linereader icfg cm proc_name with - | None -> () - | Some (procdesc, _, jbir_code) -> - let start_node = Procdesc.get_start_node procdesc in + | None + -> () + | Some (procdesc, _, jbir_code) + -> let start_node = Procdesc.get_start_node procdesc in let exit_node = Procdesc.get_exit_node procdesc in let exn_node = match JContext.get_exn_node procdesc with - | Some node -> node - | None -> - failwithf "No exn node found for %s" (Typ.Procname.to_string proc_name) in + | Some node + -> node + | None + -> failwithf "No exn node found for %s" (Typ.Procname.to_string proc_name) + in let instrs = JBir.code jbir_code in - let context = - JContext.create_context icfg procdesc jbir_code cn source_file program in + let context = JContext.create_context icfg procdesc jbir_code cn source_file program in let method_body_nodes = Array.mapi ~f:(JTrans.instruction context) instrs in add_edges context start_node exn_node [exit_node] method_body_nodes jbir_code false - let path_of_cached_classname cn = let root_path = Filename.concat Config.results_dir "classnames" in - let package_path = - List.fold ~f:Filename.concat ~init:root_path (JBasics.cn_package cn) in - Filename.concat package_path ((JBasics.cn_simple_name cn)^".java") - + let package_path = List.fold ~f:Filename.concat ~init:root_path (JBasics.cn_package cn) in + Filename.concat package_path (JBasics.cn_simple_name cn ^ ".java") let cache_classname cn = let path = path_of_cached_classname cn in let splitted_root_dir = let rec split l p = match p with - | p when String.equal p Filename.current_dir_name -> l - | p when String.equal p Filename.dir_sep -> l - | p -> split ((Filename.basename p):: l) (Filename.dirname p) in - split [] (Filename.dirname path) in + | p when String.equal p Filename.current_dir_name + -> l + | p when String.equal p Filename.dir_sep + -> l + | p + -> split (Filename.basename p :: l) (Filename.dirname p) + in + split [] (Filename.dirname path) + in let rec mkdir l p = - let () = - if (Sys.file_exists p) <> `Yes then - Unix.mkdir p ~perm:493 in - match l with - | [] -> () - | d:: tl -> mkdir tl (Filename.concat p d) in - mkdir splitted_root_dir Filename.dir_sep; + let () = if Sys.file_exists p <> `Yes then Unix.mkdir p ~perm:493 in + match l with [] -> () | d :: tl -> mkdir tl (Filename.concat p d) + in + mkdir splitted_root_dir Filename.dir_sep ; let file_out = Out_channel.create path in - Out_channel.output_string file_out (string_of_float (Unix.time ())); + Out_channel.output_string file_out (string_of_float (Unix.time ())) ; Out_channel.close file_out -let is_classname_cached cn = - Sys.file_exists (path_of_cached_classname cn) = `Yes - +let is_classname_cached cn = Sys.file_exists (path_of_cached_classname cn) = `Yes (* Given a source file and a class, translates the code of this class. In init - mode, finds out whether this class contains initializers at all, in this case translates it. In standard mode, all methods are translated *) let create_icfg source_file linereader program icfg cn node = - L.(debug Capture Verbose) "\tclassname: %s@." (JBasics.cn_name cn); - if Config.dependency_mode && not (is_classname_cached cn) then - cache_classname cn; + L.(debug Capture Verbose) "\tclassname: %s@." (JBasics.cn_name cn) ; + if Config.dependency_mode && not (is_classname_cached cn) then cache_classname cn ; let translate m = let proc_name = JTransType.translate_method_name m in if JClasspath.is_model proc_name then (* do not translate the method if there is a model for it *) - L.(debug Capture Verbose) "Skipping method with a model: %s@." - (Typ.Procname.to_string proc_name) + L.(debug Capture Verbose) + "Skipping method with a model: %s@." (Typ.Procname.to_string proc_name) else try (* each procedure has different scope: start names from id 0 *) - Ident.NameGenerator.reset (); - begin - match m with - | Javalib.AbstractMethod am -> - ignore (JTrans.create_am_procdesc source_file program icfg am proc_name) - | Javalib.ConcreteMethod cm when JTrans.is_java_native cm -> - ignore (JTrans.create_native_procdesc source_file program icfg cm proc_name) - | Javalib.ConcreteMethod cm -> - add_cmethod source_file program linereader icfg cm proc_name - end; + Ident.NameGenerator.reset () ; + ( match m with + | Javalib.AbstractMethod am + -> ignore (JTrans.create_am_procdesc source_file program icfg am proc_name) + | Javalib.ConcreteMethod cm when JTrans.is_java_native cm + -> ignore (JTrans.create_native_procdesc source_file program icfg cm proc_name) + | Javalib.ConcreteMethod cm + -> add_cmethod source_file program linereader icfg cm proc_name ) ; Cg.add_defined_node icfg.JContext.cg proc_name with JBasics.Class_structure_error _ -> - L.internal_error - "create_icfg raised JBasics.Class_structure_error on %a@." - Typ.Procname.pp proc_name in + L.internal_error "create_icfg raised JBasics.Class_structure_error on %a@." Typ.Procname.pp + proc_name + in Javalib.m_iter translate node - (* returns true for the set of classes that are selected to be translated *) let should_capture classes package_opt source_basename node = let classname = Javalib.get_name node in let match_package pkg cn = match JTransType.package_to_string (JBasics.cn_package cn) with - | None -> String.equal pkg "" - | Some found_pkg -> String.equal found_pkg pkg in + | None + -> String.equal pkg "" + | Some found_pkg + -> String.equal found_pkg pkg + in if JBasics.ClassSet.mem classname classes then - begin - match Javalib.get_sourcefile node with - | None -> false - | Some found_basename -> - begin - match package_opt with - | None -> String.equal found_basename source_basename - | Some pkg -> - match_package pkg classname - && String.equal found_basename source_basename - end - end + match Javalib.get_sourcefile node with + | None + -> false + | Some found_basename -> + match package_opt with + | None + -> String.equal found_basename source_basename + | Some pkg + -> match_package pkg classname && String.equal found_basename source_basename else false - (* Computes the control - flow graph and call - graph of a given source file. In the standard - mode, it translated all the classes that correspond to this source file. *) -let compute_source_icfg - linereader classes program tenv - source_basename package_opt source_file = +let compute_source_icfg linereader classes program tenv source_basename package_opt source_file = let icfg = - { JContext.cg = Cg.create source_file; - JContext.cfg = Cfg.create_cfg (); - JContext.tenv = tenv } in + {JContext.cg= Cg.create source_file; JContext.cfg= Cfg.create_cfg (); JContext.tenv= tenv} + in let select test procedure cn node = - if test node then - try - procedure cn node - with - | Bir.Subroutine -> () - | e -> raise e in + if test node then try procedure cn node with Bir.Subroutine -> () | e -> raise e + in let () = JBasics.ClassMap.iter - (select - (should_capture classes package_opt source_basename) + (select (should_capture classes package_opt source_basename) (create_icfg source_file linereader program icfg)) - (JClasspath.get_classmap program) in + (JClasspath.get_classmap program) + in (icfg.JContext.cg, icfg.JContext.cfg) let compute_class_icfg source_file linereader program tenv node = let icfg = - { JContext.cg = Cg.create source_file; - JContext.cfg = Cfg.create_cfg (); - JContext.tenv = tenv } in - begin - try - create_icfg source_file linereader program icfg (Javalib.get_name node) node - with - | Bir.Subroutine -> () - | e -> raise e - end; + {JContext.cg= Cg.create source_file; JContext.cfg= Cfg.create_cfg (); JContext.tenv= tenv} + in + ( try create_icfg source_file linereader program icfg (Javalib.get_name node) node with + | Bir.Subroutine + -> () + | e + -> raise e ) ; (icfg.JContext.cg, icfg.JContext.cfg) diff --git a/infer/src/java/jFrontend.mli b/infer/src/java/jFrontend.mli index 8734c1752..53f6392dd 100644 --- a/infer/src/java/jFrontend.mli +++ b/infer/src/java/jFrontend.mli @@ -9,34 +9,23 @@ *) open! IStd - open Javalib_pack -(** [path_of_cached_classname cn] returns the path of a cached classname *) val path_of_cached_classname : JBasics.class_name -> string +(** [path_of_cached_classname cn] returns the path of a cached classname *) -(** [cache_classname cn] stores the classname to the disk *) val cache_classname : JBasics.class_name -> unit +(** [cache_classname cn] stores the classname to the disk *) -(** [is_classname_cached cn] *) val is_classname_cached : JBasics.class_name -> bool +(** [is_classname_cached cn] *) -(** [compute_icfg linereader classes program tenv source_basename source_file] create the call graph and control flow graph for the file [source_file] by translating all the classes in [program] originating from [source_file] *) val compute_source_icfg : - Printer.LineReader.t -> - JBasics.ClassSet.t -> - JClasspath.program -> - Tenv.t -> - string -> - string option -> - SourceFile.t -> - Cg.t * Cfg.cfg + Printer.LineReader.t -> JBasics.ClassSet.t -> JClasspath.program -> Tenv.t -> string + -> string option -> SourceFile.t -> Cg.t * Cfg.cfg +(** [compute_icfg linereader classes program tenv source_basename source_file] create the call graph and control flow graph for the file [source_file] by translating all the classes in [program] originating from [source_file] *) -(** Compute the CFG for a class *) val compute_class_icfg : - SourceFile.t -> - Printer.LineReader.t -> - JClasspath.program -> - Tenv.t -> - JCode.jcode Javalib.interface_or_class -> - Cg.t * Cfg.cfg + SourceFile.t -> Printer.LineReader.t -> JClasspath.program -> Tenv.t + -> JCode.jcode Javalib.interface_or_class -> Cg.t * Cfg.cfg +(** Compute the CFG for a class *) diff --git a/infer/src/java/jMain.ml b/infer/src/java/jMain.ml index 7606a8371..1f2726e1c 100644 --- a/infer/src/java/jMain.ml +++ b/infer/src/java/jMain.ml @@ -10,101 +10,85 @@ open! IStd open! PVariant - open Javalib_pack - module L = Logging - let register_perf_stats_report source_file = let stats_dir = Filename.concat Config.results_dir Config.frontend_stats_dir_name in let abbrev_source_file = DB.source_file_encoding source_file in let stats_file = Config.perf_stats_prefix ^ "_" ^ abbrev_source_file ^ ".json" in PerfStats.register_report_at_exit (Filename.concat stats_dir stats_file) - let init_global_state source_file = register_perf_stats_report source_file ; - Config.curr_language := Config.Java; - DB.Results_dir.init source_file; - Ident.NameGenerator.reset (); + Config.curr_language := Config.Java ; + DB.Results_dir.init source_file ; + Ident.NameGenerator.reset () ; JContext.reset_exn_node_table () - let store_icfg source_file tenv cg cfg = let source_dir = DB.source_dir_from_source_file source_file in let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in - if Config.create_harness then Harness.create_harness cfg cg tenv; - Cg.store_to_file cg_file cg; - Cfg.store_cfg_to_file ~source_file cfg_file cfg; - if Config.debug_mode || Config.frontend_tests then - begin - Dotty.print_icfg_dotty source_file cfg; - Cg.save_call_graph_dotty source_file cg - end; + if Config.create_harness then Harness.create_harness cfg cg tenv ; + Cg.store_to_file cg_file cg ; + Cfg.store_cfg_to_file ~source_file cfg_file cfg ; + if Config.debug_mode || Config.frontend_tests then ( + Dotty.print_icfg_dotty source_file cfg ; Cg.save_call_graph_dotty source_file cg ) ; (* NOTE: nothing should be written to source_dir after this *) DB.mark_file_updated (DB.source_dir_to_string source_dir) - (* Given a source file, its code is translated, and the call-graph, control-flow-graph and type *) (* environment are obtained and saved. *) -let do_source_file - linereader classes program tenv - source_basename package_opt source_file = - L.(debug Capture Medium) "@\nfilename: %a (%s)@." SourceFile.pp source_file source_basename; +let do_source_file linereader classes program tenv source_basename package_opt source_file = + L.(debug Capture Medium) "@\nfilename: %a (%s)@." SourceFile.pp source_file source_basename ; let call_graph, cfg = - JFrontend.compute_source_icfg - linereader classes program tenv - source_basename package_opt source_file in + JFrontend.compute_source_icfg linereader classes program tenv source_basename package_opt + source_file + in store_icfg source_file tenv call_graph cfg - let capture_libs linereader program tenv = let capture_class tenv cn node = match node with - | Javalib.JInterface _ -> () - | Javalib.JClass _ when JFrontend.is_classname_cached cn -> () - | Javalib.JClass _ -> - begin - let fake_source_file = - SourceFile.from_abs_path (JFrontend.path_of_cached_classname cn) in - init_global_state fake_source_file; - let call_graph, cfg = - JFrontend.compute_class_icfg fake_source_file linereader program tenv node in - store_icfg fake_source_file tenv call_graph cfg; - JFrontend.cache_classname cn; - end in + | Javalib.JInterface _ + -> () + | Javalib.JClass _ when JFrontend.is_classname_cached cn + -> () + | Javalib.JClass _ + -> let fake_source_file = SourceFile.from_abs_path (JFrontend.path_of_cached_classname cn) in + init_global_state fake_source_file ; + let call_graph, cfg = + JFrontend.compute_class_icfg fake_source_file linereader program tenv node + in + store_icfg fake_source_file tenv call_graph cfg ; JFrontend.cache_classname cn + in JBasics.ClassMap.iter (capture_class tenv) (JClasspath.get_classmap program) - (* load a stored global tenv if the file is found, and create a new one otherwise *) let load_tenv () = match Tenv.load_from_file DB.global_tenv_fname with - | None -> - Tenv.create () - | Some _ when Config.models_mode -> - failwithf - "Unexpected tenv file %s found while generating the models" + | None + -> Tenv.create () + | Some _ when Config.models_mode + -> failwithf "Unexpected tenv file %s found while generating the models" (DB.filename_to_string DB.global_tenv_fname) - | Some tenv -> - tenv - + | Some tenv + -> tenv (* Store to a file the type environment containing all the types required to perform the analysis *) let save_tenv tenv = - if not Config.models_mode then JTransType.add_models_types tenv; + if not Config.models_mode then JTransType.add_models_types tenv ; (* TODO: this prevents per compilation step incremental analysis at this stage *) - if DB.file_exists DB.global_tenv_fname then DB.file_remove DB.global_tenv_fname; - L.(debug Capture Medium) "writing new tenv %s@." (DB.filename_to_string DB.global_tenv_fname); + if DB.file_exists DB.global_tenv_fname then DB.file_remove DB.global_tenv_fname ; + L.(debug Capture Medium) "writing new tenv %s@." (DB.filename_to_string DB.global_tenv_fname) ; Tenv.store_to_file DB.global_tenv_fname tenv - (* The program is loaded and translated *) let do_all_files classpath sources classes = - L.(debug Capture Quiet) "Translating %d source files (%d classes)@." - (String.Map.length sources) - (JBasics.ClassSet.cardinal classes); + L.(debug Capture Quiet) + "Translating %d source files (%d classes)@." (String.Map.length sources) + (JBasics.ClassSet.cardinal classes) ; let program = JClasspath.load_program classpath classes in let tenv = load_tenv () in let linereader = Printer.LineReader.create () in @@ -112,52 +96,54 @@ let do_all_files classpath sources classes = let is_path_matching path = List.exists ~f:(fun pattern -> Str.string_match (Str.regexp pattern) path 0) - Config.skip_analysis_in_path in + Config.skip_analysis_in_path + in is_path_matching (SourceFile.to_rel_path source_file) - || Inferconfig.skip_translation_matcher source_file Typ.Procname.empty_block in + || Inferconfig.skip_translation_matcher source_file Typ.Procname.empty_block + in let translate_source_file basename (package_opt, _) source_file = - init_global_state source_file; + init_global_state source_file ; if not (skip source_file) then - do_source_file linereader classes program tenv basename package_opt source_file in + do_source_file linereader classes program tenv basename package_opt source_file + in String.Map.iteri ~f:(fun ~key:basename ~data:file_entry -> - match file_entry with - | JClasspath.Singleton source_file -> - translate_source_file basename (None, source_file) source_file - | JClasspath.Duplicate source_files -> - List.iter - ~f:(fun (package, source_file) -> - translate_source_file basename (Some package, source_file) source_file) - source_files) - sources; - if Config.dependency_mode then - capture_libs linereader program tenv; - save_tenv tenv; - JClasspath.cleanup program; + match file_entry with + | JClasspath.Singleton source_file + -> translate_source_file basename (None, source_file) source_file + | JClasspath.Duplicate source_files + -> List.iter + ~f:(fun (package, source_file) -> + translate_source_file basename (Some package, source_file) source_file) + source_files) + sources ; + if Config.dependency_mode then capture_libs linereader program tenv ; + save_tenv tenv ; + JClasspath.cleanup program ; L.(debug Capture Quiet) "done capturing all files@." (* loads the source files and translates them *) let main load_sources_and_classes = - (match Config.models_mode, Sys.file_exists Config.models_jar = `Yes with - | true, false -> - () - | false, false -> - failwith "Java model file is required" - | true, true -> - failwith "Not expecting model file when analyzing the models" - | false, true -> - JClasspath.add_models Config.models_jar - ); - JBasics.set_permissive true; - let classpath, sources, classes = match load_sources_and_classes with - | `FromVerboseOut verbose_out_file -> - JClasspath.load_from_verbose_output verbose_out_file - | `FromArguments path -> - JClasspath.load_from_arguments path in - if String.Map.is_empty sources then - failwith "Failed to load any Java source code" - else - do_all_files classpath sources classes + ( match (Config.models_mode, Sys.file_exists Config.models_jar = `Yes) with + | true, false + -> () + | false, false + -> failwith "Java model file is required" + | true, true + -> failwith "Not expecting model file when analyzing the models" + | false, true + -> JClasspath.add_models Config.models_jar ) ; + JBasics.set_permissive true ; + let classpath, sources, classes = + match load_sources_and_classes with + | `FromVerboseOut verbose_out_file + -> JClasspath.load_from_verbose_output verbose_out_file + | `FromArguments path + -> JClasspath.load_from_arguments path + in + if String.Map.is_empty sources then failwith "Failed to load any Java source code" + else do_all_files classpath sources classes let from_arguments path = main (`FromArguments path) + let from_verbose_out verbose_out_file = main (`FromVerboseOut verbose_out_file) diff --git a/infer/src/java/jMain.mli b/infer/src/java/jMain.mli index bd747c24e..baea857d5 100644 --- a/infer/src/java/jMain.mli +++ b/infer/src/java/jMain.mli @@ -10,8 +10,8 @@ open! IStd -(** loads the source files from command line arguments and translates them *) val from_arguments : string -> unit +(** loads the source files from command line arguments and translates them *) -(** loads the source files from javac's verbose output translates them *) val from_verbose_out : string -> unit +(** loads the source files from javac's verbose output translates them *) diff --git a/infer/src/java/jTrans.ml b/infer/src/java/jTrans.ml index 1f1001aea..581ba2aef 100644 --- a/infer/src/java/jTrans.ml +++ b/infer/src/java/jTrans.ml @@ -9,17 +9,11 @@ *) open! IStd - open Javalib_pack open Sawja_pack - module L = Logging -type invoke_kind = - | I_Virtual - | I_Interface - | I_Special - | I_Static +type invoke_kind = I_Virtual | I_Interface | I_Special | I_Static exception Frontend_error of string @@ -31,108 +25,108 @@ let init_loc_map : Location.t JBasics.ClassMap.t ref = ref JBasics.ClassMap.empt Since Sawja often reports a method off by a few lines, we search backwards for a line where the method name is. *) let fix_method_definition_line linereader proc_name loc = - let proc_name_java = - match proc_name with - | Typ.Procname.Java p -> p - | _ -> assert false in + let proc_name_java = match proc_name with Typ.Procname.Java p -> p | _ -> assert false in let method_name = if Typ.Procname.is_constructor proc_name then let inner_class_name cname = - match String.rsplit2 cname ~on:'$' with Some (_, icn) -> icn | None -> cname in + match String.rsplit2 cname ~on:'$' with Some (_, icn) -> icn | None -> cname + in inner_class_name (Typ.Procname.java_get_simple_class_name proc_name_java) - else Typ.Procname.java_get_method proc_name_java in + else Typ.Procname.java_get_method proc_name_java + in let regex = Str.regexp (Str.quote method_name) in let method_is_defined_here linenum = match Printer.LineReader.from_file_linenum_original linereader loc.Location.file linenum with - | None -> raise Not_found + | None + -> raise Not_found | Some line -> - (try ignore (Str.search_forward regex line 0); true - with Not_found -> false) in + try + ignore (Str.search_forward regex line 0) ; + true + with Not_found -> false + in let line = ref loc.Location.line in try while not (method_is_defined_here !line) do - line := !line -1; + line := !line - 1 ; if !line < 0 then raise Not_found - done; - { loc with Location.line = !line } + done ; + {loc with Location.line= !line} with Not_found -> loc let get_location source_file impl pc = let line_number = let ln = try JBir.get_source_line_number pc impl - with Invalid_argument _ -> None in - match ln with - | None -> 0 - | Some n -> n in - { Location.line = line_number; - col = -1; - file = source_file } + with Invalid_argument _ -> None + in + match ln with None -> 0 | Some n -> n + in + {Location.line= line_number; col= -1; file= source_file} let get_undefined_method_call ovt = let get_undefined_method ovt = match ovt with - | None -> JConfig.void^"_undefined" + | None + -> JConfig.void ^ "_undefined" | Some vt -> - match vt with - | JBasics.TBasic bt -> (JTransType.string_of_basic_type bt)^"_undefined" - | JBasics.TObject ot -> - begin - match ot with - | JBasics.TArray _ -> assert false - | JBasics.TClass cn -> - if String.equal (JBasics.cn_name cn) JConfig.string_cl then - "string_undefined" - else - if JBasics.cn_equal cn JBasics.java_lang_object then - "object_undefined" - else assert false - end in + match vt with + | JBasics.TBasic bt + -> JTransType.string_of_basic_type bt ^ "_undefined" + | JBasics.TObject ot -> + match ot with + | JBasics.TArray _ + -> assert false + | JBasics.TClass cn + -> if String.equal (JBasics.cn_name cn) JConfig.string_cl then "string_undefined" + else if JBasics.cn_equal cn JBasics.java_lang_object then "object_undefined" + else assert false + in let undef_cn = JBasics.make_cn JConfig.infer_undefined_cl in let undef_name = get_undefined_method ovt in let undef_ms = JBasics.make_ms undef_name [] ovt in (undef_cn, undef_ms) - let retrieve_fieldname fieldname = try let subs = Str.split (Str.regexp (Str.quote ".")) (Typ.Fieldname.to_string fieldname) in - if Int.equal (List.length subs) 0 then - assert false - else - List.last_exn subs + if Int.equal (List.length subs) 0 then assert false else List.last_exn subs with _ -> assert false - let get_field_name program static tenv cn fs = - let { Typ.Struct.fields; statics; } = JTransType.get_class_struct_typ program tenv cn in + let {Typ.Struct.fields; statics} = JTransType.get_class_struct_typ program tenv cn in match List.find - ~f:(fun (fieldname, _, _) -> String.equal (retrieve_fieldname fieldname) (JBasics.fs_name fs)) + ~f:(fun (fieldname, _, _) -> + String.equal (retrieve_fieldname fieldname) (JBasics.fs_name fs)) (if static then statics else fields) with - | Some (fieldname, _, _) -> - fieldname - | None -> - (* TODO: understand why fields cannot be found here *) - L.internal_error "cannot find %s.%s@." (JBasics.cn_name cn) (JBasics.fs_name fs); + | Some (fieldname, _, _) + -> fieldname + | None + -> (* TODO: understand why fields cannot be found here *) + L.internal_error "cannot find %s.%s@." (JBasics.cn_name cn) (JBasics.fs_name fs) ; raise (Frontend_error "Cannot find fieldname") - let formals_from_signature program tenv cn ms kind = let counter = ref 0 in let method_name = JBasics.ms_name ms in let get_arg_name () = - let arg = method_name^"_arg_"^(string_of_int !counter) in - incr counter; - Mangled.from_string arg in + let arg = method_name ^ "_arg_" ^ string_of_int !counter in + incr counter ; Mangled.from_string arg + in let collect l vt = let arg_name = get_arg_name () in let arg_type = JTransType.value_type program tenv vt in - (arg_name, arg_type):: l in - let init_arg_list = match kind with - | Typ.Procname.Static -> [] - | Typ.Procname.Non_Static -> [(JConfig.this, JTransType.get_class_type program tenv cn)] in + (arg_name, arg_type) :: l + in + let init_arg_list = + match kind with + | Typ.Procname.Static + -> [] + | Typ.Procname.Non_Static + -> [(JConfig.this, JTransType.get_class_type program tenv cn)] + in List.rev (List.fold ~f:collect ~init:init_arg_list (JBasics.ms_args ms)) (** Creates the list of formal variables from a procedure based on ... *) @@ -140,105 +134,130 @@ let translate_formals program tenv cn impl = let collect l (vt, var) = let name = Mangled.from_string (JBir.var_name_g var) in let typ = JTransType.param_type program tenv cn var vt in - (name, typ):: l in + (name, typ) :: l + in List.rev (List.fold ~f:collect ~init:[] (JBir.params impl)) (** Creates the list of local variables from the bytecode and add the variables from the JBir representation *) let translate_locals program tenv formals bytecode jbir_code = let formal_set = - List.fold - ~f:(fun set (var, _) -> Mangled.Set.add var set) - ~init:Mangled.Set.empty - formals in + List.fold ~f:(fun set (var, _) -> Mangled.Set.add var set) ~init:Mangled.Set.empty formals + in let collect (seen_vars, l) (var, typ) = - if Mangled.Set.mem var seen_vars then - (seen_vars, l) - else - (Mangled.Set.add var seen_vars, (var, typ) :: l) in + if Mangled.Set.mem var seen_vars then (seen_vars, l) + else (Mangled.Set.add var seen_vars, (var, typ) :: l) + in let with_bytecode_vars = (* Do not consider parameters as local variables *) let init = (formal_set, []) in match bytecode.JCode.c_local_variable_table with - | None -> init - | Some variable_table -> - List.fold + | None + -> init + | Some variable_table + -> List.fold ~f:(fun accu (_, _, var_name, var_type, _) -> - let var = Mangled.from_string var_name - and typ = JTransType.value_type program tenv var_type in - collect accu (var, typ)) - ~init - variable_table in + let var = Mangled.from_string var_name + and typ = JTransType.value_type program tenv var_type in + collect accu (var, typ)) + ~init variable_table + in (* TODO (#4040807): Needs to add the JBir temporary variables since other parts of the code are still relying on those *) let with_jbir_vars = Array.fold ~f:(fun accu jbir_var -> - let var = Mangled.from_string (JBir.var_name_g jbir_var) in - collect accu (var, Typ.mk Tvoid)) - ~init:with_bytecode_vars - (JBir.vars jbir_code) in + let var = Mangled.from_string (JBir.var_name_g jbir_var) in + collect accu (var, Typ.mk Tvoid)) + ~init:with_bytecode_vars (JBir.vars jbir_code) + in snd with_jbir_vars -let get_constant (c : JBir.const) = +let get_constant (c: JBir.const) = match c with - | `Int i -> Const.Cint (IntLit.of_int32 i) - | `ANull -> Const.Cint IntLit.null - | `Class ot -> Const.Cclass (Ident.string_to_name (JTransType.object_type_to_string ot)) - | `Double f -> Const.Cfloat f - | `Float f -> Const.Cfloat f - | `Long i64 -> Const.Cint (IntLit.of_int64 i64) - | `String jstr -> Const.Cstr (JBasics.jstr_pp jstr) + | `Int i + -> Const.Cint (IntLit.of_int32 i) + | `ANull + -> Const.Cint IntLit.null + | `Class ot + -> Const.Cclass (Ident.string_to_name (JTransType.object_type_to_string ot)) + | `Double f + -> Const.Cfloat f + | `Float f + -> Const.Cfloat f + | `Long i64 + -> Const.Cint (IntLit.of_int64 i64) + | `String jstr + -> Const.Cstr (JBasics.jstr_pp jstr) let get_binop binop = match binop with - | JBir.Add _ -> Binop.PlusA - | JBir.Sub _ -> Binop.MinusA - | JBir.Mult _ -> Binop.Mult - | JBir.Div _ -> Binop.Div - | JBir.Rem _ -> Binop.Mod - | JBir.IAnd -> Binop.BAnd - | JBir.IShl -> Binop.Shiftlt - | JBir.IShr -> Binop.Shiftrt - | JBir.IOr -> Binop.BOr - | JBir.IXor -> Binop.BXor - | JBir.IUshr -> - raise (Frontend_error "Unsigned right shift operator") - | JBir.LShl -> Binop.Shiftlt - | JBir.LShr -> Binop.Shiftrt - | JBir.LAnd -> Binop.BAnd - | JBir.LOr -> Binop.BOr - | JBir.LXor -> Binop.BXor - | JBir.LUshr -> - raise (Frontend_error "Unsigned right shift operator") - | JBir.CMP _ -> - raise (Frontend_error "Unsigned right shift operator") - | JBir.ArrayLoad _ -> - raise (Frontend_error "Array load operator") + | JBir.Add _ + -> Binop.PlusA + | JBir.Sub _ + -> Binop.MinusA + | JBir.Mult _ + -> Binop.Mult + | JBir.Div _ + -> Binop.Div + | JBir.Rem _ + -> Binop.Mod + | JBir.IAnd + -> Binop.BAnd + | JBir.IShl + -> Binop.Shiftlt + | JBir.IShr + -> Binop.Shiftrt + | JBir.IOr + -> Binop.BOr + | JBir.IXor + -> Binop.BXor + | JBir.IUshr + -> raise (Frontend_error "Unsigned right shift operator") + | JBir.LShl + -> Binop.Shiftlt + | JBir.LShr + -> Binop.Shiftrt + | JBir.LAnd + -> Binop.BAnd + | JBir.LOr + -> Binop.BOr + | JBir.LXor + -> Binop.BXor + | JBir.LUshr + -> raise (Frontend_error "Unsigned right shift operator") + | JBir.CMP _ + -> raise (Frontend_error "Unsigned right shift operator") + | JBir.ArrayLoad _ + -> raise (Frontend_error "Array load operator") let get_test_operator op = match op with - | `Eq -> Binop.Eq - | `Ge -> Binop.Ge - | `Gt -> Binop.Gt - | `Le -> Binop.Le - | `Lt -> Binop.Lt - | `Ne -> Binop.Ne + | `Eq + -> Binop.Eq + | `Ge + -> Binop.Ge + | `Gt + -> Binop.Gt + | `Le + -> Binop.Le + | `Lt + -> Binop.Lt + | `Ne + -> Binop.Ne -let is_java_native cm = - Poly.(=) cm.Javalib.cm_implementation Javalib.Native +let is_java_native cm = Poly.( = ) cm.Javalib.cm_implementation Javalib.Native -let is_clone ms = - String.equal (JBasics.ms_name ms) JConfig.clone_name +let is_clone ms = String.equal (JBasics.ms_name ms) JConfig.clone_name let get_implementation cm = match cm.Javalib.cm_implementation with - | Javalib.Native -> - let cms = cm.Javalib.cm_class_method_signature in + | Javalib.Native + -> let cms = cm.Javalib.cm_class_method_signature in let cn, ms = JBasics.cms_split cms in failwithf "native method %s found in %s@." (JBasics.ms_name ms) (JBasics.cn_name cn) - | Javalib.Java t -> - (* Sawja doesn't handle invokedynamic, and it will crash with a Match_failure if we give it + | Javalib.Java t + -> (* Sawja doesn't handle invokedynamic, and it will crash with a Match_failure if we give it bytecode with this instruction. hack around this problem by converting all invokedynamic's to invokestatic's that call a method with the same signature as the lambda on java.lang.Object. this isn't great, but it's a lot better than crashing *) @@ -246,65 +265,69 @@ let get_implementation cm = let c_code = Array.map ~f:(function - | (JCode.OpInvoke (`Dynamic _, ms)) -> - JCode.OpInvoke (`Static JBasics.java_lang_object, ms) - | opcode -> - opcode) - bytecode.JCode.c_code in - let hacked_bytecode = { bytecode with JCode.c_code; } in + | JCode.OpInvoke (`Dynamic _, ms) + -> JCode.OpInvoke (`Static JBasics.java_lang_object, ms) + | opcode + -> opcode) + bytecode.JCode.c_code + in + let hacked_bytecode = {bytecode with JCode.c_code= c_code} in let jbir_code = - JBir.transform - ~bcv: false ~ch_link: false ~formula: false ~formula_cmd:[] cm hacked_bytecode in + JBir.transform ~bcv:false ~ch_link:false ~formula:false ~formula_cmd:[] cm hacked_bytecode + in (hacked_bytecode, jbir_code) let update_constr_loc cn ms loc_start = if String.equal (JBasics.ms_name ms) JConfig.constructor_name then - try ignore(JBasics.ClassMap.find cn !constr_loc_map) - with Not_found -> constr_loc_map := (JBasics.ClassMap.add cn loc_start !constr_loc_map) + try ignore (JBasics.ClassMap.find cn !constr_loc_map) + with Not_found -> constr_loc_map := JBasics.ClassMap.add cn loc_start !constr_loc_map let update_init_loc cn ms loc_start = if JBasics.ms_equal ms JBasics.clinit_signature then - try ignore(JBasics.ClassMap.find cn !init_loc_map) - with Not_found -> init_loc_map := (JBasics.ClassMap.add cn loc_start !init_loc_map) + try ignore (JBasics.ClassMap.find cn !init_loc_map) + with Not_found -> init_loc_map := JBasics.ClassMap.add cn loc_start !init_loc_map let trans_access = function - | `Default -> PredSymb.Default - | `Public -> PredSymb.Public - | `Private -> PredSymb.Private - | `Protected -> PredSymb.Protected + | `Default + -> PredSymb.Default + | `Public + -> PredSymb.Public + | `Private + -> PredSymb.Private + | `Protected + -> PredSymb.Protected let create_am_procdesc source_file program icfg am proc_name : Procdesc.t = let cfg = icfg.JContext.cfg in let tenv = icfg.JContext.tenv in let m = Javalib.AbstractMethod am in let cn, ms = JBasics.cms_split (Javalib.get_class_method_signature m) in - let formals = - formals_from_signature program tenv cn ms (JTransType.get_method_kind m) in - let method_annotation = - JAnnotation.translate_method am.Javalib.am_annotations in + let formals = formals_from_signature program tenv cn ms (JTransType.get_method_kind m) in + let method_annotation = JAnnotation.translate_method am.Javalib.am_annotations in let procdesc = let proc_attributes = { (ProcAttributes.default proc_name Config.Java) with - ProcAttributes.access = trans_access am.Javalib.am_access; - exceptions = List.map ~f:JBasics.cn_name am.Javalib.am_exceptions; - formals; - is_abstract = true; - is_bridge_method = am.Javalib.am_bridge; - is_defined = true; - is_model = Config.models_mode; - is_synthetic_method = am.Javalib.am_synthetic; - method_annotation; - ret_type = JTransType.return_type program tenv ms; - loc = Location.none source_file; - } in - Cfg.create_proc_desc cfg proc_attributes in + ProcAttributes.access= trans_access am.Javalib.am_access + ; exceptions= List.map ~f:JBasics.cn_name am.Javalib.am_exceptions + ; formals + ; is_abstract= true + ; is_bridge_method= am.Javalib.am_bridge + ; is_defined= true + ; is_model= Config.models_mode + ; is_synthetic_method= am.Javalib.am_synthetic + ; method_annotation + ; ret_type= JTransType.return_type program tenv ms + ; loc= Location.none source_file } + in + Cfg.create_proc_desc cfg proc_attributes + in let start_kind = Procdesc.Node.Start_node proc_name in let start_node = Procdesc.create_node procdesc (Location.none source_file) start_kind [] in - let exit_kind = (Procdesc.Node.Exit_node proc_name) in + let exit_kind = Procdesc.Node.Exit_node proc_name in let exit_node = Procdesc.create_node procdesc (Location.none source_file) exit_kind [] in - Procdesc.node_set_succs_exn procdesc start_node [exit_node] [exit_node]; - Procdesc.set_start_node procdesc start_node; - Procdesc.set_exit_node procdesc exit_node; + Procdesc.node_set_succs_exn procdesc start_node [exit_node] [exit_node] ; + Procdesc.set_start_node procdesc start_node ; + Procdesc.set_exit_node procdesc exit_node ; procdesc let create_native_procdesc source_file program icfg cm proc_name = @@ -312,22 +335,20 @@ let create_native_procdesc source_file program icfg cm proc_name = let tenv = icfg.JContext.tenv in let m = Javalib.ConcreteMethod cm in let cn, ms = JBasics.cms_split (Javalib.get_class_method_signature m) in - let formals = - formals_from_signature program tenv cn ms (JTransType.get_method_kind m) in - let method_annotation = - JAnnotation.translate_method cm.Javalib.cm_annotations in + let formals = formals_from_signature program tenv cn ms (JTransType.get_method_kind m) in + let method_annotation = JAnnotation.translate_method cm.Javalib.cm_annotations in let proc_attributes = { (ProcAttributes.default proc_name Config.Java) with - ProcAttributes.access = trans_access cm.Javalib.cm_access; - exceptions = List.map ~f:JBasics.cn_name cm.Javalib.cm_exceptions; - formals; - is_bridge_method = cm.Javalib.cm_bridge; - is_model = Config.models_mode; - is_synthetic_method = cm.Javalib.cm_synthetic; - method_annotation; - ret_type = JTransType.return_type program tenv ms; - loc = Location.none source_file; - } in + ProcAttributes.access= trans_access cm.Javalib.cm_access + ; exceptions= List.map ~f:JBasics.cn_name cm.Javalib.cm_exceptions + ; formals + ; is_bridge_method= cm.Javalib.cm_bridge + ; is_model= Config.models_mode + ; is_synthetic_method= cm.Javalib.cm_synthetic + ; method_annotation + ; ret_type= JTransType.return_type program tenv ms + ; loc= Location.none source_file } + in Cfg.create_proc_desc cfg proc_attributes (** Creates a procedure description. *) @@ -343,60 +364,55 @@ let create_cm_procdesc source_file program linereader icfg cm proc_name = let locals = translate_locals program tenv formals bytecode jbir_code in let loc_start = let loc = get_location source_file jbir_code 0 in - fix_method_definition_line linereader proc_name loc in - let loc_exit = - get_location source_file jbir_code (Array.length (JBir.code jbir_code) - 1) in - let method_annotation = - JAnnotation.translate_method cm.Javalib.cm_annotations in - update_constr_loc cn ms loc_start; - update_init_loc cn ms loc_exit; + fix_method_definition_line linereader proc_name loc + in + let loc_exit = get_location source_file jbir_code (Array.length (JBir.code jbir_code) - 1) in + let method_annotation = JAnnotation.translate_method cm.Javalib.cm_annotations in + update_constr_loc cn ms loc_start ; + update_init_loc cn ms loc_exit ; let proc_attributes = { (ProcAttributes.default proc_name Config.Java) with - ProcAttributes.access = trans_access cm.Javalib.cm_access; - exceptions = List.map ~f:JBasics.cn_name cm.Javalib.cm_exceptions; - formals; - is_bridge_method = cm.Javalib.cm_bridge; - is_defined = true; - is_model = Config.models_mode; - is_synthetic_method = cm.Javalib.cm_synthetic; - is_java_synchronized_method = cm.Javalib.cm_synchronized; - loc = loc_start; - locals; - method_annotation; - ret_type = JTransType.return_type program tenv ms; - } in - let procdesc = - Cfg.create_proc_desc cfg proc_attributes in + ProcAttributes.access= trans_access cm.Javalib.cm_access + ; exceptions= List.map ~f:JBasics.cn_name cm.Javalib.cm_exceptions + ; formals + ; is_bridge_method= cm.Javalib.cm_bridge + ; is_defined= true + ; is_model= Config.models_mode + ; is_synthetic_method= cm.Javalib.cm_synthetic + ; is_java_synchronized_method= cm.Javalib.cm_synchronized + ; loc= loc_start + ; locals + ; method_annotation + ; ret_type= JTransType.return_type program tenv ms } + in + let procdesc = Cfg.create_proc_desc cfg proc_attributes in let start_kind = Procdesc.Node.Start_node proc_name in let start_node = Procdesc.create_node procdesc loc_start start_kind [] in - let exit_kind = (Procdesc.Node.Exit_node proc_name) in + let exit_kind = Procdesc.Node.Exit_node proc_name in let exit_node = Procdesc.create_node procdesc loc_exit exit_kind [] in let exn_kind = Procdesc.Node.exn_sink_kind in let exn_node = Procdesc.create_node procdesc loc_exit exn_kind [] in - JContext.add_exn_node proc_name exn_node; - Procdesc.set_start_node procdesc start_node; - Procdesc.set_exit_node procdesc exit_node; - Procdesc.Node.add_locals_ret_declaration start_node proc_attributes locals; - procdesc in + JContext.add_exn_node proc_name exn_node ; + Procdesc.set_start_node procdesc start_node ; + Procdesc.set_exit_node procdesc exit_node ; + Procdesc.Node.add_locals_ret_declaration start_node proc_attributes locals ; + procdesc + in Some (procdesc, bytecode, jbir_code) - with JBir.Subroutine -> - L.internal_error - "create_procdesc raised JBir.Subroutine on %a@." - Typ.Procname.pp proc_name; + with JBir.Subroutine -> + L.internal_error "create_procdesc raised JBir.Subroutine on %a@." Typ.Procname.pp proc_name ; None -let builtin_new = - Exp.Const (Const.Cfun BuiltinDecl.__new) +let builtin_new = Exp.Const (Const.Cfun BuiltinDecl.__new) -let builtin_get_array_length = - Exp.Const (Const.Cfun BuiltinDecl.__get_array_length) +let builtin_get_array_length = Exp.Const (Const.Cfun BuiltinDecl.__get_array_length) let create_sil_deref exp typ loc = let no_id = Ident.create_none () in Sil.Load (no_id, exp, typ, loc) (** translate an expression used as an r-value *) -let rec expression (context : JContext.t) pc expr = +let rec expression (context: JContext.t) pc expr = let program = context.program in let loc = get_location context.source_file context.impl pc in let file = loc.Location.file in @@ -405,100 +421,108 @@ let rec expression (context : JContext.t) pc expr = let trans_var pvar = let id = Ident.create_fresh Ident.knormal in let sil_instr = Sil.Load (id, Exp.Lvar pvar, type_of_expr, loc) in - ([sil_instr], Exp.Var id, type_of_expr) in + ([sil_instr], Exp.Var id, type_of_expr) + in match expr with - | JBir.Var (_, var) -> - let pvar = (JContext.set_pvar context var type_of_expr) in + | JBir.Var (_, var) + -> let pvar = JContext.set_pvar context var type_of_expr in trans_var pvar - | JBir.Const c -> - begin - match c with (* We use the constant internally to mean a variable. *) - | `String s when String.equal (JBasics.jstr_pp s) JConfig.field_cst -> - let varname = JConfig.field_st in - let procname = (Procdesc.get_proc_name context.procdesc) in - let pvar = Pvar.mk varname procname in - trans_var pvar - | _ -> ([], Exp.Const (get_constant c), type_of_expr) - end - | JBir.Unop (unop, ex) -> + | JBir.Const c -> ( + match c with + (* We use the constant internally to mean a variable. *) + | `String s + when String.equal (JBasics.jstr_pp s) JConfig.field_cst + -> let varname = JConfig.field_st in + let procname = Procdesc.get_proc_name context.procdesc in + let pvar = Pvar.mk varname procname in + trans_var pvar + | _ + -> ([], Exp.Const (get_constant c), type_of_expr) ) + | JBir.Unop (unop, ex) + -> ( let type_of_ex = JTransType.expr_type context ex in - let (instrs, sil_ex, _) = expression context pc ex in - begin - match unop with - | JBir.Neg _ -> (instrs, Exp.UnOp (Unop.Neg, sil_ex, Some type_of_expr), type_of_expr) - | JBir.ArrayLength -> - let array_typ_no_ptr = - match type_of_ex.Typ.desc with - | Typ.Tptr (typ, _) -> typ - | _ -> type_of_ex in - let deref = create_sil_deref sil_ex array_typ_no_ptr loc in - let args = [(sil_ex, type_of_ex)] in - let ret_id = Ident.create_fresh Ident.knormal in - let ret_typ = Typ.mk (Tint IInt) in - let call_instr = - Sil.Call - (Some (ret_id, ret_typ), builtin_get_array_length, args, loc, CallFlags.default) in - (instrs @ [deref; call_instr], Exp.Var ret_id, type_of_expr) - | JBir.Conv conv -> - let cast_ex = Exp.Cast (JTransType.cast_type conv, sil_ex) in - (instrs, cast_ex, type_of_expr) - | JBir.InstanceOf ot | JBir.Cast ot -> - let subtypes = - (match unop with - | JBir.InstanceOf _ -> Subtype.subtypes_instof - | JBir.Cast _ -> Subtype.subtypes_cast - | _ -> assert false) in - let sizeof_expr = - JTransType.sizeof_of_object_type program tenv ot subtypes in - let builtin = - (match unop with - | JBir.InstanceOf _ -> Exp.Const (Const.Cfun BuiltinDecl.__instanceof) - | JBir.Cast _ -> Exp.Const (Const.Cfun BuiltinDecl.__cast) - | _ -> assert false) in - let args = [(sil_ex, type_of_ex); (sizeof_expr, Typ.mk Tvoid)] in - let ret_id = Ident.create_fresh Ident.knormal in - let call = - Sil.Call (Some (ret_id, Typ.mk (Tint IBool)), builtin, args, loc, CallFlags.default) in - let res_ex = Exp.Var ret_id in - (instrs @ [call], res_ex, type_of_expr) - end - | JBir.Binop (binop, ex1, ex2) -> - let (instrs1, sil_ex1, _) = expression context pc ex1 - and (instrs2, sil_ex2, _) = expression context pc ex2 in - begin - match binop with - | JBir.ArrayLoad _ -> - (* add an instruction that dereferences the array *) - let array_typ = Typ.mk (Tarray (type_of_expr, None, None)) in - let deref_array_instr = create_sil_deref sil_ex1 array_typ loc in - let id = Ident.create_fresh Ident.knormal in - let load_instr = - Sil.Load (id, Exp.Lindex (sil_ex1, sil_ex2), type_of_expr, loc) in - let instrs = (instrs1 @ (deref_array_instr :: instrs2)) @ [load_instr] in - instrs, Exp.Var id, type_of_expr - | other_binop -> - let sil_binop = get_binop other_binop in - let sil_expr = Exp.BinOp (sil_binop, sil_ex1, sil_ex2) in - ((instrs1 @ instrs2), sil_expr, type_of_expr) - end - | JBir.Field (ex, cn, fs) -> - let (instrs, sil_expr, _) = expression context pc ex in + let instrs, sil_ex, _ = expression context pc ex in + match unop with + | JBir.Neg _ + -> (instrs, Exp.UnOp (Unop.Neg, sil_ex, Some type_of_expr), type_of_expr) + | JBir.ArrayLength + -> let array_typ_no_ptr = + match type_of_ex.Typ.desc with Typ.Tptr (typ, _) -> typ | _ -> type_of_ex + in + let deref = create_sil_deref sil_ex array_typ_no_ptr loc in + let args = [(sil_ex, type_of_ex)] in + let ret_id = Ident.create_fresh Ident.knormal in + let ret_typ = Typ.mk (Tint IInt) in + let call_instr = + Sil.Call + (Some (ret_id, ret_typ), builtin_get_array_length, args, loc, CallFlags.default) + in + (instrs @ [deref; call_instr], Exp.Var ret_id, type_of_expr) + | JBir.Conv conv + -> let cast_ex = Exp.Cast (JTransType.cast_type conv, sil_ex) in + (instrs, cast_ex, type_of_expr) + | JBir.InstanceOf ot | JBir.Cast ot + -> let subtypes = + match unop with + | JBir.InstanceOf _ + -> Subtype.subtypes_instof + | JBir.Cast _ + -> Subtype.subtypes_cast + | _ + -> assert false + in + let sizeof_expr = JTransType.sizeof_of_object_type program tenv ot subtypes in + let builtin = + match unop with + | JBir.InstanceOf _ + -> Exp.Const (Const.Cfun BuiltinDecl.__instanceof) + | JBir.Cast _ + -> Exp.Const (Const.Cfun BuiltinDecl.__cast) + | _ + -> assert false + in + let args = [(sil_ex, type_of_ex); (sizeof_expr, Typ.mk Tvoid)] in + let ret_id = Ident.create_fresh Ident.knormal in + let call = + Sil.Call (Some (ret_id, Typ.mk (Tint IBool)), builtin, args, loc, CallFlags.default) + in + let res_ex = Exp.Var ret_id in + (instrs @ [call], res_ex, type_of_expr) ) + | JBir.Binop (binop, ex1, ex2) + -> ( + let instrs1, sil_ex1, _ = expression context pc ex1 + and instrs2, sil_ex2, _ = expression context pc ex2 in + match binop with + | JBir.ArrayLoad _ + -> (* add an instruction that dereferences the array *) + let array_typ = Typ.mk (Tarray (type_of_expr, None, None)) in + let deref_array_instr = create_sil_deref sil_ex1 array_typ loc in + let id = Ident.create_fresh Ident.knormal in + let load_instr = Sil.Load (id, Exp.Lindex (sil_ex1, sil_ex2), type_of_expr, loc) in + let instrs = (instrs1 @ deref_array_instr :: instrs2) @ [load_instr] in + (instrs, Exp.Var id, type_of_expr) + | other_binop + -> let sil_binop = get_binop other_binop in + let sil_expr = Exp.BinOp (sil_binop, sil_ex1, sil_ex2) in + (instrs1 @ instrs2, sil_expr, type_of_expr) ) + | JBir.Field (ex, cn, fs) + -> let instrs, sil_expr, _ = expression context pc ex in let field_name = get_field_name program false tenv cn fs in let sil_type = JTransType.get_class_type_no_pointer program tenv cn in let sil_expr = Exp.Lfield (sil_expr, field_name, sil_type) in let tmp_id = Ident.create_fresh Ident.knormal in let lderef_instr = Sil.Load (tmp_id, sil_expr, sil_type, loc) in (instrs @ [lderef_instr], Exp.Var tmp_id, type_of_expr) - | JBir.StaticField (cn, fs) -> - let class_exp = + | JBir.StaticField (cn, fs) + -> let class_exp = let classname = Mangled.from_string (JBasics.cn_name cn) in let var_name = Pvar.mk_global classname (Pvar.TUFile file) in - Exp.Lvar var_name in - let (instrs, sil_expr) = [], class_exp in + Exp.Lvar var_name + in + let instrs, sil_expr = ([], class_exp) in let field_name = get_field_name program true tenv cn fs in let sil_type = JTransType.get_class_type_no_pointer program tenv cn in - if JTransType.is_autogenerated_assert_field field_name - then + if JTransType.is_autogenerated_assert_field field_name then (* assume that reading from C.$assertionsDisabled always yields "false". this allows *) (* Infer to understand the assert keyword in the expected way *) (instrs, Exp.zero, type_of_expr) @@ -508,154 +532,171 @@ let rec expression (context : JContext.t) pc expr = let lderef_instr = Sil.Load (tmp_id, sil_expr, sil_type, loc) in (instrs @ [lderef_instr], Exp.Var tmp_id, type_of_expr) -let method_invocation - (context : JContext.t) loc pc var_opt cn ms sil_obj_opt expr_list invoke_code method_kind = +let method_invocation (context: JContext.t) loc pc var_opt cn ms sil_obj_opt expr_list invoke_code + method_kind = (* This function tries to recursively search for the classname of the class *) (* where the method is defined. It returns the classname given as argument*) (* when this classname cannot be found *) - let resolve_method (context : JContext.t) cn ms = + let resolve_method (context: JContext.t) cn ms = let rec loop fallback_cn cn = match JClasspath.lookup_node cn context.program with - | None -> fallback_cn - | Some node -> - if Javalib.defines_method node ms then cn + | None + -> fallback_cn + | Some node + -> if Javalib.defines_method node ms then cn else match node with - | Javalib.JInterface _ -> fallback_cn + | Javalib.JInterface _ + -> fallback_cn | Javalib.JClass jclass -> - begin - match jclass.Javalib.c_super_class with - | None -> fallback_cn - | Some super_cn -> loop fallback_cn super_cn - end in - loop cn cn in + match jclass.Javalib.c_super_class with + | None + -> fallback_cn + | Some super_cn + -> loop fallback_cn super_cn + in + loop cn cn + in let cn' = resolve_method context cn ms in let tenv = JContext.get_tenv context in let program = context.program in - let cf_virtual, cf_interface = match invoke_code with - | I_Virtual -> (true, false) - | I_Interface -> (true, true) - | _ -> (false, false) in - let call_flags = { CallFlags.default with cf_virtual; cf_interface; } in + let cf_virtual, cf_interface = + match invoke_code with + | I_Virtual + -> (true, false) + | I_Interface + -> (true, true) + | _ + -> (false, false) + in + let call_flags = {CallFlags.default with cf_virtual; cf_interface} in let init = match sil_obj_opt with - | None -> [], [] - | Some (sil_obj_expr, sil_obj_type) -> - (* for non-constructors, add an instruction that dereferences the receiver *) + | None + -> ([], []) + | Some (sil_obj_expr, sil_obj_type) + -> (* for non-constructors, add an instruction that dereferences the receiver *) let instrs = - let is_non_constructor_call = - match invoke_code with - | I_Special -> false - | _ -> true in + let is_non_constructor_call = match invoke_code with I_Special -> false | _ -> true in match sil_obj_expr with - | Exp.Var _ when is_non_constructor_call && not Config.tracing -> - let obj_typ_no_ptr = - match sil_obj_type.Typ.desc with - | Typ.Tptr (typ, _) -> typ - | _ -> sil_obj_type in + | Exp.Var _ when is_non_constructor_call && not Config.tracing + -> let obj_typ_no_ptr = + match sil_obj_type.Typ.desc with Typ.Tptr (typ, _) -> typ | _ -> sil_obj_type + in [create_sil_deref sil_obj_expr obj_typ_no_ptr loc] - | _ -> [] in - (instrs, [(sil_obj_expr, sil_obj_type)]) in - let (instrs, call_args) = + | _ + -> [] + in + (instrs, [(sil_obj_expr, sil_obj_type)]) + in + let instrs, call_args = List.fold ~f:(fun (instrs_accu, args_accu) expr -> - let (instrs, sil_expr, sil_expr_type) = expression context pc expr in - (instrs_accu @ instrs, args_accu @ [(sil_expr, sil_expr_type)])) - ~init - expr_list in + let instrs, sil_expr, sil_expr_type = expression context pc expr in + (instrs_accu @ instrs, args_accu @ [(sil_expr, sil_expr_type)])) + ~init expr_list + in let callee_procname = let proc = Typ.Procname.from_string_c_fun (JBasics.ms_name ms) in - if JBasics.cn_equal cn' (JBasics.make_cn JConfig.infer_builtins_cl) && - BuiltinDecl.is_declared proc + if JBasics.cn_equal cn' (JBasics.make_cn JConfig.infer_builtins_cl) + && BuiltinDecl.is_declared proc then proc - else JTransType.get_method_procname cn' ms method_kind in + else JTransType.get_method_procname cn' ms method_kind + in let call_instrs = let callee_fun = Exp.Const (Const.Cfun callee_procname) in let return_type = match JBasics.ms_rtype ms with - | None -> Typ.mk Tvoid - | Some vt -> JTransType.value_type program tenv vt in + | None + -> Typ.mk Tvoid + | Some vt + -> JTransType.value_type program tenv vt + in let call_ret_instrs sil_var = let ret_id = Ident.create_fresh Ident.knormal in let call_instr = - Sil.Call (Some (ret_id, return_type), callee_fun, call_args, loc, call_flags) in + Sil.Call (Some (ret_id, return_type), callee_fun, call_args, loc, call_flags) + in let set_instr = Sil.Store (Exp.Lvar sil_var, return_type, Exp.Var ret_id, loc) in - (instrs @ [call_instr; set_instr]) in + instrs @ [call_instr; set_instr] + in match var_opt with - | None -> - let call_instr = Sil.Call (None, callee_fun, call_args, loc, call_flags) in + | None + -> let call_instr = Sil.Call (None, callee_fun, call_args, loc, call_flags) in instrs @ [call_instr] - | Some var -> - let sil_var = JContext.set_pvar context var return_type in - call_ret_instrs sil_var in + | Some var + -> let sil_var = JContext.set_pvar context var return_type in + call_ret_instrs sil_var + in let instrs = match call_args with (* modeling a class bypasses the treatment of Closeable *) - | _ when Config.models_mode || JClasspath.is_model callee_procname -> call_instrs - + | _ + when Config.models_mode || JClasspath.is_model callee_procname + -> call_instrs (* add a file attribute when calling the constructor of a subtype of Closeable *) - | (_, typ) as exp :: _ - when Typ.Procname.is_constructor callee_procname && JTransType.is_closeable program tenv typ -> - let set_file_attr = + | (_, typ as exp) :: _ + when Typ.Procname.is_constructor callee_procname && JTransType.is_closeable program tenv typ + -> let set_file_attr = let set_builtin = Exp.Const (Const.Cfun BuiltinDecl.__set_file_attribute) in - Sil.Call (None, set_builtin, [exp], loc, CallFlags.default) in + Sil.Call (None, set_builtin, [exp], loc, CallFlags.default) + in (* Exceptions thrown in the constructor should prevent adding the resource attribute *) call_instrs @ [set_file_attr] - (* remove file attribute when calling the close method of a subtype of Closeable *) - | exp :: [] when Typ.Procname.java_is_close callee_procname -> - let set_mem_attr = + | [exp] + when Typ.Procname.java_is_close callee_procname + -> let set_mem_attr = let set_builtin = Exp.Const (Const.Cfun BuiltinDecl.__set_mem_attribute) in - Sil.Call (None, set_builtin, [exp], loc, CallFlags.default) in + Sil.Call (None, set_builtin, [exp], loc, CallFlags.default) + in (* Exceptions thrown in the close method should not prevent the resource from being *) (* considered as closed *) [set_mem_attr] @ call_instrs - - | _ -> call_instrs in - + | _ + -> call_instrs + in (callee_procname, instrs) let get_array_length context pc expr_list content_type = let get_expr_instr expr other_instrs = - let (instrs, sil_len_expr, _) = expression context pc expr in - match other_instrs with - | (other_instrs, other_exprs) -> - (instrs @ other_instrs, sil_len_expr :: other_exprs) in - let (instrs, sil_len_exprs) = List.fold_right ~f:get_expr_instr expr_list ~init:([],[]) in + let instrs, sil_len_expr, _ = expression context pc expr in + match other_instrs + with other_instrs, other_exprs -> (instrs @ other_instrs, sil_len_expr :: other_exprs) + in + let instrs, sil_len_exprs = List.fold_right ~f:get_expr_instr expr_list ~init:([], []) in let get_array_type_len sil_len_expr (content_type, _) = - (Typ.mk (Tarray (content_type, None, None)), Some sil_len_expr) in + (Typ.mk (Tarray (content_type, None, None)), Some sil_len_expr) + in let array_type, array_len = - List.fold_right ~f:get_array_type_len sil_len_exprs ~init:(content_type, None) in - let array_size = Exp.Sizeof {typ=array_type; nbytes=None; - dynamic_length=array_len; subtype=Subtype.exact} in + List.fold_right ~f:get_array_type_len sil_len_exprs ~init:(content_type, None) + in + let array_size = + Exp.Sizeof {typ= array_type; nbytes= None; dynamic_length= array_len; subtype= Subtype.exact} + in (instrs, array_size) let detect_loop entry_pc impl = - let code = (JBir.code impl) in + let code = JBir.code impl in let pc_bound = Array.length code in let empty = Int.Set.empty in let rec loop visited pc = - if (Int.Set.mem visited pc) || pc >= pc_bound then - (false, visited) + if Int.Set.mem visited pc || pc >= pc_bound then (false, visited) else - begin - let visited_updated = Int.Set.add visited pc in - match code.(pc) with - | JBir.Goto goto_pc when Int.equal goto_pc entry_pc -> (true, empty) - | JBir.Goto goto_pc -> loop visited_updated goto_pc - | JBir.Ifd (_, if_pc) when Int.equal if_pc entry_pc -> (true, empty) - | JBir.Ifd (_, if_pc) -> - let (loop_detected, visited_after) = loop visited_updated (pc + 1) in - if loop_detected then - (true, empty) - else - loop visited_after if_pc - | _ -> - if Int.equal (pc + 1) entry_pc then - (true, empty) - else - loop visited_updated (pc + 1) - end in + let visited_updated = Int.Set.add visited pc in + match code.(pc) with + | JBir.Goto goto_pc when Int.equal goto_pc entry_pc + -> (true, empty) + | JBir.Goto goto_pc + -> loop visited_updated goto_pc + | JBir.Ifd (_, if_pc) when Int.equal if_pc entry_pc + -> (true, empty) + | JBir.Ifd (_, if_pc) + -> let loop_detected, visited_after = loop visited_updated (pc + 1) in + if loop_detected then (true, empty) else loop visited_after if_pc + | _ + -> if Int.equal (pc + 1) entry_pc then (true, empty) else loop visited_updated (pc + 1) + in fst (loop empty entry_pc) type translation = @@ -664,39 +705,35 @@ type translation = | Prune of Procdesc.Node.t * Procdesc.Node.t | Loop of Procdesc.Node.t * Procdesc.Node.t * Procdesc.Node.t - let instruction_array_call ms obj_type obj args var_opt = if is_clone ms then - (let cn = JBasics.make_cn JConfig.infer_array_cl in - let vt = (JBasics.TObject obj_type) in - let ms = JBasics.make_ms JConfig.clone_name [vt] (Some vt) in - JBir.InvokeStatic (var_opt, cn, ms, obj:: args)) + let cn = JBasics.make_cn JConfig.infer_array_cl in + let vt = JBasics.TObject obj_type in + let ms = JBasics.make_ms JConfig.clone_name [vt] (Some vt) in + JBir.InvokeStatic (var_opt, cn, ms, obj :: args) else - (let undef_cn, undef_ms = get_undefined_method_call (JBasics.ms_rtype ms) in - JBir.InvokeStatic (var_opt, undef_cn, undef_ms, [])) - + let undef_cn, undef_ms = get_undefined_method_call (JBasics.ms_rtype ms) in + JBir.InvokeStatic (var_opt, undef_cn, undef_ms, []) let is_this expr = match expr with - | JBir.Var (_, var) -> - begin - match JBir.var_name_debug var with - | None -> false - | Some name_opt -> String.equal (Mangled.to_string JConfig.this) name_opt - end - | _ -> false - + | JBir.Var (_, var) -> ( + match JBir.var_name_debug var with + | None + -> false + | Some name_opt + -> String.equal (Mangled.to_string JConfig.this) name_opt ) + | _ + -> false let assume_not_null loc sil_expr = let builtin_infer_assume = Exp.Const (Const.Cfun BuiltinDecl.__infer_assume) in - let not_null_expr = - Exp.BinOp (Binop.Ne, sil_expr, Exp.null) in - let assume_call_flag = { CallFlags.default with CallFlags.cf_noreturn = true; } in + let not_null_expr = Exp.BinOp (Binop.Ne, sil_expr, Exp.null) in + let assume_call_flag = {CallFlags.default with CallFlags.cf_noreturn= true} in let call_args = [(not_null_expr, Typ.mk (Tint Typ.IBool))] in Sil.Call (None, builtin_infer_assume, call_args, loc, assume_call_flag) - -let rec instruction (context : JContext.t) pc instr : translation = +let rec instruction (context: JContext.t) pc instr : translation = let tenv = JContext.get_tenv context in let cg = JContext.get_cg context in let program = context.program in @@ -707,236 +744,244 @@ let rec instruction (context : JContext.t) pc instr : translation = let file = loc.Location.file in let match_never_null = Inferconfig.never_return_null_matcher in let create_node node_kind sil_instrs = - Procdesc.create_node context.procdesc loc node_kind sil_instrs in - let return_not_null () = - match_never_null loc.Location.file proc_name in + Procdesc.create_node context.procdesc loc node_kind sil_instrs + in + let return_not_null () = match_never_null loc.Location.file proc_name in let trans_monitor_enter_exit context expr pc loc builtin node_desc = let instrs, sil_expr, sil_type = expression context pc expr in let builtin_const = Exp.Const (Const.Cfun builtin) in let instr = Sil.Call (None, builtin_const, [(sil_expr, sil_type)], loc, CallFlags.default) in - let typ_no_ptr = match sil_type.Typ.desc with - | Typ.Tptr (typ, _) -> typ - | _ -> sil_type in + let typ_no_ptr = match sil_type.Typ.desc with Typ.Tptr (typ, _) -> typ | _ -> sil_type in let deref_instr = create_sil_deref sil_expr typ_no_ptr loc in let node_kind = Procdesc.Node.Stmt_node node_desc in - Instr (create_node node_kind (instrs @ [deref_instr; instr] )) in + Instr (create_node node_kind (instrs @ [deref_instr; instr])) + in let create_node_kind procname = let assume_noexcept = - match Typ.Procname.get_method procname with - | "close" -> true - | _ -> false in + match Typ.Procname.get_method procname with "close" -> true | _ -> false + in let desc = - if assume_noexcept - then "call_noexcept" - else "Call " ^ (Typ.Procname.to_string procname) in - Procdesc.Node.Stmt_node desc in + if assume_noexcept then "call_noexcept" else "Call " ^ Typ.Procname.to_string procname + in + Procdesc.Node.Stmt_node desc + in try match instr with - | JBir.AffectVar (var, expr) -> - let (stml, sil_expr, sil_type) = expression context pc expr in - let pvar = (JContext.set_pvar context var sil_type) in + | JBir.AffectVar (var, expr) + -> let stml, sil_expr, sil_type = expression context pc expr in + let pvar = JContext.set_pvar context var sil_type in let sil_instr = Sil.Store (Exp.Lvar pvar, sil_type, sil_expr, loc) in let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = create_node node_kind (stml @ [sil_instr]) in Instr node - | JBir.Return expr_option -> - let node_kind = Procdesc.Node.Stmt_node "method_body" in + | JBir.Return expr_option + -> let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = match expr_option with - | None -> - create_node node_kind [] - | Some expr -> - let (stml, sil_expr, _) = expression context pc expr in + | None + -> create_node node_kind [] + | Some expr + -> let stml, sil_expr, _ = expression context pc expr in let sil_instrs = let return_instr = Sil.Store (Exp.Lvar ret_var, ret_type, sil_expr, loc) in - if return_not_null () then - [assume_not_null loc sil_expr; return_instr] - else - [return_instr] in - create_node node_kind (stml @ sil_instrs) in - JContext.add_goto_jump context pc JContext.Exit; - Instr node - | JBir.AffectArray (array_ex, index_ex, value_ex) -> - let (instrs_array, sil_expr_array, _) = expression context pc array_ex - and (instrs_index, sil_expr_index, _) = expression context pc index_ex - and (instrs_value, sil_expr_value, value_typ) = expression context pc value_ex in + if return_not_null () then [assume_not_null loc sil_expr; return_instr] + else [return_instr] + in + create_node node_kind (stml @ sil_instrs) + in + JContext.add_goto_jump context pc JContext.Exit ; Instr node + | JBir.AffectArray (array_ex, index_ex, value_ex) + -> let instrs_array, sil_expr_array, _ = expression context pc array_ex + and instrs_index, sil_expr_index, _ = expression context pc index_ex + and instrs_value, sil_expr_value, value_typ = expression context pc value_ex in let sil_instr = - Sil.Store ( - Exp.Lindex (sil_expr_array, sil_expr_index), value_typ, sil_expr_value, loc) in + Sil.Store (Exp.Lindex (sil_expr_array, sil_expr_index), value_typ, sil_expr_value, loc) + in let final_instrs = instrs_array @ instrs_index @ instrs_value @ [sil_instr] in let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = create_node node_kind final_instrs in Instr node - | JBir.AffectField (e_lhs, cn, fs, e_rhs) -> - let (stml1, sil_expr_lhs, _) = expression context pc e_lhs in - let (stml2, sil_expr_rhs, _) = expression context pc e_rhs in + | JBir.AffectField (e_lhs, cn, fs, e_rhs) + -> let stml1, sil_expr_lhs, _ = expression context pc e_lhs in + let stml2, sil_expr_rhs, _ = expression context pc e_rhs in let field_name = get_field_name program false tenv cn fs in let type_of_the_surrounding_class = JTransType.get_class_type_no_pointer program tenv cn in let type_of_the_root_of_e_lhs = type_of_the_surrounding_class in - let expr_off = Exp.Lfield(sil_expr_lhs, field_name, type_of_the_surrounding_class) in + let expr_off = Exp.Lfield (sil_expr_lhs, field_name, type_of_the_surrounding_class) in let sil_instr = Sil.Store (expr_off, type_of_the_root_of_e_lhs, sil_expr_rhs, loc) in let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = create_node node_kind (stml1 @ stml2 @ [sil_instr]) in Instr node - | JBir.AffectStaticField (cn, fs, e_rhs) -> - let class_exp = + | JBir.AffectStaticField (cn, fs, e_rhs) + -> let class_exp = let classname = Mangled.from_string (JBasics.cn_name cn) in let var_name = Pvar.mk_global classname (Pvar.TUFile file) in - Exp.Lvar var_name in - let (stml1, sil_expr_lhs) = [], class_exp in - let (stml2, sil_expr_rhs, _) = expression context pc e_rhs in + Exp.Lvar var_name + in + let stml1, sil_expr_lhs = ([], class_exp) in + let stml2, sil_expr_rhs, _ = expression context pc e_rhs in let field_name = get_field_name program true tenv cn fs in let type_of_the_surrounding_class = JTransType.get_class_type_no_pointer program tenv cn in let type_of_the_root_of_e_lhs = type_of_the_surrounding_class in - let expr_off = Exp.Lfield(sil_expr_lhs, field_name, type_of_the_surrounding_class) in + let expr_off = Exp.Lfield (sil_expr_lhs, field_name, type_of_the_surrounding_class) in let sil_instr = Sil.Store (expr_off, type_of_the_root_of_e_lhs, sil_expr_rhs, loc) in let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = create_node node_kind (stml1 @ stml2 @ [sil_instr]) in Instr node - | JBir.Goto goto_pc -> - JContext.reset_pvar_type context; - JContext.add_goto_jump context pc (JContext.Jump goto_pc); + | JBir.Goto goto_pc + -> JContext.reset_pvar_type context ; + JContext.add_goto_jump context pc (JContext.Jump goto_pc) ; Skip - | JBir.Ifd ((op, e1, e2), if_pc) -> (* Note: JBir provides the condition for the false branch, under which to jump *) - JContext.reset_pvar_type context; - let (instrs1, sil_ex1, _) = expression context pc e1 - and (instrs2, sil_ex2, _) = expression context pc e2 in + | JBir.Ifd ((op, e1, e2), if_pc) + -> (* Note: JBir provides the condition for the false branch, under which to jump *) + JContext.reset_pvar_type context ; + let instrs1, sil_ex1, _ = expression context pc e1 + and instrs2, sil_ex2, _ = expression context pc e2 in let sil_op = get_test_operator op in let sil_test_false = Exp.BinOp (sil_op, sil_ex1, sil_ex2) in - let sil_test_true = Exp.UnOp(Unop.LNot, sil_test_false, None) in + let sil_test_true = Exp.UnOp (Unop.LNot, sil_test_false, None) in let sil_instrs_true = Sil.Prune (sil_test_true, loc, true, Sil.Ik_if) in let sil_instrs_false = Sil.Prune (sil_test_false, loc, false, Sil.Ik_if) in let node_kind_true = Procdesc.Node.Prune_node (true, Sil.Ik_if, "method_body") in let node_kind_false = Procdesc.Node.Prune_node (false, Sil.Ik_if, "method_body") in let prune_node_true = create_node node_kind_true (instrs1 @ instrs2 @ [sil_instrs_true]) and prune_node_false = - create_node node_kind_false (instrs1 @ instrs2 @ [sil_instrs_false]) in - JContext.add_if_jump context prune_node_false if_pc; + create_node node_kind_false (instrs1 @ instrs2 @ [sil_instrs_false]) + in + JContext.add_if_jump context prune_node_false if_pc ; if detect_loop pc context.impl then let join_node_kind = Procdesc.Node.Join_node in let join_node = create_node join_node_kind [] in Loop (join_node, prune_node_true, prune_node_false) - else - Prune (prune_node_true, prune_node_false) - | JBir.Throw expr -> - let (instrs, sil_expr, _) = expression context pc expr in + else Prune (prune_node_true, prune_node_false) + | JBir.Throw expr + -> let instrs, sil_expr, _ = expression context pc expr in let sil_exn = Exp.Exn sil_expr in let sil_instr = Sil.Store (Exp.Lvar ret_var, ret_type, sil_exn, loc) in let node = create_node Procdesc.Node.throw_kind (instrs @ [sil_instr]) in - JContext.add_goto_jump context pc JContext.Exit; - Instr node - | JBir.New (var, cn, constr_type_list, constr_arg_list) -> - let builtin_new = Exp.Const (Const.Cfun BuiltinDecl.__new) in + JContext.add_goto_jump context pc JContext.Exit ; Instr node + | JBir.New (var, cn, constr_type_list, constr_arg_list) + -> let builtin_new = Exp.Const (Const.Cfun BuiltinDecl.__new) in let class_type = JTransType.get_class_type program tenv cn in let class_type_np = JTransType.get_class_type_no_pointer program tenv cn in - let sizeof_exp = Exp.Sizeof {typ=class_type_np; nbytes=None; - dynamic_length=None; subtype=Subtype.exact} in + let sizeof_exp = + Exp.Sizeof + {typ= class_type_np; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in let args = [(sizeof_exp, class_type)] in let ret_id = Ident.create_fresh Ident.knormal in let new_instr = - Sil.Call (Some (ret_id, class_type), builtin_new, args, loc, CallFlags.default) in + Sil.Call (Some (ret_id, class_type), builtin_new, args, loc, CallFlags.default) + in let constr_ms = JBasics.make_ms JConfig.constructor_name constr_type_list None in let constr_procname, call_instrs = let ret_opt = Some (Exp.Var ret_id, class_type) in - method_invocation - context loc pc None cn constr_ms ret_opt constr_arg_list I_Special Typ.Procname.Non_Static in + method_invocation context loc pc None cn constr_ms ret_opt constr_arg_list I_Special + Typ.Procname.Non_Static + in let pvar = JContext.set_pvar context var class_type in let set_instr = Sil.Store (Exp.Lvar pvar, class_type, Exp.Var ret_id, loc) in - let instrs = (new_instr :: call_instrs) @ [set_instr] in + let instrs = new_instr :: call_instrs @ [set_instr] in let node_kind = create_node_kind constr_procname in let node = create_node node_kind instrs in - let caller_procname = (Procdesc.get_proc_name context.procdesc) in - Cg.add_edge cg caller_procname constr_procname; - Instr node - | JBir.NewArray (var, vt, expr_list) -> - let builtin_new_array = Exp.Const (Const.Cfun BuiltinDecl.__new_array) in + let caller_procname = Procdesc.get_proc_name context.procdesc in + Cg.add_edge cg caller_procname constr_procname ; Instr node + | JBir.NewArray (var, vt, expr_list) + -> let builtin_new_array = Exp.Const (Const.Cfun BuiltinDecl.__new_array) in let content_type = JTransType.value_type program tenv vt in let array_type = JTransType.create_array_type content_type (List.length expr_list) in let array_name = JContext.set_pvar context var array_type in - let (instrs, array_size) = get_array_length context pc expr_list content_type in + let instrs, array_size = get_array_length context pc expr_list content_type in let call_args = [(array_size, array_type)] in let ret_id = Ident.create_fresh Ident.knormal in let call_instr = - Sil.Call - (Some (ret_id, array_type), builtin_new_array, call_args, loc, CallFlags.default) in + Sil.Call (Some (ret_id, array_type), builtin_new_array, call_args, loc, CallFlags.default) + in let set_instr = Sil.Store (Exp.Lvar array_name, array_type, Exp.Var ret_id, loc) in let node_kind = Procdesc.Node.Stmt_node "method_body" in let node = create_node node_kind (instrs @ [call_instr; set_instr]) in Instr node - | JBir.InvokeStatic (var_opt, cn, ms, args) -> - let sil_obj_opt, args, instrs = + | JBir.InvokeStatic (var_opt, cn, ms, args) + -> let sil_obj_opt, args, instrs = match args with - | [arg] when is_clone ms -> - (* hack to null check the receiver of clone when clone is an array. in the array.clone() + | [arg] when is_clone ms + -> (* hack to null check the receiver of clone when clone is an array. in the array.clone() case, clone is a virtual call that we translate as a static call *) - let (instrs, sil_arg_expr, arg_typ) = expression context pc arg in - Some (sil_arg_expr, arg_typ), [], instrs - | _ -> None, args, [] in + let instrs, sil_arg_expr, arg_typ = expression context pc arg in + (Some (sil_arg_expr, arg_typ), [], instrs) + | _ + -> (None, args, []) + in let callee_procname, call_instrs = - method_invocation context loc pc var_opt cn ms sil_obj_opt args I_Static Typ.Procname.Static in + method_invocation context loc pc var_opt cn ms sil_obj_opt args I_Static + Typ.Procname.Static + in let node_kind = create_node_kind callee_procname in let call_node = create_node node_kind (instrs @ call_instrs) in - let caller_procname = (Procdesc.get_proc_name context.procdesc) in - Cg.add_edge cg caller_procname callee_procname; - Instr call_node - | JBir.InvokeVirtual (var_opt, obj, call_kind, ms, args) -> - let caller_procname = (Procdesc.get_proc_name context.procdesc) in - let (instrs, sil_obj_expr, sil_obj_type) = expression context pc obj in + let caller_procname = Procdesc.get_proc_name context.procdesc in + Cg.add_edge cg caller_procname callee_procname ; Instr call_node + | JBir.InvokeVirtual (var_opt, obj, call_kind, ms, args) + -> ( + let caller_procname = Procdesc.get_proc_name context.procdesc in + let instrs, sil_obj_expr, sil_obj_type = expression context pc obj in let create_call_node cn invoke_kind = let callee_procname, call_instrs = let ret_opt = Some (sil_obj_expr, sil_obj_type) in - method_invocation - context loc pc var_opt cn ms ret_opt args invoke_kind Typ.Procname.Non_Static in + method_invocation context loc pc var_opt cn ms ret_opt args invoke_kind + Typ.Procname.Non_Static + in let node_kind = create_node_kind callee_procname in let call_node = create_node node_kind (instrs @ call_instrs) in - Cg.add_edge cg caller_procname callee_procname; - call_node in + Cg.add_edge cg caller_procname callee_procname ; call_node + in let trans_virtual_call original_cn invoke_kind = - let cn' = match JTransType.extract_cn_no_obj sil_obj_type with - | Some cn -> cn - | None -> original_cn in + let cn' = + match JTransType.extract_cn_no_obj sil_obj_type with + | Some cn + -> cn + | None + -> original_cn + in let call_node = create_call_node cn' invoke_kind in - Instr call_node in - begin - match call_kind with - | JBir.VirtualCall obj_type -> - begin - match obj_type with - | JBasics.TClass cn -> trans_virtual_call cn I_Virtual - | JBasics.TArray _ -> - let instr = instruction_array_call ms obj_type obj args var_opt in - instruction context pc instr - end - | JBir.InterfaceCall cn -> - trans_virtual_call cn I_Interface - end - | JBir.InvokeNonVirtual (var_opt, obj, cn, ms, args) -> - let (instrs, sil_obj_expr, sil_obj_type) = expression context pc obj in + Instr call_node + in + match call_kind with + | JBir.VirtualCall obj_type -> ( + match obj_type with + | JBasics.TClass cn + -> trans_virtual_call cn I_Virtual + | JBasics.TArray _ + -> let instr = instruction_array_call ms obj_type obj args var_opt in + instruction context pc instr ) + | JBir.InterfaceCall cn + -> trans_virtual_call cn I_Interface ) + | JBir.InvokeNonVirtual (var_opt, obj, cn, ms, args) + -> let instrs, sil_obj_expr, sil_obj_type = expression context pc obj in let callee_procname, call_instrs = - method_invocation context loc pc var_opt cn ms (Some (sil_obj_expr, sil_obj_type)) args I_Special Typ.Procname.Non_Static in + method_invocation context loc pc var_opt cn ms (Some (sil_obj_expr, sil_obj_type)) args + I_Special Typ.Procname.Non_Static + in let node_kind = create_node_kind callee_procname in let call_node = create_node node_kind (instrs @ call_instrs) in let procdesc = context.procdesc in - let caller_procname = (Procdesc.get_proc_name procdesc) in - Cg.add_edge cg caller_procname callee_procname; - Instr call_node - - | JBir.Check (JBir.CheckNullPointer expr) - when Config.tracing && is_this expr -> - (* TODO #6509339: refactor the boilerplate code in the translation of JVM checks *) - let (instrs, sil_expr, _) = expression context pc expr in + let caller_procname = Procdesc.get_proc_name procdesc in + Cg.add_edge cg caller_procname callee_procname ; Instr call_node + | JBir.Check JBir.CheckNullPointer expr when Config.tracing && is_this expr + -> (* TODO #6509339: refactor the boilerplate code in the translation of JVM checks *) + let instrs, sil_expr, _ = expression context pc expr in let this_not_null_node = - create_node - (Procdesc.Node.Stmt_node "this not null") (instrs @ [assume_not_null loc sil_expr]) in + create_node (Procdesc.Node.Stmt_node "this not null") + (instrs @ [assume_not_null loc sil_expr]) + in Instr this_not_null_node - - | JBir.Check (JBir.CheckNullPointer expr) when Config.tracing -> - let (instrs, sil_expr, _) = expression context pc expr in + | JBir.Check JBir.CheckNullPointer expr when Config.tracing + -> let instrs, sil_expr, _ = expression context pc expr in let not_null_node = let sil_not_null = Exp.BinOp (Binop.Ne, sil_expr, Exp.null) in let sil_prune_not_null = Sil.Prune (sil_not_null, loc, true, Sil.Ik_if) and not_null_kind = Procdesc.Node.Prune_node (true, Sil.Ik_if, "Not null") in - create_node not_null_kind (instrs @ [sil_prune_not_null]) in + create_node not_null_kind (instrs @ [sil_prune_not_null]) + in let throw_npe_node = let sil_is_null = Exp.BinOp (Binop.Eq, sil_expr, Exp.null) in let sil_prune_null = Sil.Prune (sil_is_null, loc, true, Sil.Ik_if) @@ -944,86 +989,93 @@ let rec instruction (context : JContext.t) pc instr : translation = and npe_cn = JBasics.make_cn JConfig.npe_cl in let class_type = JTransType.get_class_type program tenv npe_cn and class_type_np = JTransType.get_class_type_no_pointer program tenv npe_cn in - let sizeof_exp = Exp.Sizeof {typ=class_type_np; nbytes=None; - dynamic_length=None; subtype=Subtype.exact} in + let sizeof_exp = + Exp.Sizeof + {typ= class_type_np; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in let args = [(sizeof_exp, class_type)] in let ret_id = Ident.create_fresh Ident.knormal in let new_instr = - Sil.Call (Some (ret_id, class_type), builtin_new, args, loc, CallFlags.default) in + Sil.Call (Some (ret_id, class_type), builtin_new, args, loc, CallFlags.default) + in let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in let _, call_instrs = let ret_opt = Some (Exp.Var ret_id, class_type) in - method_invocation context loc pc None npe_cn constr_ms ret_opt [] I_Special Typ.Procname.Static in + method_invocation context loc pc None npe_cn constr_ms ret_opt [] I_Special + Typ.Procname.Static + in let sil_exn = Exp.Exn (Exp.Var ret_id) in let set_instr = Sil.Store (Exp.Lvar ret_var, ret_type, sil_exn, loc) in - let npe_instrs = instrs @ [sil_prune_null] @ (new_instr :: call_instrs) @ [set_instr] in - create_node npe_kind npe_instrs in + let npe_instrs = instrs @ [sil_prune_null] @ new_instr :: call_instrs @ [set_instr] in + create_node npe_kind npe_instrs + in Prune (not_null_node, throw_npe_node) - - | JBir.Check (JBir.CheckArrayBound (array_expr, index_expr)) - when Config.tracing -> - let instrs, _, sil_length_expr, sil_index_expr = - let array_instrs, sil_array_expr, _ = - expression context pc array_expr + | JBir.Check JBir.CheckArrayBound (array_expr, index_expr) when Config.tracing + -> let instrs, _, sil_length_expr, sil_index_expr = + let array_instrs, sil_array_expr, _ = expression context pc array_expr and length_instrs, sil_length_expr, _ = expression context pc (JBir.Unop (JBir.ArrayLength, array_expr)) - and index_instrs, sil_index_expr, _ = - expression context pc index_expr in + and index_instrs, sil_index_expr, _ = expression context pc index_expr in let instrs = array_instrs @ index_instrs @ length_instrs in - (instrs, sil_array_expr, sil_length_expr, sil_index_expr) in - + (instrs, sil_array_expr, sil_length_expr, sil_index_expr) + in let in_bound_node = - let in_bound_node_kind = - Procdesc.Node.Prune_node (true, Sil.Ik_if, "In bound") in + let in_bound_node_kind = Procdesc.Node.Prune_node (true, Sil.Ik_if, "In bound") in let sil_assume_in_bound = let sil_in_bound = let sil_positive_index = Exp.BinOp (Binop.Ge, sil_index_expr, Exp.Const (Const.Cint IntLit.zero)) - and sil_less_than_length = - Exp.BinOp (Binop.Lt, sil_index_expr, sil_length_expr) in - Exp.BinOp (Binop.LAnd, sil_positive_index, sil_less_than_length) in - Sil.Prune (sil_in_bound, loc, true, Sil.Ik_if) in + and sil_less_than_length = Exp.BinOp (Binop.Lt, sil_index_expr, sil_length_expr) in + Exp.BinOp (Binop.LAnd, sil_positive_index, sil_less_than_length) + in + Sil.Prune (sil_in_bound, loc, true, Sil.Ik_if) + in create_node in_bound_node_kind (instrs @ [sil_assume_in_bound]) - and throw_out_of_bound_node = - let out_of_bound_node_kind = - Procdesc.Node.Stmt_node "Out of bound" in + let out_of_bound_node_kind = Procdesc.Node.Stmt_node "Out of bound" in let sil_assume_out_of_bound = let sil_out_of_bound = let sil_negative_index = Exp.BinOp (Binop.Lt, sil_index_expr, Exp.Const (Const.Cint IntLit.zero)) and sil_greater_than_length = - Exp.BinOp (Binop.Gt, sil_index_expr, sil_length_expr) in - Exp.BinOp (Binop.LOr, sil_negative_index, sil_greater_than_length) in - Sil.Prune (sil_out_of_bound, loc, true, Sil.Ik_if) in + Exp.BinOp (Binop.Gt, sil_index_expr, sil_length_expr) + in + Exp.BinOp (Binop.LOr, sil_negative_index, sil_greater_than_length) + in + Sil.Prune (sil_out_of_bound, loc, true, Sil.Ik_if) + in let out_of_bound_cn = JBasics.make_cn JConfig.out_of_bound_cl in let class_type = JTransType.get_class_type program tenv out_of_bound_cn and class_type_np = JTransType.get_class_type_no_pointer program tenv out_of_bound_cn in - let sizeof_exp = Exp.Sizeof {typ=class_type_np; nbytes=None; - dynamic_length=None; subtype=Subtype.exact} in + let sizeof_exp = + Exp.Sizeof + {typ= class_type_np; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in let args = [(sizeof_exp, class_type)] in let ret_id = Ident.create_fresh Ident.knormal in let new_instr = - Sil.Call (Some (ret_id, ret_type), builtin_new, args, loc, CallFlags.default) in + Sil.Call (Some (ret_id, ret_type), builtin_new, args, loc, CallFlags.default) + in let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in let _, call_instrs = - method_invocation - context loc pc None out_of_bound_cn constr_ms - (Some (Exp.Var ret_id, class_type)) [] I_Special Typ.Procname.Static in + method_invocation context loc pc None out_of_bound_cn constr_ms + (Some (Exp.Var ret_id, class_type)) [] I_Special Typ.Procname.Static + in let sil_exn = Exp.Exn (Exp.Var ret_id) in let set_instr = Sil.Store (Exp.Lvar ret_var, ret_type, sil_exn, loc) in let out_of_bound_instrs = - instrs @ [sil_assume_out_of_bound] @ (new_instr :: call_instrs) @ [set_instr] in - create_node out_of_bound_node_kind out_of_bound_instrs in - + instrs @ [sil_assume_out_of_bound] @ new_instr :: call_instrs @ [set_instr] + in + create_node out_of_bound_node_kind out_of_bound_instrs + in Prune (in_bound_node, throw_out_of_bound_node) - - | JBir.Check (JBir.CheckCast (expr, object_type)) when Config.tracing -> - let sil_type = JTransType.expr_type context expr + | JBir.Check JBir.CheckCast (expr, object_type) when Config.tracing + -> let sil_type = JTransType.expr_type context expr and instrs, sil_expr, _ = expression context pc expr and ret_id = Ident.create_fresh Ident.knormal and sizeof_expr = - JTransType.sizeof_of_object_type program tenv object_type Subtype.subtypes_instof in + JTransType.sizeof_of_object_type program tenv object_type Subtype.subtypes_instof + in let check_cast = Exp.Const (Const.Cfun BuiltinDecl.__instanceof) in let args = [(sil_expr, sil_type); (sizeof_expr, Typ.mk Tvoid)] in let call = Sil.Call (Some (ret_id, ret_type), check_cast, args, loc, CallFlags.default) in @@ -1040,32 +1092,34 @@ let rec instruction (context : JContext.t) pc instr : translation = and cce_cn = JBasics.make_cn JConfig.cce_cl in let class_type = JTransType.get_class_type program tenv cce_cn and class_type_np = JTransType.get_class_type_no_pointer program tenv cce_cn in - let sizeof_exp = Exp.Sizeof {typ=class_type_np; nbytes=None; - dynamic_length=None; subtype=Subtype.exact} in + let sizeof_exp = + Exp.Sizeof + {typ= class_type_np; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in let args = [(sizeof_exp, class_type)] in let ret_id = Ident.create_fresh Ident.knormal in let new_instr = - Sil.Call (Some (ret_id, ret_type), builtin_new, args, loc, CallFlags.default) in + Sil.Call (Some (ret_id, ret_type), builtin_new, args, loc, CallFlags.default) + in let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in let _, call_instrs = method_invocation context loc pc None cce_cn constr_ms - (Some (Exp.Var ret_id, class_type)) [] I_Special Typ.Procname.Static in + (Some (Exp.Var ret_id, class_type)) [] I_Special Typ.Procname.Static + in let sil_exn = Exp.Exn (Exp.Var ret_id) in let set_instr = Sil.Store (Exp.Lvar ret_var, ret_type, sil_exn, loc) in let cce_instrs = - instrs @ [call; asssume_not_instance_of] @ (new_instr :: call_instrs) @ [set_instr] in - create_node throw_cast_exception_kind cce_instrs in - + instrs @ [call; asssume_not_instance_of] @ new_instr :: call_instrs @ [set_instr] + in + create_node throw_cast_exception_kind cce_instrs + in Prune (is_instance_node, throw_cast_exception_node) - | JBir.MonitorEnter expr -> - trans_monitor_enter_exit - context expr pc loc BuiltinDecl.__set_locked_attribute "MonitorEnter" - - | JBir.MonitorExit expr -> - trans_monitor_enter_exit - context expr pc loc BuiltinDecl.__delete_locked_attribute "MonitorExit" - - | _ -> Skip - with Frontend_error s -> - L.internal_error "Skipping because of: %s@." s; - Skip + | JBir.MonitorEnter expr + -> trans_monitor_enter_exit context expr pc loc BuiltinDecl.__set_locked_attribute + "MonitorEnter" + | JBir.MonitorExit expr + -> trans_monitor_enter_exit context expr pc loc BuiltinDecl.__delete_locked_attribute + "MonitorExit" + | _ + -> Skip + with Frontend_error s -> L.internal_error "Skipping because of: %s@." s ; Skip diff --git a/infer/src/java/jTrans.mli b/infer/src/java/jTrans.mli index 8f85a80a2..9a9029b6d 100644 --- a/infer/src/java/jTrans.mli +++ b/infer/src/java/jTrans.mli @@ -9,7 +9,6 @@ *) open! IStd - open Javalib_pack open Sawja_pack @@ -20,38 +19,26 @@ type translation = | Prune of Procdesc.Node.t * Procdesc.Node.t | Loop of Procdesc.Node.t * Procdesc.Node.t * Procdesc.Node.t -val is_java_native : JCode.jcode Javalib.concrete_method -> bool +val is_java_native : JCode.jcode Javalib.concrete_method -> bool -(** Create the procedure description for an abstract method *) val create_am_procdesc : - SourceFile.t -> - JClasspath.program -> - JContext.icfg -> - Javalib.abstract_method -> - Typ.Procname.t -> - Procdesc.t + SourceFile.t -> JClasspath.program -> JContext.icfg -> Javalib.abstract_method -> Typ.Procname.t + -> Procdesc.t +(** Create the procedure description for an abstract method *) -(** Create the procedure description for a concrete method *) val create_native_procdesc : - SourceFile.t -> - JClasspath.program -> - JContext.icfg -> - JCode.jcode Javalib.concrete_method -> - Typ.Procname.t -> - Procdesc.t + SourceFile.t -> JClasspath.program -> JContext.icfg -> JCode.jcode Javalib.concrete_method + -> Typ.Procname.t -> Procdesc.t +(** Create the procedure description for a concrete method *) +val create_cm_procdesc : + SourceFile.t -> JClasspath.program -> Printer.LineReader.t -> JContext.icfg + -> JCode.jcode Javalib.concrete_method -> Typ.Procname.t + -> (Procdesc.t * Javalib_pack.JCode.jcode * JBir.t) option (** [create_procdesc source_file program linereader icfg cm proc_name] creates a procedure description for the concrete method cm and adds it to cfg *) -val create_cm_procdesc : - SourceFile.t -> - JClasspath.program -> - Printer.LineReader.t -> - JContext.icfg -> - JCode.jcode Javalib.concrete_method -> - Typ.Procname.t -> - (Procdesc.t * Javalib_pack.JCode.jcode * JBir.t) option -(** translates an instruction into a statement node or prune nodes in the cfg *) val instruction : JContext.t -> int -> JBir.instr -> translation +(** translates an instruction into a statement node or prune nodes in the cfg *) exception Frontend_error of string diff --git a/infer/src/java/jTransExn.ml b/infer/src/java/jTransExn.ml index fe34bf772..9cac8c5c0 100644 --- a/infer/src/java/jTransExn.ml +++ b/infer/src/java/jTransExn.ml @@ -10,10 +10,8 @@ open! IStd module Hashtbl = Caml.Hashtbl - open Javalib_pack open Sawja_pack - module E = Logging let create_handler_table impl = @@ -21,13 +19,13 @@ let create_handler_table impl = let collect (pc, exn_handler) = try let handlers = Hashtbl.find handler_tb pc in - Hashtbl.replace handler_tb pc (exn_handler:: handlers) - with Not_found -> - Hashtbl.add handler_tb pc [exn_handler] in - List.iter ~f:collect (JBir.exception_edges impl); + Hashtbl.replace handler_tb pc (exn_handler :: handlers) + with Not_found -> Hashtbl.add handler_tb pc [exn_handler] + in + List.iter ~f:collect (JBir.exception_edges impl) ; handler_tb -let translate_exceptions (context : JContext.t) exit_nodes get_body_nodes handler_table = +let translate_exceptions (context: JContext.t) exit_nodes get_body_nodes handler_table = let catch_block_table = Hashtbl.create 1 in let exn_message = "exception handler" in let procdesc = context.procdesc in @@ -43,88 +41,127 @@ let translate_exceptions (context : JContext.t) exit_nodes get_body_nodes handle let instr_unwrap_ret_val = let unwrap_builtin = Exp.Const (Const.Cfun BuiltinDecl.__unwrap_exception) in Sil.Call - (Some (id_exn_val, ret_type), unwrap_builtin, [(Exp.Var id_ret_val, ret_type)], loc, - CallFlags.default) in - create_node - loc - Procdesc.Node.exn_handler_kind - [instr_get_ret_val; instr_deactivate_exn; instr_unwrap_ret_val] in + ( Some (id_exn_val, ret_type) + , unwrap_builtin + , [(Exp.Var id_ret_val, ret_type)] + , loc + , CallFlags.default ) + in + create_node loc Procdesc.Node.exn_handler_kind + [instr_get_ret_val; instr_deactivate_exn; instr_unwrap_ret_val] + in let create_entry_block handler_list = - try - ignore (Hashtbl.find catch_block_table handler_list) + try ignore (Hashtbl.find catch_block_table handler_list) with Not_found -> let collect succ_nodes rethrow_exception handler = let catch_nodes = get_body_nodes handler.JBir.e_handler in - let loc = match catch_nodes with - | n:: _ -> Procdesc.Node.get_loc n - | [] -> Location.none context.source_file in + let loc = + match catch_nodes with + | n :: _ + -> Procdesc.Node.get_loc n + | [] + -> Location.none context.source_file + in let exn_type = let class_name = match handler.JBir.e_catch_type with - | None -> JBasics.make_cn "java.lang.Exception" - | Some cn -> cn in - match JTransType.get_class_type - context.program (JContext.get_tenv context) class_name with - | {Typ.desc=Tptr (typ, _)} -> typ - | _ -> assert false in + | None + -> JBasics.make_cn "java.lang.Exception" + | Some cn + -> cn + in + match + JTransType.get_class_type context.program (JContext.get_tenv context) class_name + with + | {Typ.desc= Tptr (typ, _)} + -> typ + | _ + -> assert false + in let id_instanceof = Ident.create_fresh Ident.knormal in let instr_call_instanceof = let instanceof_builtin = Exp.Const (Const.Cfun BuiltinDecl.__instanceof) in - let args = [ - (Exp.Var id_exn_val, Typ.mk (Tptr(exn_type, Typ.Pk_pointer))); - (Exp.Sizeof {typ=exn_type; nbytes=None; dynamic_length=None; subtype=Subtype.exact}, - Typ.mk Tvoid)] in + let args = + [ (Exp.Var id_exn_val, Typ.mk (Tptr (exn_type, Typ.Pk_pointer))) + ; ( Exp.Sizeof + {typ= exn_type; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + , Typ.mk Tvoid ) ] + in Sil.Call - (Some (id_instanceof, Typ.mk (Tint IBool)), instanceof_builtin, args, loc, CallFlags.default) in + ( Some (id_instanceof, Typ.mk (Tint IBool)) + , instanceof_builtin + , args + , loc + , CallFlags.default ) + in let if_kind = Sil.Ik_switch in let instr_prune_true = Sil.Prune (Exp.Var id_instanceof, loc, true, if_kind) in let instr_prune_false = - Sil.Prune (Exp.UnOp(Unop.LNot, Exp.Var id_instanceof, None), loc, false, if_kind) in + Sil.Prune (Exp.UnOp (Unop.LNot, Exp.Var id_instanceof, None), loc, false, if_kind) + in let instr_set_catch_var = let catch_var = JContext.set_pvar context handler.JBir.e_catch_var ret_type in - Sil.Store (Exp.Lvar catch_var, ret_type, Exp.Var id_exn_val, loc) in + Sil.Store (Exp.Lvar catch_var, ret_type, Exp.Var id_exn_val, loc) + in let instr_rethrow_exn = - Sil.Store (Exp.Lvar ret_var, ret_type, Exp.Exn (Exp.Var id_exn_val), loc) in + Sil.Store (Exp.Lvar ret_var, ret_type, Exp.Exn (Exp.Var id_exn_val), loc) + in let node_kind_true = Procdesc.Node.Prune_node (true, if_kind, exn_message) in let node_kind_false = Procdesc.Node.Prune_node (false, if_kind, exn_message) in let node_true = let instrs_true = [instr_call_instanceof; instr_prune_true; instr_set_catch_var] in - create_node loc node_kind_true instrs_true in + create_node loc node_kind_true instrs_true + in let node_false = - let instrs_false = [instr_call_instanceof; instr_prune_false] @ (if rethrow_exception then [instr_rethrow_exn] else []) in - create_node loc node_kind_false instrs_false in - Procdesc.node_set_succs_exn procdesc node_true catch_nodes exit_nodes; - Procdesc.node_set_succs_exn procdesc node_false succ_nodes exit_nodes; + let instrs_false = + [instr_call_instanceof; instr_prune_false] + @ if rethrow_exception then [instr_rethrow_exn] else [] + in + create_node loc node_kind_false instrs_false + in + Procdesc.node_set_succs_exn procdesc node_true catch_nodes exit_nodes ; + Procdesc.node_set_succs_exn procdesc node_false succ_nodes exit_nodes ; let is_finally = is_none handler.JBir.e_catch_type in - if is_finally - then [node_true] (* TODO (#4759480): clean up the translation so prune nodes are not created at all *) - else [node_true; node_false] in + if is_finally then [node_true] + (* TODO (#4759480): clean up the translation so prune nodes are not created at all *) + else [node_true; node_false] + in let is_last_handler = ref true in - let process_handler succ_nodes handler = (* process handlers starting from the last one *) - let remove_temps = !is_last_handler in (* remove temporary variables on last handler *) - is_last_handler := false; - collect succ_nodes remove_temps handler in - + let process_handler succ_nodes handler = + (* process handlers starting from the last one *) + let remove_temps = !is_last_handler in + (* remove temporary variables on last handler *) + is_last_handler := false ; + collect succ_nodes remove_temps handler + in let nodes_first_handler = - List.fold ~f:process_handler ~init:exit_nodes (List.rev handler_list) in - let loc = match nodes_first_handler with - | n:: _ -> Procdesc.Node.get_loc n - | [] -> Location.none context.source_file in + List.fold ~f:process_handler ~init:exit_nodes (List.rev handler_list) + in + let loc = + match nodes_first_handler with + | n :: _ + -> Procdesc.Node.get_loc n + | [] + -> Location.none context.source_file + in let entry_node = create_entry_node loc in - Procdesc.node_set_succs_exn procdesc entry_node nodes_first_handler exit_nodes; - Hashtbl.add catch_block_table handler_list [entry_node] in - Hashtbl.iter (fun _ handler_list -> create_entry_block handler_list) handler_table; + Procdesc.node_set_succs_exn procdesc entry_node nodes_first_handler exit_nodes ; + Hashtbl.add catch_block_table handler_list [entry_node] + in + Hashtbl.iter (fun _ handler_list -> create_entry_block handler_list) handler_table ; catch_block_table let create_exception_handlers context exit_nodes get_body_nodes impl = match JBir.exc_tbl impl with - | [] -> fun _ -> exit_nodes - | _ -> - let handler_table = create_handler_table impl in - let catch_block_table = translate_exceptions context exit_nodes get_body_nodes handler_table in + | [] + -> fun _ -> exit_nodes + | _ + -> let handler_table = create_handler_table impl in + let catch_block_table = + translate_exceptions context exit_nodes get_body_nodes handler_table + in fun pc -> try let handler_list = Hashtbl.find handler_table pc in Hashtbl.find catch_block_table handler_list - with Not_found -> - exit_nodes + with Not_found -> exit_nodes diff --git a/infer/src/java/jTransExn.mli b/infer/src/java/jTransExn.mli index 7d5de2edb..05b3fe193 100644 --- a/infer/src/java/jTransExn.mli +++ b/infer/src/java/jTransExn.mli @@ -9,10 +9,8 @@ *) open! IStd - open Sawja_pack - val create_exception_handlers : - JContext.t -> Procdesc.Node.t list -> (int -> Procdesc.Node.t list) -> - JBir.t -> int -> Procdesc.Node.t list + JContext.t -> Procdesc.Node.t list -> (int -> Procdesc.Node.t list) -> JBir.t -> int + -> Procdesc.Node.t list diff --git a/infer/src/java/jTransType.ml b/infer/src/java/jTransType.ml index 6569f191e..74649c4e6 100644 --- a/infer/src/java/jTransType.ml +++ b/infer/src/java/jTransType.ml @@ -9,10 +9,8 @@ *) open! IStd - open Javalib_pack open Sawja_pack - module L = Logging (** Type transformations between Javalib datatypes and sil datatypes *) @@ -20,185 +18,202 @@ module L = Logging exception Type_tranlsation_error of string let basic_type = function - | `Int -> Typ.mk (Tint Typ.IInt) - | `Bool -> Typ.mk (Tint Typ.IBool) - | `Byte -> Typ.mk (Tint Typ.IChar) - | `Char -> Typ.mk (Tint Typ.IChar) - | `Double -> Typ.mk (Tfloat Typ.FDouble) - | `Float -> Typ.mk (Tfloat Typ.FFloat) - | `Long -> Typ.mk (Tint Typ.ILong) - | `Short -> Typ.mk (Tint Typ.IShort) - + | `Int + -> Typ.mk (Tint Typ.IInt) + | `Bool + -> Typ.mk (Tint Typ.IBool) + | `Byte + -> Typ.mk (Tint Typ.IChar) + | `Char + -> Typ.mk (Tint Typ.IChar) + | `Double + -> Typ.mk (Tfloat Typ.FDouble) + | `Float + -> Typ.mk (Tfloat Typ.FFloat) + | `Long + -> Typ.mk (Tint Typ.ILong) + | `Short + -> Typ.mk (Tint Typ.IShort) let cast_type = function - | JBir.F2I - | JBir.L2I - | JBir.D2I -> Typ.mk (Typ.Tint Typ.IInt) - | JBir.D2L - | JBir.F2L - | JBir.I2L -> Typ.mk (Typ.Tint Typ.ILong) - | JBir.I2F - | JBir.L2F - | JBir.D2F -> Typ.mk (Typ.Tfloat Typ.FFloat) - | JBir.L2D - | JBir.F2D - | JBir.I2D -> Typ.mk (Typ.Tfloat Typ.FDouble) - | JBir.I2B -> Typ.mk (Typ.Tint Typ.IBool) - | JBir.I2C -> Typ.mk (Typ.Tint Typ.IChar) - | JBir.I2S -> Typ.mk (Typ.Tint Typ.IShort) - + | JBir.F2I | JBir.L2I | JBir.D2I + -> Typ.mk (Typ.Tint Typ.IInt) + | JBir.D2L | JBir.F2L | JBir.I2L + -> Typ.mk (Typ.Tint Typ.ILong) + | JBir.I2F | JBir.L2F | JBir.D2F + -> Typ.mk (Typ.Tfloat Typ.FFloat) + | JBir.L2D | JBir.F2D | JBir.I2D + -> Typ.mk (Typ.Tfloat Typ.FDouble) + | JBir.I2B + -> Typ.mk (Typ.Tint Typ.IBool) + | JBir.I2C + -> Typ.mk (Typ.Tint Typ.IChar) + | JBir.I2S + -> Typ.mk (Typ.Tint Typ.IShort) let const_type const = match const with - | `String _ -> (JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.string_cl))) - | `Class _ -> (JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.class_cl))) - | `Double _ -> (JBasics.TBasic `Double) - | `Int _ -> (JBasics.TBasic`Int) - | `Float _ -> (JBasics.TBasic`Float) - | `Long _ -> (JBasics.TBasic`Long) - | `ANull -> JConfig.obj_type - - -let typename_of_classname cn = - Typ.Name.Java.from_string (JBasics.cn_name cn) - + | `String _ + -> JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.string_cl)) + | `Class _ + -> JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.class_cl)) + | `Double _ + -> JBasics.TBasic `Double + | `Int _ + -> JBasics.TBasic `Int + | `Float _ + -> JBasics.TBasic `Float + | `Long _ + -> JBasics.TBasic `Long + | `ANull + -> JConfig.obj_type + +let typename_of_classname cn = Typ.Name.Java.from_string (JBasics.cn_name cn) let rec get_named_type vt : Typ.t = match vt with - | JBasics.TBasic bt -> basic_type bt + | JBasics.TBasic bt + -> basic_type bt | JBasics.TObject ot -> - begin - match ot with - | JBasics.TArray vt -> - let content_type = get_named_type vt in - Typ.mk (Tptr (Typ.mk (Tarray (content_type, None, None)), Typ.Pk_pointer)) - | JBasics.TClass cn -> - Typ.mk (Tptr (Typ.mk (Tstruct (typename_of_classname cn)), Typ.Pk_pointer)) - end - + match ot with + | JBasics.TArray vt + -> let content_type = get_named_type vt in + Typ.mk (Tptr (Typ.mk (Tarray (content_type, None, None)), Typ.Pk_pointer)) + | JBasics.TClass cn + -> Typ.mk (Tptr (Typ.mk (Tstruct (typename_of_classname cn)), Typ.Pk_pointer)) let extract_cn_type_np typ = - match typ.Typ.desc with - | Typ.Tptr(vtyp, Typ.Pk_pointer) -> - vtyp - | _ -> typ + match typ.Typ.desc with Typ.Tptr (vtyp, Typ.Pk_pointer) -> vtyp | _ -> typ let rec create_array_type typ dim = if dim > 0 then let content_typ = create_array_type typ (dim - 1) in - Typ.mk (Tptr(Typ.mk (Tarray (content_typ, None, None)), Typ.Pk_pointer)) + Typ.mk (Tptr (Typ.mk (Tarray (content_typ, None, None)), Typ.Pk_pointer)) else typ let extract_cn_no_obj typ = match typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct (JavaClass _ as name)}, Pk_pointer) -> - let class_name = JBasics.make_cn (Typ.Name.name name) in + | Typ.Tptr ({desc= Tstruct (JavaClass _ as name)}, Pk_pointer) + -> let class_name = JBasics.make_cn (Typ.Name.name name) in if JBasics.cn_equal class_name JBasics.java_lang_object then None else let jbir_class_name = class_name in Some jbir_class_name - | _ -> None + | _ + -> None (** Printing types *) let rec array_type_to_string vt = let s = match vt with - | JBasics.TBasic bt -> - (match bt with - | `Bool -> JConfig.boolean_code - | `Byte -> JConfig.byte_code - | `Char -> JConfig.char_code - | `Double -> JConfig.double_code - | `Float -> JConfig.float_code - | `Int -> JConfig.int_code - | `Long -> JConfig.long_code - | `Short -> JConfig.short_code) - | JBasics.TObject ot -> object_type_to_string' ot in - "["^s + | JBasics.TBasic bt -> ( + match bt with + | `Bool + -> JConfig.boolean_code + | `Byte + -> JConfig.byte_code + | `Char + -> JConfig.char_code + | `Double + -> JConfig.double_code + | `Float + -> JConfig.float_code + | `Int + -> JConfig.int_code + | `Long + -> JConfig.long_code + | `Short + -> JConfig.short_code ) + | JBasics.TObject ot + -> object_type_to_string' ot + in + "[" ^ s + and object_type_to_string' ot = match ot with - | JBasics.TClass class_name -> JConfig.class_code (JBasics.cn_name class_name) - | JBasics.TArray vt -> (array_type_to_string vt) + | JBasics.TClass class_name + -> JConfig.class_code (JBasics.cn_name class_name) + | JBasics.TArray vt + -> array_type_to_string vt let object_type_to_string ot = match ot with - | JBasics.TClass class_name -> (JBasics.cn_name class_name) - | JBasics.TArray vt -> (array_type_to_string vt) + | JBasics.TClass class_name + -> JBasics.cn_name class_name + | JBasics.TArray vt + -> array_type_to_string vt let string_of_basic_type = function - | `Bool -> JConfig.boolean_st - | `Byte -> JConfig.byte_st - | `Char -> JConfig.char_st - | `Double -> JConfig.double_st - | `Float -> JConfig.float_st - | `Int -> JConfig.int_st - | `Long -> JConfig.long_st - | `Short -> JConfig.short_st + | `Bool + -> JConfig.boolean_st + | `Byte + -> JConfig.byte_st + | `Char + -> JConfig.char_st + | `Double + -> JConfig.double_st + | `Float + -> JConfig.float_st + | `Int + -> JConfig.int_st + | `Long + -> JConfig.long_st + | `Short + -> JConfig.short_st let rec string_of_type vt = match vt with - | JBasics.TBasic bt -> string_of_basic_type bt + | JBasics.TBasic bt + -> string_of_basic_type bt | JBasics.TObject ot -> - begin - match ot with - | JBasics.TArray vt -> (string_of_type vt)^"[]" - | JBasics.TClass cn -> JBasics.cn_name cn - end + match ot with + | JBasics.TArray vt + -> string_of_type vt ^ "[]" + | JBasics.TClass cn + -> JBasics.cn_name cn let package_to_string p = - let rec aux p = - match p with - | [] -> "" - | p::[] -> p - | p:: rest -> p^"."^(aux rest) in - match p with - | [] -> None - | _ -> Some (aux p) - - -let cn_to_java_type cn = - (package_to_string (JBasics.cn_package cn), - (JBasics.cn_simple_name cn)) + let rec aux p = match p with [] -> "" | [p] -> p | p :: rest -> p ^ "." ^ aux rest in + match p with [] -> None | _ -> Some (aux p) +let cn_to_java_type cn = (package_to_string (JBasics.cn_package cn), JBasics.cn_simple_name cn) let vt_to_java_type vt = match vt with - | JBasics.TBasic bt -> None, string_of_basic_type bt + | JBasics.TBasic bt + -> (None, string_of_basic_type bt) | JBasics.TObject ot -> - begin - match ot with - | JBasics.TArray vt -> None, (string_of_type vt)^"[]" - | JBasics.TClass cn -> cn_to_java_type cn - - end + match ot with + | JBasics.TArray vt + -> (None, string_of_type vt ^ "[]") + | JBasics.TClass cn + -> cn_to_java_type cn let method_signature_names ms = let return_type_name = match JBasics.ms_rtype ms with - | None -> - if String.equal (JBasics.ms_name ms) JConfig.constructor_name then - None - else - Some (None, JConfig.void) - | Some vt -> Some (vt_to_java_type vt) in + | None + -> if String.equal (JBasics.ms_name ms) JConfig.constructor_name then None + else Some (None, JConfig.void) + | Some vt + -> Some (vt_to_java_type vt) + in let rec args_to_signature l = - match l with - | [] -> [] - | vt:: tail -> (vt_to_java_type vt) :: (args_to_signature tail) in + match l with [] -> [] | vt :: tail -> vt_to_java_type vt :: args_to_signature tail + in let method_name = JBasics.ms_name ms in let args_types = args_to_signature (JBasics.ms_args ms) in (return_type_name, method_name, args_types) let get_method_kind m = - if Javalib.is_static_method m - then Typ.Procname.Static - else Typ.Procname.Non_Static + if Javalib.is_static_method m then Typ.Procname.Static else Typ.Procname.Non_Static let get_method_procname cn ms method_kind = let return_type_name, method_name, args_type_name = method_signature_names ms in let class_name = Typ.Name.Java.from_string (JBasics.cn_name cn) in let proc_name_java = - Typ.Procname.java class_name return_type_name method_name args_type_name method_kind in + Typ.Procname.java class_name return_type_name method_name args_type_name method_kind + in Typ.Procname.Java proc_name_java (* create a mangled procname from an abstract or concrete method *) @@ -206,11 +221,10 @@ let translate_method_name m = let cn, ms = JBasics.cms_split (Javalib.get_class_method_signature m) in get_method_procname cn ms (get_method_kind m) - let fieldname_create cn fs = - let fieldname = (JBasics.fs_name fs) in - let classname = (JBasics.cn_name cn) in - Typ.Fieldname.Java.from_string (classname^"."^fieldname) + let fieldname = JBasics.fs_name fs in + let classname = JBasics.cn_name cn in + Typ.Fieldname.Java.from_string (classname ^ "." ^ fieldname) let create_sil_class_field cn cf = let fs = cf.Javalib.cf_signature in @@ -220,19 +234,18 @@ let create_sil_class_field cn cf = let real_annotations = JAnnotation.translate_item cf.Javalib.cf_annotations in (* translate modifers like "volatile" as annotations *) match cf.Javalib.cf_kind with - | Javalib.Volatile -> (Annot.volatile, true) :: real_annotations - | Javalib.NotFinal | Final -> real_annotations in + | Javalib.Volatile + -> (Annot.volatile, true) :: real_annotations + | Javalib.NotFinal | Final + -> real_annotations + in (field_name, field_type, annotation) - (** Collect static field if static is true, otherwise non-static ones. *) let collect_class_field cn cf (statics, nonstatics) = let field = create_sil_class_field cn cf in - if Javalib.is_static_field (Javalib.ClassField cf) then - (field :: statics, nonstatics) - else - (statics, field :: nonstatics) - + if Javalib.is_static_field (Javalib.ClassField cf) then (field :: statics, nonstatics) + else (statics, field :: nonstatics) (** Collect an interface field. *) let collect_interface_field cn inf l = @@ -242,7 +255,6 @@ let collect_interface_field cn inf l = let annotation = JAnnotation.translate_item inf.Javalib.if_annotations in (field_name, field_type, annotation) :: l - let collect_models_class_fields classpath_field_map cn cf fields = let static, nonstatic = fields in let field_name, field_type, annotation = create_sil_class_field cn cf in @@ -251,99 +263,107 @@ let collect_models_class_fields classpath_field_map cn cf fields = if Typ.equal classpath_ft field_type then fields else (* TODO (#6711750): fix type equality for arrays before failing here *) - let () = L.(debug Capture Quiet) + let () = + L.(debug Capture Quiet) "Found inconsistent types for %s@\n\tclasspath: %a@\n\tmodels: %a@\n@." - (Typ.Fieldname.to_string field_name) - (Typ.pp_full Pp.text) classpath_ft - (Typ.pp_full Pp.text) field_type in fields + (Typ.Fieldname.to_string field_name) (Typ.pp_full Pp.text) classpath_ft + (Typ.pp_full Pp.text) field_type + in + fields with Not_found -> if Javalib.is_static_field (Javalib.ClassField cf) then - ((field_name, field_type, annotation):: static, nonstatic) - else - (static, (field_name, field_type, annotation):: nonstatic) - + ((field_name, field_type, annotation) :: static, nonstatic) + else (static, (field_name, field_type, annotation) :: nonstatic) let add_model_fields program classpath_fields cn = let statics, nonstatics = classpath_fields in let classpath_field_map = let collect_fields map = - List.fold - ~f:(fun map (fn, ft, _) -> Typ.Fieldname.Map.add fn ft map) ~init:map in - collect_fields (collect_fields Typ.Fieldname.Map.empty statics) nonstatics in + List.fold ~f:(fun map (fn, ft, _) -> Typ.Fieldname.Map.add fn ft map) ~init:map + in + collect_fields (collect_fields Typ.Fieldname.Map.empty statics) nonstatics + in try match JBasics.ClassMap.find cn (JClasspath.get_models program) with - | Javalib.JClass _ as jclass -> - Javalib.cf_fold - (collect_models_class_fields classpath_field_map cn) - jclass + | Javalib.JClass _ as jclass + -> Javalib.cf_fold (collect_models_class_fields classpath_field_map cn) jclass classpath_fields - | _ -> - classpath_fields + | _ + -> classpath_fields with Not_found -> classpath_fields - let rec get_all_fields program tenv cn = let extract_class_fields classname = - let { Typ.Struct.fields; statics } = get_class_struct_typ program tenv classname in - (statics, fields) in + let {Typ.Struct.fields; statics} = get_class_struct_typ program tenv classname in + (statics, fields) + in let trans_fields classname = match JClasspath.lookup_node classname program with - | Some (Javalib.JClass jclass) -> - let super_fields = + | Some Javalib.JClass jclass + -> let super_fields = match jclass.Javalib.c_super_class with - | None -> ([], []) - | Some super_classname -> extract_class_fields super_classname in + | None + -> ([], []) + | Some super_classname + -> extract_class_fields super_classname + in Javalib.cf_fold (collect_class_field classname) (Javalib.JClass jclass) super_fields - | Some (Javalib.JInterface jinterface) -> - let interface_fields = - Javalib.if_fold (collect_interface_field classname) (Javalib.JInterface jinterface) [] in + | Some Javalib.JInterface jinterface + -> let interface_fields = + Javalib.if_fold (collect_interface_field classname) (Javalib.JInterface jinterface) [] + in (interface_fields, []) - | _ -> ([], []) in + | _ + -> ([], []) + in trans_fields cn - and get_class_struct_typ program tenv cn = let name = typename_of_classname cn in match Tenv.lookup tenv name with - | Some struct_typ -> - struct_typ + | Some struct_typ + -> struct_typ | None -> - match JClasspath.lookup_node cn program with - | None -> - Tenv.mk_struct tenv name - | Some node -> - let create_super_list interface_names = - List.iter ~f:(fun cn -> ignore (get_class_struct_typ program tenv cn)) interface_names; - List.map ~f:typename_of_classname interface_names in - let supers, fields, statics, annots = - match node with - | Javalib.JInterface jinterface -> - let statics, _ = get_all_fields program tenv cn in - let sil_interface_list = create_super_list jinterface.Javalib.i_interfaces in - let item_annotation = JAnnotation.translate_item jinterface.Javalib.i_annotations in - (sil_interface_list, [], statics, item_annotation) - | Javalib.JClass jclass -> - let statics, nonstatics = - let classpath_static, classpath_nonstatic = get_all_fields program tenv cn in - add_model_fields program (classpath_static, classpath_nonstatic) cn in - let item_annotation = JAnnotation.translate_item jclass.Javalib.c_annotations in - let interface_list = create_super_list jclass.Javalib.c_interfaces in - let super_classname_list = - match jclass.Javalib.c_super_class with - | None -> interface_list (* base case of the recursion *) - | Some super_cn -> - ignore (get_class_struct_typ program tenv super_cn); - let super_classname = typename_of_classname super_cn in - super_classname :: interface_list in - (super_classname_list, nonstatics, statics, item_annotation) in - let methods = - Javalib.m_fold - (fun m procnames -> (translate_method_name m) :: procnames) - node [] in - Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots name + match JClasspath.lookup_node cn program with + | None + -> Tenv.mk_struct tenv name + | Some node + -> let create_super_list interface_names = + List.iter ~f:(fun cn -> ignore (get_class_struct_typ program tenv cn)) interface_names ; + List.map ~f:typename_of_classname interface_names + in + let supers, fields, statics, annots = + match node with + | Javalib.JInterface jinterface + -> let statics, _ = get_all_fields program tenv cn in + let sil_interface_list = create_super_list jinterface.Javalib.i_interfaces in + let item_annotation = JAnnotation.translate_item jinterface.Javalib.i_annotations in + (sil_interface_list, [], statics, item_annotation) + | Javalib.JClass jclass + -> let statics, nonstatics = + let classpath_static, classpath_nonstatic = get_all_fields program tenv cn in + add_model_fields program (classpath_static, classpath_nonstatic) cn + in + let item_annotation = JAnnotation.translate_item jclass.Javalib.c_annotations in + let interface_list = create_super_list jclass.Javalib.c_interfaces in + let super_classname_list = + match jclass.Javalib.c_super_class with + | None + -> interface_list (* base case of the recursion *) + | Some super_cn + -> ignore (get_class_struct_typ program tenv super_cn) ; + let super_classname = typename_of_classname super_cn in + super_classname :: interface_list + in + (super_classname_list, nonstatics, statics, item_annotation) + in + let methods = + Javalib.m_fold (fun m procnames -> translate_method_name m :: procnames) node [] + in + Tenv.mk_struct tenv ~fields ~statics ~methods ~supers ~annots name let get_class_type_no_pointer program tenv cn = - ignore (get_class_struct_typ program tenv cn); + ignore (get_class_struct_typ program tenv cn) ; Typ.mk (Tstruct (typename_of_classname cn)) let get_class_type program tenv cn = @@ -361,89 +381,78 @@ let is_closeable program tenv typ = let implements t = Prover.Subtyping_check.check_subtype tenv typ t in implements closeable_typ || implements autocloseable_typ - (** translate an object type *) let rec object_type program tenv ot = match ot with - | JBasics.TClass cn -> get_class_type program tenv cn - | JBasics.TArray at -> - Typ.mk (Tptr (Typ.mk (Tarray (value_type program tenv at, None, None)), Typ.Pk_pointer)) + | JBasics.TClass cn + -> get_class_type program tenv cn + | JBasics.TArray at + -> Typ.mk (Tptr (Typ.mk (Tarray (value_type program tenv at, None, None)), Typ.Pk_pointer)) (** translate a value type *) and value_type program tenv vt = match vt with - | JBasics.TBasic bt -> basic_type bt - | JBasics.TObject ot -> object_type program tenv ot - + | JBasics.TBasic bt + -> basic_type bt + | JBasics.TObject ot + -> object_type program tenv ot (** Translate object types into Exp.Sizeof expressions *) let sizeof_of_object_type program tenv ot subtype = match (object_type program tenv ot).Typ.desc with - | Typ.Tptr (typ, _) -> - Exp.Sizeof {typ; nbytes=None; dynamic_length=None; subtype} - | _ -> - raise (Type_tranlsation_error "Pointer or array type expected in tenv") - + | Typ.Tptr (typ, _) + -> Exp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype} + | _ + -> raise (Type_tranlsation_error "Pointer or array type expected in tenv") (** return the name and type of a formal parameter, looking up the class name in case of "this" *) let param_type program tenv cn name vt = - if String.equal (JBir.var_name_g name) (Mangled.to_string JConfig.this) - then get_class_type program tenv cn + if String.equal (JBir.var_name_g name) (Mangled.to_string JConfig.this) then + get_class_type program tenv cn else value_type program tenv vt - -let get_var_type_from_sig (context : JContext.t) var = +let get_var_type_from_sig (context: JContext.t) var = let program = context.program in let tenv = JContext.get_tenv context in - List.find_map ~f:( - fun (vt', var') -> - if JBir.var_equal var var' - then Some (param_type program tenv context.cn var' vt') - else None - ) + List.find_map + ~f:(fun (vt', var') -> + if JBir.var_equal var var' then Some (param_type program tenv context.cn var' vt') else None) (JBir.params context.impl) - let get_var_type context var = let typ_opt = JContext.get_var_type context var in - match typ_opt with - | Some _ -> typ_opt - | None -> get_var_type_from_sig context var - + match typ_opt with Some _ -> typ_opt | None -> get_var_type_from_sig context var let extract_array_type typ = match typ.Typ.desc with - | Typ.Tptr({desc=Tarray (vtyp, _, _)}, Typ.Pk_pointer) -> vtyp - | _ -> typ - + | Typ.Tptr ({desc= Tarray (vtyp, _, _)}, Typ.Pk_pointer) + -> vtyp + | _ + -> typ (** translate the type of an expression, looking in the method signature for formal parameters this is because variables in expressions do not have accurate types *) -let rec expr_type (context : JContext.t) expr = +let rec expr_type (context: JContext.t) expr = let program = context.program in let tenv = JContext.get_tenv context in match expr with - | JBir.Const const -> value_type program tenv (const_type const) - | JBir.Var (vt, var) -> - (match get_var_type context var with - | Some typ -> typ - | None -> (value_type program tenv vt)) - | JBir.Binop ((JBir.ArrayLoad _), e1, _) -> - let typ = expr_type context e1 in - (extract_array_type typ) - | _ -> value_type program tenv (JBir.type_of_expr expr) - + | JBir.Const const + -> value_type program tenv (const_type const) + | JBir.Var (vt, var) -> ( + match get_var_type context var with Some typ -> typ | None -> value_type program tenv vt ) + | JBir.Binop (JBir.ArrayLoad _, e1, _) + -> let typ = expr_type context e1 in + extract_array_type typ + | _ + -> value_type program tenv (JBir.type_of_expr expr) (** Returns the return type of the method based on the return type specified in ms. *) let return_type program tenv ms = - match JBasics.ms_rtype ms with - | None -> Typ.mk Tvoid - | Some vt -> value_type program tenv vt - + match JBasics.ms_rtype ms with None -> Typ.mk Tvoid | Some vt -> value_type program tenv vt let add_models_types tenv = let add_type t typename struct_typ = - if not (Tenv.mem t typename) then - Tenv.add tenv typename struct_typ in + if not (Tenv.mem t typename) then Tenv.add tenv typename struct_typ + in Tenv.iter (add_type tenv) !JClasspath.models_tenv diff --git a/infer/src/java/jTransType.mli b/infer/src/java/jTransType.mli index d9b78bd0c..f993e12c8 100644 --- a/infer/src/java/jTransType.mli +++ b/infer/src/java/jTransType.mli @@ -9,90 +9,89 @@ *) open! IStd - open Javalib_pack open Sawja_pack -(** transforms a Java type into a Sil named type *) val get_named_type : JBasics.value_type -> Typ.t +(** transforms a Java type into a Sil named type *) -(** transforms a Java class name into a Sil class name *) val typename_of_classname : JBasics.class_name -> Typ.Name.t +(** transforms a Java class name into a Sil class name *) -(** returns a name for a field based on a class name and a field name *) val fieldname_create : JBasics.class_name -> JBasics.field_signature -> Typ.Fieldname.t +(** returns a name for a field based on a class name and a field name *) val get_method_kind : JCode.jcode Javalib.jmethod -> Typ.Procname.method_kind -(** returns a procedure name based on the class name and the method's signature. *) val get_method_procname : JBasics.class_name -> JBasics.method_signature -> Typ.Procname.method_kind -> Typ.Procname.t +(** returns a procedure name based on the class name and the method's signature. *) -(** translate the SIL procedure name of the Java method *) val translate_method_name : JCode.jcode Javalib.jmethod -> Typ.Procname.t +(** translate the SIL procedure name of the Java method *) +val get_class_struct_typ : JClasspath.program -> Tenv.t -> JBasics.class_name -> Typ.Struct.t (** [get_class_struct_typ program tenv cn] returns the struct_typ representation of the class *) -val get_class_struct_typ: JClasspath.program -> Tenv.t -> JBasics.class_name -> Typ.Struct.t +val get_class_type_no_pointer : JClasspath.program -> Tenv.t -> JBasics.class_name -> Typ.t (** [get_class_type_no_pointer program tenv cn] returns the sil type representation of the class without the pointer part *) -val get_class_type_no_pointer: JClasspath.program -> Tenv.t -> JBasics.class_name -> Typ.t -(** [get_class_type program tenv cn] returns the sil type representation of the class *) val get_class_type : JClasspath.program -> Tenv.t -> JBasics.class_name -> Typ.t +(** [get_class_type program tenv cn] returns the sil type representation of the class *) -(** return true if [field_name] is the autogenerated C.$assertionsDisabled field for class C *) val is_autogenerated_assert_field : Typ.Fieldname.t -> bool +(** return true if [field_name] is the autogenerated C.$assertionsDisabled field for class C *) -(** [is_closeable program tenv typ] check if typ is an implemtation of the Closeable interface *) val is_closeable : JClasspath.program -> Tenv.t -> Typ.t -> bool +(** [is_closeable program tenv typ] check if typ is an implemtation of the Closeable interface *) -(** transforms a Java object type to a Typ.t *) val object_type : JClasspath.program -> Tenv.t -> JBasics.object_type -> Typ.t +(** transforms a Java object type to a Typ.t *) +val sizeof_of_object_type : + JClasspath.program -> Tenv.t -> JBasics.object_type -> Subtype.t -> Exp.t (** create sizeof expressions from the object type and the list of subtypes *) -val sizeof_of_object_type : JClasspath.program -> Tenv.t -> JBasics.object_type -> Subtype.t - -> Exp.t -(** transforms a Java type to a Typ.t. *) val value_type : JClasspath.program -> Tenv.t -> JBasics.value_type -> Typ.t +(** transforms a Java type to a Typ.t. *) -(** return the type of a formal parameter, looking up the class name in case of "this" *) val param_type : JClasspath.program -> Tenv.t -> JBasics.class_name -> JBir.var -> JBasics.value_type -> Typ.t +(** return the type of a formal parameter, looking up the class name in case of "this" *) -(** Returns the return type of the method based on the return type specified in ms. *) val return_type : JClasspath.program -> Tenv.t -> JBasics.method_signature -> Typ.t +(** Returns the return type of the method based on the return type specified in ms. *) -(** translates the type of an expression *) val expr_type : JContext.t -> JBir.expr -> Typ.t +(** translates the type of an expression *) -(** translates a conversion type from Java to Sil. *) val cast_type : JBir.conv -> Typ.t +(** translates a conversion type from Java to Sil. *) val package_to_string : string list -> string option -(** [create_array_type typ dim] creates an array type with dimension dim and content typ *) val create_array_type : Typ.t -> int -> Typ.t +(** [create_array_type typ dim] creates an array type with dimension dim and content typ *) -(** [extract_cn_type_np] returns the internal type of type when typ is a pointer type, otherwise returns typ *) val extract_cn_type_np : Typ.t -> Typ.t +(** [extract_cn_type_np] returns the internal type of type when typ is a pointer type, otherwise returns typ *) -(** [extract_cn_type_np] returns the Java class name of typ when typ is a pointer type, otherwise returns None *) val extract_cn_no_obj : Typ.t -> JBasics.class_name option +(** [extract_cn_type_np] returns the Java class name of typ when typ is a pointer type, otherwise returns None *) -(** returns a string representation of a Java basic type. *) val string_of_basic_type : JBasics.java_basic_type -> string +(** returns a string representation of a Java basic type. *) -(** returns a string representation of a Java type *) val string_of_type : JBasics.value_type -> string +(** returns a string representation of a Java type *) -(** returns a string representation of an object Java type *) val object_type_to_string : JBasics.object_type -> string +(** returns a string representation of an object Java type *) val vt_to_java_type : JBasics.value_type -> Typ.Procname.java_type val cn_to_java_type : JBasics.class_name -> Typ.Procname.java_type -(** Add the types of the models to the type environment passed as parameter *) val add_models_types : Tenv.t -> unit +(** Add the types of the models to the type environment passed as parameter *) diff --git a/infer/src/java_stubs/jMain.ml b/infer/src/java_stubs/jMain.ml index 908e49e31..7a1e066a2 100644 --- a/infer/src/java_stubs/jMain.ml +++ b/infer/src/java_stubs/jMain.ml @@ -10,4 +10,5 @@ open! IStd let from_arguments _ = () + let from_verbose_out _ = () diff --git a/infer/src/labs/ResourceLeakDomain.ml b/infer/src/labs/ResourceLeakDomain.ml index 8fcacb796..9829a2a0b 100644 --- a/infer/src/labs/ResourceLeakDomain.ml +++ b/infer/src/labs/ResourceLeakDomain.ml @@ -8,28 +8,26 @@ *) open! IStd - module F = Format module L = Logging (* Extremely simple abstraction of resources: count the number of acquired resources. If there's not a corresponding number of releases, there may be a leak. *) -type astate = int (* 2(a) *) +type astate = int +(* 2(a) *) (* For now, type of abstract state and summary are the same *) -type summary = astate (* 4(a) *) +type summary = astate + +(* 4(a) *) -let (<=) ~lhs ~rhs = - lhs <= rhs +let ( <= ) ~lhs ~rhs = lhs <= rhs -let join = - Pervasives.max +let join = Pervasives.max -let widen ~prev ~next ~num_iters:_ = - join prev next +let widen ~prev ~next ~num_iters:_ = join prev next -let pp fmt astate = - F.fprintf fmt "Resource count: %d" astate +let pp fmt astate = F.fprintf fmt "Resource count: %d" astate (* At the beginning of a procedure, assume no resources are held *) let initial = 0 diff --git a/infer/src/labs/ResourceLeaks.ml b/infer/src/labs/ResourceLeaks.ml index 50f434a92..56b32b108 100644 --- a/infer/src/labs/ResourceLeaks.ml +++ b/infer/src/labs/ResourceLeaks.ml @@ -8,79 +8,84 @@ *) open! IStd - module F = Format module L = Logging - module Domain = ResourceLeakDomain (* Boilerplate to write/read our summaries alongside the summaries of other analyzers *) -module Summary = Summary.Make(struct - type payload = Domain.astate +module Summary = Summary.Make (struct + type payload = Domain.astate - let update_payload resources_payload (summary : Specs.summary) = - { summary with payload = { summary.payload with resources = Some resources_payload }} + let update_payload resources_payload (summary: Specs.summary) = + {summary with payload= {summary.payload with resources= Some resources_payload}} - let read_payload (summary : Specs.summary) = - summary.payload.resources - end) + let read_payload (summary: Specs.summary) = summary.payload.resources +end) type extras = FormalMap.t module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type nonrec extras = extras (* Take an abstract state and instruction, produce a new abstract state *) - let exec_instr (astate : Domain.astate) { ProcData.pdesc; tenv; } _ (instr : HilInstr.t) = - let is_closeable procname tenv = match procname with - | Typ.Procname.Java java_procname -> - let is_closable_interface typename _ = match Typ.Name.name typename with - | "java.io.AutoCloseable" | "java.io.Closeable" -> true - | _ -> false in - PatternMatch.supertype_exists - tenv - is_closable_interface + let exec_instr (astate: Domain.astate) {ProcData.pdesc; tenv} _ (instr: HilInstr.t) = + let is_closeable procname tenv = + match procname with + | Typ.Procname.Java java_procname + -> let is_closable_interface typename _ = + match Typ.Name.name typename with + | "java.io.AutoCloseable" | "java.io.Closeable" + -> true + | _ + -> false + in + PatternMatch.supertype_exists tenv is_closable_interface (Typ.Name.Java.from_string (Typ.Procname.java_get_class_name java_procname)) - | _ -> - false in + | _ + -> false + in (* We assume all constructors of a subclass of Closeable acquire a resource *) let acquires_resource procname tenv = - Typ.Procname.is_constructor procname && is_closeable procname tenv in + Typ.Procname.is_constructor procname && is_closeable procname tenv + in (* We assume the close method of a Closeable releases all of its resources *) - let releases_resource procname tenv = match Typ.Procname.get_method procname with - | "close" -> is_closeable procname tenv - | _ -> false in + let releases_resource procname tenv = + match Typ.Procname.get_method procname with + | "close" + -> is_closeable procname tenv + | _ + -> false + in match instr with - | Call (_return_opt, Direct callee_procname, _actuals, _, _loc) -> + | Call (_return_opt, Direct callee_procname, _actuals, _, _loc) + -> ( (* function call [return_opt] := invoke [callee_procname]([actuals]) *) (* 1(e) *) let astate' = - if acquires_resource callee_procname tenv - then astate + 1 (* 2(a) *) - else if releases_resource callee_procname tenv - then astate - 1 (* 2(a) *) - else astate in - begin - match Summary.read_summary pdesc callee_procname with - | Some _summary -> - (* Looked up the summary for callee_procname... do something with it *) - (* 4(a) *) - (* 5(b) *) - astate' - | None -> - (* No summary for callee_procname; it's native code or missing for some reason *) - astate' - end - | Assign (_lhs_access_path, _rhs_exp, _loc) -> - (* an assigment [lhs_access_path] := [rhs_exp] *) + if acquires_resource callee_procname tenv then astate + 1 (* 2(a) *) + else if releases_resource callee_procname tenv then astate - 1 (* 2(a) *) + else astate + in + match Summary.read_summary pdesc callee_procname with + | Some _summary + -> (* Looked up the summary for callee_procname... do something with it *) + (* 4(a) *) + (* 5(b) *) + astate' + | None + -> (* No summary for callee_procname; it's native code or missing for some reason *) + astate' ) + | Assign (_lhs_access_path, _rhs_exp, _loc) + -> (* an assigment [lhs_access_path] := [rhs_exp] *) astate - | Assume (_assume_exp, _, _, _loc) -> - (* a conditional assume([assume_exp]). blocks if [assume_exp] evaluates to false *) + | Assume (_assume_exp, _, _, _loc) + -> (* a conditional assume([assume_exp]). blocks if [assume_exp] evaluates to false *) astate - | Call (_, Indirect _, _, _, _) -> - (* This should never happen in Java. Fail if it does. *) + | Call (_, Indirect _, _, _, _) + -> (* This should never happen in Java. Fail if it does. *) failwithf "Unexpected indirect call %a" HilInstr.pp instr end @@ -89,34 +94,34 @@ module Analyzer = AbstractInterpreter.Make (* Type of CFG to analyze--Exceptional to follow exceptional control-flow edges, Normal to ignore them *) - (ProcCfg.Normal) (* 5(a) *) - (LowerHil.Make(TransferFunctions)) + (ProcCfg.Normal) + (* 5(a) *) + (LowerHil.Make (TransferFunctions)) (* Callback for invoking the checker from the outside--registered in RegisterCheckers *) -let checker { Callbacks.summary; proc_desc; tenv; } : Specs.summary = +let checker {Callbacks.summary; proc_desc; tenv} : Specs.summary = (* Report an error when we have acquired more resources than we have released *) - let report leak_count (proc_data : extras ProcData.t) = - if leak_count > 0 (* 2(a) *) - then + let report leak_count (proc_data: extras ProcData.t) = + if leak_count > 0 (* 2(a) *) then let last_loc = Procdesc.Node.get_loc (Procdesc.get_exit_node proc_data.pdesc) in let issue_kind = Localise.to_issue_id Localise.resource_leak in let message = F.asprintf "Leaked %d resource(s)" leak_count in let exn = Exceptions.Checkers (issue_kind, Localise.verbatim_desc message) in - Reporting.log_error summary ~loc:last_loc exn in - + Reporting.log_error summary ~loc:last_loc exn + in (* Convert the abstract state to a summary. for now, just the identity function *) - let convert_to_summary (post : Domain.astate) : Domain.summary = + let convert_to_summary (post: Domain.astate) : Domain.summary = (* 4(a) *) - post in + post + in let extras = FormalMap.make proc_desc in let proc_data = ProcData.make proc_desc tenv extras in - let initial = ResourceLeakDomain.initial, IdAccessPathMapDomain.empty in + let initial = (ResourceLeakDomain.initial, IdAccessPathMapDomain.empty) in match Analyzer.compute_post proc_data ~initial ~debug:false with - | Some (post, _) -> - (* 1(f) *) - report post proc_data; + | Some (post, _) + -> (* 1(f) *) + report post proc_data ; Summary.update_summary (convert_to_summary post) summary - | None -> - failwithf - "Analyzer failed to compute post for %a" - Typ.Procname.pp (Procdesc.get_proc_name proc_data.pdesc) + | None + -> failwithf "Analyzer failed to compute post for %a" Typ.Procname.pp + (Procdesc.get_proc_name proc_data.pdesc) diff --git a/infer/src/opensource/FbThreadSafety.ml b/infer/src/opensource/FbThreadSafety.ml index d949237f0..e275e4796 100644 --- a/infer/src/opensource/FbThreadSafety.ml +++ b/infer/src/opensource/FbThreadSafety.ml @@ -9,6 +9,6 @@ open! IStd -let is_custom_init _ _ = false +let is_custom_init _ _ = false let is_logging_method _ = false diff --git a/infer/src/opensource/GraphQL.mli b/infer/src/opensource/GraphQL.mli index 483ecdbe7..79ce0cd92 100644 --- a/infer/src/opensource/GraphQL.mli +++ b/infer/src/opensource/GraphQL.mli @@ -9,9 +9,6 @@ open! IStd -module DeprecatedAPIUsage : -sig - val checker : - CLintersContext.context -> Ctl_parser_types.ast_node -> - CIssue.issue_desc option +module DeprecatedAPIUsage : sig + val checker : CLintersContext.context -> Ctl_parser_types.ast_node -> CIssue.issue_desc option end diff --git a/infer/src/opensource/fbTaint.ml b/infer/src/opensource/fbTaint.ml index e7fb2d139..9ad6ea170 100644 --- a/infer/src/opensource/fbTaint.ml +++ b/infer/src/opensource/fbTaint.ml @@ -10,5 +10,7 @@ open! IStd let sources = [] + let sinks = [] + let functions_with_tainted_params = [] diff --git a/infer/src/quandary/ClangTaintAnalysis.ml b/infer/src/quandary/ClangTaintAnalysis.ml index f37642252..7341099a1 100644 --- a/infer/src/quandary/ClangTaintAnalysis.ml +++ b/infer/src/quandary/ClangTaintAnalysis.ml @@ -8,104 +8,105 @@ *) open! IStd - module F = Format module L = Logging -include - TaintAnalysis.Make(struct - module Trace = ClangTrace - module AccessTree = AccessTree.Make(Trace) +include TaintAnalysis.Make (struct + module Trace = ClangTrace + module AccessTree = AccessTree.Make (Trace) - let to_summary_access_tree tree = QuandarySummary.AccessTree.Clang tree + let to_summary_access_tree tree = QuandarySummary.AccessTree.Clang tree - let of_summary_access_tree = function - | QuandarySummary.AccessTree.Clang tree -> tree - | _ -> assert false + let of_summary_access_tree = function + | QuandarySummary.AccessTree.Clang tree + -> tree + | _ + -> assert false - let handle_unknown_call pname ret_typ_opt actuals _ = - let handle_generic_unknown ret_typ_opt actuals = - match ret_typ_opt, List.rev actuals with - | Some _, _ -> - (* propagate taint from actuals to return value *) - [TaintSpec.Propagate_to_return] - | None, [] -> - [] - | None, _ when Typ.Procname.is_constructor pname -> - (* "this" is always the first arg of a constructor; propagate taint there *) - [TaintSpec.Propagate_to_receiver] - | None, - HilExp.AccessPath ((Var.ProgramVar pvar, { desc=Typ.Tptr (_, Typ.Pk_pointer) }), []) :: _ - when Pvar.is_frontend_tmp pvar -> - (* no return value, but the frontend has introduced a dummy return variable and will + let handle_unknown_call pname ret_typ_opt actuals _ = + let handle_generic_unknown ret_typ_opt actuals = + match (ret_typ_opt, List.rev actuals) with + | Some _, _ + -> (* propagate taint from actuals to return value *) + [TaintSpec.Propagate_to_return] + | None, [] + -> [] + | None, _ when Typ.Procname.is_constructor pname + -> (* "this" is always the first arg of a constructor; propagate taint there *) + [TaintSpec.Propagate_to_receiver] + | ( None + , (HilExp.AccessPath ((Var.ProgramVar pvar, {desc= Typ.Tptr (_, Typ.Pk_pointer)}), [])) + :: _ ) + when Pvar.is_frontend_tmp pvar + -> (* no return value, but the frontend has introduced a dummy return variable and will assign the return value to this variable. So propagate taint to the dummy return variable *) - let actual_index = List.length actuals - 1 in - [TaintSpec.Propagate_to_actual actual_index] - | None, _ -> - (* no return value; propagate taint from actuals to receiver *) - [TaintSpec.Propagate_to_receiver] in - - (* if we have a specific model for a procedure, use that. otherwise, use the generic - heuristics for dealing with unknown code *) - match Typ.Procname.get_method pname with - | "operator+=" - | "operator-=" - | "operator*=" - | "operator/=" - | "operator%=" - | "operator<<=" - | "operator>>=" - | "operator&=" - | "operator^=" - | "operator|=" -> - [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] - | "memcpy" | "memmove" | "strcpy" | "strncpy" -> - [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] - | "sprintf" -> + let actual_index = List.length actuals - 1 in + [TaintSpec.Propagate_to_actual actual_index] + | None, _ + -> (* no return value; propagate taint from actuals to receiver *) [TaintSpec.Propagate_to_receiver] - | _ -> - handle_generic_unknown ret_typ_opt actuals + in + (* if we have a specific model for a procedure, use that. otherwise, use the generic + heuristics for dealing with unknown code *) + match Typ.Procname.get_method pname with + | "operator+=" + | "operator-=" + | "operator*=" + | "operator/=" + | "operator%=" + | "operator<<=" + | "operator>>=" + | "operator&=" + | "operator^=" + | "operator|=" + -> [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] + | "memcpy" | "memmove" | "strcpy" | "strncpy" + -> [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] + | "sprintf" + -> [TaintSpec.Propagate_to_receiver] + | _ + -> handle_generic_unknown ret_typ_opt actuals - (* treat folly functions as unknown library code. we often specify folly functions as sinks, + (* treat folly functions as unknown library code. we often specify folly functions as sinks, and we don't want to double-report if these functions eventually call other sinks (e.g., when folly::Subprocess calls exec), in addition some folly functions are heavily optimized in a way that obscures what they're actually doing (e.g., they use assembly code). it's better to write models for these functions or treat them as unknown *) - let models_matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["folly"] + let models_matcher = QualifiedCppName.Match.of_fuzzy_qual_names ["folly"] - let get_model pname ret_typ_opt actuals tenv summary = - (* hack for default C++ constructors, which get translated as an empty body (and will thus + let get_model pname ret_typ_opt actuals tenv summary = + (* hack for default C++ constructors, which get translated as an empty body (and will thus have an empty summary). We don't want that because we want to be able to propagate taint from comstructor parameters to the constructed object. so we treat the empty constructor as a skip function instead *) - let is_default_constructor pname = - Typ.Procname.is_c_method pname && - Typ.Procname.is_constructor pname && - AccessTree.BaseMap.is_empty summary in - match pname with - | Typ.Procname.ObjC_Cpp _ - when is_default_constructor pname || - QualifiedCppName.Match.match_qualifiers - models_matcher (Typ.Procname.get_qualifiers pname) -> - Some (handle_unknown_call pname ret_typ_opt actuals tenv) - | _ -> - None + let is_default_constructor pname = + Typ.Procname.is_c_method pname && Typ.Procname.is_constructor pname + && AccessTree.BaseMap.is_empty summary + in + match pname with + | Typ.Procname.ObjC_Cpp _ + when is_default_constructor pname + || QualifiedCppName.Match.match_qualifiers models_matcher + (Typ.Procname.get_qualifiers pname) + -> Some (handle_unknown_call pname ret_typ_opt actuals tenv) + | _ + -> None - let external_sanitizers = - List.map - ~f:(fun { QuandaryConfig.Sanitizer.procedure; } -> - QualifiedCppName.Match.of_fuzzy_qual_names [procedure]) - (QuandaryConfig.Sanitizer.of_json Config.quandary_sanitizers) + let external_sanitizers = + List.map + ~f:(fun {QuandaryConfig.Sanitizer.procedure} -> + QualifiedCppName.Match.of_fuzzy_qual_names [procedure]) + (QuandaryConfig.Sanitizer.of_json Config.quandary_sanitizers) - let get_sanitizer pname = - let qualified_pname = Typ.Procname.get_qualifiers pname in - List.find_map - ~f:(fun qualifiers -> - if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname - then Some TaintSpec.Return - else None) - external_sanitizers + let get_sanitizer pname = + let qualified_pname = Typ.Procname.get_qualifiers pname in + List.find_map + ~f:(fun qualifiers -> + if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname then + Some TaintSpec.Return + else None) + external_sanitizers - let is_taintable_type _ = true - end) + let is_taintable_type _ = true +end) diff --git a/infer/src/quandary/ClangTrace.ml b/infer/src/quandary/ClangTrace.ml index 8444a83f6..bd7ddc9b4 100644 --- a/infer/src/quandary/ClangTrace.ml +++ b/infer/src/quandary/ClangTrace.ml @@ -8,31 +8,34 @@ *) open! IStd - module F = Format module L = Logging module SourceKind = struct type t = - | Endpoint of (Mangled.t * Typ.desc) (** source originating from formal of an endpoint *) - | EnvironmentVariable (** source that was read from an environment variable *) - | File (** source that was read from a file *) - | Other (** for testing or uncategorized sources *) + | Endpoint of (Mangled.t * Typ.desc) (** source originating from formal of an endpoint *) + | EnvironmentVariable (** source that was read from an environment variable *) + | File (** source that was read from a file *) + | Other (** for testing or uncategorized sources *) | Unknown - [@@deriving compare] + [@@deriving compare] let unknown = Unknown let of_string = function - | "Endpoint" -> Endpoint (Mangled.from_string "NONE", Typ.Tvoid) - | "EnvironmentVariable" -> EnvironmentVariable - | "File" -> File - | _ -> Other + | "Endpoint" + -> Endpoint (Mangled.from_string "NONE", Typ.Tvoid) + | "EnvironmentVariable" + -> EnvironmentVariable + | "File" + -> File + | _ + -> Other let external_sources = List.map - ~f:(fun { QuandaryConfig.Source.procedure; kind; index; } -> - QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index) + ~f:(fun {QuandaryConfig.Source.procedure; kind; index} -> + (QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index)) (QuandaryConfig.Source.of_json Config.quandary_sources) let endpoints = String.Set.of_list (QuandaryConfig.Endpoint.of_json Config.quandary_endpoints) @@ -42,99 +45,102 @@ module SourceKind = struct let return = None in List.find_map ~f:(fun (qualifiers, kind, index) -> - if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname - then - let source_index = - try Some (int_of_string index) - with Failure _ -> return in - Some (of_string kind, source_index) - else None) + if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname then + let source_index = + try Some (int_of_string index) + with Failure _ -> return + in + Some (of_string kind, source_index) + else None) external_sources let get pname _ = let return = None in match pname with - | Typ.Procname.ObjC_Cpp cpp_name -> + | Typ.Procname.ObjC_Cpp cpp_name + -> ( let qualified_pname = Typ.Procname.get_qualifiers pname in - begin - match - (QualifiedCppName.to_list - (Typ.Name.unqualified_name (Typ.Procname.objc_cpp_get_class_type_name cpp_name))), - Typ.Procname.get_method pname with - | ["std"; ("basic_istream" | "basic_iostream")], - ("getline" | "read" | "readsome" | "operator>>") -> - Some (File, Some 1) - | _ -> - get_external_source qualified_pname - end - | Typ.Procname.C _ -> - begin - match Typ.Procname.to_string pname with - | "getenv" -> - Some (EnvironmentVariable, return) - | _ -> - get_external_source (Typ.Procname.get_qualifiers pname) - end - | Typ.Procname.Block _ -> - None - | pname when BuiltinDecl.is_declared pname -> - None - | pname -> - failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname + match + ( QualifiedCppName.to_list + (Typ.Name.unqualified_name (Typ.Procname.objc_cpp_get_class_type_name cpp_name)) + , Typ.Procname.get_method pname ) + with + | ( ["std"; ("basic_istream" | "basic_iostream")] + , ("getline" | "read" | "readsome" | "operator>>") ) + -> Some (File, Some 1) + | _ + -> get_external_source qualified_pname ) + | Typ.Procname.C _ -> ( + match Typ.Procname.to_string pname with + | "getenv" + -> Some (EnvironmentVariable, return) + | _ + -> get_external_source (Typ.Procname.get_qualifiers pname) ) + | Typ.Procname.Block _ + -> None + | pname when BuiltinDecl.is_declared pname + -> None + | pname + -> failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname let get_tainted_formals pdesc _ = match Procdesc.get_proc_name pdesc with - | (Typ.Procname.ObjC_Cpp objc) as pname -> - let qualified_pname = - F.sprintf "%s::%s" - (Typ.Procname.objc_cpp_get_class_name objc) - (Typ.Procname.get_method pname) in - if String.Set.mem endpoints qualified_pname - then + | Typ.Procname.ObjC_Cpp objc as pname + -> let qualified_pname = + F.sprintf "%s::%s" (Typ.Procname.objc_cpp_get_class_name objc) + (Typ.Procname.get_method pname) + in + if String.Set.mem endpoints qualified_pname then List.map - ~f:(fun (name, typ) -> name, typ, Some (Endpoint (name, typ.Typ.desc))) + ~f:(fun (name, typ) -> (name, typ, Some (Endpoint (name, typ.Typ.desc)))) (Procdesc.get_formals pdesc) - else - Source.all_formals_untainted pdesc - | _ -> - Source.all_formals_untainted pdesc + else Source.all_formals_untainted pdesc + | _ + -> Source.all_formals_untainted pdesc let pp fmt kind = F.fprintf fmt "%s" - (match kind with - | Endpoint (formal_name, _) -> F.sprintf "Endpoint[%s]" (Mangled.to_string formal_name) - | EnvironmentVariable -> "EnvironmentVariable" - | File -> "File" - | Other -> "Other" - | Unknown -> "Unknown") + ( match kind with + | Endpoint (formal_name, _) + -> F.sprintf "Endpoint[%s]" (Mangled.to_string formal_name) + | EnvironmentVariable + -> "EnvironmentVariable" + | File + -> "File" + | Other + -> "Other" + | Unknown + -> "Unknown" ) end -module CppSource = Source.Make(SourceKind) +module CppSource = Source.Make (SourceKind) module SinkKind = struct - type t = - | Allocation (** memory allocation *) - | ShellExec (** shell exec function *) - | SQL (** SQL query *) - | Other (** for testing or uncategorized sinks *) - [@@deriving compare] + | Allocation (** memory allocation *) + | ShellExec (** shell exec function *) + | SQL (** SQL query *) + | Other (** for testing or uncategorized sinks *) + [@@deriving compare] let of_string = function - | "Allocation" -> Allocation - | "ShellExec" -> ShellExec - | "SQL" -> SQL - | _ -> Other + | "Allocation" + -> Allocation + | "ShellExec" + -> ShellExec + | "SQL" + -> SQL + | _ + -> Other let external_sinks = List.map - ~f:(fun { QuandaryConfig.Sink.procedure; kind; index; } -> - QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index) + ~f:(fun {QuandaryConfig.Sink.procedure; kind; index} -> + (QualifiedCppName.Match.of_fuzzy_qual_names [procedure], kind, index)) (QuandaryConfig.Sink.of_json Config.quandary_sinks) (* taint the nth parameter (0-indexed) *) - let taint_nth n kind = - Some (kind, IntSet.singleton n) + let taint_nth n kind = Some (kind, IntSet.singleton n) let taint_all actuals kind = Some (kind, IntSet.of_list (List.mapi ~f:(fun actual_num _ -> actual_num) actuals)) @@ -144,89 +150,89 @@ module SinkKind = struct let qualified_pname = Typ.Procname.get_qualifiers pname in List.find_map ~f:(fun (qualifiers, kind, index) -> - if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname - then - let kind = of_string kind in - try - let n = int_of_string index in - taint_nth n kind - with Failure _ -> - (* couldn't parse the index, just taint everything *) - taint_all actuals kind - else - None) + if QualifiedCppName.Match.match_qualifiers qualifiers qualified_pname then + let kind = of_string kind in + try + let n = int_of_string index in + taint_nth n kind + with Failure _ -> + (* couldn't parse the index, just taint everything *) + taint_all actuals kind + else None) external_sinks let get pname actuals _ = match pname with - | Typ.Procname.ObjC_Cpp _ -> - get_external_sink pname actuals - | Typ.Procname.C _ -> - begin - match Typ.Procname.to_string pname with - | "execl" | "execlp" | "execle" | "execv" | "execve" | "execvp" | "system" -> - taint_all actuals ShellExec - | "brk" | "calloc" | "malloc" | "realloc" | "sbrk" -> - taint_all actuals Allocation - | _ -> - get_external_sink pname actuals - end - | Typ.Procname.Block _ -> - None - | pname when BuiltinDecl.is_declared pname -> - None - | pname -> - failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname + | Typ.Procname.ObjC_Cpp _ + -> get_external_sink pname actuals + | Typ.Procname.C _ -> ( + match Typ.Procname.to_string pname with + | "execl" | "execlp" | "execle" | "execv" | "execve" | "execvp" | "system" + -> taint_all actuals ShellExec + | "brk" | "calloc" | "malloc" | "realloc" | "sbrk" + -> taint_all actuals Allocation + | _ + -> get_external_sink pname actuals ) + | Typ.Procname.Block _ + -> None + | pname when BuiltinDecl.is_declared pname + -> None + | pname + -> failwithf "Non-C++ procname %a in C++ analysis@." Typ.Procname.pp pname let pp fmt kind = F.fprintf fmt - (match kind with - | Allocation -> "Allocation" - | ShellExec -> "ShellExec" - | SQL -> "SQL" - | Other -> "Other") + ( match kind with + | Allocation + -> "Allocation" + | ShellExec + -> "ShellExec" + | SQL + -> "SQL" + | Other + -> "Other" ) end -module CppSink = Sink.Make(SinkKind) - -include - Trace.Make(struct - module Source = CppSource - module Sink = CppSink - - let should_report source sink = - (* using this to match custom string wrappers such as folly::StringPiece *) - let is_stringy typ = - let lowercase_typ = String.lowercase (Typ.to_string (Typ.mk typ)) in - String.is_substring ~substring:"string" lowercase_typ || - String.is_substring ~substring:"char*" lowercase_typ in - match Source.kind source, Sink.kind sink with - | Endpoint (_, typ), (ShellExec | SQL) -> - (* untrusted string data flowing to shell exec/SQL *) - is_stringy typ - | (EnvironmentVariable | File), (ShellExec | SQL) -> - (* untrusted environment var or file data flowing to shell exec *) - true - | (Endpoint _ | EnvironmentVariable | File), Allocation -> - (* untrusted data flowing to memory allocation *) - true - | _, (Allocation | Other | ShellExec | SQL) when Source.is_footprint source -> - (* is this var a command line flag created by the popular gflags library? *) - let is_gflag pvar = - String.is_substring ~substring:"FLAGS_" (Pvar.get_simplified_name pvar) in - begin - match Option.map ~f:AccessPath.extract (Source.get_footprint_access_path source) with - | Some ((Var.ProgramVar pvar, _), _) when Pvar.is_global pvar && is_gflag pvar -> - (* gflags globals come from the environment; treat them as sources *) - true - | _ -> - false - end - | Other, _ -> - (* Other matches everything *) - true - | _, Other -> - true - | Unknown, (Allocation | ShellExec | SQL) -> - false - end) +module CppSink = Sink.Make (SinkKind) + +include Trace.Make (struct + module Source = CppSource + module Sink = CppSink + + let should_report source sink = + (* using this to match custom string wrappers such as folly::StringPiece *) + let is_stringy typ = + let lowercase_typ = String.lowercase (Typ.to_string (Typ.mk typ)) in + String.is_substring ~substring:"string" lowercase_typ + || String.is_substring ~substring:"char*" lowercase_typ + in + match (Source.kind source, Sink.kind sink) with + | Endpoint (_, typ), (ShellExec | SQL) + -> (* untrusted string data flowing to shell exec/SQL *) + is_stringy typ + | (EnvironmentVariable | File), (ShellExec | SQL) + -> (* untrusted environment var or file data flowing to shell exec *) + true + | (Endpoint _ | EnvironmentVariable | File), Allocation + -> (* untrusted data flowing to memory allocation *) + true + | _, (Allocation | Other | ShellExec | SQL) when Source.is_footprint source + -> ( + (* is this var a command line flag created by the popular gflags library? *) + let is_gflag pvar = + String.is_substring ~substring:"FLAGS_" (Pvar.get_simplified_name pvar) + in + match Option.map ~f:AccessPath.extract (Source.get_footprint_access_path source) with + | Some ((Var.ProgramVar pvar, _), _) when Pvar.is_global pvar && is_gflag pvar + -> (* gflags globals come from the environment; treat them as sources *) + true + | _ + -> false ) + | Other, _ + -> (* Other matches everything *) + true + | _, Other + -> true + | Unknown, (Allocation | ShellExec | SQL) + -> false +end) diff --git a/infer/src/quandary/JavaTaintAnalysis.ml b/infer/src/quandary/JavaTaintAnalysis.ml index 70c4ee448..f09857cdc 100644 --- a/infer/src/quandary/JavaTaintAnalysis.ml +++ b/infer/src/quandary/JavaTaintAnalysis.ml @@ -8,107 +8,102 @@ *) open! IStd - module F = Format module L = Logging -include - TaintAnalysis.Make(struct - module Trace = JavaTrace - module AccessTree = AccessTree.Make(Trace) +include TaintAnalysis.Make (struct + module Trace = JavaTrace + module AccessTree = AccessTree.Make (Trace) - let to_summary_access_tree access_tree = QuandarySummary.AccessTree.Java access_tree + let to_summary_access_tree access_tree = QuandarySummary.AccessTree.Java access_tree - let of_summary_access_tree = function - | QuandarySummary.AccessTree.Java access_tree -> access_tree - | _ -> assert false + let of_summary_access_tree = function + | QuandarySummary.AccessTree.Java access_tree + -> access_tree + | _ + -> assert false - let handle_unknown_call pname ret_typ_opt actuals tenv = - let get_receiver_typ tenv = function - | HilExp.AccessPath access_path -> AccessPath.Raw.get_typ access_path tenv - | _ -> None in - let types_match typ class_string tenv = - match typ with - | Some ({ Typ.desc=Typ.Tptr ({desc=Tstruct original_typename}, _) }) -> - PatternMatch.supertype_exists - tenv - (fun typename _ -> String.equal (Typ.Name.name typename) class_string) - original_typename - | _ -> - false in - match pname with - | (Typ.Procname.Java java_pname) as pname -> - let is_static = Typ.Procname.java_is_static pname in - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname, - ret_typ_opt with - | "android.content.Intent", ("putExtra" | "putExtras"), _ -> - (* don't care about tainted extras. instead. we'll check that result of getExtra is + let handle_unknown_call pname ret_typ_opt actuals tenv = + let get_receiver_typ tenv = function + | HilExp.AccessPath access_path + -> AccessPath.Raw.get_typ access_path tenv + | _ + -> None + in + let types_match typ class_string tenv = + match typ with + | Some {Typ.desc= Typ.Tptr ({desc= Tstruct original_typename}, _)} + -> PatternMatch.supertype_exists tenv + (fun typename _ -> String.equal (Typ.Name.name typename) class_string) + original_typename + | _ + -> false + in + match pname with + | Typ.Procname.Java java_pname as pname + -> ( + let is_static = Typ.Procname.java_is_static pname in + match + ( Typ.Procname.java_get_class_name java_pname + , Typ.Procname.java_get_method java_pname + , ret_typ_opt ) + with + | "android.content.Intent", ("putExtra" | "putExtras"), _ + -> (* don't care about tainted extras. instead. we'll check that result of getExtra is always used safely *) - [] - | _ when Typ.Procname.is_constructor pname -> - [TaintSpec.Propagate_to_receiver] - | _, _, (Some {Typ.desc=Tvoid} | None) when not is_static -> - (* for instance methods with no return value, propagate the taint to the receiver *) - [TaintSpec.Propagate_to_receiver] - | classname, _, Some ({Typ.desc=Tptr _ | Tstruct _}) -> - begin - match actuals with - | receiver_exp :: _ - when not is_static && - types_match (get_receiver_typ tenv receiver_exp) classname tenv -> - (* if the receiver and return type are the same, propagate to both. we're + [] + | _ when Typ.Procname.is_constructor pname + -> [TaintSpec.Propagate_to_receiver] + | _, _, (Some {Typ.desc= Tvoid} | None) when not is_static + -> (* for instance methods with no return value, propagate the taint to the receiver *) + [TaintSpec.Propagate_to_receiver] + | classname, _, Some {Typ.desc= Tptr _ | Tstruct _} -> ( + match actuals with + | receiver_exp :: _ + when not is_static && types_match (get_receiver_typ tenv receiver_exp) classname tenv + -> (* if the receiver and return type are the same, propagate to both. we're assuming the call is one of the common "builder-style" methods that both updates and returns the receiver *) - [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] - | _ -> - (* receiver doesn't match return type; just propagate to the return type *) - [TaintSpec.Propagate_to_return] - end - | _ -> - [] - end - | pname when BuiltinDecl.is_declared pname -> - [] - | pname -> - failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname + [TaintSpec.Propagate_to_receiver; TaintSpec.Propagate_to_return] + | _ + -> (* receiver doesn't match return type; just propagate to the return type *) + [TaintSpec.Propagate_to_return] ) + | _ + -> [] ) + | pname when BuiltinDecl.is_declared pname + -> [] + | pname + -> failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname - let get_model _ _ _ _ _ = None + let get_model _ _ _ _ _ = None - let external_sanitizers = - List.map - ~f:(fun { QuandaryConfig.Sanitizer.procedure; } -> Str.regexp procedure) - (QuandaryConfig.Sanitizer.of_json Config.quandary_sanitizers) + let external_sanitizers = + List.map + ~f:(fun {QuandaryConfig.Sanitizer.procedure} -> Str.regexp procedure) + (QuandaryConfig.Sanitizer.of_json Config.quandary_sanitizers) - let get_sanitizer = function - | Typ.Procname.Java java_pname -> - let procedure_string = - Printf.sprintf "%s.%s" - (Typ.Procname.java_get_class_name java_pname) - (Typ.Procname.java_get_method java_pname) in - List.find_map - ~f:(fun procedure_regex -> - if Str.string_match procedure_regex procedure_string 0 - then Some TaintSpec.Return - else None) - external_sanitizers - | _ -> - None + let get_sanitizer = function + | Typ.Procname.Java java_pname + -> let procedure_string = + Printf.sprintf "%s.%s" (Typ.Procname.java_get_class_name java_pname) + (Typ.Procname.java_get_method java_pname) + in + List.find_map + ~f:(fun procedure_regex -> + if Str.string_match procedure_regex procedure_string 0 then Some TaintSpec.Return + else None) + external_sanitizers + | _ + -> None - let is_taintable_type typ = - match typ.Typ.desc with - | Typ.Tptr ({desc=Tstruct (JavaClass typename)}, _) | Tstruct (JavaClass typename) -> - begin - match Mangled.to_string_full typename with - | "android.content.Intent" - | "android.net.Uri" - | "java.lang.String" - | "java.net.URI" -> - true - | _ -> - false - end - | _ -> - false - end) + let is_taintable_type typ = + match typ.Typ.desc with + | Typ.Tptr ({desc= Tstruct JavaClass typename}, _) | Tstruct JavaClass typename -> ( + match Mangled.to_string_full typename with + | "android.content.Intent" | "android.net.Uri" | "java.lang.String" | "java.net.URI" + -> true + | _ + -> false ) + | _ + -> false +end) diff --git a/infer/src/quandary/JavaTrace.ml b/infer/src/quandary/JavaTrace.ml index cc1b0ae64..cfbc312b0 100644 --- a/infer/src/quandary/JavaTrace.ml +++ b/infer/src/quandary/JavaTrace.ml @@ -8,350 +8,331 @@ *) open! IStd - module F = Format module L = Logging module SourceKind = struct type t = - | Clipboard (** data read from the clipboard service *) - | Intent (** external Intent or a value read from one *) - | Other (** for testing or uncategorized sources *) - | PrivateData (** private user or device-specific data *) - | UserControlledURI (** resource locator controller by user *) + | Clipboard (** data read from the clipboard service *) + | Intent (** external Intent or a value read from one *) + | Other (** for testing or uncategorized sources *) + | PrivateData (** private user or device-specific data *) + | UserControlledURI (** resource locator controller by user *) | Unknown - [@@deriving compare] + [@@deriving compare] let unknown = Unknown let of_string = function - | "Clipboard" -> Clipboard - | "Intent" -> Intent - | "PrivateData" -> PrivateData - | "UserControlledURI" -> UserControlledURI - | _ -> Other + | "Clipboard" + -> Clipboard + | "Intent" + -> Intent + | "PrivateData" + -> PrivateData + | "UserControlledURI" + -> UserControlledURI + | _ + -> Other let external_sources = List.map - ~f:(fun { QuandaryConfig.Source.procedure; kind; } -> Str.regexp procedure, kind) + ~f:(fun {QuandaryConfig.Source.procedure; kind} -> (Str.regexp procedure, kind)) (QuandaryConfig.Source.of_json Config.quandary_sources) let get pname tenv = let return = None in match pname with - | Typ.Procname.Java pname -> - begin - match Typ.Procname.java_get_class_name pname, Typ.Procname.java_get_method pname with - | "android.location.Location", - ("getAltitude" | "getBearing" | "getLatitude" | "getLongitude" | "getSpeed") -> - Some (PrivateData, return) - | "android.telephony.TelephonyManager", - ("getDeviceId" | - "getLine1Number" | - "getSimSerialNumber" | - "getSubscriberId" | - "getVoiceMailNumber") -> - Some (PrivateData, return) - | "com.facebook.infer.builtins.InferTaint", "inferSecretSource" -> - Some (Other, return) - | class_name, method_name -> - let taint_matching_supertype typename _ = - match Typ.Name.name typename, method_name with - | "android.app.Activity", "getIntent" -> - Some (Intent, return) - | "android.content.Intent", "getStringExtra" -> - Some (Intent, return) - | "android.content.SharedPreferences", "getString" -> - Some (PrivateData, return) - | ("android.content.ClipboardManager" | "android.text.ClipboardManager"), - ("getPrimaryClip" | "getText") -> - Some (Clipboard, return) - | _ -> - None in - let kind_opt = - PatternMatch.supertype_find_map_opt - tenv - taint_matching_supertype - (Typ.Name.Java.from_string class_name) in - begin - match kind_opt with - | Some _ -> kind_opt - | None -> - (* check the list of externally specified sources *) - let procedure = class_name ^ "." ^ method_name in - List.find_map - ~f:(fun (procedure_regex, kind) -> - if Str.string_match procedure_regex procedure 0 - then Some (of_string kind, return) - else None) - external_sources - end - end - | pname when BuiltinDecl.is_declared pname -> None - | pname -> failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname + | Typ.Procname.Java pname -> ( + match (Typ.Procname.java_get_class_name pname, Typ.Procname.java_get_method pname) with + | ( "android.location.Location" + , ("getAltitude" | "getBearing" | "getLatitude" | "getLongitude" | "getSpeed") ) + -> Some (PrivateData, return) + | ( "android.telephony.TelephonyManager" + , ( "getDeviceId" | "getLine1Number" | "getSimSerialNumber" | "getSubscriberId" + | "getVoiceMailNumber" ) ) + -> Some (PrivateData, return) + | "com.facebook.infer.builtins.InferTaint", "inferSecretSource" + -> Some (Other, return) + | class_name, method_name + -> let taint_matching_supertype typename _ = + match (Typ.Name.name typename, method_name) with + | "android.app.Activity", "getIntent" + -> Some (Intent, return) + | "android.content.Intent", "getStringExtra" + -> Some (Intent, return) + | "android.content.SharedPreferences", "getString" + -> Some (PrivateData, return) + | ( ("android.content.ClipboardManager" | "android.text.ClipboardManager") + , ("getPrimaryClip" | "getText") ) + -> Some (Clipboard, return) + | _ + -> None + in + let kind_opt = + PatternMatch.supertype_find_map_opt tenv taint_matching_supertype + (Typ.Name.Java.from_string class_name) + in + match kind_opt with + | Some _ + -> kind_opt + | None + -> (* check the list of externally specified sources *) + let procedure = class_name ^ "." ^ method_name in + List.find_map + ~f:(fun (procedure_regex, kind) -> + if Str.string_match procedure_regex procedure 0 then Some (of_string kind, return) + else None) + external_sources ) + | pname when BuiltinDecl.is_declared pname + -> None + | pname + -> failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname let get_tainted_formals pdesc tenv = - let make_untainted (name, typ) = - name, typ, None in + let make_untainted (name, typ) = (name, typ, None) in let taint_formals_with_types type_strs kind formals = - let taint_formal_with_types ((formal_name, formal_typ) as formal) = - let matches_classname = match formal_typ.Typ.desc with - | Tptr ({desc=Tstruct typename}, _) -> - List.mem ~equal:String.equal type_strs (Typ.Name.name typename) - | _ -> - false in - if matches_classname - then - formal_name, formal_typ, Some kind - else - make_untainted formal in - List.map ~f:taint_formal_with_types formals in - + let taint_formal_with_types (formal_name, formal_typ as formal) = + let matches_classname = + match formal_typ.Typ.desc with + | Tptr ({desc= Tstruct typename}, _) + -> List.mem ~equal:String.equal type_strs (Typ.Name.name typename) + | _ + -> false + in + if matches_classname then (formal_name, formal_typ, Some kind) else make_untainted formal + in + List.map ~f:taint_formal_with_types formals + in let formals = Procdesc.get_formals pdesc in match Procdesc.get_proc_name pdesc with - | Typ.Procname.Java java_pname -> - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | "codetoanalyze.java.quandary.TaintedFormals", "taintedContextBad" -> - taint_formals_with_types ["java.lang.Integer"; "java.lang.String"] Other formals - | class_name, method_name -> - let taint_matching_supertype typename _ = - match Typ.Name.name typename, method_name with - | "android.app.Activity", ("onActivityResult" | "onNewIntent") -> - Some (taint_formals_with_types ["android.content.Intent"] Intent formals) - | "android.app.Service", - ("onBind" | - "onRebind" | - "onStart" | - "onStartCommand" | - "onTaskRemoved" | - "onUnbind") -> - Some (taint_formals_with_types ["android.content.Intent"] Intent formals) - | "android.content.BroadcastReceiver", "onReceive" -> - Some (taint_formals_with_types ["android.content.Intent"] Intent formals) - | "android.content.ContentProvider", - ("bulkInsert" | - "call" | - "delete" | - "insert" | - "getType" | - "openAssetFile" | - "openFile" | - "openPipeHelper" | - "openTypedAssetFile" | - "query" | - "refresh" | - "update") -> - Some - (taint_formals_with_types - ["android.net.Uri"; "java.lang.String"] UserControlledURI formals) - | "android.webkit.WebViewClient", - ("onLoadResource" | "shouldInterceptRequest" | "shouldOverrideUrlLoading") -> - Some - (taint_formals_with_types - ["android.webkit.WebResourceRequest"; "java.lang.String"] - UserControlledURI - formals) - | "android.webkit.WebChromeClient", - ("onJsAlert" | "onJsBeforeUnload" | "onJsConfirm" | "onJsPrompt") -> - Some (taint_formals_with_types ["java.lang.String"] UserControlledURI formals) - | _ -> - None in - begin - match - PatternMatch.supertype_find_map_opt - tenv - taint_matching_supertype - (Typ.Name.Java.from_string class_name) with - | Some tainted_formals -> tainted_formals - | None -> Source.all_formals_untainted pdesc - end - end - | procname -> - failwithf - "Non-Java procedure %a where only Java procedures are expected" - Typ.Procname.pp procname + | Typ.Procname.Java java_pname -> ( + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | "codetoanalyze.java.quandary.TaintedFormals", "taintedContextBad" + -> taint_formals_with_types ["java.lang.Integer"; "java.lang.String"] Other formals + | class_name, method_name + -> let taint_matching_supertype typename _ = + match (Typ.Name.name typename, method_name) with + | "android.app.Activity", ("onActivityResult" | "onNewIntent") + -> Some (taint_formals_with_types ["android.content.Intent"] Intent formals) + | ( "android.app.Service" + , ( "onBind" | "onRebind" | "onStart" | "onStartCommand" | "onTaskRemoved" + | "onUnbind" ) ) + -> Some (taint_formals_with_types ["android.content.Intent"] Intent formals) + | "android.content.BroadcastReceiver", "onReceive" + -> Some (taint_formals_with_types ["android.content.Intent"] Intent formals) + | ( "android.content.ContentProvider" + , ( "bulkInsert" | "call" | "delete" | "insert" | "getType" | "openAssetFile" + | "openFile" | "openPipeHelper" | "openTypedAssetFile" | "query" | "refresh" + | "update" ) ) + -> Some + (taint_formals_with_types ["android.net.Uri"; "java.lang.String"] + UserControlledURI formals) + | ( "android.webkit.WebViewClient" + , ("onLoadResource" | "shouldInterceptRequest" | "shouldOverrideUrlLoading") ) + -> Some + (taint_formals_with_types + ["android.webkit.WebResourceRequest"; "java.lang.String"] UserControlledURI + formals) + | ( "android.webkit.WebChromeClient" + , ("onJsAlert" | "onJsBeforeUnload" | "onJsConfirm" | "onJsPrompt") ) + -> Some (taint_formals_with_types ["java.lang.String"] UserControlledURI formals) + | _ + -> None + in + match + PatternMatch.supertype_find_map_opt tenv taint_matching_supertype + (Typ.Name.Java.from_string class_name) + with + | Some tainted_formals + -> tainted_formals + | None + -> Source.all_formals_untainted pdesc ) + | procname + -> failwithf "Non-Java procedure %a where only Java procedures are expected" Typ.Procname.pp + procname let pp fmt kind = F.fprintf fmt - (match kind with - | Clipboard -> "Clipboard" - | Intent -> "Intent" - | UserControlledURI -> "UserControlledURI" - | PrivateData -> "PrivateData" - | Other -> "Other" - | Unknown -> "Unknown") + ( match kind with + | Clipboard + -> "Clipboard" + | Intent + -> "Intent" + | UserControlledURI + -> "UserControlledURI" + | PrivateData + -> "PrivateData" + | Other + -> "Other" + | Unknown + -> "Unknown" ) end -module JavaSource = Source.Make(SourceKind) +module JavaSource = Source.Make (SourceKind) module SinkKind = struct type t = - | CreateFile (** sink that creates a file *) - | CreateIntent (** sink that creates an Intent *) - | JavaScript (** sink that passes its arguments to untrusted JS code *) - | Logging (** sink that logs one or more of its arguments *) - | StartComponent (** sink that launches an Activity, Service, etc. *) - | Other (** for testing or uncategorized sinks *) - [@@deriving compare] + | CreateFile (** sink that creates a file *) + | CreateIntent (** sink that creates an Intent *) + | JavaScript (** sink that passes its arguments to untrusted JS code *) + | Logging (** sink that logs one or more of its arguments *) + | StartComponent (** sink that launches an Activity, Service, etc. *) + | Other (** for testing or uncategorized sinks *) + [@@deriving compare] let of_string = function - | "CreateFile" -> CreateFile - | "CreateIntent" -> CreateIntent - | "JavaScript" -> JavaScript - | "Logging" -> Logging - | "StartComponent" -> StartComponent - | _ -> Other + | "CreateFile" + -> CreateFile + | "CreateIntent" + -> CreateIntent + | "JavaScript" + -> JavaScript + | "Logging" + -> Logging + | "StartComponent" + -> StartComponent + | _ + -> Other let external_sinks = List.map - ~f:(fun { QuandaryConfig.Sink.procedure; kind; index; } -> - Str.regexp procedure, kind, index) + ~f:(fun {QuandaryConfig.Sink.procedure; kind; index} -> (Str.regexp procedure, kind, index)) (QuandaryConfig.Sink.of_json Config.quandary_sinks) let get pname actuals tenv = (* taint all the inputs of [pname]. for non-static procedures, taints the "this" parameter only if [taint_this] is true. *) - let taint_all ?(taint_this=false) kind = + let taint_all ?(taint_this= false) kind = let actuals_to_taint, offset = - if Typ.Procname.java_is_static pname || taint_this - then actuals, 0 - else List.tl_exn actuals, 1 in + if Typ.Procname.java_is_static pname || taint_this then (actuals, 0) + else (List.tl_exn actuals, 1) + in let indexes = - IntSet.of_list (List.mapi ~f:(fun param_num _ -> param_num + offset) actuals_to_taint) in - Some (kind, indexes) in - + IntSet.of_list (List.mapi ~f:(fun param_num _ -> param_num + offset) actuals_to_taint) + in + Some (kind, indexes) + in (* taint the nth non-"this" parameter (0-indexed) *) let taint_nth n kind = let first_index = if Typ.Procname.java_is_static pname then n else n + 1 in - Some (kind, IntSet.singleton first_index) in + Some (kind, IntSet.singleton first_index) + in match pname with - | Typ.Procname.Java java_pname -> - begin - match Typ.Procname.java_get_class_name java_pname, - Typ.Procname.java_get_method java_pname with - | "android.util.Log", ("e" | "println" | "w" | "wtf") -> - taint_all Logging - | "java.io.File", "" - | "java.nio.file.FileSystem", "getPath" - | "java.nio.file.Paths", "get" -> - taint_all CreateFile - | "com.facebook.infer.builtins.InferTaint", "inferSensitiveSink" -> - taint_nth 0 Other - | class_name, method_name -> - let taint_matching_supertype typename _ = - match Typ.Name.name typename, method_name with - | "android.app.Activity", - ("startActivityFromChild" | "startActivityFromFragment") -> - taint_nth 1 StartComponent - | "android.app.Activity", "startIntentSenderForResult" -> - taint_nth 2 StartComponent - | "android.app.Activity", "startIntentSenderFromChild" -> - taint_nth 3 StartComponent - | "android.content.Context", - ("bindService" | - "sendBroadcast" | - "sendBroadcastAsUser" | - "sendOrderedBroadcast" | - "sendOrderedBroadcastAsUser" | - "sendStickyBroadcast" | - "sendStickyBroadcastAsUser" | - "sendStickyOrderedBroadcast" | - "sendStickyOrderedBroadcastAsUser" | - "startActivities" | - "startActivity" | - "startActivityForResult" | - "startActivityIfNeeded" | - "startNextMatchingActivity" | - "startService" | - "stopService") -> - taint_nth 0 StartComponent - | "android.content.Context", "startIntentSender" -> - taint_nth 1 StartComponent - | "android.content.Intent", - ("parseUri" | - "getIntent" | - "getIntentOld" | - "setComponent" | - "setData" | - "setDataAndNormalize" | - "setDataAndType" | - "setDataAndTypeAndNormalize" | - "setPackage") -> - taint_nth 0 CreateIntent - | "android.content.Intent", "setClassName" -> - taint_all CreateIntent - | "android.webkit.WebView", - ("evaluateJavascript" | - "loadData" | - "loadDataWithBaseURL" | - "loadUrl" | - "postUrl" | - "postWebMessage") -> - taint_all JavaScript - | class_name, method_name -> - (* check the list of externally specified sinks *) - let procedure = class_name ^ "." ^ method_name in - List.find_map - ~f:(fun (procedure_regex, kind, index) -> - if Str.string_match procedure_regex procedure 0 - then - let kind = of_string kind in - try - let n = int_of_string index in - taint_nth n kind - with Failure _ -> - (* couldn't parse the index, just taint everything *) - taint_all kind - else - None) - external_sinks in - PatternMatch.supertype_find_map_opt - tenv - taint_matching_supertype - (Typ.Name.Java.from_string class_name) - end - | pname when BuiltinDecl.is_declared pname -> None - | pname -> failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname + | Typ.Procname.Java java_pname -> ( + match + (Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname) + with + | "android.util.Log", ("e" | "println" | "w" | "wtf") + -> taint_all Logging + | "java.io.File", "" + | "java.nio.file.FileSystem", "getPath" + | "java.nio.file.Paths", "get" + -> taint_all CreateFile + | "com.facebook.infer.builtins.InferTaint", "inferSensitiveSink" + -> taint_nth 0 Other + | class_name, method_name + -> let taint_matching_supertype typename _ = + match (Typ.Name.name typename, method_name) with + | "android.app.Activity", ("startActivityFromChild" | "startActivityFromFragment") + -> taint_nth 1 StartComponent + | "android.app.Activity", "startIntentSenderForResult" + -> taint_nth 2 StartComponent + | "android.app.Activity", "startIntentSenderFromChild" + -> taint_nth 3 StartComponent + | ( "android.content.Context" + , ( "bindService" | "sendBroadcast" | "sendBroadcastAsUser" | "sendOrderedBroadcast" + | "sendOrderedBroadcastAsUser" | "sendStickyBroadcast" + | "sendStickyBroadcastAsUser" | "sendStickyOrderedBroadcast" + | "sendStickyOrderedBroadcastAsUser" | "startActivities" | "startActivity" + | "startActivityForResult" | "startActivityIfNeeded" | "startNextMatchingActivity" + | "startService" | "stopService" ) ) + -> taint_nth 0 StartComponent + | "android.content.Context", "startIntentSender" + -> taint_nth 1 StartComponent + | ( "android.content.Intent" + , ( "parseUri" | "getIntent" | "getIntentOld" | "setComponent" | "setData" + | "setDataAndNormalize" | "setDataAndType" | "setDataAndTypeAndNormalize" + | "setPackage" ) ) + -> taint_nth 0 CreateIntent + | "android.content.Intent", "setClassName" + -> taint_all CreateIntent + | ( "android.webkit.WebView" + , ( "evaluateJavascript" | "loadData" | "loadDataWithBaseURL" | "loadUrl" | "postUrl" + | "postWebMessage" ) ) + -> taint_all JavaScript + | class_name, method_name + -> (* check the list of externally specified sinks *) + let procedure = class_name ^ "." ^ method_name in + List.find_map + ~f:(fun (procedure_regex, kind, index) -> + if Str.string_match procedure_regex procedure 0 then + let kind = of_string kind in + try + let n = int_of_string index in + taint_nth n kind + with Failure _ -> + (* couldn't parse the index, just taint everything *) + taint_all kind + else None) + external_sinks + in + PatternMatch.supertype_find_map_opt tenv taint_matching_supertype + (Typ.Name.Java.from_string class_name) ) + | pname when BuiltinDecl.is_declared pname + -> None + | pname + -> failwithf "Non-Java procname %a in Java analysis@." Typ.Procname.pp pname let pp fmt kind = F.fprintf fmt - (match kind with - | CreateFile -> "CreateFile" - | CreateIntent -> "CreateIntent" - | JavaScript -> "JavaScript" - | Logging -> "Logging" - | StartComponent -> "StartComponent" - | Other -> "Other") + ( match kind with + | CreateFile + -> "CreateFile" + | CreateIntent + -> "CreateIntent" + | JavaScript + -> "JavaScript" + | Logging + -> "Logging" + | StartComponent + -> "StartComponent" + | Other + -> "Other" ) end -module JavaSink = Sink.Make(SinkKind) +module JavaSink = Sink.Make (SinkKind) -include - Trace.Make(struct - module Source = JavaSource - module Sink = JavaSink +include Trace.Make (struct + module Source = JavaSource + module Sink = JavaSink - let should_report source sink = - if Source.is_footprint source - then false - else - match Source.kind source, Sink.kind sink with - | PrivateData, Logging (* logging private data issue *) - | Intent, StartComponent (* intent reuse issue *) - | Intent, CreateIntent (* intent configured with external values issue *) - | Intent, JavaScript (* external data flows into JS: remote code execution risk *) - | PrivateData, JavaScript (* leaking private data into JS *) - | UserControlledURI, (CreateIntent | StartComponent) - (* create intent/launch component from user-controlled URI *) - | UserControlledURI, CreateFile - (* create file from user-controller URI; potential path-traversal vulnerability *) - | Clipboard, (StartComponent | CreateIntent | JavaScript | CreateFile) -> - (* do something sensitive with user-controlled data from the clipboard *) - true - | Other, _ | _, Other -> (* for testing purposes, Other matches everything *) - true - | _ -> - false - end) + let should_report source sink = + if Source.is_footprint source then false + else + match (Source.kind source, Sink.kind sink) with + | PrivateData, Logging + (* logging private data issue *) + | Intent, StartComponent + (* intent reuse issue *) + | Intent, CreateIntent + (* intent configured with external values issue *) + | Intent, JavaScript + (* external data flows into JS: remote code execution risk *) + | PrivateData, JavaScript + (* leaking private data into JS *) + | UserControlledURI, (CreateIntent | StartComponent) + (* create intent/launch component from user-controlled URI *) + | UserControlledURI, CreateFile + (* create file from user-controller URI; potential path-traversal vulnerability *) + | Clipboard, (StartComponent | CreateIntent | JavaScript | CreateFile) + -> (* do something sensitive with user-controlled data from the clipboard *) + true + | Other, _ | _, Other + -> (* for testing purposes, Other matches everything *) + true + | _ + -> false +end) diff --git a/infer/src/quandary/QuandaryConfig.ml b/infer/src/quandary/QuandaryConfig.ml index 816d7d87d..6ae1d13d9 100644 --- a/infer/src/quandary/QuandaryConfig.ml +++ b/infer/src/quandary/QuandaryConfig.ml @@ -8,66 +8,70 @@ *) open! IStd - module F = Format (** utilities for importing JSON specifications of sources/sinks into Quandary *) module Source = struct - type t = { procedure : string; kind : string; index : string; } + type t = {procedure: string; kind: string; index: string} let of_json = function - | `List sources -> - let parse_source json = + | `List sources + -> let parse_source json = let open Yojson.Basic in let procedure = Util.member "procedure" json |> Util.to_string in let kind = Util.member "kind" json |> Util.to_string in let index = - Util.member "index" json |> Util.to_string_option |> Option.value ~default:"return" in - { procedure; kind; index; } in + Util.member "index" json |> Util.to_string_option |> Option.value ~default:"return" + in + {procedure; kind; index} + in List.map ~f:parse_source sources - | _ -> - [] + | _ + -> [] end module Sink = struct - type t = { procedure : string; kind : string; index : string; } + type t = {procedure: string; kind: string; index: string} let of_json = function - | `List sinks -> - let parse_sink json = + | `List sinks + -> let parse_sink json = let open Yojson.Basic in let procedure = Util.member "procedure" json |> Util.to_string in let kind = Util.member "kind" json |> Util.to_string in let index = - Util.member "index" json |> Util.to_string_option |> Option.value ~default:"all" in - { procedure; kind; index; } in + Util.member "index" json |> Util.to_string_option |> Option.value ~default:"all" + in + {procedure; kind; index} + in List.map ~f:parse_sink sinks - | _ -> - [] + | _ + -> [] end module Sanitizer = struct - type t = { procedure : string; } + type t = {procedure: string} let of_json = function - | `List sinks -> - let parse_sanitizer json = + | `List sinks + -> let parse_sanitizer json = let open Yojson.Basic in let procedure = Util.member "procedure" json |> Util.to_string in - { procedure; } in + {procedure} + in List.map ~f:parse_sanitizer sinks - | _ -> - [] + | _ + -> [] end module Endpoint = struct type t = string let of_json = function - | `List endpoints -> - let parse_endpoint = Yojson.Basic.Util.to_string in + | `List endpoints + -> let parse_endpoint = Yojson.Basic.Util.to_string in List.map ~f:parse_endpoint endpoints - | _ -> - [] + | _ + -> [] end diff --git a/infer/src/quandary/QuandaryConfig.mli b/infer/src/quandary/QuandaryConfig.mli index 5b2a5c579..3685c0b0a 100644 --- a/infer/src/quandary/QuandaryConfig.mli +++ b/infer/src/quandary/QuandaryConfig.mli @@ -12,25 +12,26 @@ open! IStd (** utilities for importing JSON specifications of sources/sinks into Quandary*) module Source : sig - type t = { procedure : string; kind : string; index : string; } + type t = {procedure: string; kind: string; index: string} - val of_json : [> `List of Yojson.Basic.json list ] -> t list + val of_json : [> `List of Yojson.Basic.json list] -> t list end module Sink : sig - type t = { procedure : string; kind : string; index : string; } + type t = {procedure: string; kind: string; index: string} - val of_json : [> `List of Yojson.Basic.json list ] -> t list + val of_json : [> `List of Yojson.Basic.json list] -> t list end module Sanitizer : sig - type t = { procedure : string; } + type t = {procedure: string} - val of_json : [> `List of Yojson.Basic.json list ] -> t list + val of_json : [> `List of Yojson.Basic.json list] -> t list end module Endpoint : sig - type t = string (** name of endpoint class *) + (** name of endpoint class *) + type t = string - val of_json : [> `List of Yojson.Basic.json list ] -> t list + val of_json : [> `List of Yojson.Basic.json list] -> t list end diff --git a/infer/src/quandary/QuandarySummary.ml b/infer/src/quandary/QuandarySummary.ml index a137a7987..c4d625a31 100644 --- a/infer/src/quandary/QuandarySummary.ml +++ b/infer/src/quandary/QuandarySummary.ml @@ -10,21 +10,19 @@ (** summary type for Quandary taint analysis *) open! IStd - module F = Format module L = Logging - -module Java = AccessTree.Make(JavaTrace) -module Clang = AccessTree.Make(ClangTrace) +module Java = AccessTree.Make (JavaTrace) +module Clang = AccessTree.Make (ClangTrace) module AccessTree = struct - type t = - | Java of Java.t - | Clang of Clang.t + type t = Java of Java.t | Clang of Clang.t let pp fmt = function - | Java access_tree -> Java.pp fmt access_tree - | Clang access_tree -> Clang.pp fmt access_tree + | Java access_tree + -> Java.pp fmt access_tree + | Clang access_tree + -> Clang.pp fmt access_tree end type t = AccessTree.t diff --git a/infer/src/quandary/QuandarySummary.mli b/infer/src/quandary/QuandarySummary.mli index c79d4645e..d299db34b 100644 --- a/infer/src/quandary/QuandarySummary.mli +++ b/infer/src/quandary/QuandarySummary.mli @@ -9,18 +9,16 @@ open! IStd - (** summary type for Quandary taint analysis *) module F = Format -module Java : module type of (AccessTree.Make(JavaTrace)) -module Clang : module type of (AccessTree.Make(ClangTrace)) +module Java : module type of AccessTree.Make (JavaTrace) + +module Clang : module type of AccessTree.Make (ClangTrace) module AccessTree : sig - type t = - | Java of Java.t - | Clang of Clang.t + type t = Java of Java.t | Clang of Clang.t end type t = AccessTree.t diff --git a/infer/src/quandary/TaintAnalysis.ml b/infer/src/quandary/TaintAnalysis.ml index 302acb34e..33c4b232e 100644 --- a/infer/src/quandary/TaintAnalysis.ml +++ b/infer/src/quandary/TaintAnalysis.ml @@ -8,73 +8,75 @@ *) open! IStd - module F = Format module L = Logging (** Create a taint analysis from a specification *) module Make (TaintSpecification : TaintSpec.S) = struct - module TraceDomain = TaintSpecification.Trace module TaintDomain = TaintSpecification.AccessTree - module Summary = Summary.Make(struct - type payload = QuandarySummary.t + module Summary = Summary.Make (struct + type payload = QuandarySummary.t - let update_payload quandary_payload (summary : Specs.summary) = - { summary with payload = { summary.payload with quandary = Some quandary_payload }} + let update_payload quandary_payload (summary: Specs.summary) = + {summary with payload= {summary.payload with quandary= Some quandary_payload}} - let read_payload (summary : Specs.summary) = - summary.payload.quandary - end) + let read_payload (summary: Specs.summary) = summary.payload.quandary + end) module Domain = TaintDomain - type extras = { formal_map : FormalMap.t; summary : Specs.summary; } + type extras = {formal_map: FormalMap.t; summary: Specs.summary} module TransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = Domain + type nonrec extras = extras (* get the node associated with [access_path] in [access_tree] *) - let access_path_get_node access_path access_tree (proc_data : extras ProcData.t) = + let access_path_get_node access_path access_tree (proc_data: extras ProcData.t) = match TaintDomain.get_node access_path access_tree with - | Some _ as node_opt -> - node_opt - | None -> - let make_footprint_trace footprint_ap = + | Some _ as node_opt + -> node_opt + | None + -> let make_footprint_trace footprint_ap = let trace = TraceDomain.of_source - (TraceDomain.Source.make_footprint footprint_ap proc_data.pdesc) in - Some (TaintDomain.make_normal_leaf trace) in + (TraceDomain.Source.make_footprint footprint_ap proc_data.pdesc) + in + Some (TaintDomain.make_normal_leaf trace) + in let root, _ = AccessPath.extract access_path in match FormalMap.get_formal_index root proc_data.extras.formal_map with - | Some formal_index -> - make_footprint_trace (AccessPath.to_footprint formal_index access_path) - | None -> - if Var.is_global (fst root) - then make_footprint_trace access_path - else None + | Some formal_index + -> make_footprint_trace (AccessPath.to_footprint formal_index access_path) + | None + -> if Var.is_global (fst root) then make_footprint_trace access_path else None (* get the trace associated with [access_path] in [access_tree]. *) let access_path_get_trace access_path access_tree proc_data = match access_path_get_node access_path access_tree proc_data with - | Some (trace, _) -> trace - | None -> TraceDomain.empty + | Some (trace, _) + -> trace + | None + -> TraceDomain.empty let exp_get_node_ ~abstracted raw_access_path access_tree proc_data = let access_path = - if abstracted - then AccessPath.Abstracted raw_access_path - else AccessPath.Exact raw_access_path in + if abstracted then AccessPath.Abstracted raw_access_path + else AccessPath.Exact raw_access_path + in access_path_get_node access_path access_tree proc_data (* get the node associated with [exp] in [access_tree] *) - let hil_exp_get_node ?(abstracted=false) (exp : HilExp.t) access_tree proc_data = + let hil_exp_get_node ?(abstracted= false) (exp: HilExp.t) access_tree proc_data = match exp with - | AccessPath access_path -> exp_get_node_ ~abstracted access_path access_tree proc_data - | _ -> None + | AccessPath access_path + -> exp_get_node_ ~abstracted access_path access_tree proc_data + | _ + -> None let add_return_source source ret_base access_tree = let trace = TraceDomain.of_source source in @@ -83,571 +85,565 @@ module Make (TaintSpecification : TaintSpec.S) = struct let add_actual_source source index actuals access_tree proc_data = match List.nth_exn actuals index with - | HilExp.AccessPath actual_ap_raw -> - let actual_ap = AccessPath.Exact actual_ap_raw in + | HilExp.AccessPath actual_ap_raw + -> let actual_ap = AccessPath.Exact actual_ap_raw in let trace = access_path_get_trace actual_ap access_tree proc_data in TaintDomain.add_trace actual_ap (TraceDomain.add_source source trace) access_tree - | _ -> - access_tree - | exception (Failure _) -> - failwithf "Bad source specification: index %d out of bounds" index + | _ + -> access_tree + | exception Failure _ + -> failwithf "Bad source specification: index %d out of bounds" index let endpoints = - lazy (String.Set.of_list (QuandaryConfig.Endpoint.of_json Config.quandary_endpoints)) + (lazy (String.Set.of_list (QuandaryConfig.Endpoint.of_json Config.quandary_endpoints))) let is_endpoint source = match CallSite.pname (TraceDomain.Source.call_site source) with - | Typ.Procname.Java java_pname -> - String.Set.mem (Lazy.force endpoints) (Typ.Procname.java_get_class_name java_pname) - | _ -> - false + | Typ.Procname.Java java_pname + -> String.Set.mem (Lazy.force endpoints) (Typ.Procname.java_get_class_name java_pname) + | _ + -> false (** log any new reportable source-sink flows in [trace] *) - let report_trace ?(sink_indexes=IntSet.empty) trace cur_site (proc_data : extras ProcData.t) = + let report_trace ?(sink_indexes= IntSet.empty) trace cur_site (proc_data: extras ProcData.t) = let get_summary pname = - if Typ.Procname.equal pname (Procdesc.get_proc_name proc_data.pdesc) - then + if Typ.Procname.equal pname (Procdesc.get_proc_name proc_data.pdesc) then (* read_summary will trigger ondemand analysis of the current proc. we don't want that. *) TaintDomain.empty else match Summary.read_summary proc_data.pdesc pname with - | Some summary -> TaintSpecification.of_summary_access_tree summary - | None -> TaintDomain.empty in - + | Some summary + -> TaintSpecification.of_summary_access_tree summary + | None + -> TaintDomain.empty + in let get_short_trace_string original_source final_sink = - F.asprintf - "%a -> %a%s" - TraceDomain.Source.pp original_source - TraceDomain.Sink.pp final_sink - (if is_endpoint original_source then ". Note: source is an endpoint." else "") in - + F.asprintf "%a -> %a%s" TraceDomain.Source.pp original_source TraceDomain.Sink.pp + final_sink + (if is_endpoint original_source then ". Note: source is an endpoint." else "") + in let report_one (source, sink, _) = let open TraceDomain in - let rec expand_source source0 ((report_acc, seen_acc) as acc) = + let rec expand_source source0 (report_acc, seen_acc as acc) = let kind = Source.kind source0 in let call_site = Source.call_site source0 in let seen_acc' = CallSite.Set.add call_site seen_acc in - let is_recursive source = - CallSite.Set.mem (Source.call_site source) seen_acc' in + let is_recursive source = CallSite.Set.mem (Source.call_site source) seen_acc' in let matching_sources = (* TODO: group by matching call sites, remember all access paths *) TaintDomain.trace_fold (fun acc access_path trace -> - match List.find - ~f:(fun source -> - [%compare.equal : Source.Kind.t] - kind (Source.kind source) && not (is_recursive source)) - (Sources.elements (sources trace)) - with - | Some matching_source -> (Some access_path, matching_source) :: acc - | None -> acc) + match + List.find + ~f:(fun source -> + [%compare.equal : Source.Kind.t] kind (Source.kind source) + && not (is_recursive source)) + (Sources.elements (sources trace)) + with + | Some matching_source + -> (Some access_path, matching_source) :: acc + | None + -> acc) (get_summary (CallSite.pname call_site)) - [] in + [] + in match matching_sources with - | ((_, matching_source) as choice) :: _ -> - expand_source matching_source (choice :: report_acc, seen_acc') - | [] -> - acc in - let rec expand_sink sink0 indexes0 ((report_acc, seen_acc) as acc) = + | (_, matching_source as choice) :: _ + -> expand_source matching_source (choice :: report_acc, seen_acc') + | [] + -> acc + in + let rec expand_sink sink0 indexes0 (report_acc, seen_acc as acc) = let kind = Sink.kind sink0 in let call_site = Sink.call_site sink0 in let seen_acc' = CallSite.Set.add call_site seen_acc in - let is_recursive sink = - CallSite.Set.mem (Sink.call_site sink) seen_acc' in + let is_recursive sink = CallSite.Set.mem (Sink.call_site sink) seen_acc' in let matching_sinks = TaintDomain.trace_fold (fun acc _ trace -> - match List.find - ~f:(fun sink -> - [%compare.equal : Sink.Kind.t] - kind (Sink.kind sink) && not (is_recursive sink)) - (Sinks.elements (sinks trace)) - with - | Some matching_sink -> - let indexes_match = not - (IntSet.is_empty (IntSet.inter indexes0 (get_footprint_indexes trace))) in - (matching_sink, indexes_match) :: acc - | None -> - acc) + match + List.find + ~f:(fun sink -> + [%compare.equal : Sink.Kind.t] kind (Sink.kind sink) + && not (is_recursive sink)) + (Sinks.elements (sinks trace)) + with + | Some matching_sink + -> let indexes_match = + not (IntSet.is_empty (IntSet.inter indexes0 (get_footprint_indexes trace))) + in + (matching_sink, indexes_match) :: acc + | None + -> acc) (get_summary (CallSite.pname call_site)) - [] in + [] + in try (* try to find a sink whose indexes match the current sink *) let matching_sink, _ = List.find_exn ~f:snd matching_sinks in - expand_sink - matching_sink (Sink.indexes matching_sink) (matching_sink :: report_acc, seen_acc') + expand_sink matching_sink (Sink.indexes matching_sink) + (matching_sink :: report_acc, seen_acc') with Not_found -> (* didn't find a sink whose indexes match; this can happen when taint flows in via a global. pick any sink whose kind matches *) - begin - match matching_sinks with - | (matching_sink, _) :: _ -> - expand_sink - matching_sink - (Sink.indexes matching_sink) - (matching_sink :: report_acc, seen_acc') - | [] -> - acc - end in - let expanded_sources, _ = expand_source source ([None, source], CallSite.Set.empty) in - let expanded_sinks, _ = - expand_sink sink sink_indexes ([sink], CallSite.Set.empty) in + match matching_sinks with + | (matching_sink, _) :: _ + -> expand_sink matching_sink (Sink.indexes matching_sink) + (matching_sink :: report_acc, seen_acc') + | [] + -> acc + in + let expanded_sources, _ = expand_source source ([(None, source)], CallSite.Set.empty) in + let expanded_sinks, _ = expand_sink sink sink_indexes ([sink], CallSite.Set.empty) in let source_trace = let pp_access_path_opt fmt = function - | None -> F.fprintf fmt "" - | Some access_path -> - let base, _ = AccessPath.extract access_path in - F.fprintf fmt " with tainted data %a" - AccessPath.pp - (if Var.is_footprint (fst base) - then - (* TODO: resolve footprint identifier to formal name *) - access_path - else - access_path) in - List.map ~f:(fun (access_path_opt, source) -> + | None + -> F.fprintf fmt "" + | Some access_path + -> let base, _ = AccessPath.extract access_path in + F.fprintf fmt " with tainted data %a" AccessPath.pp + ( if Var.is_footprint (fst base) then + (* TODO: resolve footprint identifier to formal name *) + access_path + else access_path ) + in + List.map + ~f:(fun (access_path_opt, source) -> let call_site = Source.call_site source in let desc = - Format.asprintf - "Return from %a%a" - Typ.Procname.pp (CallSite.pname call_site) - pp_access_path_opt access_path_opt in + Format.asprintf "Return from %a%a" Typ.Procname.pp (CallSite.pname call_site) + pp_access_path_opt access_path_opt + in Errlog.make_trace_element 0 (CallSite.loc call_site) desc []) - expanded_sources in + expanded_sources + in let sink_trace = - List.map ~f:(fun sink -> + List.map + ~f:(fun sink -> let call_site = Sink.call_site sink in let desc = Format.asprintf "Call to %a" Typ.Procname.pp (CallSite.pname call_site) in Errlog.make_trace_element 0 (CallSite.loc call_site) desc []) - expanded_sinks in + expanded_sinks + in let msg = Localise.to_issue_id Localise.quandary_taint_error in let _, original_source = List.hd_exn expanded_sources in let final_sink = List.hd_exn expanded_sinks in let trace_str = get_short_trace_string original_source final_sink in - let ltr = source_trace @ (List.rev sink_trace) in + let ltr = source_trace @ List.rev sink_trace in let exn = Exceptions.Checkers (msg, Localise.verbatim_desc trace_str) in - Reporting.log_error proc_data.extras.summary ~loc:(CallSite.loc cur_site) ~ltr exn in - + Reporting.log_error proc_data.extras.summary ~loc:(CallSite.loc cur_site) ~ltr exn + in List.iter ~f:report_one (TraceDomain.get_reports ~cur_site trace) let add_sink sink actuals access_tree proc_data callee_site = (* add [sink] to the trace associated with the [formal_index]th actual *) let add_sink_to_actual sink_index access_tree_acc = match List.nth_exn actuals sink_index with - | HilExp.AccessPath actual_ap_raw -> + | HilExp.AccessPath actual_ap_raw + -> ( let actual_ap = AccessPath.Abstracted actual_ap_raw in - begin - match access_path_get_node actual_ap access_tree_acc proc_data with - | Some (actual_trace, _) -> - let sink' = - let indexes = TraceDomain.get_footprint_indexes actual_trace in - TraceDomain.Sink.make ~indexes (TraceDomain.Sink.kind sink) callee_site in - let actual_trace' = TraceDomain.add_sink sink' actual_trace in - report_trace actual_trace' callee_site proc_data; - TaintDomain.add_trace actual_ap actual_trace' access_tree_acc - | None -> - access_tree_acc - end - | _ -> - access_tree_acc in + match access_path_get_node actual_ap access_tree_acc proc_data with + | Some (actual_trace, _) + -> let sink' = + let indexes = TraceDomain.get_footprint_indexes actual_trace in + TraceDomain.Sink.make ~indexes (TraceDomain.Sink.kind sink) callee_site + in + let actual_trace' = TraceDomain.add_sink sink' actual_trace in + report_trace actual_trace' callee_site proc_data ; + TaintDomain.add_trace actual_ap actual_trace' access_tree_acc + | None + -> access_tree_acc ) + | _ + -> access_tree_acc + in IntSet.fold add_sink_to_actual (TraceDomain.Sink.indexes sink) access_tree - let apply_summary - ret_opt - (actuals : HilExp.t list) - summary - caller_access_tree - (proc_data : extras ProcData.t) - callee_site = - + let apply_summary ret_opt (actuals: HilExp.t list) summary caller_access_tree + (proc_data: extras ProcData.t) callee_site = let get_caller_ap formal_ap = - let apply_return ret_ap = match ret_opt with - | Some base_var -> Some (AccessPath.with_base base_var ret_ap) - | None -> - Logging.internal_error "Have summary for retval, but no ret id to bind it to: %a@\n" - AccessPath.pp ret_ap; - None in + let apply_return ret_ap = + match ret_opt with + | Some base_var + -> Some (AccessPath.with_base base_var ret_ap) + | None + -> Logging.internal_error "Have summary for retval, but no ret id to bind it to: %a@\n" + AccessPath.pp ret_ap ; + None + in let get_actual_ap formal_index = Option.value_map - ~f:(function - | HilExp.AccessPath access_path -> Some access_path - | _ -> None) - ~default:None - (List.nth actuals formal_index) in + ~f:(function HilExp.AccessPath access_path -> Some access_path | _ -> None) + ~default:None (List.nth actuals formal_index) + in let project ~formal_ap ~actual_ap = let projected_ap = AccessPath.append actual_ap (snd (AccessPath.extract formal_ap)) in - if AccessPath.is_exact formal_ap - then AccessPath.Exact projected_ap - else AccessPath.Abstracted projected_ap in + if AccessPath.is_exact formal_ap then AccessPath.Exact projected_ap + else AccessPath.Abstracted projected_ap + in let base_var, _ = fst (AccessPath.extract formal_ap) in match base_var with - | Var.ProgramVar pvar -> - if Pvar.is_return pvar - then apply_return formal_ap - else Some formal_ap - | Var.LogicalVar id when Ident.is_footprint id -> - begin - (* summaries store the index of the formal parameter in the ident stamp *) - match get_actual_ap (Ident.get_stamp id) with - | Some actual_ap -> - let projected_ap = project ~formal_ap ~actual_ap in - Some projected_ap - | None -> - None - end - | _ -> - None in - + | Var.ProgramVar pvar + -> if Pvar.is_return pvar then apply_return formal_ap else Some formal_ap + | Var.LogicalVar id when Ident.is_footprint id -> ( + match + (* summaries store the index of the formal parameter in the ident stamp *) + get_actual_ap (Ident.get_stamp id) + with + | Some actual_ap + -> let projected_ap = project ~formal_ap ~actual_ap in + Some projected_ap + | None + -> None ) + | _ + -> None + in let get_caller_ap_node_opt ap access_tree = let get_caller_node caller_ap = let caller_node_opt = access_path_get_node caller_ap access_tree proc_data in let caller_node = Option.value ~default:TaintDomain.empty_node caller_node_opt in - caller_ap, caller_node in - Option.map (get_caller_ap ap) ~f:get_caller_node in - + (caller_ap, caller_node) + in + Option.map (get_caller_ap ap) ~f:get_caller_node + in let replace_footprint_sources callee_trace caller_trace access_tree = let replace_footprint_source source acc = match TraceDomain.Source.get_footprint_access_path source with - | Some footprint_access_path -> - begin - match get_caller_ap_node_opt footprint_access_path access_tree with - | Some (_, (caller_ap_trace, _)) -> TraceDomain.join caller_ap_trace acc - | None -> acc - end - | None -> - acc in - TraceDomain.Sources.fold - replace_footprint_source (TraceDomain.sources callee_trace) caller_trace in - + | Some footprint_access_path -> ( + match get_caller_ap_node_opt footprint_access_path access_tree with + | Some (_, (caller_ap_trace, _)) + -> TraceDomain.join caller_ap_trace acc + | None + -> acc ) + | None + -> acc + in + TraceDomain.Sources.fold replace_footprint_source (TraceDomain.sources callee_trace) + caller_trace + in let instantiate_and_report callee_trace caller_trace access_tree = let caller_trace' = replace_footprint_sources callee_trace caller_trace access_tree in let sink_indexes = TraceDomain.get_footprint_indexes callee_trace in let appended_trace = TraceDomain.append caller_trace' callee_trace callee_site in - report_trace appended_trace callee_site ~sink_indexes proc_data; - appended_trace in - + report_trace appended_trace callee_site ~sink_indexes proc_data ; appended_trace + in let add_to_caller_tree access_tree_acc callee_ap callee_trace = match get_caller_ap_node_opt callee_ap access_tree_acc with - | Some (caller_ap, (caller_trace, caller_tree)) -> - let trace = instantiate_and_report callee_trace caller_trace access_tree_acc in + | Some (caller_ap, (caller_trace, caller_tree)) + -> let trace = instantiate_and_report callee_trace caller_trace access_tree_acc in TaintDomain.add_node caller_ap (trace, caller_tree) access_tree_acc - | None -> - ignore (instantiate_and_report callee_trace TraceDomain.empty access_tree_acc); - access_tree_acc in - - TaintDomain.trace_fold - add_to_caller_tree - summary - caller_access_tree - - let exec_instr (astate : Domain.astate) (proc_data : extras ProcData.t) _ (instr : HilInstr.t) = - let exec_write lhs_access_path rhs_exp astate= + | None + -> ignore (instantiate_and_report callee_trace TraceDomain.empty access_tree_acc) ; + access_tree_acc + in + TaintDomain.trace_fold add_to_caller_tree summary caller_access_tree + + let exec_instr (astate: Domain.astate) (proc_data: extras ProcData.t) _ (instr: HilInstr.t) = + let exec_write lhs_access_path rhs_exp astate = let rhs_node = - Option.value - (hil_exp_get_node rhs_exp astate proc_data) - ~default:TaintDomain.empty_node in - TaintDomain.add_node (AccessPath.Exact lhs_access_path) rhs_node astate in + Option.value (hil_exp_get_node rhs_exp astate proc_data) ~default:TaintDomain.empty_node + in + TaintDomain.add_node (AccessPath.Exact lhs_access_path) rhs_node astate + in match instr with - | Assign (((Var.ProgramVar pvar, _), []), HilExp.Exception _, _) when Pvar.is_return pvar -> - (* the Java frontend translates `throw Exception` as `return Exception`, which is a bit + | Assign (((Var.ProgramVar pvar, _), []), HilExp.Exception _, _) when Pvar.is_return pvar + -> (* the Java frontend translates `throw Exception` as `return Exception`, which is a bit wonky. this translation causes problems for us in computing a summary when an exception is "returned" from a void function. skip code like this for now, fix via t14159157 later *) astate - | Assign (((Var.ProgramVar pvar, _), []), rhs_exp, _) - when Pvar.is_return pvar && HilExp.is_null_literal rhs_exp && - Typ.equal_desc Tvoid (Procdesc.get_ret_type proc_data.pdesc).desc -> - (* similar to the case above; the Java frontend translates "return no exception" as + when Pvar.is_return pvar && HilExp.is_null_literal rhs_exp + && Typ.equal_desc Tvoid (Procdesc.get_ret_type proc_data.pdesc).desc + -> (* similar to the case above; the Java frontend translates "return no exception" as `return null` in a void function *) astate - - | Assign (lhs_access_path, rhs_exp, _) -> - exec_write lhs_access_path rhs_exp astate - - | Call (ret_opt, Direct called_pname, actuals, call_flags, callee_loc) -> - let handle_model callee_pname access_tree model = - let is_variadic = match callee_pname with - | Typ.Procname.Java pname -> - begin - match List.rev (Typ.Procname.java_get_parameters pname) with - | (_, "java.lang.Object[]") :: _ -> true - | _ -> false - end - | _ -> false in + | Assign (lhs_access_path, rhs_exp, _) + -> exec_write lhs_access_path rhs_exp astate + | Call (ret_opt, Direct called_pname, actuals, call_flags, callee_loc) + -> let handle_model callee_pname access_tree model = + let is_variadic = + match callee_pname with + | Typ.Procname.Java pname -> ( + match List.rev (Typ.Procname.java_get_parameters pname) with + | (_, "java.lang.Object[]") :: _ + -> true + | _ + -> false ) + | _ + -> false + in let should_taint_typ typ = is_variadic || TaintSpecification.is_taintable_type typ in let exp_join_traces trace_acc exp = match hil_exp_get_node ~abstracted:true exp access_tree proc_data with - | Some (trace, _) -> TraceDomain.join trace trace_acc - | None -> trace_acc in + | Some (trace, _) + -> TraceDomain.join trace trace_acc + | None + -> trace_acc + in let propagate_to_access_path access_path actuals access_tree = - let initial_trace = - access_path_get_trace access_path access_tree proc_data in + let initial_trace = access_path_get_trace access_path access_tree proc_data in let trace_with_propagation = - List.fold ~f:exp_join_traces ~init:initial_trace actuals in + List.fold ~f:exp_join_traces ~init:initial_trace actuals + in let filtered_sources = - TraceDomain.Sources.filter (fun source -> + TraceDomain.Sources.filter + (fun source -> match TraceDomain.Source.get_footprint_access_path source with - | Some access_path -> - Option.exists + | Some access_path + -> Option.exists (AccessPath.Raw.get_typ (AccessPath.extract access_path) proc_data.tenv) ~f:should_taint_typ - | None -> - true) - (TraceDomain.sources trace_with_propagation) in - if TraceDomain.Sources.is_empty filtered_sources - then - access_tree + | None + -> true) + (TraceDomain.sources trace_with_propagation) + in + if TraceDomain.Sources.is_empty filtered_sources then access_tree else let trace' = TraceDomain.update_sources trace_with_propagation filtered_sources in - TaintDomain.add_trace access_path trace' access_tree in + TaintDomain.add_trace access_path trace' access_tree + in let handle_model_ astate_acc propagation = - match propagation, actuals, ret_opt with - | _, [], _ -> - astate_acc - | TaintSpec.Propagate_to_return, actuals, Some ret_ap -> - propagate_to_access_path (AccessPath.Exact (ret_ap, [])) actuals astate_acc - | TaintSpec.Propagate_to_receiver, - AccessPath receiver_ap :: (_ :: _ as other_actuals), - _ -> - propagate_to_access_path (AccessPath.Exact receiver_ap) other_actuals astate_acc - | TaintSpec.Propagate_to_actual actual_index, _, _ -> - begin - match List.nth actuals actual_index with - | Some (HilExp.AccessPath actual_ap) -> - propagate_to_access_path (AccessPath.Exact actual_ap) actuals astate_acc - | _ -> - astate_acc - end - | _ -> - astate_acc in - List.fold ~f:handle_model_ ~init:access_tree model in - + match (propagation, actuals, ret_opt) with + | _, [], _ + -> astate_acc + | TaintSpec.Propagate_to_return, actuals, Some ret_ap + -> propagate_to_access_path (AccessPath.Exact (ret_ap, [])) actuals astate_acc + | ( TaintSpec.Propagate_to_receiver + , (AccessPath receiver_ap) :: (_ :: _ as other_actuals) + , _ ) + -> propagate_to_access_path (AccessPath.Exact receiver_ap) other_actuals astate_acc + | TaintSpec.Propagate_to_actual actual_index, _, _ -> ( + match List.nth actuals actual_index with + | Some HilExp.AccessPath actual_ap + -> propagate_to_access_path (AccessPath.Exact actual_ap) actuals astate_acc + | _ + -> astate_acc ) + | _ + -> astate_acc + in + List.fold ~f:handle_model_ ~init:access_tree model + in let handle_unknown_call callee_pname access_tree = match Typ.Procname.get_method callee_pname with - | "operator=" when not (Typ.Procname.is_java callee_pname) -> - (* treat unknown calls to C++ operator= as assignment *) - begin - match actuals with - | [AccessPath lhs_access_path; rhs_exp] -> - exec_write lhs_access_path rhs_exp access_tree - | _ -> - failwithf "Unexpected call to operator= %a" HilInstr.pp instr - end - | _ -> - let model = - TaintSpecification.handle_unknown_call - callee_pname - (Option.map ~f:snd ret_opt) - actuals - proc_data.tenv in - handle_model callee_pname access_tree model in - - let dummy_ret_opt = match ret_opt with - | None when not (Typ.Procname.is_java called_pname) -> + | "operator=" when not (Typ.Procname.is_java callee_pname) -> ( + match (* treat unknown calls to C++ operator= as assignment *) + actuals with + | [(AccessPath lhs_access_path); rhs_exp] + -> exec_write lhs_access_path rhs_exp access_tree + | _ + -> failwithf "Unexpected call to operator= %a" HilInstr.pp instr ) + | _ + -> let model = + TaintSpecification.handle_unknown_call callee_pname (Option.map ~f:snd ret_opt) + actuals proc_data.tenv + in + handle_model callee_pname access_tree model + in + let dummy_ret_opt = + match ret_opt with + | None when not (Typ.Procname.is_java called_pname) -> ( + match (* the C++ frontend handles returns of non-pointers by adding a dummy pass-by-reference variable as the last actual, then returning the value by assigning to it. understand this pattern by pretending it's the return value *) - begin - match List.last actuals with - | Some (HilExp.AccessPath ((Var.ProgramVar pvar, _) as ret_base, [])) - when Pvar.is_frontend_tmp pvar -> - Some ret_base - | _ -> None - end - | _ -> - ret_opt in - + List.last actuals + with + | Some HilExp.AccessPath ((Var.ProgramVar pvar, _ as ret_base), []) + when Pvar.is_frontend_tmp pvar + -> Some ret_base + | _ + -> None ) + | _ + -> ret_opt + in let analyze_call astate_acc callee_pname = let call_site = CallSite.make callee_pname callee_loc in - let sink = TraceDomain.Sink.get call_site actuals proc_data.ProcData.tenv in let astate_with_sink = match sink with - | Some sink -> add_sink sink actuals astate proc_data call_site - | None -> astate in + | Some sink + -> add_sink sink actuals astate proc_data call_site + | None + -> astate + in let source = TraceDomain.Source.get call_site proc_data.tenv in let astate_with_source = match source with - | Some { TraceDomain.Source.source; index=None; } -> - Option.value_map + | Some {TraceDomain.Source.source; index= None} + -> Option.value_map ~f:(fun ret_base -> add_return_source source ret_base astate_with_sink) - ~default:astate_with_sink - dummy_ret_opt - | Some { TraceDomain.Source.source; index=Some index; } -> - add_actual_source source index actuals astate_with_sink proc_data - | None -> - astate_with_sink in - + ~default:astate_with_sink dummy_ret_opt + | Some {TraceDomain.Source.source; index= Some index} + -> add_actual_source source index actuals astate_with_sink proc_data + | None + -> astate_with_sink + in let astate_with_summary = - if Option.is_some source || Option.is_some sink - then + if Option.is_some source || Option.is_some sink then (* don't use a summary for a procedure that is a direct source or sink *) astate_with_source else match Summary.read_summary proc_data.pdesc callee_pname with - | None -> - handle_unknown_call callee_pname astate_with_source - | Some summary -> - let ret_typ_opt = Option.map ~f:snd ret_opt in + | None + -> handle_unknown_call callee_pname astate_with_source + | Some summary + -> let ret_typ_opt = Option.map ~f:snd ret_opt in let access_tree = TaintSpecification.of_summary_access_tree summary in match - TaintSpecification.get_model - callee_pname ret_typ_opt actuals proc_data.tenv access_tree with - | Some model -> - handle_model callee_pname astate_with_source model - | None -> - apply_summary - ret_opt actuals access_tree astate_with_source proc_data call_site in - + TaintSpecification.get_model callee_pname ret_typ_opt actuals proc_data.tenv + access_tree + with + | Some model + -> handle_model callee_pname astate_with_source model + | None + -> apply_summary ret_opt actuals access_tree astate_with_source proc_data + call_site + in let astate_with_sanitizer = match dummy_ret_opt with - | None -> astate_with_summary + | None + -> astate_with_summary | Some ret_base -> - match TaintSpecification.get_sanitizer callee_pname with - | Some Return -> - (* clear the trace associated with the return value. ideally, we would + match TaintSpecification.get_sanitizer callee_pname with + | Some Return + -> (* clear the trace associated with the return value. ideally, we would associate a kind with the sanitizer and only clear the trace when its kind matches the source. but this gets complicated to do properly with footprint sources, since we don't know their kind. so do the simple thing for now. *) - TaintDomain.BaseMap.remove ret_base astate_with_summary - | None -> astate_with_summary in - - Domain.join astate_acc astate_with_sanitizer in - + TaintDomain.BaseMap.remove ret_base astate_with_summary + | None + -> astate_with_summary + in + Domain.join astate_acc astate_with_sanitizer + in (* highly polymorphic call sites stress reactive mode too much by using too much memory. here, we choose an arbitrary call limit that allows us to finish the analysis in practice. this is obviously unsound; will try to remove in the future. *) let max_calls = 3 in let targets = - if List.length call_flags.cf_targets <= max_calls - then - called_pname :: call_flags.cf_targets - else - begin - L.(debug Analysis Medium) - "Skipping highly polymorphic call site for %a@." Typ.Procname.pp called_pname; - [called_pname] - end in + if List.length call_flags.cf_targets <= max_calls then called_pname + :: call_flags.cf_targets + else ( + L.(debug Analysis Medium) + "Skipping highly polymorphic call site for %a@." Typ.Procname.pp called_pname ; + [called_pname] ) + in (* for each possible target of the call, apply the summary. join all results together *) List.fold ~f:analyze_call ~init:Domain.empty targets - | _ -> - astate + | _ + -> astate end module Analyzer = - AbstractInterpreter.Make (ProcCfg.Exceptional) (LowerHil.Make(TransferFunctions)) + AbstractInterpreter.Make (ProcCfg.Exceptional) (LowerHil.Make (TransferFunctions)) - let make_summary { ProcData.pdesc; extras={ formal_map; } } access_tree = + let make_summary {ProcData.pdesc; extras= {formal_map}} access_tree = let is_java = Typ.Procname.is_java (Procdesc.get_proc_name pdesc) in (* if a trace has footprint sources, attach them to the appropriate footprint var *) let access_tree' = TaintDomain.fold - (fun access_tree_acc _ ((trace, _) as node) -> - if TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) - then - (* if this trace has no sinks, we don't need to attach it to anything *) - access_tree_acc - else - TraceDomain.Sources.fold - (fun source acc -> - match TraceDomain.Source.get_footprint_access_path source with - | Some footprint_access_path -> - let node' = - match TaintDomain.get_node footprint_access_path acc with - | Some n -> TaintDomain.node_join node n - | None -> node in - TaintDomain.add_node footprint_access_path node' acc - | None -> - acc) - (TraceDomain.sources trace) - access_tree_acc) - access_tree - access_tree in - + (fun access_tree_acc _ (trace, _ as node) -> + if TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) then + (* if this trace has no sinks, we don't need to attach it to anything *) + access_tree_acc + else + TraceDomain.Sources.fold + (fun source acc -> + match TraceDomain.Source.get_footprint_access_path source with + | Some footprint_access_path + -> let node' = + match TaintDomain.get_node footprint_access_path acc with + | Some n + -> TaintDomain.node_join node n + | None + -> node + in + TaintDomain.add_node footprint_access_path node' acc + | None + -> acc) + (TraceDomain.sources trace) access_tree_acc) + access_tree access_tree + in (* should only be used on nodes associated with a footprint base *) let is_empty_node (trace, tree) = (* In C++, we can reassign the value pointed to by a pointer type formal, and we can assign to a value type passed by reference. these mechanisms can be used to associate a source directly with a formal. In Java this can't happen, so we only care if the formal flows to a sink *) - (if is_java - then TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) - else TraceDomain.is_empty trace) && + ( if is_java then TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) + else TraceDomain.is_empty trace ) + && match tree with - | TaintDomain.Subtree subtree -> TaintDomain.AccessMap.is_empty subtree - | TaintDomain.Star -> true in - + | TaintDomain.Subtree subtree + -> TaintDomain.AccessMap.is_empty subtree + | TaintDomain.Star + -> true + in (* replace formal names with footprint vars for their indices. For example, for `foo(o)`, we'll replace `o` with FP(1) *) let with_footprint_vars = AccessPath.BaseMap.fold - (fun base ((trace, subtree) as node) acc -> - if Var.is_global (fst base) || Var.is_return (fst base) - then AccessPath.BaseMap.add base node acc - else if Var.is_footprint (fst base) - then - if is_empty_node node - then - acc - else - let node' = - if TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) - then TraceDomain.empty, subtree - else node in - AccessPath.BaseMap.add base node' acc - else - match FormalMap.get_formal_index base formal_map with - | Some formal_index -> - let base' = Var.of_formal_index formal_index, snd base in - let joined_node = - try TaintDomain.node_join (AccessPath.BaseMap.find base' acc) node - with Not_found -> node in - if is_empty_node joined_node - then acc - else AccessPath.BaseMap.add base' joined_node acc - | None -> - (* base is a local var *) - acc) - access_tree' - TaintDomain.empty in - + (fun base (trace, subtree as node) acc -> + if Var.is_global (fst base) || Var.is_return (fst base) then + AccessPath.BaseMap.add base node acc + else if Var.is_footprint (fst base) then + if is_empty_node node then acc + else + let node' = + if TraceDomain.Sinks.is_empty (TraceDomain.sinks trace) then + (TraceDomain.empty, subtree) + else node + in + AccessPath.BaseMap.add base node' acc + else + match FormalMap.get_formal_index base formal_map with + | Some formal_index + -> let base' = (Var.of_formal_index formal_index, snd base) in + let joined_node = + try TaintDomain.node_join (AccessPath.BaseMap.find base' acc) node + with Not_found -> node + in + if is_empty_node joined_node then acc + else AccessPath.BaseMap.add base' joined_node acc + | None + -> (* base is a local var *) + acc) + access_tree' TaintDomain.empty + in TaintSpecification.to_summary_access_tree with_footprint_vars - let checker { Callbacks.tenv; summary; proc_desc; } : Specs.summary = - + let checker {Callbacks.tenv; summary; proc_desc} : Specs.summary = (* bind parameters to a trace with a tainted source (if applicable) *) let make_initial pdesc = let pname = Procdesc.get_proc_name pdesc in let access_tree = - List.fold ~f:(fun acc (name, typ, taint_opt) -> + List.fold + ~f:(fun acc (name, typ, taint_opt) -> match taint_opt with - | Some source -> - let base_ap = AccessPath.Exact (AccessPath.of_pvar (Pvar.mk name pname) typ) in + | Some source + -> let base_ap = AccessPath.Exact (AccessPath.of_pvar (Pvar.mk name pname) typ) in TaintDomain.add_trace base_ap (TraceDomain.of_source source) acc - | None -> - acc) - ~init:TaintDomain.empty - (TraceDomain.Source.get_tainted_formals pdesc tenv) in - access_tree, IdAccessPathMapDomain.empty in - - if not (Procdesc.did_preanalysis proc_desc) - then - begin - Preanal.do_liveness proc_desc tenv; - Preanal.do_dynamic_dispatch proc_desc (Cg.create (SourceFile.invalid __FILE__)) tenv; - end; + | None + -> acc) + ~init:TaintDomain.empty (TraceDomain.Source.get_tainted_formals pdesc tenv) + in + (access_tree, IdAccessPathMapDomain.empty) + in + if not (Procdesc.did_preanalysis proc_desc) then ( + Preanal.do_liveness proc_desc tenv ; + Preanal.do_dynamic_dispatch proc_desc (Cg.create (SourceFile.invalid __FILE__)) tenv ) ; let initial = make_initial proc_desc in let extras = let formal_map = FormalMap.make proc_desc in - { formal_map; summary; } in + {formal_map; summary} + in let proc_data = ProcData.make proc_desc tenv extras in match Analyzer.compute_post proc_data ~initial ~debug:false with - | Some (access_tree, _) -> - Summary.update_summary (make_summary proc_data access_tree) summary - | None -> - if Procdesc.Node.get_succs (Procdesc.get_start_node proc_desc) <> [] - then failwith "Couldn't compute post" + | Some (access_tree, _) + -> Summary.update_summary (make_summary proc_data access_tree) summary + | None + -> if Procdesc.Node.get_succs (Procdesc.get_start_node proc_desc) <> [] then + failwith "Couldn't compute post" else summary end diff --git a/infer/src/quandary/TaintSpec.ml b/infer/src/quandary/TaintSpec.ml index 4aa13d246..958d6ad05 100644 --- a/infer/src/quandary/TaintSpec.ml +++ b/infer/src/quandary/TaintSpec.ml @@ -14,34 +14,33 @@ open! IStd type action = | Propagate_to_actual of int - (** Propagate taint from all actuals to the actual with the given index *) + (** Propagate taint from all actuals to the actual with the given index *) | Propagate_to_receiver - (** Propagate taint from all non-receiver actuals to the receiver actual *) - | Propagate_to_return - (** Propagate taint from all actuals to the return value *) + (** Propagate taint from all non-receiver actuals to the receiver actual *) + | Propagate_to_return (** Propagate taint from all actuals to the return value *) -type sanitizer = - | Return (** a sanitizer that removes taint from its return value *) +type sanitizer = Return (** a sanitizer that removes taint from its return value *) module type S = sig module Trace : Trace.S - module AccessTree : module type of AccessTree.Make(Trace) - (** return a summary for handling an unknown call at the given site with the given return type - and actuals *) + module AccessTree : module type of AccessTree.Make (Trace) + val handle_unknown_call : Typ.Procname.t -> Typ.t option -> HilExp.t list -> Tenv.t -> action list + (** return a summary for handling an unknown call at the given site with the given return type + and actuals *) - (** returns a model that should be used for the given (procname, return type, actuals, summary) - instead of using the summary for the procname *) val get_model : Typ.Procname.t -> Typ.t option -> HilExp.t list -> Tenv.t -> AccessTree.t -> action list option + (** returns a model that should be used for the given (procname, return type, actuals, summary) + instead of using the summary for the procname *) - (** return true if the given typ can be tainted *) val is_taintable_type : Typ.t -> bool + (** return true if the given typ can be tainted *) - (** get the sanitizer associated with the given type, if any *) val get_sanitizer : Typ.Procname.t -> sanitizer option + (** get the sanitizer associated with the given type, if any *) val to_summary_access_tree : AccessTree.t -> QuandarySummary.AccessTree.t diff --git a/infer/src/scripts/checkCopyright.ml b/infer/src/scripts/checkCopyright.ml index 082b90ca0..76687c0cd 100644 --- a/infer/src/scripts/checkCopyright.ml +++ b/infer/src/scripts/checkCopyright.ml @@ -8,30 +8,36 @@ *) open! Core.Std - module F = Format let copyright_modified_exit_code = 1 -let copyright_malformed_exit_code = 3 (* error code 2 is for OCaml uncaught exceptions *) + +let copyright_malformed_exit_code = 3 + +(* error code 2 is for OCaml uncaught exceptions *) type comment_style = - | Line of string * bool (** line comments, eg "#" for shell, and whether there should be a + | Line of string * bool + (** line comments, eg "#" for shell, and whether there should be a newline before the copyright notice *) - | Block of string * string * string (** block comments, eg ("(*", "*", "*)") for ocaml *) + | Block of string * string * string (** block comments, eg ("(*", "*", "*)") for ocaml *) let comment_style_ocaml = Block ("(*", "*", "*)") + let comment_style_c = Block ("/*", "*", "*/") + let comment_style_shell = Line ("#", true) + let comment_style_make = Line ("#", false) + let comment_style_llvm = Line (";", true) -let comment_styles = [ - comment_style_ocaml; - comment_style_c; - comment_style_shell; - comment_style_llvm; - comment_style_make; -] +let comment_styles = + [ comment_style_ocaml + ; comment_style_c + ; comment_style_shell + ; comment_style_llvm + ; comment_style_make ] let lang_of_com_style style = if style = comment_style_ocaml then "ocaml" @@ -42,84 +48,86 @@ let lang_of_com_style style = else "??unknown??" let default_start_line_of_com_style style = - match style with - | Line (_, true) -> 2 - | Line (_, false) -> 0 - | Block _ -> 0 + match style with Line (_, true) -> 2 | Line (_, false) -> 0 | Block _ -> 0 let prefix_of_comment_style = function - | Line _ -> "" - | Block (_, inter, _) -> String.make (String.length inter) ' ' + | Line _ + -> "" + | Block (_, inter, _) + -> String.make (String.length inter) ' ' (** If true, update the copyright message of the files. *) let update_files = ref false -let line_contains_copyright line = - String.is_substring ~substring:"opyright " line +let line_contains_copyright line = String.is_substring ~substring:"opyright " line -let rec find_copyright_line lines n = match lines with - | [] -> None - | line :: lines' -> - if line_contains_copyright line then Some n - else find_copyright_line lines' (n + 1) +let rec find_copyright_line lines n = + match lines with + | [] + -> None + | line :: lines' + -> if line_contains_copyright line then Some n else find_copyright_line lines' (n + 1) let find_comment_start_and_style lines_arr n = (* are we in a line comment? *) let cur_line_comment = List.find comment_styles ~f:(function - | Line (s, starts_with_newline) when String.is_prefix ~prefix:s lines_arr.(n) -> - if starts_with_newline then n <> 0 else true - | _ -> false - ) in - let is_start line = match cur_line_comment with - | Some (Line _) -> - cur_line_comment - | _ -> - List.find comment_styles ~f:(function - | Block(s, _, _) -> String.is_substring ~substring:s line - | _ -> false - ) in + | Line (s, starts_with_newline) when String.is_prefix ~prefix:s lines_arr.(n) + -> if starts_with_newline then n <> 0 else true + | _ + -> false ) + in + let is_start line = + match cur_line_comment with + | Some Line _ + -> cur_line_comment + | _ + -> List.find comment_styles ~f:(function + | Block (s, _, _) + -> String.is_substring ~substring:s line + | _ + -> false ) + in let i = ref (max (n - 1) 0) in (* hacky fake line comment to avoid an option type *) - let found = ref (-1, Line(">>>>>>>>>>>", false)) in - while !i >= 0 && fst (!found) = -1 do - match is_start lines_arr.(!i) with - | Some style -> found := (!i, style); - | None -> decr i - done; + let found = ref (-1, Line (">>>>>>>>>>>", false)) in + while !i >= 0 && fst !found = -1 do + match is_start lines_arr.(!i) with Some style -> found := (!i, style) | None -> decr i + done ; !found let find_comment_end lines_arr n com_style = - let is_end line = match com_style with - | Line (s, _) -> not (String.is_prefix ~prefix:s line) - | Block (_, _, s) -> String.is_substring ~substring:s line in + let is_end line = + match com_style with + | Line (s, _) + -> not (String.is_prefix ~prefix:s line) + | Block (_, _, s) + -> String.is_substring ~substring:s line + in let i = ref (n + 1) in let len = Array.length lines_arr in let found = ref (len - 1) in while !i < len && !found = len - 1 do - if is_end lines_arr.(!i) then found := !i; + if is_end lines_arr.(!i) then found := !i ; incr i - done; - match com_style with - | Line _ -> !found - | Block _ -> !found + done ; + match com_style with Line _ -> !found | Block _ -> !found (** Heuristic to check if this looks like a copyright message. *) let looks_like_copyright_message cstart cend lines_arr = let max_len = 100 in let check_len () = let ok = ref true in - for i = cstart to cend do - if String.length lines_arr.(i) > max_len then ok := false - done; - !ok in - cstart >= 0 && (cend - cstart) <= 10 && check_len () + for i = cstart to cend do if String.length lines_arr.(i) > max_len then ok := false done ; + !ok + in + cstart >= 0 && cend - cstart <= 10 && check_len () let contains_monoidics cstart cend lines_arr = let found = ref false in for i = cstart to cend do if String.is_substring ~substring:"Monoidics" lines_arr.(i) then found := true - done; + done ; !found let get_fb_year cstart cend lines_arr = @@ -130,84 +138,88 @@ let get_fb_year cstart cend lines_arr = let _ = Str.search_forward fmt_re line 0 in let fmt_match = Str.matched_string line in if String.length fmt_match = 4 then - try - found := Some (int_of_string fmt_match) + try found := Some (int_of_string fmt_match) with _ -> () - with Not_found -> () in + with Not_found -> () + in for i = cstart to cend do let line = lines_arr.(i) in - if String.is_substring ~substring:"Facebook" line then - do_line line - done; + if String.is_substring ~substring:"Facebook" line then do_line line + done ; !found let pp_copyright mono fb_year com_style fmt _prefix = - let running_comment = match com_style with | Line (s, _) | Block (_, s, _) -> s in + let running_comment = + match com_style + with Line (s, _) | Block (_, s, _) -> s + in let prefix = _prefix ^ running_comment in let pp_line str = F.fprintf fmt "%s%s@\n" prefix str in - let pp_start () = match com_style with - | Line (_, starts_with_newline) -> if starts_with_newline then F.fprintf fmt "@\n"; - | Block (start, _, _) -> F.fprintf fmt "%s@\n" start in - let pp_end () = match com_style with - | Line _ -> F.fprintf fmt "@\n"; - | Block (_, _, finish) -> F.fprintf fmt "%s%s@\n" _prefix finish in - pp_start (); - if mono then - pp_line " Copyright (c) 2009 - 2013 Monoidics ltd."; - pp_line (F.sprintf " Copyright (c) %d - present Facebook, Inc." fb_year); - pp_line " All rights reserved."; - pp_line ""; - pp_line " This source code is licensed under the BSD style license found in the"; - pp_line " LICENSE file in the root directory of this source tree. An additional grant"; - pp_line " of patent rights can be found in the PATENTS file in the same directory."; + let pp_start () = + match com_style with + | Line (_, starts_with_newline) + -> if starts_with_newline then F.fprintf fmt "@\n" + | Block (start, _, _) + -> F.fprintf fmt "%s@\n" start + in + let pp_end () = + match com_style with + | Line _ + -> F.fprintf fmt "@\n" + | Block (_, _, finish) + -> F.fprintf fmt "%s%s@\n" _prefix finish + in + pp_start () ; + if mono then pp_line " Copyright (c) 2009 - 2013 Monoidics ltd." ; + pp_line (F.sprintf " Copyright (c) %d - present Facebook, Inc." fb_year) ; + pp_line " All rights reserved." ; + pp_line "" ; + pp_line " This source code is licensed under the BSD style license found in the" ; + pp_line " LICENSE file in the root directory of this source tree. An additional grant" ; + pp_line " of patent rights can be found in the PATENTS file in the same directory." ; pp_end () let copyright_has_changed mono fb_year com_style prefix cstart cend lines_arr = let old_copyright = let r = ref "" in - for i = cstart to cend do - r := !r ^ lines_arr.(i) ^ "\n" - done; - !r in + for i = cstart to cend do r := !r ^ lines_arr.(i) ^ "\n" done ; + !r + in let new_copyright = let pp fmt = pp_copyright mono fb_year com_style fmt prefix in - Format.asprintf "%t" pp in + Format.asprintf "%t" pp + in old_copyright <> new_copyright let update_file fname mono fb_year com_style prefix cstart cend lines_arr = try let cout = open_out fname in let fmt = F.formatter_of_out_channel cout in - for i = 0 to cstart - 1 do - F.fprintf fmt "%s@." lines_arr.(i) - done; - pp_copyright mono fb_year com_style fmt prefix; - for i = cend + 1 to Array.length lines_arr - 1 do - F.fprintf fmt "%s@\n" lines_arr.(i) - done; - F.fprintf fmt "@?"; + for i = 0 to cstart - 1 do F.fprintf fmt "%s@." lines_arr.(i) done ; + pp_copyright mono fb_year com_style fmt prefix ; + for i = cend + 1 to Array.length lines_arr - 1 do F.fprintf fmt "%s@\n" lines_arr.(i) done ; + F.fprintf fmt "@?" ; Out_channel.close cout with _ -> () -let com_style_of_lang = [ - (".ml", comment_style_ocaml); - (".mli", comment_style_ocaml); - (".mly", comment_style_c); - (".mll", comment_style_ocaml); - (".re", comment_style_c); - (".rei", comment_style_c); - (".c", comment_style_c); - (".h", comment_style_c); - (".cpp", comment_style_c); - (".m", comment_style_c); - (".mm", comment_style_c); - (".ll", comment_style_llvm); - (".java", comment_style_c); - (".sh", comment_style_shell); - (".py", comment_style_shell); - ("Makefile", comment_style_make); - (".make", comment_style_make); -] +let com_style_of_lang = + [ (".ml", comment_style_ocaml) + ; (".mli", comment_style_ocaml) + ; (".mly", comment_style_c) + ; (".mll", comment_style_ocaml) + ; (".re", comment_style_c) + ; (".rei", comment_style_c) + ; (".c", comment_style_c) + ; (".h", comment_style_c) + ; (".cpp", comment_style_c) + ; (".m", comment_style_c) + ; (".mm", comment_style_c) + ; (".ll", comment_style_llvm) + ; (".java", comment_style_c) + ; (".sh", comment_style_shell) + ; (".py", comment_style_shell) + ; ("Makefile", comment_style_make) + ; (".make", comment_style_make) ] let file_should_have_copyright fname = List.Assoc.mem com_style_of_lang ~equal:Filename.check_suffix fname @@ -215,71 +227,49 @@ let file_should_have_copyright fname = let output_diff fname lines_arr cstart n cend len mono fb_year com_style prefix = let range = cend - cstart in let lang = lang_of_com_style com_style in - F.eprintf "%s (start:%d n:%d end:%d len:%d range:%d lang:%s mono:%b year:%d)@." - fname cstart n cend len range lang mono fb_year; - for i = cstart to cend do - F.printf "%s@." lines_arr.(i) - done; - F.printf "-----@."; - F.printf "@[%a@]" (pp_copyright mono fb_year com_style) prefix; - if !update_files then - update_file fname mono fb_year com_style prefix cstart cend lines_arr + F.eprintf "%s (start:%d n:%d end:%d len:%d range:%d lang:%s mono:%b year:%d)@." fname cstart n + cend len range lang mono fb_year ; + for i = cstart to cend do F.printf "%s@." lines_arr.(i) done ; + F.printf "-----@." ; + F.printf "@[%a@]" (pp_copyright mono fb_year com_style) prefix ; + if !update_files then update_file fname mono fb_year com_style prefix cstart cend lines_arr let check_copyright fname = let lines = In_channel.with_file fname ~f:In_channel.input_lines in let lines_arr = Array.of_list lines in match find_copyright_line lines 0 with - | None -> - if file_should_have_copyright fname then - begin - let year = 1900 + (Unix.localtime (Unix.time ())).Unix.tm_year in - let com_style = - List.Assoc.find_exn com_style_of_lang ~equal:Filename.check_suffix fname in - let prefix = prefix_of_comment_style com_style in - let start = default_start_line_of_com_style com_style in - output_diff fname lines_arr start (-1) (-1) 0 false year com_style prefix; - exit copyright_modified_exit_code - end - | Some n -> - let line = lines_arr.(n) in - let (cstart, com_style) = find_comment_start_and_style lines_arr n in + | None + -> if file_should_have_copyright fname then + let year = 1900 + (Unix.localtime (Unix.time ())).Unix.tm_year in + let com_style = List.Assoc.find_exn com_style_of_lang ~equal:Filename.check_suffix fname in + let prefix = prefix_of_comment_style com_style in + let start = default_start_line_of_com_style com_style in + output_diff fname lines_arr start (-1) (-1) 0 false year com_style prefix ; + exit copyright_modified_exit_code + | Some n + -> let line = lines_arr.(n) in + let cstart, com_style = find_comment_start_and_style lines_arr n in let cend = find_comment_end lines_arr n com_style in - if looks_like_copyright_message cstart cend lines_arr then - begin - let mono = contains_monoidics cstart cend lines_arr in - match get_fb_year cstart cend lines_arr with - | None -> - F.eprintf "Can't find fb year: %s@." fname; - exit copyright_malformed_exit_code - | Some fb_year -> - let prefix = prefix_of_comment_style com_style in - if copyright_has_changed mono fb_year com_style prefix cstart cend lines_arr then - begin - let len = String.length line in - output_diff fname lines_arr cstart n cend len mono fb_year com_style prefix; - exit copyright_modified_exit_code - end - end - else - begin - F.eprintf "Copyright not recognized: %s@." fname; - exit copyright_malformed_exit_code - end - - -let speclist = [ - "-i", - Arg.Set update_files, - "Update copyright notice in-place" - ; -] + if looks_like_copyright_message cstart cend lines_arr then ( + let mono = contains_monoidics cstart cend lines_arr in + match get_fb_year cstart cend lines_arr with + | None + -> F.eprintf "Can't find fb year: %s@." fname ; exit copyright_malformed_exit_code + | Some fb_year + -> let prefix = prefix_of_comment_style com_style in + if copyright_has_changed mono fb_year com_style prefix cstart cend lines_arr then + let len = String.length line in + output_diff fname lines_arr cstart n cend len mono fb_year com_style prefix ; + exit copyright_modified_exit_code ) + else ( F.eprintf "Copyright not recognized: %s@." fname ; exit copyright_malformed_exit_code ) + +let speclist = [("-i", Arg.Set update_files, "Update copyright notice in-place")] let usage_msg = "checkCopyright [-i] file1 ..." let () = let to_check = ref [] in - let add_file_to_check fname = - to_check := fname :: !to_check in - Arg.parse (Arg.align speclist) add_file_to_check usage_msg; - List.iter ~f:check_copyright (List.rev !to_check); + let add_file_to_check fname = to_check := fname :: !to_check in + Arg.parse (Arg.align speclist) add_file_to_check usage_msg ; + List.iter ~f:check_copyright (List.rev !to_check) ; exit 0 diff --git a/infer/src/unit/BoundedCallTreeTests.ml b/infer/src/unit/BoundedCallTreeTests.ml index b66a2f5e2..a1ef70af2 100644 --- a/infer/src/unit/BoundedCallTreeTests.ml +++ b/infer/src/unit/BoundedCallTreeTests.ml @@ -8,13 +8,9 @@ *) open! IStd - module F = Format - module TestInterpreter = - AnalyzerTester.Make - (ProcCfg.Exceptional) - (BoundedCallTree.TransferFunctions) + AnalyzerTester.Make (ProcCfg.Exceptional) (BoundedCallTree.TransferFunctions) let mock_get_proc_desc _ = None @@ -24,84 +20,76 @@ let tests = let initial = BoundedCallTree.Domain.empty in let f_proc_name = Typ.Procname.from_string_c_fun "f" in let g_proc_name = Typ.Procname.from_string_c_fun "g" in - let g_args = [((Exp.Const (Const.Cint (IntLit.one))), (Typ.mk (Tint IInt)))] in + let g_args = [(Exp.Const (Const.Cint IntLit.one), Typ.mk (Tint IInt))] in let g_ret_id = Some (ident_of_str "r", Typ.mk (Tint IInt)) in let class_name = "com.example.SomeClass" in let file_name = "SomeClass.java" in - let trace = Stacktrace.make "java.lang.NullPointerException" - [Stacktrace.make_frame class_name "foo" file_name (Some 16); - Stacktrace.make_frame class_name "bar" file_name (Some 20)] in - let extras = { BoundedCallTree.get_proc_desc = mock_get_proc_desc; - stacktraces = [trace]; } in - let multi_trace_1 = Stacktrace.make "java.lang.NullPointerException" - [Stacktrace.make_frame class_name "foo" file_name (Some 16)] in - let multi_trace_2 = Stacktrace.make "java.lang.NullPointerException" - [Stacktrace.make_frame class_name "bar" file_name (Some 20)] in - let multi_trace_extras = { BoundedCallTree.get_proc_desc = mock_get_proc_desc; - stacktraces = [multi_trace_1; multi_trace_2]; } in + let trace = + Stacktrace.make "java.lang.NullPointerException" + [ Stacktrace.make_frame class_name "foo" file_name (Some 16) + ; Stacktrace.make_frame class_name "bar" file_name (Some 20) ] + in + let extras = {BoundedCallTree.get_proc_desc= mock_get_proc_desc; stacktraces= [trace]} in + let multi_trace_1 = + Stacktrace.make "java.lang.NullPointerException" + [Stacktrace.make_frame class_name "foo" file_name (Some 16)] + in + let multi_trace_2 = + Stacktrace.make "java.lang.NullPointerException" + [Stacktrace.make_frame class_name "bar" file_name (Some 20)] + in + let multi_trace_extras = + {BoundedCallTree.get_proc_desc= mock_get_proc_desc; stacktraces= [multi_trace_1; multi_trace_2]} + in let caller_foo_name = Typ.Procname.from_string_c_fun "foo" in let caller_bar_name = Typ.Procname.from_string_c_fun "bar" in let caller_baz_name = Typ.Procname.from_string_c_fun "baz" in - let test_list_from_foo = [ - "on_call_add_proc_name", - [ - make_call ~procname:f_proc_name None []; (* means f() *) - invariant "{ f }" - ]; - "on_call_add_proc_name_w_args", - [ - make_call ~procname:g_proc_name g_ret_id g_args; (* means r = a.g(1) *) - invariant "{ g }" - ]; - "handle_two_proc_calls", - [ - make_call ~procname:f_proc_name None []; - invariant "{ f }"; - make_call ~procname:g_proc_name g_ret_id g_args; - invariant "{ f, g }" - ]; - "dont_record_procs_twice", - [ - make_call ~procname:f_proc_name None []; - invariant "{ f }"; - make_call ~procname:f_proc_name None []; - invariant "{ f }" - ]; - ] |> TestInterpreter.create_tests - ~test_pname:caller_foo_name - ~initial:BoundedCallTree.Domain.empty - extras in - let test_list_from_bar = [ - "on_call_anywhere_on_stack_add_proc_name", - [ - make_call ~procname:f_proc_name None []; (* means f() *) - invariant "{ f }" - ]; - ] |> TestInterpreter.create_tests ~test_pname:caller_bar_name extras ~initial in - let test_list_from_baz = [ - "ignore_procs_unrelated_to_trace", - [ - make_call ~procname:f_proc_name None []; (* means f() *) - invariant "{ }" - ]; - ] |> TestInterpreter.create_tests ~test_pname:caller_baz_name extras ~initial in - let test_list_multiple_traces_from_foo = [ - "on_call_add_proc_name_in_any_stack_1", - [ - make_call ~procname:f_proc_name None []; (* means f() *) - invariant "{ f }" - ]; - ] |> TestInterpreter.create_tests ~test_pname:caller_foo_name multi_trace_extras ~initial in - let test_list_multiple_traces_from_bar = [ - "on_call_add_proc_name_in_any_stack_2", - [ - make_call ~procname:f_proc_name None []; (* means f() *) - invariant "{ f }" - ]; - ] |> TestInterpreter.create_tests ~test_pname:caller_bar_name multi_trace_extras ~initial in - let test_list = test_list_from_foo @ - test_list_from_bar @ - test_list_from_baz @ - test_list_multiple_traces_from_foo @ - test_list_multiple_traces_from_bar in - "bounded_calltree_test_suite">:::test_list + let test_list_from_foo = + [ ( "on_call_add_proc_name" + , [make_call ~procname:f_proc_name None []; (* means f() *) + invariant "{ f }"] ) + ; ( "on_call_add_proc_name_w_args" + , [make_call ~procname:g_proc_name g_ret_id g_args; (* means r = a.g(1) *) + invariant "{ g }"] ) + ; ( "handle_two_proc_calls" + , [ make_call ~procname:f_proc_name None [] + ; invariant "{ f }" + ; make_call ~procname:g_proc_name g_ret_id g_args + ; invariant "{ f, g }" ] ) + ; ( "dont_record_procs_twice" + , [ make_call ~procname:f_proc_name None [] + ; invariant "{ f }" + ; make_call ~procname:f_proc_name None [] + ; invariant "{ f }" ] ) ] + |> TestInterpreter.create_tests ~test_pname:caller_foo_name + ~initial:BoundedCallTree.Domain.empty extras + in + let test_list_from_bar = + [ ( "on_call_anywhere_on_stack_add_proc_name" + , [make_call ~procname:f_proc_name None []; (* means f() *) + invariant "{ f }"] ) ] + |> TestInterpreter.create_tests ~test_pname:caller_bar_name extras ~initial + in + let test_list_from_baz = + [ ( "ignore_procs_unrelated_to_trace" + , [make_call ~procname:f_proc_name None []; (* means f() *) + invariant "{ }"] ) ] + |> TestInterpreter.create_tests ~test_pname:caller_baz_name extras ~initial + in + let test_list_multiple_traces_from_foo = + [ ( "on_call_add_proc_name_in_any_stack_1" + , [make_call ~procname:f_proc_name None []; (* means f() *) + invariant "{ f }"] ) ] + |> TestInterpreter.create_tests ~test_pname:caller_foo_name multi_trace_extras ~initial + in + let test_list_multiple_traces_from_bar = + [ ( "on_call_add_proc_name_in_any_stack_2" + , [make_call ~procname:f_proc_name None []; (* means f() *) + invariant "{ f }"] ) ] + |> TestInterpreter.create_tests ~test_pname:caller_bar_name multi_trace_extras ~initial + in + let test_list = + test_list_from_foo @ test_list_from_bar @ test_list_from_baz + @ test_list_multiple_traces_from_foo @ test_list_multiple_traces_from_bar + in + "bounded_calltree_test_suite" >::: test_list diff --git a/infer/src/unit/DifferentialFiltersTests.ml b/infer/src/unit/DifferentialFiltersTests.ml index 4d8a6ff45..4354f2f0e 100644 --- a/infer/src/unit/DifferentialFiltersTests.ml +++ b/infer/src/unit/DifferentialFiltersTests.ml @@ -9,7 +9,6 @@ open! IStd open OUnit2 - open DifferentialTestsUtils type 'a outcome = Return of 'a | Raise of exn @@ -19,543 +18,390 @@ let test_file_renamings_from_json = let test_output input = DifferentialFilters.FileRenamings.from_json input in let pp_diff fmt (expected, actual) = let pp = DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.pp in - Format.fprintf fmt "Expected %a but got %a" pp expected pp actual in + Format.fprintf fmt "Expected %a but got %a" pp expected pp actual + in match expected_output with - | Return exp -> - assert_equal - ~pp_diff - ~cmp:DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.equal - exp + | Return exp + -> assert_equal ~pp_diff + ~cmp:DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.equal exp (test_output test_input) - | Raise exc -> - assert_raises exc (fun () -> test_output test_input) in - [ - ( - "test_file_renamings_from_json_with_good_input", - "[" ^ - "{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}," ^ - "{\"current\": \"ccc.java\", \"previous\": \"DDD.java\"}," ^ - "{\"current\": \"eee.java\", \"previous\": \"FFF.java\"}" ^ - "]", - Return ( - DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [ - {DifferentialFilters.FileRenamings.current = "aaa.java"; previous = "BBB.java"}; - {DifferentialFilters.FileRenamings.current = "ccc.java"; previous = "DDD.java"}; - {DifferentialFilters.FileRenamings.current = "eee.java"; previous = "FFF.java"}; - ] - ) - ); - ( - "test_file_renamings_from_json_with_good_empty_input", - "[]", - Return ( - DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [] - ) - ); - ( - "test_file_renamings_from_json_with_well_formed_but_unexpected_input", - "{}", - Raise ( - Failure "Expected JSON list but got '{}'" - ) - ); - ( - "test_file_renamings_from_json_with_well_formed_but_unexpected_value", - "[{\"current\": 1, \"previous\": \"BBB.java\"}]", - Raise ( - Failure ("Expected JSON object of the following form: " ^ - "'{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}', " ^ - "but instead got: '{\"current\":1,\"previous\":\"BBB.java\"}'") - ) - ); - ( - "test_file_renamings_from_json_with_malformed_input", - "A", - Raise ( - Yojson.Json_error "Line 1, bytes 0-1:\nInvalid token 'A'" - ) - ); - ] - |> List.map - ~f:(fun (name, test_input, expected_output) -> - name >:: create_test test_input expected_output) + | Raise exc + -> assert_raises exc (fun () -> test_output test_input) + in + [ ( "test_file_renamings_from_json_with_good_input" + , "[" ^ "{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}," + ^ "{\"current\": \"ccc.java\", \"previous\": \"DDD.java\"}," + ^ "{\"current\": \"eee.java\", \"previous\": \"FFF.java\"}" ^ "]" + , Return + (DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings + [ {DifferentialFilters.FileRenamings.current= "aaa.java"; previous= "BBB.java"} + ; {DifferentialFilters.FileRenamings.current= "ccc.java"; previous= "DDD.java"} + ; {DifferentialFilters.FileRenamings.current= "eee.java"; previous= "FFF.java"} ]) ) + ; ( "test_file_renamings_from_json_with_good_empty_input" + , "[]" + , Return + (DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings + []) ) + ; ( "test_file_renamings_from_json_with_well_formed_but_unexpected_input" + , "{}" + , Raise (Failure "Expected JSON list but got '{}'") ) + ; ( "test_file_renamings_from_json_with_well_formed_but_unexpected_value" + , "[{\"current\": 1, \"previous\": \"BBB.java\"}]" + , Raise + (Failure + ( "Expected JSON object of the following form: " + ^ "'{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}', " + ^ "but instead got: '{\"current\":1,\"previous\":\"BBB.java\"}'" )) ) + ; ( "test_file_renamings_from_json_with_malformed_input" + , "A" + , Raise (Yojson.Json_error "Line 1, bytes 0-1:\nInvalid token 'A'") ) ] + |> List.map ~f:(fun (name, test_input, expected_output) -> + name >:: create_test test_input expected_output ) let test_file_renamings_find_previous = let renamings = - DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [ - {DifferentialFilters.FileRenamings.current = "aaa.java"; previous = "BBB.java"}; - {DifferentialFilters.FileRenamings.current = "ccc.java"; previous = "DDD.java"}; - {DifferentialFilters.FileRenamings.current = "eee.java"; previous = "FFF.java"}; - ] in + DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings + [ {DifferentialFilters.FileRenamings.current= "aaa.java"; previous= "BBB.java"} + ; {DifferentialFilters.FileRenamings.current= "ccc.java"; previous= "DDD.java"} + ; {DifferentialFilters.FileRenamings.current= "eee.java"; previous= "FFF.java"} ] + in let cmp s1 s2 = [%compare.equal : string option] s1 s2 in let find_previous = DifferentialFilters.FileRenamings.find_previous in let pp_diff fmt (expected, actual) = let pp_str_opt fmt str_opt = - let out = match str_opt with - | Some str -> "Some " ^ str - | None -> "None" in - Format.fprintf fmt "%s" out in - Format.fprintf fmt "Expected '%a' but got '%a'" pp_str_opt expected pp_str_opt actual in + let out = match str_opt with Some str -> "Some " ^ str | None -> "None" in + Format.fprintf fmt "%s" out + in + Format.fprintf fmt "Expected '%a' but got '%a'" pp_str_opt expected pp_str_opt actual + in let create_test input expected_previous _ = - assert_equal ~cmp ~pp_diff expected_previous (find_previous renamings input) in - [ - ( - "test_file_renamings_find_previous_with_existing_value", - "ccc.java", - Some "DDD.java" - ); - ( - "test_file_renamings_find_previous_with_existing_value", - "abc.java", - None - ) - ] - |> List.map - ~f:(fun (name, test_input, expected_output) -> - name >:: create_test test_input expected_output) + assert_equal ~cmp ~pp_diff expected_previous (find_previous renamings input) + in + [ ("test_file_renamings_find_previous_with_existing_value", "ccc.java", Some "DDD.java") + ; ("test_file_renamings_find_previous_with_existing_value", "abc.java", None) ] + |> List.map ~f:(fun (name, test_input, expected_output) -> + name >:: create_test test_input expected_output ) let test_relative_complements = let create_test pred (l1, l2) (expected_l1, expected_l2, expected_l3) _ = let cmp = Int.compare in let output_l1, output_l2, output_l3 = - DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.relative_complements - ~cmp ~pred l1 l2 in + DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.relative_complements ~cmp ~pred + l1 l2 + in let list_equal l1 l2 = List.equal ~equal:(fun v1 v2 -> Int.equal (cmp v1 v2) 0) l1 l2 in - assert_equal - ~pp_diff:(pp_diff_of_int_list "First list") ~cmp:list_equal expected_l1 output_l1; - assert_equal - ~pp_diff:(pp_diff_of_int_list "Second list") ~cmp:list_equal expected_l2 output_l2; - assert_equal - ~pp_diff:(pp_diff_of_int_list "Third list") ~cmp:list_equal expected_l3 output_l3 in - [ - ( - "test_relative_complements_with_always_true_pred", - (fun _ -> true), - ([0;1;2;3;4;5], [5;3;7;1;1;2]), - ([4;0], [5;3;2;1], [7]) - ); - ( - "test_relative_complements_with_even_numbers_pred", - (fun i -> Int.equal (i mod 2) 0), (* skip when even, keep odd *) - ([0;1;2;3;4;5], [5;3;7;1;1;2]), - ([5;4;3;1;0], [2], [7;5;3;1;1]) - ); - ( - "test_relative_complements_with_even_numbers_pred_2", - (fun i -> Int.equal (i mod 2) 0), (* skip when even, keep odd *) - ([0;1;2;3;5;5], [1;1;2;3;4;7]), - ([5;5;3;1;0], [2], [7;4;3;1;1]) - ); - ( - "test_relative_complements_with_always_true_pred_and_disjoint_lists_of_different_length", - (fun _ -> true), - ([0;3;2;3;5], [9;7;6;8;4;6;9]), - ([5;3;3;2;0], [], [9;9;8;7;6;6;4]) - ); - ( - "test_relative_complements_with_always_true_pred_and_lists_of_different_length", - (fun _ -> true), - ([0;3;2;3], [9;7;3;8;0;6;9;4]), - ([2], [3;0], [9;9;8;7;6;4]) - ); - ( - "test_relative_complements_with_odd_numbers_on_lists_of_different_length", - (fun i -> Int.equal (i mod 2) 1), (* skip when odd, keep even *) - ([0;3;2;3], [9;7;3;8;0;6;9;4]), - ([2;0], [3], [9;9;8;7;6;4;0]) - ); - ( - "test_relative_complements_with_singleton_lists1", - (fun _ -> true), - ([0], [0;1;0;0]), - ([], [0], [1]) - ); - ( - "test_relative_complements_with_singleton_lists2", - (fun _ -> true), - ([0;1;0;0], [0]), - ([1], [0], []) - ); - ( - "test_relative_complements_with_singleton_lists3", - (fun _ -> true), - ([0], [0]), - ([], [0], []) - ); - ( - "test_relative_complements_with_singleton_lists4", - (fun _ -> true), - ([0], [1]), - ([0], [], [1]) - ); - ( - "test_relative_complements_with_empty_lists1", - (fun _ -> true), - ([], [0;1;0;0]), - ([], [], [1;0;0;0]) - ); - ( - "test_relative_complements_with_empty_lists2", - (fun _ -> true), - ([0;1;0;0], []), - ([1;0;0;0], [], []) - ); - ( - "test_relative_complements_with_empty_lists3", - (fun _ -> true), - ([], []), - ([], [], []) - ); - ] - |> List.map - ~f:(fun (name, pred, test_input, expected_output) -> - name >:: create_test pred test_input expected_output) + assert_equal ~pp_diff:(pp_diff_of_int_list "First list") ~cmp:list_equal expected_l1 output_l1 ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Second list") ~cmp:list_equal expected_l2 output_l2 ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Third list") ~cmp:list_equal expected_l3 output_l3 + in + [ ( "test_relative_complements_with_always_true_pred" + , (fun _ -> true) + , ([0; 1; 2; 3; 4; 5], [5; 3; 7; 1; 1; 2]) + , ([4; 0], [5; 3; 2; 1], [7]) ) + ; ( "test_relative_complements_with_even_numbers_pred" + , (fun i -> Int.equal (i mod 2) 0) + , (* skip when even, keep odd *) + ([0; 1; 2; 3; 4; 5], [5; 3; 7; 1; 1; 2]) + , ([5; 4; 3; 1; 0], [2], [7; 5; 3; 1; 1]) ) + ; ( "test_relative_complements_with_even_numbers_pred_2" + , (fun i -> Int.equal (i mod 2) 0) + , (* skip when even, keep odd *) + ([0; 1; 2; 3; 5; 5], [1; 1; 2; 3; 4; 7]) + , ([5; 5; 3; 1; 0], [2], [7; 4; 3; 1; 1]) ) + ; ( "test_relative_complements_with_always_true_pred_and_disjoint_lists_of_different_length" + , (fun _ -> true) + , ([0; 3; 2; 3; 5], [9; 7; 6; 8; 4; 6; 9]) + , ([5; 3; 3; 2; 0], [], [9; 9; 8; 7; 6; 6; 4]) ) + ; ( "test_relative_complements_with_always_true_pred_and_lists_of_different_length" + , (fun _ -> true) + , ([0; 3; 2; 3], [9; 7; 3; 8; 0; 6; 9; 4]) + , ([2], [3; 0], [9; 9; 8; 7; 6; 4]) ) + ; ( "test_relative_complements_with_odd_numbers_on_lists_of_different_length" + , (fun i -> Int.equal (i mod 2) 1) + , (* skip when odd, keep even *) + ([0; 3; 2; 3], [9; 7; 3; 8; 0; 6; 9; 4]) + , ([2; 0], [3], [9; 9; 8; 7; 6; 4; 0]) ) + ; ( "test_relative_complements_with_singleton_lists1" + , (fun _ -> true) + , ([0], [0; 1; 0; 0]) + , ([], [0], [1]) ) + ; ( "test_relative_complements_with_singleton_lists2" + , (fun _ -> true) + , ([0; 1; 0; 0], [0]) + , ([1], [0], []) ) + ; ("test_relative_complements_with_singleton_lists3", (fun _ -> true), ([0], [0]), ([], [0], [])) + ; ("test_relative_complements_with_singleton_lists4", (fun _ -> true), ([0], [1]), ([0], [], [1])) + ; ( "test_relative_complements_with_empty_lists1" + , (fun _ -> true) + , ([], [0; 1; 0; 0]) + , ([], [], [1; 0; 0; 0]) ) + ; ( "test_relative_complements_with_empty_lists2" + , (fun _ -> true) + , ([0; 1; 0; 0], []) + , ([1; 0; 0; 0], [], []) ) + ; ("test_relative_complements_with_empty_lists3", (fun _ -> true), ([], []), ([], [], [])) ] + |> List.map ~f:(fun (name, pred, test_input, expected_output) -> + name >:: create_test pred test_input expected_output ) let test_skip_duplicated_types_on_filenames = - let current_report = [ - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:22 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:11 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:111 (); - create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_4.java" ~hash:4 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 (); - create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:55 (); - ] in - let previous_report = [ - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 (); - create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:5 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 (); - create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_3.java" ~hash:3 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2.java" ~hash:2 (); - ] in + let current_report = + [ create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:22 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:11 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:111 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_4.java" ~hash:4 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:55 () ] + in + let previous_report = + [ create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:5 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_3.java" ~hash:3 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2.java" ~hash:2 () ] + in let renamings = - DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [ - {DifferentialFilters.FileRenamings.current = "file_2'.java"; previous = "file_2.java"}; - {DifferentialFilters.FileRenamings.current = "file_1'.java"; previous = "file_1.java"}; - ] in + DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings + [ {DifferentialFilters.FileRenamings.current= "file_2'.java"; previous= "file_2.java"} + ; {DifferentialFilters.FileRenamings.current= "file_1'.java"; previous= "file_1.java"} ] + in let diff = Differential.of_reports ~current_report ~previous_report in let diff' = DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.skip_duplicated_types_on_filenames - renamings diff in + renamings diff + in let do_assert _ = - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") - [4] (sorted_hashes_of_issues diff'.introduced); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") - [3] (sorted_hashes_of_issues diff'.fixed); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") - [22; 55; 111; 222] (sorted_hashes_of_issues diff'.preexisting) in + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") [4] + (sorted_hashes_of_issues diff'.introduced) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") [3] + (sorted_hashes_of_issues diff'.fixed) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") [22; 55; 111; 222] + (sorted_hashes_of_issues diff'.preexisting) + in "test_skip_duplicated_types_on_filenames" >:: do_assert let test_value_of_qualifier_tag = - let qts = - [{Jsonbug_t.tag = "tag1"; value = "value1"}; {Jsonbug_t.tag = "tag2"; value = "value2"}] in + let qts = [{Jsonbug_t.tag= "tag1"; value= "value1"}; {Jsonbug_t.tag= "tag2"; value= "value2"}] in let pp_diff fmt (expected, actual) = let to_str v = Option.value v ~default:"NONE" in - Format.fprintf fmt "Expected: %s Found: %s" (to_str expected) (to_str actual) in + Format.fprintf fmt "Expected: %s Found: %s" (to_str expected) (to_str actual) + in let do_assert _ = - assert_equal - ~cmp:(Option.equal String.equal) - ~pp_diff - (Some "value2") - (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag - qts "tag2"); - assert_equal - ~cmp:(Option.equal String.equal) - ~pp_diff - None - (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag - qts "tag3"); - assert_equal - ~cmp:(Option.equal String.equal) - ~pp_diff - (Some "value1") - (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag - qts "tag1") in + assert_equal ~cmp:(Option.equal String.equal) ~pp_diff (Some "value2") + (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag qts + "tag2") ; + assert_equal ~cmp:(Option.equal String.equal) ~pp_diff None + (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag qts + "tag3") ; + assert_equal ~cmp:(Option.equal String.equal) ~pp_diff (Some "value1") + (DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag qts + "tag1") + in "test_value_of_qualifier_tag" >:: do_assert let test_skip_anonymous_class_renamings = - let qt1 = [{Jsonbug_t.tag = "call_procedure"; value = "aValue1"}] in - let qt2 = [{Jsonbug_t.tag = "call_procedure"; value = "aValue2"}] in - + let qt1 = [{Jsonbug_t.tag= "call_procedure"; value= "aValue1"}] in + let qt2 = [{Jsonbug_t.tag= "call_procedure"; value= "aValue2"}] in let create_test input_diff (exp_introduced, exp_fixed, exp_preexisting) _ = let diff' = DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.skip_anonymous_class_renamings - input_diff in - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") - exp_introduced (sorted_hashes_of_issues diff'.introduced); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") - exp_fixed (sorted_hashes_of_issues diff'.fixed); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") - exp_preexisting (sorted_hashes_of_issues diff'.preexisting) in + input_diff + in + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") exp_introduced + (sorted_hashes_of_issues diff'.introduced) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") exp_fixed + (sorted_hashes_of_issues diff'.fixed) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") exp_preexisting + (sorted_hashes_of_issues diff'.preexisting) + in (* [(test_name, diff, expected hashes); ...] *) - [ - ("test_skip_anonymous_class_renamings_with_long_procedure_ids", - Differential.of_reports - ~current_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - ("com.whatever.package00.abcd." ^ - "ABasicExampleFragment$83.onMenuItemActionExpand(android.view.MenuItem):b." ^ - "5ab5e18cae498c35d887ce88f3d5fa82") - ~file:"a.java" - ~key:1 - ~qualifier_tags:qt1 - ~hash:3 (); - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - ("com.whatever.package00.abcd." ^ - "ABasicExampleFragment$83$7.onMenuItemActionExpand(android.view.MenuItem)." ^ - "522cc747174466169781c9d2fc980dbc") - ~file:"a.java" - ~key:1 - ~qualifier_tags:qt1 - ~hash:4 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"procid5.c854fd4a98113d9ab5b82deb3545de89" - ~file:"b.java" - ~key:5 - ~hash:5 (); - ] - ~previous_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - ("com.whatever.package00.abcd." ^ - "ABasicExampleFragment$9.onMenuItemActionExpand(android.view.MenuItem):bo." ^ - "ba1776155fba2899542401da5bc779a5") - ~file:"a.java" - ~key:1 - ~qualifier_tags:qt1 - ~hash:1 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"procid2.92095aee3f1884c37e96feae031f4931" - ~file:"b.java" - ~key:2 - ~hash:2 (); - ], - ([4;5], [2], [3])); - ("test_skip_anonymous_class_renamings_with_empty_qualifier_tags", - Differential.of_reports - ~current_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" - ~file:"a.java" - ~key:1 - ~hash:1 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" - ~file:"a.java" - ~key:1 - ~hash:3 (); - ] - ~previous_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" - ~file:"a.java" - ~key:1 - ~hash:2 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d" - ~file:"a.java" - ~key:1 - ~hash:4 (); - ], - ([1], [2], [3])); - ("test_skip_anonymous_class_renamings_with_matching_non_anonymous_procedure_ids", - Differential.of_reports - ~current_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" - ~file:"a.java" - ~key:1 - ~hash:1 (); - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" - ~file:"a.java" - ~key:1 - ~hash:3 (); - ] - ~previous_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" - ~file:"a.java" - ~key:1 - ~hash:2 (); - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" - ~file:"a.java" - ~key:1 - ~hash:4 (); - ], - ([1;3], [2;4], [])); - ("test_skip_anonymous_class_renamings_with_non_java_files", - Differential.of_reports - ~current_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - "com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6" - ~file:"a.java" - ~key:1 - ~hash:1 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" - ~file:"a.mm" - ~key:1 - ~hash:3 (); - ] - ~previous_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" - ~file:"a.java" - ~key:1 - ~hash:2 (); - create_fake_jsonbug - ~bug_type:"bug_type_2" - ~procedure_id:"com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d" - ~file:"a.mm" - ~key:1 - ~hash:4 (); - ], - ([3], [4], [1])); - ("test_skip_anonymous_class_renamings_with_different_call_procedure_qualifier_tags", - Differential.of_reports - ~current_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - "com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6" - ~file:"a.java" - ~key:1 - ~qualifier_tags:qt1 - ~hash:1 (); - ] - ~previous_report:[ - create_fake_jsonbug - ~bug_type:"bug_type_1" - ~procedure_id: - "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" - ~file:"a.java" - ~key:1 - ~qualifier_tags:qt2 - ~hash:2 (); - ], - ([1], [2], [])); - ] |> List.map - ~f:(fun (name, diff, expected_output) -> - name >:: create_test diff expected_output) + [ ( "test_skip_anonymous_class_renamings_with_long_procedure_ids" + , Differential.of_reports + ~current_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + ( "com.whatever.package00.abcd." + ^ "ABasicExampleFragment$83.onMenuItemActionExpand(android.view.MenuItem):b." + ^ "5ab5e18cae498c35d887ce88f3d5fa82" ) + ~file:"a.java" ~key:1 ~qualifier_tags:qt1 ~hash:3 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + ( "com.whatever.package00.abcd." + ^ "ABasicExampleFragment$83$7.onMenuItemActionExpand(android.view.MenuItem)." + ^ "522cc747174466169781c9d2fc980dbc" ) + ~file:"a.java" ~key:1 ~qualifier_tags:qt1 ~hash:4 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id:"procid5.c854fd4a98113d9ab5b82deb3545de89" ~file:"b.java" ~key:5 + ~hash:5 () ] + ~previous_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + ( "com.whatever.package00.abcd." + ^ "ABasicExampleFragment$9.onMenuItemActionExpand(android.view.MenuItem):bo." + ^ "ba1776155fba2899542401da5bc779a5" ) + ~file:"a.java" ~key:1 ~qualifier_tags:qt1 ~hash:1 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id:"procid2.92095aee3f1884c37e96feae031f4931" ~file:"b.java" ~key:2 + ~hash:2 () ] + , ([4; 5], [2], [3]) ) + ; ( "test_skip_anonymous_class_renamings_with_empty_qualifier_tags" + , Differential.of_reports + ~current_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" + ~file:"a.java" ~key:1 ~hash:1 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id: + "com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" + ~file:"a.java" ~key:1 ~hash:3 () ] + ~previous_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" + ~file:"a.java" ~key:1 ~hash:2 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id: + "com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d" + ~file:"a.java" ~key:1 ~hash:4 () ] + , ([1], [2], [3]) ) + ; ( "test_skip_anonymous_class_renamings_with_matching_non_anonymous_procedure_ids" + , Differential.of_reports + ~current_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" + ~file:"a.java" ~key:1 ~hash:1 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" + ~file:"a.java" ~key:1 ~hash:3 () ] + ~previous_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" + ~file:"a.java" ~key:1 ~hash:2 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247" + ~file:"a.java" ~key:1 ~hash:4 () ] + , ([1; 3], [2; 4], []) ) + ; ( "test_skip_anonymous_class_renamings_with_non_java_files" + , Differential.of_reports + ~current_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6" + ~file:"a.java" ~key:1 ~hash:1 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id: + "com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f" + ~file:"a.mm" ~key:1 ~hash:3 () ] + ~previous_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" + ~file:"a.java" ~key:1 ~hash:2 () + ; create_fake_jsonbug ~bug_type:"bug_type_2" + ~procedure_id: + "com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d" + ~file:"a.mm" ~key:1 ~hash:4 () ] + , ([3], [4], [1]) ) + ; ( "test_skip_anonymous_class_renamings_with_different_call_procedure_qualifier_tags" + , Differential.of_reports + ~current_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6" + ~file:"a.java" ~key:1 ~qualifier_tags:qt1 ~hash:1 () ] + ~previous_report: + [ create_fake_jsonbug ~bug_type:"bug_type_1" + ~procedure_id: + "com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03" + ~file:"a.java" ~key:1 ~qualifier_tags:qt2 ~hash:2 () ] + , ([1], [2], []) ) ] + |> List.map ~f:(fun (name, diff, expected_output) -> name >:: create_test diff expected_output) let test_resolve_infer_eradicate_conflict = let fake_filters_factory analyzer = match analyzer with - | Config.Eradicate -> - { - Inferconfig.path_filter = (function _ -> true); (* all paths are whitelisted *) - error_filter = (function _ -> failwith "error_filter is not needed"); - proc_filter = (function _ -> failwith "proc_filter is not needed"); - } - | _ -> failwith "This mock only supports Eradicate" in + | Config.Eradicate + -> { Inferconfig.path_filter= (function _ -> true) + ; (* all paths are whitelisted *) + error_filter= (function _ -> failwith "error_filter is not needed") + ; proc_filter= (function _ -> failwith "proc_filter is not needed") } + | _ + -> failwith "This mock only supports Eradicate" + in let create_test analyzer (exp_introduced, exp_fixed, exp_preexisting) _ = let null_dereference = Localise.to_issue_id Localise.null_dereference in - let current_report = [ - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 (); - create_fake_jsonbug ~bug_type:null_dereference ~file:"file_2.java" ~hash:2 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 (); - ] in - let previous_report = [ - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:11 (); - create_fake_jsonbug ~bug_type:null_dereference ~file:"file_3.java" ~hash:3 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 (); - ] in + let current_report = + [ create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 () + ; create_fake_jsonbug ~bug_type:null_dereference ~file:"file_2.java" ~hash:2 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 () ] + in + let previous_report = + [ create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:11 () + ; create_fake_jsonbug ~bug_type:null_dereference ~file:"file_3.java" ~hash:3 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 () ] + in let diff = Differential.of_reports ~current_report ~previous_report in let diff' = DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.resolve_infer_eradicate_conflict - analyzer fake_filters_factory diff in - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") - exp_introduced (sorted_hashes_of_issues diff'.introduced); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") - exp_fixed (sorted_hashes_of_issues diff'.fixed); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") - exp_preexisting (sorted_hashes_of_issues diff'.preexisting) in + analyzer fake_filters_factory diff + in + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") exp_introduced + (sorted_hashes_of_issues diff'.introduced) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") exp_fixed + (sorted_hashes_of_issues diff'.fixed) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") exp_preexisting + (sorted_hashes_of_issues diff'.preexisting) + in (* [(test_name, analyzer, expected_hashes); ...] *) - [ - ("test_resolve_infer_eradicate_conflict_runs_with_infer_analyzer", - Config.BiAbduction, - ([1], [11], [4])); - ("test_resolve_infer_eradicate_conflict_skips_with_checkers_analyzer", - Config.Checkers, - ([1;2], [3;11], [4])); - ("test_resolve_infer_eradicate_conflict_skips_with_linters_analyzer", - Config.Linters, - ([1;2], [3;11], [4])); - ] |> List.map - ~f:(fun (name, analyzer, expected_output) -> - name >:: create_test analyzer expected_output) + [ ( "test_resolve_infer_eradicate_conflict_runs_with_infer_analyzer" + , Config.BiAbduction + , ([1], [11], [4]) ) + ; ( "test_resolve_infer_eradicate_conflict_skips_with_checkers_analyzer" + , Config.Checkers + , ([1; 2], [3; 11], [4]) ) + ; ( "test_resolve_infer_eradicate_conflict_skips_with_linters_analyzer" + , Config.Linters + , ([1; 2], [3; 11], [4]) ) ] + |> List.map ~f:(fun (name, analyzer, expected_output) -> + name >:: create_test analyzer expected_output ) let test_interesting_paths_filter = - let report = [ - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 (); - create_fake_jsonbug - ~bug_type:(Localise.to_issue_id Localise.null_dereference) ~file:"file_2.java" ~hash:2 (); - create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 (); - ] in + let report = + [ create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 () + ; create_fake_jsonbug ~bug_type:(Localise.to_issue_id Localise.null_dereference) + ~file:"file_2.java" ~hash:2 () + ; create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 () ] + in let create_test interesting_paths expected_hashes _ = let filter = DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.interesting_paths_filter - interesting_paths in + interesting_paths + in let filtered_report = filter report in - assert_equal - ~pp_diff:(pp_diff_of_int_list "Bug hash") - expected_hashes (sorted_hashes_of_issues filtered_report) in - [ - ("test_interesting_paths_filter_with_none_interesting_paths", - None, - [1;2;4]); - ("test_interesting_paths_filter_with_some_interesting_paths", - Some [ - SourceFile.create ~warn_on_error:false "file_not_existing.java"; - SourceFile.create ~warn_on_error:false "file_4.java"; - ], - [4]); - ("test_interesting_paths_filter_with_some_interesting_paths_that_are_not_in_report", - Some [ - SourceFile.create ~warn_on_error:false "file_not_existing.java"; - SourceFile.create ~warn_on_error:false "file_whatever.java"; - ], - []); - ] |> List.map - ~f:(fun (name, interesting_paths, expected_output) -> - name >:: create_test interesting_paths expected_output) + assert_equal ~pp_diff:(pp_diff_of_int_list "Bug hash") expected_hashes + (sorted_hashes_of_issues filtered_report) + in + [ ("test_interesting_paths_filter_with_none_interesting_paths", None, [1; 2; 4]) + ; ( "test_interesting_paths_filter_with_some_interesting_paths" + , Some + [ SourceFile.create ~warn_on_error:false "file_not_existing.java" + ; SourceFile.create ~warn_on_error:false "file_4.java" ] + , [4] ) + ; ( "test_interesting_paths_filter_with_some_interesting_paths_that_are_not_in_report" + , Some + [ SourceFile.create ~warn_on_error:false "file_not_existing.java" + ; SourceFile.create ~warn_on_error:false "file_whatever.java" ] + , [] ) ] + |> List.map ~f:(fun (name, interesting_paths, expected_output) -> + name >:: create_test interesting_paths expected_output ) -let tests = "differential_filters_suite" >::: - test_file_renamings_from_json @ - test_file_renamings_find_previous @ - test_relative_complements @ - test_skip_anonymous_class_renamings @ - test_resolve_infer_eradicate_conflict @ - test_interesting_paths_filter @ - [test_skip_duplicated_types_on_filenames; test_value_of_qualifier_tag] +let tests = + "differential_filters_suite" + >::: test_file_renamings_from_json @ test_file_renamings_find_previous + @ test_relative_complements @ test_skip_anonymous_class_renamings + @ test_resolve_infer_eradicate_conflict @ test_interesting_paths_filter + @ [test_skip_duplicated_types_on_filenames; test_value_of_qualifier_tag] diff --git a/infer/src/unit/DifferentialTests.ml b/infer/src/unit/DifferentialTests.ml index 38b7f6c7b..2f2f42dd8 100644 --- a/infer/src/unit/DifferentialTests.ml +++ b/infer/src/unit/DifferentialTests.ml @@ -8,54 +8,46 @@ *) open! IStd - open OUnit2 - open DifferentialTestsUtils -let current_report = [ - create_fake_jsonbug ~hash:3 (); - create_fake_jsonbug ~hash:1 (); - create_fake_jsonbug ~hash:2 (); - create_fake_jsonbug ~hash:2 (); - create_fake_jsonbug ~hash:2 (); -] +let current_report = + [ create_fake_jsonbug ~hash:3 () + ; create_fake_jsonbug ~hash:1 () + ; create_fake_jsonbug ~hash:2 () + ; create_fake_jsonbug ~hash:2 () + ; create_fake_jsonbug ~hash:2 () ] -let previous_report = [ - create_fake_jsonbug ~hash:1 (); - create_fake_jsonbug ~hash:4 (); - create_fake_jsonbug ~hash:1 (); -] +let previous_report = + [create_fake_jsonbug ~hash:1 (); create_fake_jsonbug ~hash:4 (); create_fake_jsonbug ~hash:1 ()] let diff = Differential.of_reports ~current_report ~previous_report (* Sets operations should keep duplicated issues with identical hashes *) let test_diff_keeps_duplicated_hashes = let hashes_expected = 3 in - let hashes_found = List.fold - ~init:0 + let hashes_found = + List.fold ~init:0 ~f:(fun acc i -> if Int.equal i.Jsonbug_t.hash 2 then acc + 1 else acc) - diff.introduced in + diff.introduced + in let pp_diff fmt (expected, actual) = - Format.fprintf fmt - "Expected %d issues with hash=2 among the introduced, but got %d instead" - expected - actual in + Format.fprintf fmt "Expected %d issues with hash=2 among the introduced, but got %d instead" + expected actual + in let do_assert _ = assert_equal ~pp_diff hashes_expected hashes_found in "test_diff_keeps_duplicated_hashes" >:: do_assert (* Sets operations to compute introduced, fixed and preexisting issues are correct *) let test_set_operations = let do_assert _ = - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") - [2;2;2;3] (sorted_hashes_of_issues diff.introduced); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") - [4] (sorted_hashes_of_issues diff.fixed); - assert_equal - ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") - [1] (sorted_hashes_of_issues diff.preexisting) in + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of introduced") [2; 2; 2; 3] + (sorted_hashes_of_issues diff.introduced) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of fixed") [4] + (sorted_hashes_of_issues diff.fixed) ; + assert_equal ~pp_diff:(pp_diff_of_int_list "Hashes of preexisting") [1] + (sorted_hashes_of_issues diff.preexisting) + in "test_set_operations" >:: do_assert let tests = "differential_suite" >::: [test_diff_keeps_duplicated_hashes; test_set_operations] diff --git a/infer/src/unit/DifferentialTestsUtils.ml b/infer/src/unit/DifferentialTestsUtils.ml index e5cc01789..c156999d0 100644 --- a/infer/src/unit/DifferentialTestsUtils.ml +++ b/infer/src/unit/DifferentialTestsUtils.ml @@ -9,58 +9,38 @@ open! IStd -let create_fake_jsonbug - ?(bug_class="bug_class") - ?(kind="kind") - ?(bug_type="bug_type") - ?(qualifier="qualifier") - ?(severity="severity") - ?(visibility="visibility") - ?(line=1) - ?(column=1) - ?(procedure="procedure") - ?(procedure_id="procedure_id") - ?(procedure_start_line=1) - ?(file="file/at/a/certain/path.java") - ?(bug_trace=[]) - ?(key=1234) - ?(qualifier_tags=[]) - ?(hash=1) - ?(dotty=None) - ?(infer_source_loc=None) - ?(linters_def_file=Some "file/at/certain/path.al") - ?doc_url () : Jsonbug_t.jsonbug = - { - bug_class; - kind; - bug_type; - qualifier; - severity; - visibility; - line; - column; - procedure; - procedure_id; - procedure_start_line; - file; - bug_trace; - key; - qualifier_tags; - hash; - dotty; - infer_source_loc; - bug_type_hum = kind; - linters_def_file; - doc_url; - traceview_id = None; - } +let create_fake_jsonbug ?(bug_class= "bug_class") ?(kind= "kind") ?(bug_type= "bug_type") + ?(qualifier= "qualifier") ?(severity= "severity") ?(visibility= "visibility") ?(line= 1) + ?(column= 1) ?(procedure= "procedure") ?(procedure_id= "procedure_id") + ?(procedure_start_line= 1) ?(file= "file/at/a/certain/path.java") ?(bug_trace= []) ?(key= 1234) + ?(qualifier_tags= []) ?(hash= 1) ?(dotty= None) ?(infer_source_loc= None) + ?(linters_def_file= Some "file/at/certain/path.al") ?doc_url () : Jsonbug_t.jsonbug = + { bug_class + ; kind + ; bug_type + ; qualifier + ; severity + ; visibility + ; line + ; column + ; procedure + ; procedure_id + ; procedure_start_line + ; file + ; bug_trace + ; key + ; qualifier_tags + ; hash + ; dotty + ; infer_source_loc + ; bug_type_hum= kind + ; linters_def_file + ; doc_url + ; traceview_id= None } let pp_diff_of_int_list group_name fmt (expected, actual) = - Format.fprintf fmt - "[%s]: Expected: [%a] Found: [%a]" - group_name - (Pp.comma_seq Format.pp_print_int) expected - (Pp.comma_seq Format.pp_print_int) actual + Format.fprintf fmt "[%s]: Expected: [%a] Found: [%a]" group_name + (Pp.comma_seq Format.pp_print_int) expected (Pp.comma_seq Format.pp_print_int) actual (* Sort hashes to make things easier to compare *) let sorted_hashes_of_issues issues = diff --git a/infer/src/unit/TaintTests.ml b/infer/src/unit/TaintTests.ml index 1266664b1..e551ca23a 100644 --- a/infer/src/unit/TaintTests.ml +++ b/infer/src/unit/TaintTests.ml @@ -8,50 +8,54 @@ *) open! IStd - module F = Format -module MockTrace = Trace.Make(struct - module MockTraceElem = CallSite - module Source = Source.Make(struct - include MockTraceElem +module MockTrace = Trace.Make (struct + module MockTraceElem = CallSite - let unknown = CallSite.dummy + module Source = Source.Make (struct + include MockTraceElem - let get pname _ = - if String.is_prefix ~prefix:"SOURCE" (Typ.Procname.to_string pname) - then Some (CallSite.make pname Location.dummy, None) - else None + let unknown = CallSite.dummy - let get_tainted_formals _ _ = - [] - end) + let get pname _ = + if String.is_prefix ~prefix:"SOURCE" (Typ.Procname.to_string pname) then + Some (CallSite.make pname Location.dummy, None) + else None - module Sink = Sink.Make(struct - include MockTraceElem + let get_tainted_formals _ _ = [] + end) - let get pname _ _ = - if String.is_prefix ~prefix:"SINK" (Typ.Procname.to_string pname) - then Some (CallSite.make pname Location.dummy, IntSet.singleton 0) - else None + module Sink = Sink.Make (struct + include MockTraceElem - let indexes _ = IntSet.empty - end) + let get pname _ _ = + if String.is_prefix ~prefix:"SINK" (Typ.Procname.to_string pname) then + Some (CallSite.make pname Location.dummy, IntSet.singleton 0) + else None - let should_report _ _ = false + let indexes _ = IntSet.empty end) -module MockTaintAnalysis = TaintAnalysis.Make(struct - module Trace = MockTrace - module AccessTree = AccessTree.Make(Trace) + let should_report _ _ = false +end) - let of_summary_access_tree _ = assert false - let to_summary_access_tree _ = assert false - let handle_unknown_call _ _ _ _ = [] - let is_taintable_type _ = true - let get_model _ _ _ _ _ = None - let get_sanitizer _ = None - end) +module MockTaintAnalysis = TaintAnalysis.Make (struct + module Trace = MockTrace + module AccessTree = AccessTree.Make (Trace) + + let of_summary_access_tree _ = assert false + + let to_summary_access_tree _ = assert false + + let handle_unknown_call _ _ _ _ = [] + + let is_taintable_type _ = true + + let get_model _ _ _ _ _ = None + + let get_sanitizer _ = None +end) module TestInterpreter = AnalyzerTester.Make (ProcCfg.Normal) (LowerHil.Make (MockTaintAnalysis.TransferFunctions)) @@ -62,173 +66,133 @@ let tests = (* less verbose form of pretty-printing to make writing tests easy *) let pp_sparse fmt astate = let pp_call_site fmt call_site = - F.fprintf fmt "%a" Typ.Procname.pp (CallSite.pname call_site) in + F.fprintf fmt "%a" Typ.Procname.pp (CallSite.pname call_site) + in let pp_sources fmt sources = - if MockTrace.Sources.is_empty sources - then F.fprintf fmt "?" + if MockTrace.Sources.is_empty sources then F.fprintf fmt "?" else MockTrace.Sources.iter (fun source -> pp_call_site fmt (MockTrace.Source.call_site source)) - sources in + sources + in let pp_sinks fmt sinks = - if MockTrace.Sinks.is_empty sinks - then F.fprintf fmt "?" + if MockTrace.Sinks.is_empty sinks then F.fprintf fmt "?" else - MockTrace.Sinks.iter - (fun sink -> - pp_call_site fmt (MockTrace.Sink.call_site sink)) - sinks in + MockTrace.Sinks.iter (fun sink -> pp_call_site fmt (MockTrace.Sink.call_site sink)) sinks + in (* just print source -> sink, no line nums or passthroughs *) let pp_trace fmt trace = - F.fprintf - fmt - "(%a -> %a)" - pp_sources (MockTrace.sources trace) - pp_sinks (MockTrace.sinks trace) in - let pp_item fmt (ap, trace) = - F.fprintf fmt "%a => %a" AccessPath.pp ap pp_trace trace in + F.fprintf fmt "(%a -> %a)" pp_sources (MockTrace.sources trace) pp_sinks + (MockTrace.sinks trace) + in + let pp_item fmt (ap, trace) = F.fprintf fmt "%a => %a" AccessPath.pp ap pp_trace trace in (* flatten access tree into list of access paths with associated traces *) let trace_assocs = MockTaintAnalysis.TaintDomain.trace_fold - (fun acc ap t -> - if not (MockTrace.is_empty t) - then (ap, t) :: acc - else acc) - (fst astate) - [] in - PrettyPrintable.pp_collection ~pp_item fmt (List.rev trace_assocs) in + (fun acc ap t -> if not (MockTrace.is_empty t) then (ap, t) :: acc else acc) + (fst astate) [] + in + PrettyPrintable.pp_collection ~pp_item fmt (List.rev trace_assocs) + in let assign_to_source ret_str = let procname = Typ.Procname.from_string_c_fun "SOURCE" in - make_call ~procname (Some (ident_of_str ret_str, dummy_typ)) [] in + make_call ~procname (Some (ident_of_str ret_str, dummy_typ)) [] + in let assign_to_non_source ret_str = let procname = Typ.Procname.from_string_c_fun "NON-SOURCE" in - make_call ~procname (Some (ident_of_str ret_str, dummy_typ)) [] in + make_call ~procname (Some (ident_of_str ret_str, dummy_typ)) [] + in let call_sink_with_exp exp = let procname = Typ.Procname.from_string_c_fun "SINK" in - make_call ~procname None [(exp, dummy_typ)] in - let call_sink actual_str = - call_sink_with_exp (Exp.Var (ident_of_str actual_str)) in + make_call ~procname None [(exp, dummy_typ)] + in + let call_sink actual_str = call_sink_with_exp (Exp.Var (ident_of_str actual_str)) in let assign_id_to_field root_str fld_str rhs_id_str = let rhs_exp = Exp.Var (ident_of_str rhs_id_str) in - make_store ~rhs_typ:(Typ.mk Tvoid) (Exp.Var (ident_of_str root_str)) fld_str ~rhs_exp in + make_store ~rhs_typ:(Typ.mk Tvoid) (Exp.Var (ident_of_str root_str)) fld_str ~rhs_exp + in let read_field_to_id lhs_id_str root_str fld_str = - make_load_fld ~rhs_typ:(Typ.mk Tvoid) lhs_id_str fld_str (Exp.Var (ident_of_str root_str)) in + make_load_fld ~rhs_typ:(Typ.mk Tvoid) lhs_id_str fld_str (Exp.Var (ident_of_str root_str)) + in let assert_empty = invariant "{ }" in (* hack: register an empty analyze_ondemand to prevent a crash because the callback is unset *) let analyze_ondemand summary _ = summary in let get_proc_desc _ = None in - let callbacks = - { - Ondemand.analyze_ondemand; - get_proc_desc; - } in - Ondemand.set_callbacks callbacks; - let test_list = [ - "source recorded", - [ - assign_to_source "ret_id"; - invariant "{ ret_id$0 => (SOURCE -> ?) }"; - ]; - "non-source not recorded", - [ - assign_to_non_source "ret_id"; - assert_empty; - ]; - "source flows to var", - [ - assign_to_source "ret_id"; - var_assign_id "var" "ret_id"; - invariant "{ ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }"; - ]; - "source flows to field", - [ - assign_to_source "ret_id"; - assign_id_to_field "base_id" "f" "ret_id"; - invariant "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?) }"; - ]; - "source flows to field then var", - [ - assign_to_source "ret_id"; - assign_id_to_field "base_id" "f" "ret_id"; - read_field_to_id "read_id" "base_id" "f"; - var_assign_id "var" "read_id"; - invariant - "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }"; - ]; - "source flows to var then cleared", - [ - assign_to_source "ret_id"; - var_assign_id "var" "ret_id"; - invariant "{ ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }"; - assign_to_non_source "non_source_id"; - var_assign_id "var" "non_source_id"; - invariant "{ ret_id$0 => (SOURCE -> ?) }"; - ]; - "source flows to field then cleared", - [ - assign_to_source "ret_id"; - assign_id_to_field "base_id" "f" "ret_id"; - invariant "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?) }"; - assign_to_non_source "non_source_id"; - assign_id_to_field "base_id" "f" "non_source_id"; - invariant "{ ret_id$0 => (SOURCE -> ?) }"; - ]; - "sink without source not tracked", - [ - assign_to_non_source "ret_id"; - call_sink "ret_id"; - assert_empty; - ]; - "source -> sink direct", - [ - assign_to_source "ret_id"; - call_sink "ret_id"; - invariant "{ ret_id$0* => (SOURCE -> SINK) }"; - ]; - "source -> sink via var", - [ - assign_to_source "ret_id"; - var_assign_id "actual" "ret_id"; - call_sink_with_exp (var_of_str "actual"); - invariant "{ ret_id$0 => (SOURCE -> ?), &actual* => (SOURCE -> SINK) }"; - ]; - "source -> sink via var then ident", - [ - assign_to_source "ret_id"; - var_assign_id "x" "ret_id"; - id_assign_var "actual_id" "x"; - call_sink "actual_id"; - invariant "{ ret_id$0 => (SOURCE -> ?), &x* => (SOURCE -> SINK) }"; - ]; - "source -> sink via field", - [ - assign_to_source "ret_id"; - assign_id_to_field "base_id" "f" "ret_id"; - read_field_to_id "actual_id" "base_id" "f"; - call_sink "actual_id"; - invariant "{ base_id$0.f* => (SOURCE -> SINK), ret_id$0 => (SOURCE -> ?) }"; - ]; - "source -> sink via field read from var", - [ - assign_to_source "ret_id"; - assign_id_to_field "base_id" "f" "ret_id"; - var_assign_id "var" "base_id"; - id_assign_var "var_id" "var"; - read_field_to_id "read_id" "var_id" "f"; - call_sink "read_id"; - invariant - "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?), &var.f* => (SOURCE -> SINK) }"; - ]; - "source -> sink via cast", - [ - assign_to_source "ret_id"; - cast_id_to_id "cast_id" (Typ.mk Tvoid) "ret_id"; - call_sink "cast_id"; - invariant "{ ret_id$0* => (SOURCE -> SINK) }"; - ]; - - ] |> TestInterpreter.create_tests - ~pp_opt:pp_sparse - { formal_map=FormalMap.empty; summary=Specs.dummy; } - ~initial:(MockTaintAnalysis.Domain.empty, IdAccessPathMapDomain.empty) in - "taint_test_suite">:::test_list + let callbacks = {Ondemand.analyze_ondemand= analyze_ondemand; get_proc_desc} in + Ondemand.set_callbacks callbacks ; + let test_list = + [ ("source recorded", [assign_to_source "ret_id"; invariant "{ ret_id$0 => (SOURCE -> ?) }"]) + ; ("non-source not recorded", [assign_to_non_source "ret_id"; assert_empty]) + ; ( "source flows to var" + , [ assign_to_source "ret_id" + ; var_assign_id "var" "ret_id" + ; invariant "{ ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }" ] ) + ; ( "source flows to field" + , [ assign_to_source "ret_id" + ; assign_id_to_field "base_id" "f" "ret_id" + ; invariant "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?) }" ] ) + ; ( "source flows to field then var" + , [ assign_to_source "ret_id" + ; assign_id_to_field "base_id" "f" "ret_id" + ; read_field_to_id "read_id" "base_id" "f" + ; var_assign_id "var" "read_id" + ; invariant + "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }" ] + ) + ; ( "source flows to var then cleared" + , [ assign_to_source "ret_id" + ; var_assign_id "var" "ret_id" + ; invariant "{ ret_id$0 => (SOURCE -> ?), &var => (SOURCE -> ?) }" + ; assign_to_non_source "non_source_id" + ; var_assign_id "var" "non_source_id" + ; invariant "{ ret_id$0 => (SOURCE -> ?) }" ] ) + ; ( "source flows to field then cleared" + , [ assign_to_source "ret_id" + ; assign_id_to_field "base_id" "f" "ret_id" + ; invariant "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?) }" + ; assign_to_non_source "non_source_id" + ; assign_id_to_field "base_id" "f" "non_source_id" + ; invariant "{ ret_id$0 => (SOURCE -> ?) }" ] ) + ; ( "sink without source not tracked" + , [assign_to_non_source "ret_id"; call_sink "ret_id"; assert_empty] ) + ; ( "source -> sink direct" + , [ assign_to_source "ret_id" + ; call_sink "ret_id" + ; invariant "{ ret_id$0* => (SOURCE -> SINK) }" ] ) + ; ( "source -> sink via var" + , [ assign_to_source "ret_id" + ; var_assign_id "actual" "ret_id" + ; call_sink_with_exp (var_of_str "actual") + ; invariant "{ ret_id$0 => (SOURCE -> ?), &actual* => (SOURCE -> SINK) }" ] ) + ; ( "source -> sink via var then ident" + , [ assign_to_source "ret_id" + ; var_assign_id "x" "ret_id" + ; id_assign_var "actual_id" "x" + ; call_sink "actual_id" + ; invariant "{ ret_id$0 => (SOURCE -> ?), &x* => (SOURCE -> SINK) }" ] ) + ; ( "source -> sink via field" + , [ assign_to_source "ret_id" + ; assign_id_to_field "base_id" "f" "ret_id" + ; read_field_to_id "actual_id" "base_id" "f" + ; call_sink "actual_id" + ; invariant "{ base_id$0.f* => (SOURCE -> SINK), ret_id$0 => (SOURCE -> ?) }" ] ) + ; ( "source -> sink via field read from var" + , [ assign_to_source "ret_id" + ; assign_id_to_field "base_id" "f" "ret_id" + ; var_assign_id "var" "base_id" + ; id_assign_var "var_id" "var" + ; read_field_to_id "read_id" "var_id" "f" + ; call_sink "read_id" + ; invariant + "{ base_id$0.f => (SOURCE -> ?), ret_id$0 => (SOURCE -> ?), &var.f* => (SOURCE -> SINK) }" + ] ) + ; ( "source -> sink via cast" + , [ assign_to_source "ret_id" + ; cast_id_to_id "cast_id" (Typ.mk Tvoid) "ret_id" + ; call_sink "cast_id" + ; invariant "{ ret_id$0* => (SOURCE -> SINK) }" ] ) ] + |> TestInterpreter.create_tests ~pp_opt:pp_sparse + {formal_map= FormalMap.empty; summary= Specs.dummy} + ~initial:(MockTaintAnalysis.Domain.empty, IdAccessPathMapDomain.empty) + in + "taint_test_suite" >::: test_list diff --git a/infer/src/unit/TraceTests.ml b/infer/src/unit/TraceTests.ml index 01b3ee1bb..d94b9eea9 100644 --- a/infer/src/unit/TraceTests.ml +++ b/infer/src/unit/TraceTests.ml @@ -8,17 +8,11 @@ *) open! IStd - module L = Logging module F = Format module MockTraceElem = struct - type t = - | Kind1 - | Kind2 - | Footprint - | Unknown - [@@deriving compare] + type t = Kind1 | Kind2 | Footprint | Unknown [@@deriving compare] let unknown = Unknown @@ -29,41 +23,50 @@ module MockTraceElem = struct let make ?indexes:_ kind _ = kind let pp fmt = function - | Kind1 -> F.fprintf fmt "Kind1" - | Kind2 -> F.fprintf fmt "Kind2" - | Footprint -> F.fprintf fmt "Footprint" - | Unknown -> F.fprintf fmt "Unknown" + | Kind1 + -> F.fprintf fmt "Kind1" + | Kind2 + -> F.fprintf fmt "Kind2" + | Footprint + -> F.fprintf fmt "Footprint" + | Unknown + -> F.fprintf fmt "Unknown" module Kind = struct type nonrec t = t + let compare = compare + let pp = pp end - module Set = PrettyPrintable.MakePPSet(struct - type nonrec t = t - let compare = compare - let pp = pp - end) + module Set = PrettyPrintable.MakePPSet (struct + type nonrec t = t + + let compare = compare + + let pp = pp + end) let with_callsite t _ = t end module MockSource = struct - include - (Source.Make(struct - include MockTraceElem + include Source.Make (struct + include MockTraceElem + + let get _ = assert false - let get _ = assert false - let get_tainted_formals _ = assert false - end)) + let get_tainted_formals _ = assert false + end) let equal = [%compare.equal : t] end module MockSink = struct include MockTraceElem - type parameter = { sink : t; index : int; } + + type parameter = {sink: t; index: int} let get _ = assert false @@ -72,68 +75,58 @@ module MockSink = struct let equal = [%compare.equal : t] end +module MockTrace = Trace.Make (struct + module Source = MockSource + module Sink = MockSink -module MockTrace = Trace.Make(struct - module Source = MockSource - module Sink = MockSink - - let should_report source sink = - [%compare.equal : MockTraceElem.t] (Source.kind source) (Sink.kind sink) - end) + let should_report source sink = + [%compare.equal : MockTraceElem.t] (Source.kind source) (Sink.kind sink) +end) let tests = let source1 = MockSource.make MockTraceElem.Kind1 CallSite.dummy in let source2 = MockSource.make MockTraceElem.Kind2 CallSite.dummy in let sink1 = MockSink.make MockTraceElem.Kind1 CallSite.dummy in let sink2 = MockSink.make MockTraceElem.Kind2 CallSite.dummy in - let open OUnit2 in let get_reports = let get_reports_ _ = let trace = - MockTrace.of_source source1 - |> MockTrace.add_source source2 - |> MockTrace.add_sink sink1 - |> MockTrace.add_sink sink2 in + MockTrace.of_source source1 |> MockTrace.add_source source2 |> MockTrace.add_sink sink1 + |> MockTrace.add_sink sink2 + in let reports = MockTrace.get_reports trace in - - assert_equal (List.length reports) 2; - assert_bool - "Reports should contain source1 -> sink1" + assert_equal (List.length reports) 2 ; + assert_bool "Reports should contain source1 -> sink1" (List.exists ~f:(fun (source, sink, _) -> - MockSource.equal source source1 && MockSink.equal sink sink1) - reports); - assert_bool - "Reports should contain source2 -> sink2" + MockSource.equal source source1 && MockSink.equal sink sink1) + reports) ; + assert_bool "Reports should contain source2 -> sink2" (List.exists ~f:(fun (source, sink, _) -> - MockSource.equal source source2 && MockSink.equal sink sink2) - reports) in - "get_reports">::get_reports_ in - + MockSource.equal source source2 && MockSink.equal sink sink2) + reports) + in + "get_reports" >:: get_reports_ + in let append = let append_ _ = let call_site = CallSite.dummy in - let footprint_ap = AccessPath.Exact (AccessPath.of_id (Ident.create_none ()) (Typ.mk Tvoid)) in + let footprint_ap = + AccessPath.Exact (AccessPath.of_id (Ident.create_none ()) (Typ.mk Tvoid)) + in let dummy_pdesc = - Cfg.create_proc_desc - (Cfg.create_cfg ()) - (ProcAttributes.default Typ.Procname.empty_block !Config.curr_language) in + Cfg.create_proc_desc (Cfg.create_cfg ()) + (ProcAttributes.default Typ.Procname.empty_block !Config.curr_language) + in let footprint_source = MockSource.make_footprint footprint_ap dummy_pdesc in - let source_trace = - MockTrace.of_source source1 in - let footprint_trace = - MockTrace.of_source footprint_source - |> MockTrace.add_sink sink1 in - - let expected_trace = - MockTrace.of_source source1 - |> MockTrace.add_sink sink1 in - assert_bool - "Appended trace should contain source and sink" - (MockTrace.equal - (MockTrace.append source_trace footprint_trace call_site) expected_trace) in - "append">::append_ in - - "trace_domain_suite">:::[get_reports; append] + let source_trace = MockTrace.of_source source1 in + let footprint_trace = MockTrace.of_source footprint_source |> MockTrace.add_sink sink1 in + let expected_trace = MockTrace.of_source source1 |> MockTrace.add_sink sink1 in + assert_bool "Appended trace should contain source and sink" + (MockTrace.equal (MockTrace.append source_trace footprint_trace call_site) expected_trace) + in + "append" >:: append_ + in + "trace_domain_suite" >::: [get_reports; append] diff --git a/infer/src/unit/abstractInterpreterTests.ml b/infer/src/unit/abstractInterpreterTests.ml index b03db33e4..e4590de2a 100644 --- a/infer/src/unit/abstractInterpreterTests.ml +++ b/infer/src/unit/abstractInterpreterTests.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (** Test the generic abstract interpreter by using a simple path counting domain. Path counting is @@ -16,204 +15,116 @@ module F = Format you'll diverge at loops if you don't widen *) module PathCountDomain = struct - - type astate = - | PathCount of int - | Top + type astate = PathCount of int | Top let make_path_count c = (* guarding against overflow *) - if c < 0 - then Top - else PathCount c + if c < 0 then Top else PathCount c let initial = make_path_count 1 - let (<=) ~lhs ~rhs = match lhs, rhs with - | PathCount c1, PathCount c2 -> c1 <= c2 - | _, Top -> true - | Top, PathCount _ -> false - - let join a1 a2 = match a1, a2 with - | PathCount c1, PathCount c2 -> make_path_count (c1 + c2) - | Top, _ | PathCount _, Top -> Top + let ( <= ) ~lhs ~rhs = + match (lhs, rhs) with + | PathCount c1, PathCount c2 + -> c1 <= c2 + | _, Top + -> true + | Top, PathCount _ + -> false + + let join a1 a2 = + match (a1, a2) with + | PathCount c1, PathCount c2 + -> make_path_count (c1 + c2) + | Top, _ | PathCount _, Top + -> Top let widen ~prev:_ ~next:_ ~num_iters:_ = Top - let pp fmt = function - | PathCount c -> F.fprintf fmt "%d" c - | Top -> F.fprintf fmt "T" - + let pp fmt = function PathCount c -> F.fprintf fmt "%d" c | Top -> F.fprintf fmt "T" end module PathCountTransferFunctions (CFG : ProcCfg.S) = struct module CFG = CFG module Domain = PathCountDomain + type extras = ProcData.no_extras (* just propagate the current path count *) let exec_instr astate _ _ _ = astate end - -module NormalTestInterpreter = AnalyzerTester.Make - (ProcCfg.Normal) - (PathCountTransferFunctions) - -module ExceptionalTestInterpreter = AnalyzerTester.Make - (ProcCfg.Exceptional) - (PathCountTransferFunctions) +module NormalTestInterpreter = AnalyzerTester.Make (ProcCfg.Normal) (PathCountTransferFunctions) +module ExceptionalTestInterpreter = + AnalyzerTester.Make (ProcCfg.Exceptional) (PathCountTransferFunctions) let tests = let open OUnit2 in let open AnalyzerTester.StructuredSil in let initial = PathCountDomain.initial in - let normal_test_list = [ - "straightline", - [ - invariant "1"; - invariant "1" - ]; - "if", - [ - invariant "1"; - If (unknown_exp, [], []); - invariant "2"; - ]; - "if_then", - [ - If (unknown_exp, - [invariant "1"], - [] - ); - invariant "2" - ]; - "if_else", - [ - If (unknown_exp, - [], - [invariant "1"] - ); - invariant "2" - ]; - "if_then_else", - [ - If (unknown_exp, - [invariant "1"], - [invariant "1"]; - ); - invariant "2" - ]; - "nested_if_then", - [ - If (unknown_exp, - [If (unknown_exp, [], []); - invariant "2"], - [] - ); - invariant "3" - ]; - "nested_if_else", - [ - If (unknown_exp, - [], - [If (unknown_exp, [], []); - invariant "2"] - ); - invariant "3" - ]; - "nested_if_then_else", - [ - If (unknown_exp, - [If (unknown_exp, [], []); - invariant "2"], - [If (unknown_exp, [], []); - invariant "2"] - ); - invariant "4" - ]; - "if_diamond", - [ - invariant "1"; - If (unknown_exp, [], []); - invariant "2"; - If (unknown_exp, [], []); - invariant "4" - ]; - "loop", - [ - invariant "1"; - While (unknown_exp, [invariant "T"]); - invariant "T" - ]; - "if_in_loop", - [ - While (unknown_exp, - [If (unknown_exp, [], []); - invariant "T"] - ); - invariant "T"; - ]; - "nested_loop_visit", - [ - invariant "1"; - While (unknown_exp, - [invariant "T"; - While (unknown_exp, - [invariant "T"]); - invariant "T"]); - invariant "T"; - ]; - "try", - [ - Try ( - [ - invariant "1"; (* we expect the try block to be visited *) - ], - [ - invariant "_|_"; (* but not the catch block *) - ], - [ - invariant "1"; (* we expect the finally block to be visited *) - ] - ); - invariant "1" - ]; - ] |> NormalTestInterpreter.create_tests ProcData.empty_extras ~initial in - let exceptional_test_list = [ - "try1", - [ - Try ( - [ - invariant "1"; - ], - [ - invariant "1"; (* catch block should be visited *) - ], - [ - invariant "2"; (* could come from try or catch block *) - ] - ); - invariant "2" - ]; - "try1", - [ - Try ( - [ - (* note: each instruction in try block is treated as potentially-excepting... *) - (* point 1 *) - invariant "1"; (* point 2 *) - invariant "1"; (* point 3 *) - ], - [ - (* ... so |paths through catch block| shoud be |number of instructions in try block| *) - invariant "2"; (* point 4 *) - ], - [ - invariant "3"; (* could arrive here via (1, 2, 3), (1, 4), or (2, 4) *) - ] - ); - invariant "3" - ]; - ] |> ExceptionalTestInterpreter.create_tests ProcData.empty_extras ~initial in - "analyzer_tests_suite">:::(normal_test_list @ exceptional_test_list) + let normal_test_list = + [ ("straightline", [invariant "1"; invariant "1"]) + ; ("if", [invariant "1"; If (unknown_exp, [], []); invariant "2"]) + ; ("if_then", [If (unknown_exp, [invariant "1"], []); invariant "2"]) + ; ("if_else", [If (unknown_exp, [], [invariant "1"]); invariant "2"]) + ; ("if_then_else", [If (unknown_exp, [invariant "1"], [invariant "1"]); invariant "2"]) + ; ( "nested_if_then" + , [If (unknown_exp, [If (unknown_exp, [], []); invariant "2"], []); invariant "3"] ) + ; ( "nested_if_else" + , [If (unknown_exp, [], [If (unknown_exp, [], []); invariant "2"]); invariant "3"] ) + ; ( "nested_if_then_else" + , [ If + ( unknown_exp + , [If (unknown_exp, [], []); invariant "2"] + , [If (unknown_exp, [], []); invariant "2"] ) + ; invariant "4" ] ) + ; ( "if_diamond" + , [ invariant "1" + ; If (unknown_exp, [], []) + ; invariant "2" + ; If (unknown_exp, [], []) + ; invariant "4" ] ) + ; ("loop", [invariant "1"; While (unknown_exp, [invariant "T"]); invariant "T"]) + ; ( "if_in_loop" + , [While (unknown_exp, [If (unknown_exp, [], []); invariant "T"]); invariant "T"] ) + ; ( "nested_loop_visit" + , [ invariant "1" + ; While (unknown_exp, [invariant "T"; While (unknown_exp, [invariant "T"]); invariant "T"]) + ; invariant "T" ] ) + ; ( "try" + , [ Try + ( (* we expect the try block to be visited *) + [invariant "1"] + , (* but not the catch block *) + [invariant "_|_"] + , (* we expect the finally block to be visited *) + [invariant "1"] ) + ; invariant "1" ] ) ] + |> NormalTestInterpreter.create_tests ProcData.empty_extras ~initial + in + let exceptional_test_list = + [ ( "try1" + , [ Try + ( [invariant "1"] + , (* catch block should be visited *) + [invariant "1"] + , (* could come from try or catch block *) + [invariant "2"] ) + ; invariant "2" ] ) + ; ( "try1" + , [ Try + ( (* point 3 *) + [ (* note: each instruction in try block is treated as potentially-excepting... *) + (* point 1 *) + invariant "1" + ; (* point 2 *) + invariant "1" ] + , (* point 4 *) + [ (* ... so |paths through catch block| shoud be |number of instructions in try block| *) + invariant "2" ] + , (* could arrive here via (1, 2, 3), (1, 4), or (2, 4) *) + [invariant "3"] ) + ; invariant "3" ] ) ] + |> ExceptionalTestInterpreter.create_tests ProcData.empty_extras ~initial + in + "analyzer_tests_suite" >::: normal_test_list @ exceptional_test_list diff --git a/infer/src/unit/accessPathTestUtils.ml b/infer/src/unit/accessPathTestUtils.ml index 030c7fe2d..acb558585 100644 --- a/infer/src/unit/accessPathTestUtils.ml +++ b/infer/src/unit/accessPathTestUtils.ml @@ -9,19 +9,15 @@ open! IStd -let make_var var_str = - Pvar.mk (Mangled.from_string var_str) Typ.Procname.empty_block +let make_var var_str = Pvar.mk (Mangled.from_string var_str) Typ.Procname.empty_block -let make_base ?(typ=Typ.mk Tvoid) base_str = - AccessPath.base_of_pvar (make_var base_str) typ +let make_base ?(typ= Typ.mk Tvoid) base_str = AccessPath.base_of_pvar (make_var base_str) typ let make_fieldname = Typ.Fieldname.Java.from_string -let make_field_access access_str = - AccessPath.FieldAccess (make_fieldname access_str) +let make_field_access access_str = AccessPath.FieldAccess (make_fieldname access_str) -let make_array_access typ = - AccessPath.ArrayAccess typ +let make_array_access typ = AccessPath.ArrayAccess typ let make_access_path base_str access_strs = - make_base base_str, List.map ~f:make_field_access access_strs + (make_base base_str, List.map ~f:make_field_access access_strs) diff --git a/infer/src/unit/accessPathTests.ml b/infer/src/unit/accessPathTests.ml index b5eab55fa..ec9018187 100644 --- a/infer/src/unit/accessPathTests.ml +++ b/infer/src/unit/accessPathTests.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format let tests = @@ -18,14 +17,14 @@ let tests = let f_access = make_field_access "f" in let g_access = make_field_access "g" in let xF = make_access_path "x" ["f"] in - let xFG = make_access_path "x" ["f"; "g";] in + let xFG = make_access_path "x" ["f"; "g"] in let yF = make_access_path "y" ["f"] in let xArr = let dummy_typ = Typ.mk Tvoid in let dummy_arr_typ = Typ.mk (Tarray (dummy_typ, None, None)) in - let base = make_base "x" ~typ:dummy_arr_typ in - base, [make_array_access dummy_typ] in - + let base = make_base "x" ~typ:dummy_arr_typ in + (base, [make_array_access dummy_typ]) + in let x_exact = AccessPath.Exact x in let y_exact = AccessPath.Exact y in let x_abstract = AccessPath.Abstracted x in @@ -33,163 +32,155 @@ let tests = let xFG_exact = AccessPath.Exact xFG in let yF_exact = AccessPath.Exact yF in let yF_abstract = AccessPath.Abstracted yF in - let open OUnit2 in let equal_test = let equal_test_ _ = - assert_bool "equal works for bases" (AccessPath.Raw.equal x (make_access_path "x" [])); - assert_bool - "equal works for paths" - (AccessPath.Raw.equal xFG (make_access_path "x" ["f"; "g";])); - assert_bool "disequality works for bases" (not (AccessPath.Raw.equal x y)); - assert_bool "disequality works for paths" (not (AccessPath.Raw.equal xF xFG)) in - "equal">::equal_test_ in - + assert_bool "equal works for bases" (AccessPath.Raw.equal x (make_access_path "x" [])) ; + assert_bool "equal works for paths" + (AccessPath.Raw.equal xFG (make_access_path "x" ["f"; "g"])) ; + assert_bool "disequality works for bases" (not (AccessPath.Raw.equal x y)) ; + assert_bool "disequality works for paths" (not (AccessPath.Raw.equal xF xFG)) + in + "equal" >:: equal_test_ + in let append_test = let pp_diff fmt (actual, expected) = - F.fprintf - fmt - "Expected output %a but got %a" - AccessPath.Raw.pp expected - AccessPath.Raw.pp actual in + F.fprintf fmt "Expected output %a but got %a" AccessPath.Raw.pp expected AccessPath.Raw.pp + actual + in let assert_eq input expected = - assert_equal ~cmp:AccessPath.Raw.equal ~pp_diff input expected in + assert_equal ~cmp:AccessPath.Raw.equal ~pp_diff input expected + in let append_test_ _ = - assert_eq xF (AccessPath.append x [f_access]); - assert_eq xFG (AccessPath.append xF [g_access]) in - "append">::append_test_ in - + assert_eq xF (AccessPath.append x [f_access]) ; + assert_eq xFG (AccessPath.append xF [g_access]) + in + "append" >:: append_test_ + in let prefix_test = let prefix_test_ _ = - assert_bool "x is prefix of self" (AccessPath.is_prefix x x); - assert_bool "x.f is prefix of self" (AccessPath.is_prefix xF xF); - assert_bool "x is not prefix of y" (not (AccessPath.is_prefix x y)); - assert_bool "x is prefix of x.f" (AccessPath.is_prefix x xF); - assert_bool "x.f not prefix of x" (not (AccessPath.is_prefix xF x)); - assert_bool "x.f is prefix of x.f.g" (AccessPath.is_prefix xF xFG); - assert_bool "x.f.g is not prefix of x.f" (not (AccessPath.is_prefix xFG xF)); - assert_bool "y.f is not prefix of x.f" (not (AccessPath.is_prefix yF xF)); - assert_bool "y.f is not prefix of x.f.g" (not (AccessPath.is_prefix yF xFG)) in - "prefix">::prefix_test_ in - + assert_bool "x is prefix of self" (AccessPath.is_prefix x x) ; + assert_bool "x.f is prefix of self" (AccessPath.is_prefix xF xF) ; + assert_bool "x is not prefix of y" (not (AccessPath.is_prefix x y)) ; + assert_bool "x is prefix of x.f" (AccessPath.is_prefix x xF) ; + assert_bool "x.f not prefix of x" (not (AccessPath.is_prefix xF x)) ; + assert_bool "x.f is prefix of x.f.g" (AccessPath.is_prefix xF xFG) ; + assert_bool "x.f.g is not prefix of x.f" (not (AccessPath.is_prefix xFG xF)) ; + assert_bool "y.f is not prefix of x.f" (not (AccessPath.is_prefix yF xF)) ; + assert_bool "y.f is not prefix of x.f.g" (not (AccessPath.is_prefix yF xFG)) + in + "prefix" >:: prefix_test_ + in let of_exp_test = let f_resolve_id _ = None in let dummy_typ = Typ.mk Tvoid in - let check_make_ap exp expected_ap ~f_resolve_id = let make_ap exp = match AccessPath.of_lhs_exp exp dummy_typ ~f_resolve_id with - | Some ap -> ap - | None -> assert false in + | Some ap + -> ap + | None + -> assert false + in let actual_ap = make_ap exp in let pp_diff fmt (actual_ap, expected_ap) = - F.fprintf - fmt - "Expected to make access path %a from expression %a, but got %a" - AccessPath.Raw.pp expected_ap - Exp.pp exp - AccessPath.Raw.pp actual_ap in - assert_equal ~cmp:AccessPath.Raw.equal ~pp_diff actual_ap expected_ap in - + F.fprintf fmt "Expected to make access path %a from expression %a, but got %a" + AccessPath.Raw.pp expected_ap Exp.pp exp AccessPath.Raw.pp actual_ap + in + assert_equal ~cmp:AccessPath.Raw.equal ~pp_diff actual_ap expected_ap + in let of_exp_test_ _ = let f_fieldname = make_fieldname "f" in let g_fieldname = make_fieldname "g" in let x_exp = Exp.Lvar (make_var "x") in - check_make_ap x_exp x ~f_resolve_id; + check_make_ap x_exp x ~f_resolve_id ; let xF_exp = Exp.Lfield (x_exp, f_fieldname, dummy_typ) in - check_make_ap xF_exp xF ~f_resolve_id; + check_make_ap xF_exp xF ~f_resolve_id ; let xFG_exp = Exp.Lfield (xF_exp, g_fieldname, dummy_typ) in - check_make_ap xFG_exp xFG ~f_resolve_id; + check_make_ap xFG_exp xFG ~f_resolve_id ; let xArr_exp = Exp.Lindex (x_exp, Exp.zero) in - check_make_ap xArr_exp xArr ~f_resolve_id; + check_make_ap xArr_exp xArr ~f_resolve_id ; (* make sure [f_resolve_id] works *) let f_resolve_id_to_xF _ = Some xF in let xFG_exp_with_id = let id_exp = Exp.Var (Ident.create_normal (Ident.string_to_name "") 0) in - Exp.Lfield (id_exp, g_fieldname, dummy_typ) in - check_make_ap xFG_exp_with_id xFG ~f_resolve_id:f_resolve_id_to_xF; + Exp.Lfield (id_exp, g_fieldname, dummy_typ) + in + check_make_ap xFG_exp_with_id xFG ~f_resolve_id:f_resolve_id_to_xF ; (* make sure we can grab access paths from compound expressions *) let binop_exp = Exp.le xF_exp xFG_exp in match AccessPath.of_exp binop_exp dummy_typ ~f_resolve_id with - | [ap1; ap2] -> - assert_equal ~cmp:AccessPath.Raw.equal ap1 xFG; - assert_equal ~cmp:AccessPath.Raw.equal ap2 xF; - | _ -> - assert false in - "of_exp">::of_exp_test_ in - + | [ap1; ap2] + -> assert_equal ~cmp:AccessPath.Raw.equal ap1 xFG ; + assert_equal ~cmp:AccessPath.Raw.equal ap2 xF + | _ + -> assert false + in + "of_exp" >:: of_exp_test_ + in let abstraction_test = let abstraction_test_ _ = - assert_bool "extract" (AccessPath.Raw.equal (AccessPath.extract xF_exact) xF); - assert_bool "is_exact" (AccessPath.is_exact x_exact); - assert_bool "not is_exact" (not (AccessPath.is_exact x_abstract)); - assert_bool "(<=)1" (AccessPath.(<=) ~lhs:x_exact ~rhs:x_abstract); - assert_bool "(<=)2" (AccessPath.(<=) ~lhs:xF_exact ~rhs:x_abstract); - assert_bool - "not (<=)1" - (not (AccessPath.(<=) ~lhs:x_abstract ~rhs:x_exact)); - assert_bool - "not (<=)2" - (not (AccessPath.(<=) ~lhs:x_abstract ~rhs:xF_exact)) in - "abstraction">::abstraction_test_ in - + assert_bool "extract" (AccessPath.Raw.equal (AccessPath.extract xF_exact) xF) ; + assert_bool "is_exact" (AccessPath.is_exact x_exact) ; + assert_bool "not is_exact" (not (AccessPath.is_exact x_abstract)) ; + assert_bool "(<=)1" (AccessPath.( <= ) ~lhs:x_exact ~rhs:x_abstract) ; + assert_bool "(<=)2" (AccessPath.( <= ) ~lhs:xF_exact ~rhs:x_abstract) ; + assert_bool "not (<=)1" (not (AccessPath.( <= ) ~lhs:x_abstract ~rhs:x_exact)) ; + assert_bool "not (<=)2" (not (AccessPath.( <= ) ~lhs:x_abstract ~rhs:xF_exact)) + in + "abstraction" >:: abstraction_test_ + in let domain_test = let domain_test_ _ = let pp_diff fmt (actual, expected) = - F.fprintf fmt "Expected %s but got %s" expected actual in + F.fprintf fmt "Expected %s but got %s" expected actual + in let assert_eq input_aps expected = let input = F.asprintf "%a" AccessPathDomains.Set.pp input_aps in - assert_equal ~cmp:String.equal ~pp_diff input expected in - let aps1 = AccessPathDomains.Set.of_list [x_exact; x_abstract] in (* { x*, x } *) - let aps2 = AccessPathDomains.Set.add xF_exact aps1 in (* x*, x, x.f *) - let aps3 = AccessPathDomains.Set.add yF_exact aps2 in (* x*, x, x.f, y.f *) - - assert_bool "mem_easy" (AccessPathDomains.Set.mem x_exact aps1); - assert_bool "mem_harder1" (AccessPathDomains.Set.mem xFG_exact aps1); - assert_bool "mem_harder2" (AccessPathDomains.Set.mem x_abstract aps1); - assert_bool "mem_negative" (not (AccessPathDomains.Set.mem y_exact aps1)); - assert_bool "mem_not_fully_contained" (not (AccessPathDomains.Set.mem yF_abstract aps3)); - - assert_bool "mem_fuzzy_easy" (AccessPathDomains.Set.mem_fuzzy x_exact aps1); - assert_bool "mem_fuzzy_harder1" (AccessPathDomains.Set.mem_fuzzy xFG_exact aps1); - assert_bool "mem_fuzzy_harder2" (AccessPathDomains.Set.mem_fuzzy x_abstract aps1); - assert_bool "mem_fuzzy_negative" (not (AccessPathDomains.Set.mem_fuzzy y_exact aps1)); + assert_equal ~cmp:String.equal ~pp_diff input expected + in + let aps1 = AccessPathDomains.Set.of_list [x_exact; x_abstract] in + (* { x*, x } *) + let aps2 = AccessPathDomains.Set.add xF_exact aps1 in + (* x*, x, x.f *) + let aps3 = AccessPathDomains.Set.add yF_exact aps2 in + (* x*, x, x.f, y.f *) + assert_bool "mem_easy" (AccessPathDomains.Set.mem x_exact aps1) ; + assert_bool "mem_harder1" (AccessPathDomains.Set.mem xFG_exact aps1) ; + assert_bool "mem_harder2" (AccessPathDomains.Set.mem x_abstract aps1) ; + assert_bool "mem_negative" (not (AccessPathDomains.Set.mem y_exact aps1)) ; + assert_bool "mem_not_fully_contained" (not (AccessPathDomains.Set.mem yF_abstract aps3)) ; + assert_bool "mem_fuzzy_easy" (AccessPathDomains.Set.mem_fuzzy x_exact aps1) ; + assert_bool "mem_fuzzy_harder1" (AccessPathDomains.Set.mem_fuzzy xFG_exact aps1) ; + assert_bool "mem_fuzzy_harder2" (AccessPathDomains.Set.mem_fuzzy x_abstract aps1) ; + assert_bool "mem_fuzzy_negative" (not (AccessPathDomains.Set.mem_fuzzy y_exact aps1)) ; (* [mem_fuzzy] should behave the same as [mem] except in this case *) - assert_bool - "mem_fuzzy_not_fully_contained" - (AccessPathDomains.Set.mem_fuzzy yF_abstract aps3); - - assert_bool "<= on same is true" (AccessPathDomains.Set.(<=) ~lhs:aps1 ~rhs:aps1); - assert_bool "aps1 <= aps2" (AccessPathDomains.Set.(<=) ~lhs:aps1 ~rhs:aps2); - assert_bool "aps2 <= aps1" (AccessPathDomains.Set.(<=) ~lhs:aps2 ~rhs:aps1); - assert_bool "aps1 <= aps3" (AccessPathDomains.Set.(<=) ~lhs:aps1 ~rhs:aps3); - assert_bool "not aps3 <= aps1" (not (AccessPathDomains.Set.(<=) ~lhs:aps3 ~rhs:aps1)); - - assert_eq (AccessPathDomains.Set.join aps1 aps1) "{ &x*, &x }"; - assert_eq (AccessPathDomains.Set.join aps1 aps2) "{ &x*, &x, &x.f }"; - assert_eq (AccessPathDomains.Set.join aps1 aps3) "{ &x*, &x, &x.f, &y.f }"; - - let widen s1 s2 = - AccessPathDomains.Set.widen ~prev:s1 ~next:s2 ~num_iters:10 in - assert_eq (widen aps1 aps1) "{ &x*, &x }"; - assert_eq (widen aps2 aps3) "{ &x*, &y.f*, &x, &x.f }"; - let aps_prev = AccessPathDomains.Set.of_list [x_exact; y_exact] in (* { x, y } *) - let aps_next = AccessPathDomains.Set.of_list [y_exact; yF_exact] in (* { y. y.f } *) + assert_bool "mem_fuzzy_not_fully_contained" + (AccessPathDomains.Set.mem_fuzzy yF_abstract aps3) ; + assert_bool "<= on same is true" (AccessPathDomains.Set.( <= ) ~lhs:aps1 ~rhs:aps1) ; + assert_bool "aps1 <= aps2" (AccessPathDomains.Set.( <= ) ~lhs:aps1 ~rhs:aps2) ; + assert_bool "aps2 <= aps1" (AccessPathDomains.Set.( <= ) ~lhs:aps2 ~rhs:aps1) ; + assert_bool "aps1 <= aps3" (AccessPathDomains.Set.( <= ) ~lhs:aps1 ~rhs:aps3) ; + assert_bool "not aps3 <= aps1" (not (AccessPathDomains.Set.( <= ) ~lhs:aps3 ~rhs:aps1)) ; + assert_eq (AccessPathDomains.Set.join aps1 aps1) "{ &x*, &x }" ; + assert_eq (AccessPathDomains.Set.join aps1 aps2) "{ &x*, &x, &x.f }" ; + assert_eq (AccessPathDomains.Set.join aps1 aps3) "{ &x*, &x, &x.f, &y.f }" ; + let widen s1 s2 = AccessPathDomains.Set.widen ~prev:s1 ~next:s2 ~num_iters:10 in + assert_eq (widen aps1 aps1) "{ &x*, &x }" ; + assert_eq (widen aps2 aps3) "{ &x*, &y.f*, &x, &x.f }" ; + let aps_prev = AccessPathDomains.Set.of_list [x_exact; y_exact] in + (* { x, y } *) + let aps_next = AccessPathDomains.Set.of_list [y_exact; yF_exact] in + (* { y. y.f } *) (* { x, y } \/ { y, y.f } = { y.f*, x, y } *) - assert_eq (widen aps_prev aps_next) "{ &y.f*, &x, &y }"; + assert_eq (widen aps_prev aps_next) "{ &y.f*, &x, &y }" ; (* { y, y.f } \/ { x, y } = { x*, y, y.f } *) - assert_eq (widen aps_next aps_prev) "{ &x*, &y, &y.f }"; - - assert_eq (AccessPathDomains.Set.normalize aps1) "{ &x* }"; - assert_eq (AccessPathDomains.Set.normalize aps2) "{ &x* }"; - assert_eq (AccessPathDomains.Set.normalize aps3) "{ &x*, &y.f }" in - "domain">::domain_test_ in - - "all_tests_suite">:::[ - equal_test; - append_test; - prefix_test; - of_exp_test; - abstraction_test; - domain_test - ] + assert_eq (widen aps_next aps_prev) "{ &x*, &y, &y.f }" ; + assert_eq (AccessPathDomains.Set.normalize aps1) "{ &x* }" ; + assert_eq (AccessPathDomains.Set.normalize aps2) "{ &x* }" ; + assert_eq (AccessPathDomains.Set.normalize aps3) "{ &x*, &y.f }" + in + "domain" >:: domain_test_ + in + "all_tests_suite" + >::: [equal_test; append_test; prefix_test; of_exp_test; abstraction_test; domain_test] diff --git a/infer/src/unit/accessTreeTests.ml b/infer/src/unit/accessTreeTests.ml index 3bc3ec945..03763171f 100644 --- a/infer/src/unit/accessTreeTests.ml +++ b/infer/src/unit/accessTreeTests.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format (* string set domain we use to ensure we're getting the expected traces *) @@ -21,41 +20,40 @@ module MockTraceDomain = struct (* stop others from creating top by accident, adding to top, or removing it *) let add e s = - assert (e <> top_str); - if phys_equal s top - then top - else add e s + assert (e <> top_str) ; + if phys_equal s top then top else add e s let singleton e = - assert (e <> top_str); + assert (e <> top_str) ; singleton e (* total hack of a widening just to test that widening of traces is working *) let widen ~prev ~next ~num_iters:_ = let trace_diff = diff next prev in - if not (is_empty trace_diff) - then top - else join prev next + if not (is_empty trace_diff) then top else join prev next (* similarly, hack printing so top looks different *) - let pp fmt s = - if phys_equal s top - then F.fprintf fmt "T" - else pp fmt s + let pp fmt s = if phys_equal s top then F.fprintf fmt "T" else pp fmt s end module Domain = AccessTree.Make (MockTraceDomain) let assert_trees_equal tree1 tree2 = let rec access_tree_equal (trace1, subtree1) (trace2, subtree2) = - MockTraceDomain.equal trace1 trace2 && match subtree1, subtree2 with - | Domain.Star, Domain.Star -> true - | Domain.Subtree t1, Domain.Subtree t2 -> Domain.AccessMap.equal access_tree_equal t1 t2 - | _ -> false in - let base_tree_equal tree1 tree2 = - Domain.BaseMap.equal access_tree_equal tree1 tree2 in + MockTraceDomain.equal trace1 trace2 + && + match (subtree1, subtree2) with + | Domain.Star, Domain.Star + -> true + | Domain.Subtree t1, Domain.Subtree t2 + -> Domain.AccessMap.equal access_tree_equal t1 t2 + | _ + -> false + in + let base_tree_equal tree1 tree2 = Domain.BaseMap.equal access_tree_equal tree1 tree2 in let pp_diff fmt (actual, expected) = - F.fprintf fmt "Expected to get tree %a but got %a" Domain.pp expected Domain.pp actual in + F.fprintf fmt "Expected to get tree %a but got %a" Domain.pp expected Domain.pp actual + in OUnit2.assert_equal ~cmp:base_tree_equal ~pp_diff tree1 tree2 let tests = @@ -63,11 +61,9 @@ let tests = let x_base = make_base "x" in let y_base = make_base "y" in let z_base = make_base "z" in - let f = make_field_access "f" in let g = make_field_access "g" in let array = make_array_access (Typ.mk Tvoid) in - let x = AccessPath.Exact (make_access_path "x" []) in let xF = AccessPath.Exact (make_access_path "x" ["f"]) in let xG = AccessPath.Exact (make_access_path "x" ["g"]) in @@ -79,13 +75,11 @@ let tests = let z = AccessPath.Exact (make_access_path "z" []) in let zF = AccessPath.Exact (make_access_path "z" ["f"]) in let zFG = AccessPath.Exact (make_access_path "z" ["f"; "g"]) in - - let xArr = - AccessPath.Exact (make_base "x", [array]) in + let xArr = AccessPath.Exact (make_base "x", [array]) in let xArrF = let accesses = [array; make_field_access "f"] in - AccessPath.Exact (make_base "x", accesses) in - + AccessPath.Exact (make_base "x", accesses) + in let a_star = AccessPath.Abstracted (make_access_path "a" []) in let x_star = AccessPath.Abstracted (make_access_path "x" []) in let xF_star = AccessPath.Abstracted (make_access_path "x" ["f"]) in @@ -93,7 +87,6 @@ let tests = let y_star = AccessPath.Abstracted (make_access_path "y" []) in let yF_star = AccessPath.Abstracted (make_access_path "y" ["f"]) in let z_star = AccessPath.Abstracted (make_access_path "z" []) in - let x_trace = MockTraceDomain.singleton "x" in let y_trace = MockTraceDomain.singleton "y" in let z_trace = MockTraceDomain.singleton "z" in @@ -102,290 +95,250 @@ let tests = let xFG_trace = MockTraceDomain.singleton "xFG" in let array_f_trace = MockTraceDomain.singleton "arrayF" in let x_star_trace = MockTraceDomain.of_list ["x"; "xF"; "xFG"] in - let g_subtree = Domain.make_access_node xF_trace g xFG_trace in - let x_subtree = - Domain.AccessMap.singleton f g_subtree - |> Domain.make_node x_trace in + let x_subtree = Domain.AccessMap.singleton f g_subtree |> Domain.make_node x_trace in let yF_subtree = Domain.make_starred_leaf yF_trace in - let y_subtree = - Domain.AccessMap.singleton f yF_subtree - |> Domain.make_node y_trace in + let y_subtree = Domain.AccessMap.singleton f yF_subtree |> Domain.make_node y_trace in let z_subtree = Domain.make_starred_leaf z_trace in - let tree = - Domain.BaseMap.singleton x_base x_subtree - |> Domain.BaseMap.add y_base y_subtree - |> Domain.BaseMap.add z_base z_subtree in + Domain.BaseMap.singleton x_base x_subtree |> Domain.BaseMap.add y_base y_subtree + |> Domain.BaseMap.add z_base z_subtree + in let x_base_tree = Domain.BaseMap.singleton x_base Domain.empty_node in let y_base_tree = Domain.BaseMap.singleton y_base Domain.empty_node in let x_y_base_tree = Domain.BaseMap.add y_base Domain.empty_node x_base_tree in let xFG_tree = Domain.BaseMap.singleton x_base x_subtree in - let x_star_tree = Domain.BaseMap.singleton x_base (Domain.make_starred_leaf x_trace) in let yF_star_tree = Domain.BaseMap.singleton y_base y_subtree in let x_yF_star_tree = Domain.BaseMap.add y_base y_subtree x_star_tree in let x_star_tree_xFG_trace = - Domain.BaseMap.singleton x_base (Domain.make_starred_leaf x_star_trace) in - + Domain.BaseMap.singleton x_base (Domain.make_starred_leaf x_star_trace) + in let open OUnit2 in let no_trace = "NONE" in - let get_trace_str access_path tree = match Domain.get_trace access_path tree with - | Some trace -> F.asprintf "%a" MockTraceDomain.pp trace - | None -> no_trace in - + | Some trace + -> F.asprintf "%a" MockTraceDomain.pp trace + | None + -> no_trace + in let assert_traces_eq access_path tree expected_trace_str = let actual_trace_str = get_trace_str access_path tree in let pp_diff fmt (actual, expected) = - F.fprintf fmt "Expected to retrieve trace %s but got %s" expected actual in - assert_equal ~pp_diff actual_trace_str expected_trace_str in - - let assert_trace_not_found access_path tree = - assert_traces_eq access_path tree no_trace in - + F.fprintf fmt "Expected to retrieve trace %s but got %s" expected actual + in + assert_equal ~pp_diff actual_trace_str expected_trace_str + in + let assert_trace_not_found access_path tree = assert_traces_eq access_path tree no_trace in let assert_node_equal access_path tree expected_node = match Domain.get_node access_path tree with - | Some actual_node -> - let pp_diff fmt (actual, expected) = - F.fprintf - fmt - "Expected to retrieve node %a but got %a" - Domain.pp_node expected - Domain.pp_node actual in + | Some actual_node + -> let pp_diff fmt (actual, expected) = + F.fprintf fmt "Expected to retrieve node %a but got %a" Domain.pp_node expected + Domain.pp_node actual + in assert_equal ~pp_diff expected_node actual_node - | None -> assert false in - + | None + -> assert false + in let get_trace_test = let get_trace_test_ _ = (* exact access path tests *) - assert_traces_eq z tree "{ z }"; - assert_traces_eq xF tree "{ xF }"; - assert_traces_eq yF tree "{ yF }"; - assert_traces_eq xFG tree "{ xFG }"; - assert_trace_not_found xG tree; - + assert_traces_eq z tree "{ z }" ; + assert_traces_eq xF tree "{ xF }" ; + assert_traces_eq yF tree "{ yF }" ; + assert_traces_eq xFG tree "{ xFG }" ; + assert_trace_not_found xG tree ; (* starred access path tests *) - assert_traces_eq x_star tree "{ x, xF, xFG }"; - assert_traces_eq xF_star tree "{ xF, xFG }"; - assert_trace_not_found xG_star tree; - assert_trace_not_found a_star tree; - + assert_traces_eq x_star tree "{ x, xF, xFG }" ; + assert_traces_eq xF_star tree "{ xF, xFG }" ; + assert_trace_not_found xG_star tree ; + assert_trace_not_found a_star tree ; (* starred tree tests *) - assert_traces_eq zF tree "{ z }"; - assert_traces_eq zFG tree "{ z }"; - assert_traces_eq z_star tree "{ z }"; - assert_traces_eq y_star tree "{ y, yF }"; - assert_traces_eq yF_star tree "{ yF }"; - assert_traces_eq yFG tree "{ yF }"; - assert_trace_not_found yG tree; - + assert_traces_eq zF tree "{ z }" ; + assert_traces_eq zFG tree "{ z }" ; + assert_traces_eq z_star tree "{ z }" ; + assert_traces_eq y_star tree "{ y, yF }" ; + assert_traces_eq yF_star tree "{ yF }" ; + assert_traces_eq yFG tree "{ yF }" ; + assert_trace_not_found yG tree ; (* get_trace is just (fst get_node), so light tests here *) (* exact access path tests *) - assert_node_equal z tree z_subtree; - assert_node_equal xF tree g_subtree; - assert_node_equal xFG tree (Domain.make_normal_leaf xFG_trace); - + assert_node_equal z tree z_subtree ; + assert_node_equal xF tree g_subtree ; + assert_node_equal xFG tree (Domain.make_normal_leaf xFG_trace) ; (* starred tree tests *) - assert_node_equal yFG tree yF_subtree; - + assert_node_equal yFG tree yF_subtree ; (* starred access path tests *) let joined_y_subtree = Domain.AccessMap.singleton f yF_subtree - |> Domain.make_node (MockTraceDomain.join y_trace yF_trace) in - assert_node_equal y_star tree joined_y_subtree in - - "get_trace">::get_trace_test_ in - + |> Domain.make_node (MockTraceDomain.join y_trace yF_trace) + in + assert_node_equal y_star tree joined_y_subtree + in + "get_trace" >:: get_trace_test_ + in let add_trace_test = let add_trace_test_ _ = (* special trace to indicate that we've added successfully *) let added_trace = MockTraceDomain.singleton "added" in - let mk_x_y_base_tree trace = Domain.BaseMap.singleton x_base (Domain.make_normal_leaf trace) - |> Domain.BaseMap.add y_base Domain.empty_node in - + |> Domain.BaseMap.add y_base Domain.empty_node + in let mk_xFG_node leaf_trace = - Domain.make_access_node MockTraceDomain.empty g leaf_trace - |> Domain.AccessMap.singleton f - |> Domain.make_node MockTraceDomain.empty in - - let mk_xFG_tree leaf_trace = - mk_xFG_node leaf_trace - |> Domain.BaseMap.singleton x_base in - + Domain.make_access_node MockTraceDomain.empty g leaf_trace |> Domain.AccessMap.singleton f + |> Domain.make_node MockTraceDomain.empty + in + let mk_xFG_tree leaf_trace = mk_xFG_node leaf_trace |> Domain.BaseMap.singleton x_base in let mk_xArrF_tree leaf_trace = Domain.make_access_node MockTraceDomain.empty f leaf_trace - |> Domain.AccessMap.singleton array - |> Domain.make_node MockTraceDomain.empty - |> Domain.BaseMap.singleton x_base in - + |> Domain.AccessMap.singleton array |> Domain.make_node MockTraceDomain.empty + |> Domain.BaseMap.singleton x_base + in (* normal tests *) (* add base when absent *) let x_y_base_tree_with_added_trace = mk_x_y_base_tree added_trace in - assert_trees_equal - (Domain.add_trace x added_trace y_base_tree) - x_y_base_tree_with_added_trace; + assert_trees_equal (Domain.add_trace x added_trace y_base_tree) + x_y_base_tree_with_added_trace ; (* add base when present *) - assert_trees_equal - (Domain.add_trace x added_trace x_y_base_tree) - x_y_base_tree_with_added_trace; + assert_trees_equal (Domain.add_trace x added_trace x_y_base_tree) + x_y_base_tree_with_added_trace ; let x_y_base_tree_with_y_trace = mk_x_y_base_tree y_trace in - assert_trees_equal - (Domain.add_trace x added_trace x_y_base_tree_with_y_trace) - x_y_base_tree_with_added_trace; + assert_trees_equal (Domain.add_trace x added_trace x_y_base_tree_with_y_trace) + x_y_base_tree_with_added_trace ; (* add path when absent *) let xFG_tree_added_trace = mk_xFG_tree added_trace in - assert_trees_equal (Domain.add_trace xFG added_trace x_base_tree) xFG_tree_added_trace; + assert_trees_equal (Domain.add_trace xFG added_trace x_base_tree) xFG_tree_added_trace ; (* add path when present *) let xFG_tree_y_trace = mk_xFG_tree y_trace in - assert_trees_equal (Domain.add_trace xFG added_trace xFG_tree_y_trace) xFG_tree_added_trace; + assert_trees_equal (Domain.add_trace xFG added_trace xFG_tree_y_trace) xFG_tree_added_trace ; (* add starred path when base absent *) let xF_star_tree_added_trace = - Domain.make_starred_leaf added_trace - |> Domain.AccessMap.singleton f - |> Domain.make_node MockTraceDomain.empty - |> Domain.BaseMap.singleton x_base in - assert_trees_equal - (Domain.add_trace xF_star added_trace Domain.empty) - xF_star_tree_added_trace; + Domain.make_starred_leaf added_trace |> Domain.AccessMap.singleton f + |> Domain.make_node MockTraceDomain.empty |> Domain.BaseMap.singleton x_base + in + assert_trees_equal (Domain.add_trace xF_star added_trace Domain.empty) + xF_star_tree_added_trace ; (* add starred path when base present *) - assert_trees_equal - (Domain.add_trace xF_star added_trace x_base_tree) - xF_star_tree_added_trace; - + assert_trees_equal (Domain.add_trace xF_star added_trace x_base_tree) + xF_star_tree_added_trace ; (* adding array path should do weak updates *) let aArrF_tree = mk_xArrF_tree array_f_trace in let aArrF_tree_joined_trace = - mk_xArrF_tree (MockTraceDomain.join added_trace array_f_trace) in - assert_trees_equal (Domain.add_trace xArrF added_trace aArrF_tree) aArrF_tree_joined_trace; - + mk_xArrF_tree (MockTraceDomain.join added_trace array_f_trace) + in + assert_trees_equal (Domain.add_trace xArrF added_trace aArrF_tree) aArrF_tree_joined_trace ; (* starred tests *) (* we should do a strong update when updating x.f* with x.f *) let yF_tree_added_trace = - Domain.make_normal_leaf added_trace - |> Domain.AccessMap.singleton f - |> Domain.make_node y_trace - |> Domain.BaseMap.singleton y_base in - assert_trees_equal - (Domain.add_trace yF added_trace yF_star_tree) - yF_tree_added_trace; + Domain.make_normal_leaf added_trace |> Domain.AccessMap.singleton f + |> Domain.make_node y_trace |> Domain.BaseMap.singleton y_base + in + assert_trees_equal (Domain.add_trace yF added_trace yF_star_tree) yF_tree_added_trace ; (* but not when updating x* with x.f *) let x_star_tree_added_trace = let joined_trace = MockTraceDomain.join x_trace added_trace in - Domain.BaseMap.singleton x_base (Domain.make_starred_leaf joined_trace) in - assert_trees_equal (Domain.add_trace xF added_trace x_star_tree) x_star_tree_added_trace; - + Domain.BaseMap.singleton x_base (Domain.make_starred_leaf joined_trace) + in + assert_trees_equal (Domain.add_trace xF added_trace x_star_tree) x_star_tree_added_trace ; (* when updating x.f.g with x.f*, we should remember traces associated with f and g even as we replace that subtree with a * *) let xF_star_tree_joined_traces = let joined_trace = - MockTraceDomain.join added_trace xFG_trace - |> MockTraceDomain.join xF_trace in - Domain.make_starred_leaf joined_trace - |> Domain.AccessMap.singleton f - |> Domain.make_node x_trace - |> Domain.BaseMap.singleton x_base in - assert_trees_equal - (Domain.add_trace xF_star added_trace xFG_tree) - xF_star_tree_joined_traces; - + MockTraceDomain.join added_trace xFG_trace |> MockTraceDomain.join xF_trace + in + Domain.make_starred_leaf joined_trace |> Domain.AccessMap.singleton f + |> Domain.make_node x_trace |> Domain.BaseMap.singleton x_base + in + assert_trees_equal (Domain.add_trace xF_star added_trace xFG_tree) xF_star_tree_joined_traces ; (* [add_node] tests are sparse, since [add_trace] is just [add_node] . main things to test are (1) adding a non-empty node works, (2) adding a non-empty node does the proper joins in the weak update case *) (* case (1): adding XFG to y base tree works *) - let y_xFG_tree = - Domain.BaseMap.add y_base Domain.empty_node (mk_xFG_tree xFG_trace) in - assert_trees_equal - (Domain.add_node x (mk_xFG_node xFG_trace) y_base_tree) y_xFG_tree; - + let y_xFG_tree = Domain.BaseMap.add y_base Domain.empty_node (mk_xFG_tree xFG_trace) in + assert_trees_equal (Domain.add_node x (mk_xFG_node xFG_trace) y_base_tree) y_xFG_tree ; (* case (2): adding a non-empty node does weak updates when required *) let arr_tree = let arr_subtree = Domain.AccessMap.singleton f (Domain.make_normal_leaf array_f_trace) - |> Domain.AccessMap.add g (Domain.make_normal_leaf xFG_trace) in + |> Domain.AccessMap.add g (Domain.make_normal_leaf xFG_trace) + in Domain.AccessMap.singleton array (Domain.make_node xF_trace arr_subtree) - |> Domain.make_node MockTraceDomain.empty - |> Domain.BaseMap.singleton x_base in - assert_trees_equal - (Domain.add_node xArr g_subtree aArrF_tree) arr_tree in - - "add_trace">::add_trace_test_ in - + |> Domain.make_node MockTraceDomain.empty |> Domain.BaseMap.singleton x_base + in + assert_trees_equal (Domain.add_node xArr g_subtree aArrF_tree) arr_tree + in + "add_trace" >:: add_trace_test_ + in let lteq_test = let lteq_test_ _ = (* regular tree tests *) - assert_bool "<= equal;" (Domain.(<=) ~lhs:tree ~rhs:tree); - assert_bool "<= bases" (Domain.(<=) ~lhs:x_base_tree ~rhs:x_y_base_tree); - assert_bool "<= regular1" (Domain.(<=) ~lhs:x_base_tree ~rhs:xFG_tree); - assert_bool "<= regular2" (Domain.(<=) ~lhs:xFG_tree ~rhs:tree); - assert_bool "<= regular3" (Domain.(<=) ~lhs:y_base_tree ~rhs:tree); - assert_bool "<= bases negative1" (not (Domain.(<=) ~lhs:x_y_base_tree ~rhs:x_base_tree)); - assert_bool "<= bases negative2" (not (Domain.(<=) ~lhs:x_base_tree ~rhs:y_base_tree)); - assert_bool "<= negative1" (not (Domain.(<=) ~lhs:xFG_tree ~rhs:y_base_tree)); - assert_bool "<= negative2" (not (Domain.(<=) ~lhs:tree ~rhs:xFG_tree)); - + assert_bool "<= equal;" (Domain.( <= ) ~lhs:tree ~rhs:tree) ; + assert_bool "<= bases" (Domain.( <= ) ~lhs:x_base_tree ~rhs:x_y_base_tree) ; + assert_bool "<= regular1" (Domain.( <= ) ~lhs:x_base_tree ~rhs:xFG_tree) ; + assert_bool "<= regular2" (Domain.( <= ) ~lhs:xFG_tree ~rhs:tree) ; + assert_bool "<= regular3" (Domain.( <= ) ~lhs:y_base_tree ~rhs:tree) ; + assert_bool "<= bases negative1" (not (Domain.( <= ) ~lhs:x_y_base_tree ~rhs:x_base_tree)) ; + assert_bool "<= bases negative2" (not (Domain.( <= ) ~lhs:x_base_tree ~rhs:y_base_tree)) ; + assert_bool "<= negative1" (not (Domain.( <= ) ~lhs:xFG_tree ~rhs:y_base_tree)) ; + assert_bool "<= negative2" (not (Domain.( <= ) ~lhs:tree ~rhs:xFG_tree)) ; (* star tree tests *) - assert_bool "<= star lhs equal" (Domain.(<=) ~lhs:x_star_tree ~rhs:x_star_tree); - assert_bool "<= star rhs1" (Domain.(<=) ~lhs:x_base_tree ~rhs:x_star_tree); - assert_bool "<= star rhs2" (Domain.(<=) ~lhs:xFG_tree ~rhs:x_star_tree); - assert_bool "<= star rhs3" (Domain.(<=) ~lhs:y_base_tree ~rhs:yF_star_tree); - assert_bool "<= star rhs4" (Domain.(<=) ~lhs:yF_star_tree ~rhs:tree); - assert_bool "<= star lhs negative1" (not (Domain.(<=) ~lhs:x_star_tree ~rhs:x_base_tree)); - assert_bool "<= star lhs negative2" (not (Domain.(<=) ~lhs:x_star_tree ~rhs:xFG_tree)); - assert_bool "<= star lhs negative3" (not (Domain.(<=) ~lhs:yF_star_tree ~rhs:y_base_tree)); - assert_bool "<= star lhs negative4" (not (Domain.(<=) ~lhs:tree ~rhs:yF_star_tree)); - + assert_bool "<= star lhs equal" (Domain.( <= ) ~lhs:x_star_tree ~rhs:x_star_tree) ; + assert_bool "<= star rhs1" (Domain.( <= ) ~lhs:x_base_tree ~rhs:x_star_tree) ; + assert_bool "<= star rhs2" (Domain.( <= ) ~lhs:xFG_tree ~rhs:x_star_tree) ; + assert_bool "<= star rhs3" (Domain.( <= ) ~lhs:y_base_tree ~rhs:yF_star_tree) ; + assert_bool "<= star rhs4" (Domain.( <= ) ~lhs:yF_star_tree ~rhs:tree) ; + assert_bool "<= star lhs negative1" (not (Domain.( <= ) ~lhs:x_star_tree ~rhs:x_base_tree)) ; + assert_bool "<= star lhs negative2" (not (Domain.( <= ) ~lhs:x_star_tree ~rhs:xFG_tree)) ; + assert_bool "<= star lhs negative3" (not (Domain.( <= ) ~lhs:yF_star_tree ~rhs:y_base_tree)) ; + assert_bool "<= star lhs negative4" (not (Domain.( <= ) ~lhs:tree ~rhs:yF_star_tree)) ; (* <= tree but not <= trace tests *) (* same as x_base_tree, but with a trace higher in the traces lattice *) let x_base_tree_higher_trace = - Domain.BaseMap.singleton x_base (Domain.make_normal_leaf y_trace) in + Domain.BaseMap.singleton x_base (Domain.make_normal_leaf y_trace) + in (* same as x_star_tree, but with a trace incomparable in the traces lattice *) let x_star_tree_diff_trace = - Domain.BaseMap.singleton x_base (Domain.make_starred_leaf y_trace) in - assert_bool - "(x, {}) <= (x, {y})" - (Domain.(<=) ~lhs:x_base_tree ~rhs:x_base_tree_higher_trace); - assert_bool - "(x, {y}) not <= (x, {})" - (not (Domain.(<=) ~lhs:x_base_tree_higher_trace ~rhs:x_base_tree)); - assert_bool - "(x*, {y})* not <= (x*, {x})" - (not (Domain.(<=) ~lhs:x_star_tree_diff_trace ~rhs:x_star_tree)); - assert_bool - "(x*, {x})* not <= (x*, {y})" - (not (Domain.(<=) ~lhs:x_star_tree ~rhs:x_star_tree_diff_trace)) in - "lteq">::lteq_test_ in - + Domain.BaseMap.singleton x_base (Domain.make_starred_leaf y_trace) + in + assert_bool "(x, {}) <= (x, {y})" + (Domain.( <= ) ~lhs:x_base_tree ~rhs:x_base_tree_higher_trace) ; + assert_bool "(x, {y}) not <= (x, {})" + (not (Domain.( <= ) ~lhs:x_base_tree_higher_trace ~rhs:x_base_tree)) ; + assert_bool "(x*, {y})* not <= (x*, {x})" + (not (Domain.( <= ) ~lhs:x_star_tree_diff_trace ~rhs:x_star_tree)) ; + assert_bool "(x*, {x})* not <= (x*, {y})" + (not (Domain.( <= ) ~lhs:x_star_tree ~rhs:x_star_tree_diff_trace)) + in + "lteq" >:: lteq_test_ + in let join_test = let join_test_ _ = (* normal |_| normal *) - assert_trees_equal (Domain.join x_base_tree y_base_tree) x_y_base_tree; - assert_trees_equal (Domain.join y_base_tree x_base_tree) x_y_base_tree; - assert_trees_equal (Domain.join x_y_base_tree x_base_tree) x_y_base_tree; - assert_trees_equal (Domain.join x_base_tree xFG_tree) xFG_tree; - + assert_trees_equal (Domain.join x_base_tree y_base_tree) x_y_base_tree ; + assert_trees_equal (Domain.join y_base_tree x_base_tree) x_y_base_tree ; + assert_trees_equal (Domain.join x_y_base_tree x_base_tree) x_y_base_tree ; + assert_trees_equal (Domain.join x_base_tree xFG_tree) xFG_tree ; (* starred |_| starred *) - assert_trees_equal (Domain.join x_star_tree yF_star_tree) x_yF_star_tree; - + assert_trees_equal (Domain.join x_star_tree yF_star_tree) x_yF_star_tree ; (* normal |_| starred *) - assert_trees_equal (Domain.join tree xFG_tree) tree; + assert_trees_equal (Domain.join tree xFG_tree) tree ; (* [x_star_tree] and [x_base_tree] both have trace "{ x }" associated with x... *) - assert_trees_equal (Domain.join x_star_tree x_base_tree) x_star_tree; + assert_trees_equal (Domain.join x_star_tree x_base_tree) x_star_tree ; (* ...but [xFG_tree] has some nested traces that should get joined with "{ x }" *) - assert_trees_equal (Domain.join x_star_tree xFG_tree) x_star_tree_xFG_trace in - "join">::join_test_ in - + assert_trees_equal (Domain.join x_star_tree xFG_tree) x_star_tree_xFG_trace + in + "join" >:: join_test_ + in let widen_test = let widen_test_ _ = let make_x_base_tree trace = - Domain.BaseMap.singleton x_base (Domain.make_normal_leaf trace) in - let widen prev next = - Domain.widen ~prev ~next ~num_iters:0 in + Domain.BaseMap.singleton x_base (Domain.make_normal_leaf trace) + in + let widen prev next = Domain.widen ~prev ~next ~num_iters:0 in (* a bit light on the tests here, since widen is implemented as a simple wrapper of join *) - (* widening traces works: x |-> ("x", empty) \/ x |-> ("y", empty) = x |-> (T, empty) @@ -393,19 +346,15 @@ let tests = let x_tree_x_trace = make_x_base_tree x_trace in let x_tree_y_trace = make_x_base_tree y_trace in let x_tree_top_trace = make_x_base_tree MockTraceDomain.top in - assert_trees_equal (widen x_tree_x_trace x_tree_y_trace) x_tree_top_trace; - + assert_trees_equal (widen x_tree_x_trace x_tree_y_trace) x_tree_top_trace ; (* adding stars to a base works: x |-> ({}, empty) \/ y |-> ({}, empty) = (x |-> ({}, empty), y |-> ({}, Star) ) *) let x_y_star_base_tree = - Domain.BaseMap.add - y_base - (Domain.make_starred_leaf MockTraceDomain.empty) - x_base_tree in - assert_trees_equal (widen x_base_tree y_base_tree) x_y_star_base_tree; - + Domain.BaseMap.add y_base (Domain.make_starred_leaf MockTraceDomain.empty) x_base_tree + in + assert_trees_equal (widen x_base_tree y_base_tree) x_y_star_base_tree ; (* adding stars to a subtree works: x |-> ("y", empty) \/ x |-> ("x" , f |-> ("f", g |-> ("g", empty))) = @@ -413,13 +362,11 @@ let tests = *) let xFG_star_tree = let g_subtree = Domain.make_starred_leaf xFG_trace in - Domain.AccessMap.singleton g g_subtree - |> Domain.make_node xF_trace - |> Domain.AccessMap.singleton f - |> Domain.make_node MockTraceDomain.top - |> Domain.BaseMap.singleton x_base in - assert_trees_equal (widen x_tree_y_trace xFG_tree) xFG_star_tree; - + Domain.AccessMap.singleton g g_subtree |> Domain.make_node xF_trace + |> Domain.AccessMap.singleton f |> Domain.make_node MockTraceDomain.top + |> Domain.BaseMap.singleton x_base + in + assert_trees_equal (widen x_tree_y_trace xFG_tree) xFG_star_tree ; (* widening is not commutative, and is it not join: x |-> ("x" , f |-> ("f", g |-> ("g", empty))) \/ x |-> ("y", empty) = @@ -427,32 +374,30 @@ let tests = *) let xFG_tree_widened_trace = let _, xFG_node = x_subtree in - Domain.BaseMap.singleton x_base (MockTraceDomain.top, xFG_node) in - assert_trees_equal (widen xFG_tree x_tree_y_trace) xFG_tree_widened_trace in - "widen">::widen_test_ in - + Domain.BaseMap.singleton x_base (MockTraceDomain.top, xFG_node) + in + assert_trees_equal (widen xFG_tree x_tree_y_trace) xFG_tree_widened_trace + in + "widen" >:: widen_test_ + in let fold_test = let fold_test_ _ = - let collect_ap_traces acc ap trace = - (ap, trace) :: acc in + let collect_ap_traces acc ap trace = (ap, trace) :: acc in let ap_traces = Domain.trace_fold collect_ap_traces tree [] in let has_ap_trace_pair ap_in trace_in = List.exists ~f:(fun (ap, trace) -> AccessPath.equal ap ap_in && MockTraceDomain.equal trace trace_in) - ap_traces in - - assert_bool "Should have six ap/trace pairs" (Int.equal (List.length ap_traces) 6); - assert_bool "has x pair" (has_ap_trace_pair x x_trace); - assert_bool "has xF pair" (has_ap_trace_pair xF xF_trace); - assert_bool "has xFG pair" (has_ap_trace_pair xFG xFG_trace); - assert_bool "has y pair" (has_ap_trace_pair y y_trace); - assert_bool "has yF* pair" (has_ap_trace_pair yF_star yF_trace); - assert_bool "has z pair" (has_ap_trace_pair z_star z_trace) in - "fold">::fold_test_ in - - "access_tree_suite">:::[get_trace_test; - add_trace_test; - lteq_test; - join_test; - widen_test; - fold_test;] + ap_traces + in + assert_bool "Should have six ap/trace pairs" (Int.equal (List.length ap_traces) 6) ; + assert_bool "has x pair" (has_ap_trace_pair x x_trace) ; + assert_bool "has xF pair" (has_ap_trace_pair xF xF_trace) ; + assert_bool "has xFG pair" (has_ap_trace_pair xFG xFG_trace) ; + assert_bool "has y pair" (has_ap_trace_pair y y_trace) ; + assert_bool "has yF* pair" (has_ap_trace_pair yF_star yF_trace) ; + assert_bool "has z pair" (has_ap_trace_pair z_star z_trace) + in + "fold" >:: fold_test_ + in + "access_tree_suite" + >::: [get_trace_test; add_trace_test; lteq_test; join_test; widen_test; fold_test] diff --git a/infer/src/unit/addressTakenTests.ml b/infer/src/unit/addressTakenTests.ml index 2b25efce7..b4e368a13 100644 --- a/infer/src/unit/addressTakenTests.ml +++ b/infer/src/unit/addressTakenTests.ml @@ -8,9 +8,7 @@ *) open! IStd - module F = Format - module TestInterpreter = AnalyzerTester.Make (ProcCfg.Exceptional) (AddressTaken.TransferFunctions) let tests = @@ -23,77 +21,46 @@ let tests = let closure_exp captureds = let mk_captured_var str = (Exp.Var (ident_of_str str), pvar_of_str str, int_ptr_typ) in let captured_vars = List.map ~f:mk_captured_var captureds in - let closure = { Exp.name=dummy_procname; captured_vars; } in - Exp.Closure closure in - let test_list = [ - "address_taken_set_instr", - [ - var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"; - invariant "{ &b }" - ]; - "address_not_taken_set_instr", - [ - var_assign_addrof_var ~rhs_typ:int_typ "a" "b"; - assert_empty - ]; - "address_not_taken_load_instr1", - [ - id_assign_var ~rhs_typ:int_ptr_typ "a" "b"; - assert_empty - ]; - "address_not_taken_load_instr2", - [ - id_assign_var ~rhs_typ:int_typ "a" "b"; - assert_empty - ]; - "take_multiple_addresses", - [ - var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"; - invariant "{ &b }"; - var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d"; - invariant "{ &b, &d }"; - var_assign_addrof_var ~rhs_typ:int_typ "e" "f"; - invariant "{ &b, &d }" - ]; - "address_not_taken_closure", - [ - var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"; - var_assign_exp ~rhs_typ:fun_ptr_typ "c" (closure_exp ["d"; "e"]); - invariant "{ &b }" - ]; - "if_conservative1", - [ - If (unknown_exp, - [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"], - [] - ); - invariant "{ &b }" - ]; - "if_conservative2", - [ - If (unknown_exp, - [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"], - [var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d"] - ); - invariant "{ &b, &d }" - ]; - "loop_as_if", - [ - While (unknown_exp, - [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"] - ); - invariant "{ &b }" - ]; - "loop_before_after", - [ - var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"; - invariant "{ &b }"; - While (unknown_exp, - [var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d"] - ); - invariant "{ &b, &d }"; - var_assign_addrof_var ~rhs_typ:int_ptr_typ "e" "f"; - invariant "{ &b, &f, &d }" - ]; - ] |> TestInterpreter.create_tests ProcData.empty_extras ~initial:AddressTaken.Domain.empty in - "address_taken_suite">:::test_list + let closure = {Exp.name= dummy_procname; captured_vars} in + Exp.Closure closure + in + let test_list = + [ ( "address_taken_set_instr" + , [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"; invariant "{ &b }"] ) + ; ( "address_not_taken_set_instr" + , [var_assign_addrof_var ~rhs_typ:int_typ "a" "b"; assert_empty] ) + ; ("address_not_taken_load_instr1", [id_assign_var ~rhs_typ:int_ptr_typ "a" "b"; assert_empty]) + ; ("address_not_taken_load_instr2", [id_assign_var ~rhs_typ:int_typ "a" "b"; assert_empty]) + ; ( "take_multiple_addresses" + , [ var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b" + ; invariant "{ &b }" + ; var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d" + ; invariant "{ &b, &d }" + ; var_assign_addrof_var ~rhs_typ:int_typ "e" "f" + ; invariant "{ &b, &d }" ] ) + ; ( "address_not_taken_closure" + , [ var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b" + ; var_assign_exp ~rhs_typ:fun_ptr_typ "c" (closure_exp ["d"; "e"]) + ; invariant "{ &b }" ] ) + ; ( "if_conservative1" + , [ If (unknown_exp, [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"], []) + ; invariant "{ &b }" ] ) + ; ( "if_conservative2" + , [ If + ( unknown_exp + , [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"] + , [var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d"] ) + ; invariant "{ &b, &d }" ] ) + ; ( "loop_as_if" + , [ While (unknown_exp, [var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b"]) + ; invariant "{ &b }" ] ) + ; ( "loop_before_after" + , [ var_assign_addrof_var ~rhs_typ:int_ptr_typ "a" "b" + ; invariant "{ &b }" + ; While (unknown_exp, [var_assign_addrof_var ~rhs_typ:int_ptr_typ "c" "d"]) + ; invariant "{ &b, &d }" + ; var_assign_addrof_var ~rhs_typ:int_ptr_typ "e" "f" + ; invariant "{ &b, &f, &d }" ] ) ] + |> TestInterpreter.create_tests ProcData.empty_extras ~initial:AddressTaken.Domain.empty + in + "address_taken_suite" >::: test_list diff --git a/infer/src/unit/analyzerTester.ml b/infer/src/unit/analyzerTester.ml index 55d439a6e..2dfb18449 100644 --- a/infer/src/unit/analyzerTester.ml +++ b/infer/src/unit/analyzerTester.ml @@ -8,7 +8,6 @@ *) open! IStd - module F = Format module L = Logging @@ -17,6 +16,7 @@ module L = Logging (** structured language that makes it easy to write small test programs in OCaml *) module StructuredSil = struct type assertion = string + type label = int type structured_instr = @@ -26,71 +26,59 @@ module StructuredSil = struct (* try/catch/finally. note: there is no throw. the semantics are that every command in the try block is assumed to be possibly-excepting, and the catch block captures all exceptions *) | Try of structured_instr list * structured_instr list * structured_instr list - | Invariant of assertion * label (* gets autotranslated into assertions about abstract state *) + | Invariant of assertion * label + + (* gets autotranslated into assertions about abstract state *) type structured_program = structured_instr list let rec pp_structured_instr fmt = function - | Cmd instr -> (Sil.pp_instr Pp.text) fmt instr - | If (exp, then_instrs, else_instrs) -> - (* TODO (t10287763): indent bodies of if/while *) - F.fprintf fmt "if (%a) {@.%a@.} else {@.%a@.}" - Exp.pp exp - pp_structured_instr_list then_instrs - pp_structured_instr_list else_instrs - | While (exp, instrs) -> - F.fprintf fmt "while (%a) {@.%a@.}" Exp.pp exp pp_structured_instr_list instrs - | Try (try_, catch, finally) -> - F.fprintf - fmt - "try {@.%a@.} catch (...) {@.%a@.} finally {@.%a@.}" - pp_structured_instr_list try_ - pp_structured_instr_list catch - pp_structured_instr_list finally - | Invariant (inv_str, label) -> - F.fprintf fmt "invariant %d: %s" label inv_str + | Cmd instr + -> Sil.pp_instr Pp.text fmt instr + | If (exp, then_instrs, else_instrs) + -> (* TODO (t10287763): indent bodies of if/while *) + F.fprintf fmt "if (%a) {@.%a@.} else {@.%a@.}" Exp.pp exp pp_structured_instr_list + then_instrs pp_structured_instr_list else_instrs + | While (exp, instrs) + -> F.fprintf fmt "while (%a) {@.%a@.}" Exp.pp exp pp_structured_instr_list instrs + | Try (try_, catch, finally) + -> F.fprintf fmt "try {@.%a@.} catch (...) {@.%a@.} finally {@.%a@.}" pp_structured_instr_list + try_ pp_structured_instr_list catch pp_structured_instr_list finally + | Invariant (inv_str, label) + -> F.fprintf fmt "invariant %d: %s" label inv_str and pp_structured_instr_list fmt instrs = - F.pp_print_list - ~pp_sep:F.pp_print_newline + F.pp_print_list ~pp_sep:F.pp_print_newline (fun fmt instr -> F.fprintf fmt "%a" pp_structured_instr instr) - fmt - instrs + fmt instrs let pp_structured_program = pp_structured_instr_list let dummy_typ = Typ.mk Tvoid + let dummy_loc = Location.dummy + let dummy_procname = Typ.Procname.empty_block let label_counter = ref 0 - let fresh_label () = - incr label_counter; - !label_counter + let fresh_label () = incr label_counter ; !label_counter - let invariant inv_str = - Invariant (inv_str, fresh_label ()) + let invariant inv_str = Invariant (inv_str, fresh_label ()) - let pvar_of_str str = - Pvar.mk (Mangled.from_string str) dummy_procname + let pvar_of_str str = Pvar.mk (Mangled.from_string str) dummy_procname - let var_of_str str = - Exp.Lvar (pvar_of_str str) + let var_of_str str = Exp.Lvar (pvar_of_str str) - let ident_of_str str = - Ident.create_normal (Ident.string_to_name str) 0 + let ident_of_str str = Ident.create_normal (Ident.string_to_name str) 0 - let unknown_exp = - var_of_str "__unknown__" + let unknown_exp = var_of_str "__unknown__" - let make_load ~rhs_typ lhs_id rhs_exp = - Cmd (Sil.Load (lhs_id, rhs_exp, rhs_typ, dummy_loc)) + let make_load ~rhs_typ lhs_id rhs_exp = Cmd (Sil.Load (lhs_id, rhs_exp, rhs_typ, dummy_loc)) - let make_set ~rhs_typ ~lhs_exp ~rhs_exp = - Cmd (Sil.Store (lhs_exp, rhs_typ, rhs_exp, dummy_loc)) + let make_set ~rhs_typ ~lhs_exp ~rhs_exp = Cmd (Sil.Store (lhs_exp, rhs_typ, rhs_exp, dummy_loc)) - let make_call ?(procname=dummy_procname) ret_id args = + let make_call ?(procname= dummy_procname) ret_id args = let call_exp = Exp.Const (Const.Cfun procname) in Cmd (Sil.Call (ret_id, call_exp, args, dummy_loc, CallFlags.default)) @@ -104,19 +92,19 @@ module StructuredSil = struct let rhs_exp = Exp.Lfield (root_exp, fld, rhs_typ) in make_load ~rhs_typ (ident_of_str lhs_str) rhs_exp - let id_assign_exp ?(rhs_typ=dummy_typ) lhs rhs_exp = + let id_assign_exp ?(rhs_typ= dummy_typ) lhs rhs_exp = let lhs_id = ident_of_str lhs in make_load ~rhs_typ lhs_id rhs_exp - let id_assign_id ?(rhs_typ=dummy_typ) lhs rhs = + let id_assign_id ?(rhs_typ= dummy_typ) lhs rhs = id_assign_exp ~rhs_typ lhs (Exp.Var (ident_of_str rhs)) - let id_assign_var ?(rhs_typ=dummy_typ) lhs rhs = + let id_assign_var ?(rhs_typ= dummy_typ) lhs rhs = let lhs_id = ident_of_str lhs in let rhs_exp = var_of_str rhs in make_load ~rhs_typ lhs_id rhs_exp - let id_set_id ?(rhs_typ=dummy_typ) lhs_id rhs_id = + let id_set_id ?(rhs_typ= dummy_typ) lhs_id rhs_id = let lhs_exp = Exp.Var (ident_of_str lhs_id) in let rhs_exp = Exp.Var (ident_of_str rhs_id) in make_set ~rhs_typ ~lhs_exp ~rhs_exp @@ -125,7 +113,8 @@ module StructuredSil = struct let lhs_id = ident_of_str lhs in let rhs_id = Exp.Var (ident_of_str rhs) in let cast_sizeof = - Exp.Sizeof { typ = cast_typ; nbytes=None; dynamic_length=None; subtype=Subtype.exact; } in + Exp.Sizeof {typ= cast_typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} + in let args = [(rhs_id, cast_typ); (cast_sizeof, cast_typ)] in make_call ~procname:BuiltinDecl.__cast (Some (lhs_id, cast_typ)) args @@ -138,13 +127,13 @@ module StructuredSil = struct let rhs_typ = Typ.mk (Tint Typ.IInt) in var_assign_exp ~rhs_typ lhs rhs_exp - let var_assign_id ?(rhs_typ=dummy_typ) lhs rhs = + let var_assign_id ?(rhs_typ= dummy_typ) lhs rhs = let lhs_exp = var_of_str lhs in let rhs_exp = Exp.Var (ident_of_str rhs) in make_set ~rhs_typ ~lhs_exp ~rhs_exp (* x = &y *) - let var_assign_addrof_var ?(rhs_typ=dummy_typ) lhs rhs = + let var_assign_addrof_var ?(rhs_typ= dummy_typ) lhs rhs = let lhs_exp = var_of_str lhs in let rhs_exp = var_of_str rhs in make_set ~rhs_typ ~lhs_exp ~rhs_exp @@ -154,15 +143,12 @@ module StructuredSil = struct let ret_id = Option.map ~f:(fun (str, typ) -> (ident_of_str str, typ)) ret_id_str_opt in make_call ret_id args - let call_unknown_no_ret arg_strs = - call_unknown None arg_strs + let call_unknown_no_ret arg_strs = call_unknown None arg_strs end -module Make - (CFG : ProcCfg.S with type node = Procdesc.Node.t) (T : TransferFunctions.MakeSIL) = struct - +module Make (CFG : ProcCfg.S with type node = Procdesc.Node.t) (T : TransferFunctions.MakeSIL) = +struct open StructuredSil - module I = AbstractInterpreter.Make (CFG) (T) module M = I.InvariantMap @@ -171,118 +157,130 @@ module Make let structured_program_to_cfg program test_pname = let cfg = Cfg.create_cfg () in let pdesc = - Cfg.create_proc_desc cfg (ProcAttributes.default test_pname !Config.curr_language) in + Cfg.create_proc_desc cfg (ProcAttributes.default test_pname !Config.curr_language) + in let pname = Procdesc.get_proc_name pdesc in - - let create_node kind cmds = - Procdesc.create_node pdesc dummy_loc kind cmds in - let set_succs cur_node succs ~exn_handlers= - Procdesc.node_set_succs_exn pdesc cur_node succs exn_handlers in + let create_node kind cmds = Procdesc.create_node pdesc dummy_loc kind cmds in + let set_succs cur_node succs ~exn_handlers = + Procdesc.node_set_succs_exn pdesc cur_node succs exn_handlers + in let mk_prune_nodes_for_cond cond_exp if_kind = let mk_prune_node cond_exp if_kind true_branch = let prune_instr = Sil.Prune (cond_exp, dummy_loc, true_branch, if_kind) in - create_node (Procdesc.Node.Prune_node (true_branch, if_kind, "")) [prune_instr] in + create_node (Procdesc.Node.Prune_node (true_branch, if_kind, "")) [prune_instr] + in let true_prune_node = mk_prune_node cond_exp if_kind true in let false_prune_node = let negated_cond_exp = Exp.UnOp (Unop.LNot, cond_exp, None) in - mk_prune_node negated_cond_exp if_kind false in - true_prune_node, false_prune_node in - + mk_prune_node negated_cond_exp if_kind false + in + (true_prune_node, false_prune_node) + in let rec structured_instr_to_node (last_node, assert_map) exn_handlers = function - | Cmd cmd -> - let node = create_node (Procdesc.Node.Stmt_node "") [cmd] in - set_succs last_node [node] ~exn_handlers; - node, assert_map - | If (exp, then_instrs, else_instrs) -> - let then_prune_node, else_prune_node = mk_prune_nodes_for_cond exp Sil.Ik_if in - set_succs last_node [then_prune_node; else_prune_node] ~exn_handlers; + | Cmd cmd + -> let node = create_node (Procdesc.Node.Stmt_node "") [cmd] in + set_succs last_node [node] ~exn_handlers ; + (node, assert_map) + | If (exp, then_instrs, else_instrs) + -> let then_prune_node, else_prune_node = mk_prune_nodes_for_cond exp Sil.Ik_if in + set_succs last_node [then_prune_node; else_prune_node] ~exn_handlers ; let then_branch_end_node, assert_map' = - structured_instrs_to_node then_prune_node assert_map exn_handlers then_instrs in + structured_instrs_to_node then_prune_node assert_map exn_handlers then_instrs + in let else_branch_end_node, assert_map'' = - structured_instrs_to_node else_prune_node assert_map' exn_handlers else_instrs in + structured_instrs_to_node else_prune_node assert_map' exn_handlers else_instrs + in let join_node = create_node Procdesc.Node.Join_node [] in - set_succs then_branch_end_node [join_node] ~exn_handlers; - set_succs else_branch_end_node [join_node] ~exn_handlers; - join_node, assert_map'' - | While (exp, body_instrs) -> - let loop_head_join_node = create_node Procdesc.Node.Join_node [] in - set_succs last_node [loop_head_join_node] ~exn_handlers; + set_succs then_branch_end_node [join_node] ~exn_handlers ; + set_succs else_branch_end_node [join_node] ~exn_handlers ; + (join_node, assert_map'') + | While (exp, body_instrs) + -> let loop_head_join_node = create_node Procdesc.Node.Join_node [] in + set_succs last_node [loop_head_join_node] ~exn_handlers ; let true_prune_node, false_prune_node = mk_prune_nodes_for_cond exp Sil.Ik_while in - set_succs loop_head_join_node [true_prune_node; false_prune_node] ~exn_handlers; + set_succs loop_head_join_node [true_prune_node; false_prune_node] ~exn_handlers ; let loop_body_end_node, assert_map' = - structured_instrs_to_node true_prune_node assert_map exn_handlers body_instrs in + structured_instrs_to_node true_prune_node assert_map exn_handlers body_instrs + in let loop_exit_node = create_node (Procdesc.Node.Skip_node "") [] in - set_succs loop_body_end_node [loop_head_join_node] ~exn_handlers; - set_succs false_prune_node [loop_exit_node] ~exn_handlers; - loop_exit_node, assert_map' - | Try (try_instrs, catch_instrs, finally_instrs) -> - let catch_start_node = create_node (Procdesc.Node.Skip_node "exn_handler") [] in + set_succs loop_body_end_node [loop_head_join_node] ~exn_handlers ; + set_succs false_prune_node [loop_exit_node] ~exn_handlers ; + (loop_exit_node, assert_map') + | Try (try_instrs, catch_instrs, finally_instrs) + -> let catch_start_node = create_node (Procdesc.Node.Skip_node "exn_handler") [] in (* use [catch_start_node] as the exn handler *) let try_end_node, assert_map' = - structured_instrs_to_node last_node assert_map [catch_start_node] try_instrs in + structured_instrs_to_node last_node assert_map [catch_start_node] try_instrs + in let catch_end_node, assert_map'' = - structured_instrs_to_node catch_start_node assert_map' exn_handlers catch_instrs in + structured_instrs_to_node catch_start_node assert_map' exn_handlers catch_instrs + in let finally_start_node = create_node (Procdesc.Node.Skip_node "finally") [] in - set_succs try_end_node [finally_start_node] ~exn_handlers; - set_succs catch_end_node [finally_start_node] ~exn_handlers; + set_succs try_end_node [finally_start_node] ~exn_handlers ; + set_succs catch_end_node [finally_start_node] ~exn_handlers ; structured_instrs_to_node finally_start_node assert_map'' exn_handlers finally_instrs - | Invariant (inv_str, inv_label) -> - let node = create_node (Procdesc.Node.Stmt_node "Invariant") [] in - set_succs last_node [node] ~exn_handlers; + | Invariant (inv_str, inv_label) + -> let node = create_node (Procdesc.Node.Stmt_node "Invariant") [] in + set_succs last_node [node] ~exn_handlers ; (* add the assertion to be checked after analysis converges *) - node, M.add (CFG.id node) (inv_str, inv_label) assert_map + (node, M.add (CFG.id node) (inv_str, inv_label) assert_map) and structured_instrs_to_node last_node assert_map exn_handlers instrs = List.fold ~f:(fun acc instr -> structured_instr_to_node acc exn_handlers instr) - ~init:(last_node, assert_map) - instrs in + ~init:(last_node, assert_map) instrs + in let start_node = create_node (Procdesc.Node.Start_node pname) [] in - Procdesc.set_start_node pdesc start_node; + Procdesc.set_start_node pdesc start_node ; let no_exn_handlers = [] in let last_node, assert_map = - structured_instrs_to_node start_node M.empty no_exn_handlers program in + structured_instrs_to_node start_node M.empty no_exn_handlers program + in let exit_node = create_node (Procdesc.Node.Exit_node pname) [] in - set_succs last_node [exit_node] ~exn_handlers:no_exn_handlers; - Procdesc.set_exit_node pdesc exit_node; - pdesc, assert_map + set_succs last_node [exit_node] ~exn_handlers:no_exn_handlers ; + Procdesc.set_exit_node pdesc exit_node ; + (pdesc, assert_map) let create_test test_program extras pp_opt ~initial test_pname _ = let pp_state = Option.value ~default:I.TransferFunctions.Domain.pp pp_opt in let pdesc, assert_map = structured_program_to_cfg test_program test_pname in let inv_map = I.exec_pdesc (ProcData.make pdesc (Tenv.create ()) extras) ~initial in - let collect_invariant_mismatches node_id (inv_str, inv_label) error_msgs_acc = let post_str = try let state = M.find node_id inv_map in F.asprintf "%a" pp_state state.post - with Not_found -> "_|_" in + with Not_found -> "_|_" + in if inv_str <> post_str then let error_msg = - F.fprintf F.str_formatter - "> Expected state %s at invariant %d, but found state %s" + F.fprintf F.str_formatter "> Expected state %s at invariant %d, but found state %s" inv_str inv_label post_str - |> F.flush_str_formatter in + |> F.flush_str_formatter + in error_msg :: error_msgs_acc - else error_msgs_acc in - + else error_msgs_acc + in match M.fold collect_invariant_mismatches assert_map [] with - | [] -> () (* no mismatches, test passed *) - | error_msgs -> - let mismatches_str = + | [] + -> () (* no mismatches, test passed *) + | error_msgs + -> let mismatches_str = F.pp_print_list - (fun fmt error_msg -> F.fprintf fmt "%s" error_msg) F.str_formatter - (List.rev error_msgs) - |> F.flush_str_formatter in + (fun fmt error_msg -> F.fprintf fmt "%s" error_msg) + F.str_formatter (List.rev error_msgs) + |> F.flush_str_formatter + in let assert_fail_message = - F.fprintf F.str_formatter "Error while analyzing@.%a:@.%s@." - pp_structured_program test_program mismatches_str - |> F.flush_str_formatter in + F.fprintf F.str_formatter "Error while analyzing@.%a:@.%s@." pp_structured_program + test_program mismatches_str + |> F.flush_str_formatter + in OUnit2.assert_failure assert_fail_message - let create_tests ?(test_pname=Typ.Procname.empty_block) ~initial ?pp_opt extras tests = + let create_tests ?(test_pname= Typ.Procname.empty_block) ~initial ?pp_opt extras tests = let open OUnit2 in - List.map ~f:(fun (name, test_program) -> - name>::create_test test_program extras ~initial pp_opt test_pname) tests - + List.map + ~f:(fun (name, test_program) -> + name >:: create_test test_program extras ~initial pp_opt test_pname) + tests end diff --git a/infer/src/unit/clang/CFrontend_errorsTests.ml b/infer/src/unit/clang/CFrontend_errorsTests.ml index 21c82fecd..bcb86c92b 100644 --- a/infer/src/unit/clang/CFrontend_errorsTests.ml +++ b/infer/src/unit/clang/CFrontend_errorsTests.ml @@ -12,20 +12,17 @@ open OUnit2 let test_correct_removing_new_lines = let pp_diff_of_desc fmt (expected, actual) = - Format.fprintf fmt "Expected: [%s] Found: [%s]" expected actual in - let create_test (desc : string) (expected_desc : string) _ = + Format.fprintf fmt "Expected: [%s] Found: [%s]" expected actual + in + let create_test (desc: string) (expected_desc: string) _ = let output = CFrontend_errors.remove_new_lines desc in - let cmp = fun s1 s2 -> String.equal s1 s2 in - assert_equal ~pp_diff:pp_diff_of_desc ~cmp expected_desc output in - [ - ( - "test_correct_removing_new_lines", - "The selector m is not available in the required iOS SDK version\n8.0", - "The selector m is not available in the required iOS SDK version 8.0" - ); - ] - |> List.map - ~f:(fun (name, test_input, expected_output) -> - name >:: create_test test_input expected_output) + let cmp s1 s2 = String.equal s1 s2 in + assert_equal ~pp_diff:pp_diff_of_desc ~cmp expected_desc output + in + [ ( "test_correct_removing_new_lines" + , "The selector m is not available in the required iOS SDK version\n8.0" + , "The selector m is not available in the required iOS SDK version 8.0" ) ] + |> List.map ~f:(fun (name, test_input, expected_output) -> + name >:: create_test test_input expected_output ) let tests = "cfrontend_errors_suite" >::: test_correct_removing_new_lines diff --git a/infer/src/unit/clang/CiOSVersionNumbersTests.ml b/infer/src/unit/clang/CiOSVersionNumbersTests.ml index ce5bc9a1f..7326a4be0 100644 --- a/infer/src/unit/clang/CiOSVersionNumbersTests.ml +++ b/infer/src/unit/clang/CiOSVersionNumbersTests.ml @@ -11,45 +11,18 @@ open! IStd open OUnit2 let test_correct_ios_version = - let create_test (version : string) (expected_version : string option) _ = + let create_test (version: string) (expected_version: string option) _ = let output = CiOSVersionNumbers.version_of version in - let cmp = fun s1 s2 -> Option.equal String.equal s1 s2 in - assert_equal ~pp_diff:CiOSVersionNumbers.pp_diff_of_version_opt - ~cmp expected_version output in - [ - ( - "test_correct_ios_version_some_version", - "847.20", - (Some "7.0") - ); - ( - "test_correct_ios_version_edge_version", - "1348.22", - (Some "10.2") - ); - ( - "test_correct_ios_version_ck", - "1223.1", - (Some "9.0") - ); - ( - "test_correct_ios_version_9", - "1240.0999", - (Some "9.0") - ); - ( - "test_correct_ios_version_2", - "478.230001", - (Some "2.0") - ); - ( - "test_correct_ios_version_smaller", - "1.49", - (None) - ) - ] - |> List.map - ~f:(fun (name, test_input, expected_output) -> - name >:: create_test test_input expected_output) + let cmp s1 s2 = Option.equal String.equal s1 s2 in + assert_equal ~pp_diff:CiOSVersionNumbers.pp_diff_of_version_opt ~cmp expected_version output + in + [ ("test_correct_ios_version_some_version", "847.20", Some "7.0") + ; ("test_correct_ios_version_edge_version", "1348.22", Some "10.2") + ; ("test_correct_ios_version_ck", "1223.1", Some "9.0") + ; ("test_correct_ios_version_9", "1240.0999", Some "9.0") + ; ("test_correct_ios_version_2", "478.230001", Some "2.0") + ; ("test_correct_ios_version_smaller", "1.49", None) ] + |> List.map ~f:(fun (name, test_input, expected_output) -> + name >:: create_test test_input expected_output ) let tests = "cios_version_numbers_suite" >::: test_correct_ios_version diff --git a/infer/src/unit/clang/ClangTests.ml b/infer/src/unit/clang/ClangTests.ml index e350dde14..9cb4d4c19 100644 --- a/infer/src/unit/clang/ClangTests.ml +++ b/infer/src/unit/clang/ClangTests.ml @@ -8,8 +8,5 @@ *) open! IStd -let tests = [ - CiOSVersionNumbersTests.tests; - QualifiedCppNameTests.tests; - CFrontend_errorsTests.tests; -] +let tests = + [CiOSVersionNumbersTests.tests; QualifiedCppNameTests.tests; CFrontend_errorsTests.tests] diff --git a/infer/src/unit/clang/ClangTests.mli b/infer/src/unit/clang/ClangTests.mli index 60afeade7..8a0692ab9 100644 --- a/infer/src/unit/clang/ClangTests.mli +++ b/infer/src/unit/clang/ClangTests.mli @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd val tests : OUnit2.test list diff --git a/infer/src/unit/clang/QualifiedCppNameTests.ml b/infer/src/unit/clang/QualifiedCppNameTests.ml index 6e41fcc47..cab5df8d8 100644 --- a/infer/src/unit/clang/QualifiedCppNameTests.ml +++ b/infer/src/unit/clang/QualifiedCppNameTests.ml @@ -15,120 +15,38 @@ let test_fuzzy_match = let output = let qualified_name = QualifiedCppName.of_list qualifiers in let matcher = QualifiedCppName.Match.of_fuzzy_qual_names fuzzy_qual_names in - QualifiedCppName.Match.match_qualifiers matcher qualified_name in - assert_equal ~cmp:Bool.equal expected_match output in - [ - ( - "test_simple_match1", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["foo"; "baz"], - true - ); - ( - "test_simple_match2", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["foo"; "bar"; "baz"], - true - ); - ( - "test_simple_match3", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["goo"; "goo"], - true - ); - ( - "test_no_simple_match1", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["foo"; "bar"], - false - ); - ( - "test_no_simple_match2", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["goo"; "foo"], - false - ); - ( - "test_no_simple_match3", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["moo"], - false - ); - ( - "test_no_simple_match4", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["foo"; "bar"; "baz"; "bad"], - false - ); - ( - "test_no_simple_match5", - ["foo::bar::baz"; "foo::baz"; "goo::goo"], - ["foo"; "bad"; "bar"; "baz"], - false - ); - ( - "test_template_match", - ["foo::bar::baz"], - ["foo"; "bar,const X&>"; "baz"], - true - ); - ( - "test_std_direct_match", - ["std::foo"], - ["std"; "foo"], - true - ); - ( - "test_std_direct_no_match1", - ["std::foo"], - ["std"; "goo"], - false - ); - ( - "test_std_direct_no_match2", - ["std::foo"], - ["std"; "foo"; "bad"], - false - ); - ( - "test_std_direct_no_match3", - ["std::foo"], - ["stdBAD"; "foo"], - false - ); - ( - "test_std_no_fuzzy_match1", - ["std::foo"], - ["std"; "__1"; "foo"], - false - ); - ( - "test_std_no_fuzzy_match2", - ["std::foo"], - ["std"; "goo"; "foo"], - false - ); - ( - "test_std_no_fuzzy_match3", - ["std::foo"], - ["std"; "goo"; "foo"], - false - ); - ( - "test_std_fuzzy_no_match1", - ["std::foo"], - ["std"; "__1"; "__2"; "foo"], - false - ); - ( - "test_std_fuzzy_no_match2", - ["std::foo"], - ["std"; "__1"; "foo"; "bad"], - false - ); - ] - |> List.map - ~f:(fun (name, fuzzy_qual_names, qualifiers, expected_output) -> - name >:: create_test fuzzy_qual_names qualifiers expected_output) + QualifiedCppName.Match.match_qualifiers matcher qualified_name + in + assert_equal ~cmp:Bool.equal expected_match output + in + [ ("test_simple_match1", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["foo"; "baz"], true) + ; ("test_simple_match2", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["foo"; "bar"; "baz"], true) + ; ("test_simple_match3", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["goo"; "goo"], true) + ; ("test_no_simple_match1", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["foo"; "bar"], false) + ; ("test_no_simple_match2", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["goo"; "foo"], false) + ; ("test_no_simple_match3", ["foo::bar::baz"; "foo::baz"; "goo::goo"], ["moo"], false) + ; ( "test_no_simple_match4" + , ["foo::bar::baz"; "foo::baz"; "goo::goo"] + , ["foo"; "bar"; "baz"; "bad"] + , false ) + ; ( "test_no_simple_match5" + , ["foo::bar::baz"; "foo::baz"; "goo::goo"] + , ["foo"; "bad"; "bar"; "baz"] + , false ) + ; ( "test_template_match" + , ["foo::bar::baz"] + , ["foo"; "bar,const X&>"; "baz"] + , true ) + ; ("test_std_direct_match", ["std::foo"], ["std"; "foo"], true) + ; ("test_std_direct_no_match1", ["std::foo"], ["std"; "goo"], false) + ; ("test_std_direct_no_match2", ["std::foo"], ["std"; "foo"; "bad"], false) + ; ("test_std_direct_no_match3", ["std::foo"], ["stdBAD"; "foo"], false) + ; ("test_std_no_fuzzy_match1", ["std::foo"], ["std"; "__1"; "foo"], false) + ; ("test_std_no_fuzzy_match2", ["std::foo"], ["std"; "goo"; "foo"], false) + ; ("test_std_no_fuzzy_match3", ["std::foo"], ["std"; "goo"; "foo"], false) + ; ("test_std_fuzzy_no_match1", ["std::foo"], ["std"; "__1"; "__2"; "foo"], false) + ; ("test_std_fuzzy_no_match2", ["std::foo"], ["std"; "__1"; "foo"; "bad"], false) ] + |> List.map ~f:(fun (name, fuzzy_qual_names, qualifiers, expected_output) -> + name >:: create_test fuzzy_qual_names qualifiers expected_output ) let tests = "qualified_cpp_name_fuzzy_match" >::: test_fuzzy_match diff --git a/infer/src/unit/clang/QualifiedCppNameTests.mli b/infer/src/unit/clang/QualifiedCppNameTests.mli index eb8aed225..7b603ffae 100644 --- a/infer/src/unit/clang/QualifiedCppNameTests.mli +++ b/infer/src/unit/clang/QualifiedCppNameTests.mli @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) + open! IStd -val tests: OUnit2.test +val tests : OUnit2.test diff --git a/infer/src/unit/copyPropagationTests.ml b/infer/src/unit/copyPropagationTests.ml index 18b8a4bc7..bcf26e1f9 100644 --- a/infer/src/unit/copyPropagationTests.ml +++ b/infer/src/unit/copyPropagationTests.ml @@ -8,9 +8,7 @@ *) open! IStd - module F = Format - module TestInterpreter = AnalyzerTester.Make (ProcCfg.Exceptional) (CopyPropagation.TransferFunctions) @@ -18,147 +16,84 @@ let tests = let open OUnit2 in let open AnalyzerTester.StructuredSil in let assert_empty = invariant "{ }" in - let test_list = [ - "id_load_id_no_gen", - [ - id_assign_id "b" "a"; (* means b = *a *) - assert_empty - ]; - "id_set_id_no_gen", - [ - id_set_id "b" "a"; (* means *b = a *) - assert_empty - ]; - "id_set_id_no_kill", - [ - id_assign_var "b" "a"; - invariant "{ b$0 -> &a }"; - id_set_id "b" "x"; - invariant "{ b$0 -> &a }" - ]; - "id_assign_var_gen", - [ - id_assign_var "b" "a"; - invariant "{ b$0 -> &a }" - ]; - "var_assign_addrof_var_no_gen", - [ - var_assign_addrof_var "b" "a"; (* means b = &a *) - assert_empty - ]; - "var_assign_id_gen", - [ - var_assign_id "b" "a"; - invariant "{ &b -> a$0 }" - ]; - "multi_command_gen", - [ - id_assign_var "b" "a"; - var_assign_id "c" "b"; - id_assign_var "d" "c"; - invariant "{ b$0 -> &a, d$0 -> &c, &c -> b$0 }" - ]; - "simple_kill", - [ - id_assign_var "b" "a"; - invariant "{ b$0 -> &a }"; - var_assign_int "a" 1; - assert_empty - ]; - "kill_then_gen", - [ - id_assign_var "b" "a"; - invariant "{ b$0 -> &a }"; - var_assign_id "a" "c"; - invariant "{ &a -> c$0 }" - ]; - "same_copy", - [ - var_assign_id "b" "a"; - var_assign_id "c" "d"; - invariant "{ &b -> a$0, &c -> d$0 }"; - var_assign_id "c" "d"; - invariant "{ &b -> a$0, &c -> d$0 }" - ]; - "conservative_if", - [ - var_assign_id "b" "a"; - If (unknown_exp, - [invariant "{ &b -> a$0 }"; - var_assign_id "b" "c"; - invariant "{ &b -> c$0 }"], - [] - ); - assert_empty - ]; - "if1", - [ - var_assign_id "b" "a"; - var_assign_id "c" "d"; - If (unknown_exp, - [invariant "{ &b -> a$0, &c -> d$0 }"; - var_assign_id "c" "e"; - invariant "{ &b -> a$0, &c -> e$0 }"; - ], - [invariant "{ &b -> a$0, &c -> d$0 }"] - ); - invariant "{ &b -> a$0 }" - ]; - "if2", - [ - If (unknown_exp, - [var_assign_id "a" "b"], - [var_assign_id "a" "b"] - ); - invariant "{ &a -> b$0 }" - ]; - "if3", - [ - If (unknown_exp, - [var_assign_id "a" "b"], - [var_assign_id "a" "c"] - ); - assert_empty - ]; - "nested_if", - [ - var_assign_id "b" "a"; - var_assign_id "c" "b"; - If (unknown_exp, - [If (var_of_str "unknown2", - [ invariant "{ &b -> a$0, &c -> b$0 }"; - var_assign_id "b" "d"; - invariant "{ &b -> d$0, &c -> b$0 }"], - [] - ) - ], - [] - ); - invariant "{ &c -> b$0 }" - ]; - "loop_as_if", - [ - var_assign_id "b" "a"; - While (unknown_exp, - [var_assign_id "b" "c"] - ); - assert_empty - ]; - "easy_loop_invariant", - [ - var_assign_id "b" "a"; - While (unknown_exp, - [var_assign_id "c" "d"; - invariant "{ &b -> a$0, &c -> d$0 }"] - ); - invariant "{ &b -> a$0 }" - ]; - "empty_loop", - [ - var_assign_id "b" "a"; - While (unknown_exp, []); - var_assign_id "c" "b"; - invariant "{ &b -> a$0, &c -> b$0 }" - ]; - ] |> TestInterpreter.create_tests ProcData.empty_extras ~initial:CopyPropagation.Domain.empty in - "copy_propagation_test_suite">:::test_list + let test_list = + [ ("id_load_id_no_gen", [id_assign_id "b" "a"; (* means b = *a *) + assert_empty]) + ; ("id_set_id_no_gen", [id_set_id "b" "a"; (* means *b = a *) + assert_empty]) + ; ( "id_set_id_no_kill" + , [ id_assign_var "b" "a" + ; invariant "{ b$0 -> &a }" + ; id_set_id "b" "x" + ; invariant "{ b$0 -> &a }" ] ) + ; ("id_assign_var_gen", [id_assign_var "b" "a"; invariant "{ b$0 -> &a }"]) + ; ( "var_assign_addrof_var_no_gen" + , [var_assign_addrof_var "b" "a"; (* means b = &a *) + assert_empty] ) + ; ("var_assign_id_gen", [var_assign_id "b" "a"; invariant "{ &b -> a$0 }"]) + ; ( "multi_command_gen" + , [ id_assign_var "b" "a" + ; var_assign_id "c" "b" + ; id_assign_var "d" "c" + ; invariant "{ b$0 -> &a, d$0 -> &c, &c -> b$0 }" ] ) + ; ( "simple_kill" + , [id_assign_var "b" "a"; invariant "{ b$0 -> &a }"; var_assign_int "a" 1; assert_empty] ) + ; ( "kill_then_gen" + , [ id_assign_var "b" "a" + ; invariant "{ b$0 -> &a }" + ; var_assign_id "a" "c" + ; invariant "{ &a -> c$0 }" ] ) + ; ( "same_copy" + , [ var_assign_id "b" "a" + ; var_assign_id "c" "d" + ; invariant "{ &b -> a$0, &c -> d$0 }" + ; var_assign_id "c" "d" + ; invariant "{ &b -> a$0, &c -> d$0 }" ] ) + ; ( "conservative_if" + , [ var_assign_id "b" "a" + ; If + ( unknown_exp + , [invariant "{ &b -> a$0 }"; var_assign_id "b" "c"; invariant "{ &b -> c$0 }"] + , [] ) + ; assert_empty ] ) + ; ( "if1" + , [ var_assign_id "b" "a" + ; var_assign_id "c" "d" + ; If + ( unknown_exp + , [ invariant "{ &b -> a$0, &c -> d$0 }" + ; var_assign_id "c" "e" + ; invariant "{ &b -> a$0, &c -> e$0 }" ] + , [invariant "{ &b -> a$0, &c -> d$0 }"] ) + ; invariant "{ &b -> a$0 }" ] ) + ; ( "if2" + , [ If (unknown_exp, [var_assign_id "a" "b"], [var_assign_id "a" "b"]) + ; invariant "{ &a -> b$0 }" ] ) + ; ("if3", [If (unknown_exp, [var_assign_id "a" "b"], [var_assign_id "a" "c"]); assert_empty]) + ; ( "nested_if" + , [ var_assign_id "b" "a" + ; var_assign_id "c" "b" + ; If + ( unknown_exp + , [ If + ( var_of_str "unknown2" + , [ invariant "{ &b -> a$0, &c -> b$0 }" + ; var_assign_id "b" "d" + ; invariant "{ &b -> d$0, &c -> b$0 }" ] + , [] ) ] + , [] ) + ; invariant "{ &c -> b$0 }" ] ) + ; ( "loop_as_if" + , [var_assign_id "b" "a"; While (unknown_exp, [var_assign_id "b" "c"]); assert_empty] ) + ; ( "easy_loop_invariant" + , [ var_assign_id "b" "a" + ; While (unknown_exp, [var_assign_id "c" "d"; invariant "{ &b -> a$0, &c -> d$0 }"]) + ; invariant "{ &b -> a$0 }" ] ) + ; ( "empty_loop" + , [ var_assign_id "b" "a" + ; While (unknown_exp, []) + ; var_assign_id "c" "b" + ; invariant "{ &b -> a$0, &c -> b$0 }" ] ) ] + |> TestInterpreter.create_tests ProcData.empty_extras ~initial:CopyPropagation.Domain.empty + in + "copy_propagation_test_suite" >::: test_list diff --git a/infer/src/unit/inferunit.ml b/infer/src/unit/inferunit.ml index d9b8c7fe5..4a2a3ec5f 100644 --- a/infer/src/unit/inferunit.ml +++ b/infer/src/unit/inferunit.ml @@ -13,21 +13,22 @@ open! IStd let () = let open OUnit2 in - let tests = [ - AbstractInterpreterTests.tests; - AccessPathTests.tests; - AccessTreeTests.tests; - AddressTakenTests.tests; - BoundedCallTreeTests.tests; - CopyPropagationTests.tests; - DifferentialTests.tests; - DifferentialFiltersTests.tests; - ProcCfgTests.tests; - LivenessTests.tests; - SchedulerTests.tests; - StacktraceTests.tests; - TaintTests.tests; - TraceTests.tests; - ] @ ClangTests.tests in + let tests = + [ AbstractInterpreterTests.tests + ; AccessPathTests.tests + ; AccessTreeTests.tests + ; AddressTakenTests.tests + ; BoundedCallTreeTests.tests + ; CopyPropagationTests.tests + ; DifferentialTests.tests + ; DifferentialFiltersTests.tests + ; ProcCfgTests.tests + ; LivenessTests.tests + ; SchedulerTests.tests + ; StacktraceTests.tests + ; TaintTests.tests + ; TraceTests.tests ] + @ ClangTests.tests + in let test_suite = "all" >::: tests in OUnit2.run_test_tt_main test_suite diff --git a/infer/src/unit/livenessTests.ml b/infer/src/unit/livenessTests.ml index 7512c294a..553cb5a4f 100644 --- a/infer/src/unit/livenessTests.ml +++ b/infer/src/unit/livenessTests.ml @@ -8,11 +8,9 @@ *) open! IStd - module F = Format - module TestInterpreter = - AnalyzerTester.Make (ProcCfg.Backward(ProcCfg.Normal)) (Liveness.TransferFunctions) + AnalyzerTester.Make (ProcCfg.Backward (ProcCfg.Normal)) (Liveness.TransferFunctions) let tests = let open OUnit2 in @@ -22,172 +20,87 @@ let tests = let closure_exp captured_pvars = let mk_captured_var str = (Exp.Var (ident_of_str str), pvar_of_str str, dummy_typ) in let captured_vars = List.map ~f:mk_captured_var captured_pvars in - let closure = { Exp.name=dummy_procname; captured_vars; } in - Exp.Closure closure in + let closure = {Exp.name= dummy_procname; captured_vars} in + Exp.Closure closure + in let unknown_cond = (* don't want to use AnalyzerTest.unknown_exp because we'll treat it as a live var! *) - Exp.zero in - let test_list = [ - "basic_live", - [ - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "basic_live_then_dead", - [ - assert_empty; - var_assign_int "b" 1; - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "iterative_live", - [ - invariant "{ &b, &f, &d }"; - id_assign_var "e" "f"; - invariant "{ &b, &d }"; - id_assign_var "c" "d"; - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "live_kill_live", - [ - invariant "{ &b }"; - id_assign_var "c" "b"; - assert_empty; - var_assign_int "b" 1; - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "basic_live_load", - [ - invariant "{ y$0 }"; - id_assign_id "x" "y" - ]; - "basic_live_then_kill_load", - [ - invariant "{ z$0 }"; - id_assign_id "y" "z"; - invariant "{ y$0 }"; - id_assign_id "x" "y" - ]; - "set_id", - [ - invariant "{ x$0, y$0 }"; - id_set_id "x" "y" (* this is *x = y, which is a read of both x and y *) - ]; - "if_exp_live", - [ - assert_empty; - var_assign_int "x" 1; - invariant "{ &x }"; - If (var_of_str "x", [], []); - ]; - "while_exp_live", - [ - assert_empty; - var_assign_int "x" 1; - invariant "{ &x }"; - While (var_of_str "x", []); - ]; - "call_params_live", - [ - invariant "{ &b, &a, &c }"; - call_unknown_no_ret ["a"; "b"; "c";] - ]; - "dead_after_call_with_retval", - [ - assert_empty; - call_unknown (Some ("y", Typ.mk (Tint IInt))) []; - invariant "{ y$0 }"; - id_assign_id "x" "y"; - ]; - "closure_captured_live", - [ - invariant "{ b$0, c$0 }"; - var_assign_exp ~rhs_typ:fun_ptr_typ "a" (closure_exp ["b"; "c"]) - ]; - "if_conservative_live1", - [ - invariant "{ &b }"; - If (unknown_cond, - [id_assign_var "a" "b"], - [] - ) - ]; - "if_conservative_live2", - [ - invariant "{ &b, &d }"; - If (unknown_cond, - [id_assign_var "a" "b"], - [id_assign_var "c" "d"] - ) - ]; - "if_conservative_kill", - [ - invariant "{ &b }"; - If (unknown_cond, - [var_assign_int "b" 1], - [] - ); - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "if_conservative_kill_live", - [ - invariant "{ &b, &d }"; - If (unknown_cond, - [var_assign_int "b" 1], - [id_assign_var "c" "d"] - ); - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "if_precise1", - [ - assert_empty; - If (unknown_cond, - [var_assign_int "b" 1; - invariant "{ &b }"; - id_assign_var "a" "b"], - [var_assign_int "d" 1; - invariant "{ &d }"; - id_assign_var "c" "d"] - ) - ]; - "if_precise2", - [ - assert_empty; - If (unknown_cond, - [var_assign_int "b" 2], - [var_assign_int "b" 1] - ); - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "loop_as_if1", - [ - invariant "{ &b }"; - While (unknown_cond, - [id_assign_var "a" "b"] - ) - ]; - "loop_as_if2", - [ - invariant "{ &b }"; - While (unknown_cond, - [var_assign_int "b" 1] - ); - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - "loop_before_after", - [ - invariant "{ &b, &d }"; - While (unknown_cond, - [id_assign_var "b" "d"] - ); - invariant "{ &b }"; - id_assign_var "a" "b" - ]; - ] |> TestInterpreter.create_tests ProcData.empty_extras ~initial:Liveness.Domain.empty in - "liveness_test_suite">:::test_list + Exp.zero + in + let test_list = + [ ("basic_live", [invariant "{ &b }"; id_assign_var "a" "b"]) + ; ( "basic_live_then_dead" + , [assert_empty; var_assign_int "b" 1; invariant "{ &b }"; id_assign_var "a" "b"] ) + ; ( "iterative_live" + , [ invariant "{ &b, &f, &d }" + ; id_assign_var "e" "f" + ; invariant "{ &b, &d }" + ; id_assign_var "c" "d" + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ( "live_kill_live" + , [ invariant "{ &b }" + ; id_assign_var "c" "b" + ; assert_empty + ; var_assign_int "b" 1 + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ("basic_live_load", [invariant "{ y$0 }"; id_assign_id "x" "y"]) + ; ( "basic_live_then_kill_load" + , [invariant "{ z$0 }"; id_assign_id "y" "z"; invariant "{ y$0 }"; id_assign_id "x" "y"] ) + ; ( "set_id" + , (* this is *x = y, which is a read of both x and y *) + [invariant "{ x$0, y$0 }"; id_set_id "x" "y"] ) + ; ( "if_exp_live" + , [assert_empty; var_assign_int "x" 1; invariant "{ &x }"; If (var_of_str "x", [], [])] ) + ; ( "while_exp_live" + , [assert_empty; var_assign_int "x" 1; invariant "{ &x }"; While (var_of_str "x", [])] ) + ; ("call_params_live", [invariant "{ &b, &a, &c }"; call_unknown_no_ret ["a"; "b"; "c"]]) + ; ( "dead_after_call_with_retval" + , [ assert_empty + ; call_unknown (Some ("y", Typ.mk (Tint IInt))) [] + ; invariant "{ y$0 }" + ; id_assign_id "x" "y" ] ) + ; ( "closure_captured_live" + , [invariant "{ b$0, c$0 }"; var_assign_exp ~rhs_typ:fun_ptr_typ "a" (closure_exp ["b"; "c"])] + ) + ; ( "if_conservative_live1" + , [invariant "{ &b }"; If (unknown_cond, [id_assign_var "a" "b"], [])] ) + ; ( "if_conservative_live2" + , [ invariant "{ &b, &d }" + ; If (unknown_cond, [id_assign_var "a" "b"], [id_assign_var "c" "d"]) ] ) + ; ( "if_conservative_kill" + , [ invariant "{ &b }" + ; If (unknown_cond, [var_assign_int "b" 1], []) + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ( "if_conservative_kill_live" + , [ invariant "{ &b, &d }" + ; If (unknown_cond, [var_assign_int "b" 1], [id_assign_var "c" "d"]) + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ( "if_precise1" + , [ assert_empty + ; If + ( unknown_cond + , [var_assign_int "b" 1; invariant "{ &b }"; id_assign_var "a" "b"] + , [var_assign_int "d" 1; invariant "{ &d }"; id_assign_var "c" "d"] ) ] ) + ; ( "if_precise2" + , [ assert_empty + ; If (unknown_cond, [var_assign_int "b" 2], [var_assign_int "b" 1]) + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ("loop_as_if1", [invariant "{ &b }"; While (unknown_cond, [id_assign_var "a" "b"])]) + ; ( "loop_as_if2" + , [ invariant "{ &b }" + ; While (unknown_cond, [var_assign_int "b" 1]) + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) + ; ( "loop_before_after" + , [ invariant "{ &b, &d }" + ; While (unknown_cond, [id_assign_var "b" "d"]) + ; invariant "{ &b }" + ; id_assign_var "a" "b" ] ) ] + |> TestInterpreter.create_tests ProcData.empty_extras ~initial:Liveness.Domain.empty + in + "liveness_test_suite" >::: test_list diff --git a/infer/src/unit/procCfgTests.ml b/infer/src/unit/procCfgTests.ml index 46dfd3142..7ee87add9 100644 --- a/infer/src/unit/procCfgTests.ml +++ b/infer/src/unit/procCfgTests.ml @@ -8,9 +8,7 @@ *) open! IStd - module F = Format - module BackwardCfg = ProcCfg.Backward (ProcCfg.Normal) module InstrCfg = ProcCfg.OneInstrPerNode (ProcCfg.Normal) module BackwardInstrCfg = ProcCfg.Backward (InstrCfg) @@ -18,150 +16,149 @@ module BackwardInstrCfg = ProcCfg.Backward (InstrCfg) let tests = let cfg = Cfg.create_cfg () in let test_pdesc = - Cfg.create_proc_desc cfg (ProcAttributes.default Typ.Procname.empty_block !Config.curr_language) in + Cfg.create_proc_desc cfg + (ProcAttributes.default Typ.Procname.empty_block !Config.curr_language) + in let dummy_instr1 = Sil.Remove_temps ([], Location.dummy) in let dummy_instr2 = Sil.Abstract Location.dummy in let dummy_instr3 = Sil.Remove_temps ([Ident.create_fresh Ident.knormal], Location.dummy) in let dummy_instr4 = Sil.Remove_temps ([], Location.dummy) in - let instrs1 = [dummy_instr1; dummy_instr2;] in + let instrs1 = [dummy_instr1; dummy_instr2] in let instrs2 = [dummy_instr3] in let instrs3 = [dummy_instr4] in let instrs4 = [] in let create_node instrs = - Procdesc.create_node test_pdesc Location.dummy (Procdesc.Node.Stmt_node "") instrs in + Procdesc.create_node test_pdesc Location.dummy (Procdesc.Node.Stmt_node "") instrs + in let n1 = create_node instrs1 in let n2 = create_node instrs2 in let n3 = create_node instrs3 in let n4 = create_node instrs4 in - - Procdesc.set_start_node test_pdesc n1; + Procdesc.set_start_node test_pdesc n1 ; (* let -> represent normal transitions and -*-> represent exceptional transitions *) (* creating graph n1 -> n2, n1 -*-> n3, n2 -> n4, n2 -*-> n3, n3 -> n4 , n3 -*> n4 *) - Procdesc.node_set_succs_exn test_pdesc n1 [n2] [n3]; - Procdesc.node_set_succs_exn test_pdesc n2 [n4] [n3]; - Procdesc.node_set_succs_exn test_pdesc n3 [n4] [n4]; - + Procdesc.node_set_succs_exn test_pdesc n1 [n2] [n3] ; + Procdesc.node_set_succs_exn test_pdesc n2 [n4] [n3] ; + Procdesc.node_set_succs_exn test_pdesc n3 [n4] [n4] ; let normal_proc_cfg = ProcCfg.Normal.from_pdesc test_pdesc in let exceptional_proc_cfg = ProcCfg.Exceptional.from_pdesc test_pdesc in let backward_proc_cfg = BackwardCfg.from_pdesc test_pdesc in let backward_instr_proc_cfg = BackwardInstrCfg.from_pdesc test_pdesc in - let open OUnit2 in let cmp l1 l2 = let sort = List.sort ~cmp:Procdesc.Node.compare in - List.equal ~equal:Procdesc.Node.equal (sort l1) (sort l2) in + List.equal ~equal:Procdesc.Node.equal (sort l1) (sort l2) + in let pp_diff fmt (actual, expected) = let pp_sep fmt _ = F.pp_print_char fmt ',' in let pp_node_list fmt l = F.pp_print_list ~pp_sep Procdesc.Node.pp fmt l in - F.fprintf fmt "Expected output %a but got %a" pp_node_list expected pp_node_list actual in - let create_test input expected _ = - assert_equal ~cmp ~pp_diff input expected in + F.fprintf fmt "Expected output %a but got %a" pp_node_list expected pp_node_list actual + in + let create_test input expected _ = assert_equal ~cmp ~pp_diff input expected in let instr_test = let instr_test_ _ = - begin - match ProcCfg.Normal.instrs n1 with - | [instr1; instr2] -> - assert_bool "First instr should be dummy_instr1" (phys_equal instr1 dummy_instr1); - assert_bool "Second instr should be dummy_instr2" (phys_equal instr2 dummy_instr2); - | _ -> assert_failure "Expected exactly two instructions" - end; - begin - match BackwardCfg.instrs n1 with - | [instr1; instr2] -> - assert_bool "First instr should be dummy_instr2" (phys_equal instr1 dummy_instr2); - assert_bool "Second instr should be dummy_instr1" (phys_equal instr2 dummy_instr1); - | _ -> assert_failure "Expected exactly two instructions" - end; - begin - let node_id, _ = InstrCfg.id n1 in - match InstrCfg.instr_ids n1 with - | [ (instr1, Some (id1, ProcCfg.Instr_index 0)); - (instr2, Some (id2, ProcCfg.Instr_index 1)); ] -> - assert_bool "First instr should be dummy_instr1" (phys_equal instr1 dummy_instr1); - assert_bool "Second instr should be dummy_instr2" (phys_equal instr2 dummy_instr2); - assert_bool "id1 should be id of underlying node" (phys_equal id1 node_id); - assert_bool "id2 should be id of underlying node" (phys_equal id2 node_id); - | _ -> assert_failure "Expected exactly two instructions with correct indices" - end; + ( match ProcCfg.Normal.instrs n1 with + | [instr1; instr2] + -> assert_bool "First instr should be dummy_instr1" (phys_equal instr1 dummy_instr1) ; + assert_bool "Second instr should be dummy_instr2" (phys_equal instr2 dummy_instr2) + | _ + -> assert_failure "Expected exactly two instructions" ) ; + ( match BackwardCfg.instrs n1 with + | [instr1; instr2] + -> assert_bool "First instr should be dummy_instr2" (phys_equal instr1 dummy_instr2) ; + assert_bool "Second instr should be dummy_instr1" (phys_equal instr2 dummy_instr1) + | _ + -> assert_failure "Expected exactly two instructions" ) ; + (let node_id, _ = InstrCfg.id n1 in + match InstrCfg.instr_ids n1 with + | [(instr1, Some (id1, ProcCfg.Instr_index 0)); (instr2, Some (id2, ProcCfg.Instr_index 1))] + -> assert_bool "First instr should be dummy_instr1" (phys_equal instr1 dummy_instr1) ; + assert_bool "Second instr should be dummy_instr2" (phys_equal instr2 dummy_instr2) ; + assert_bool "id1 should be id of underlying node" (phys_equal id1 node_id) ; + assert_bool "id2 should be id of underlying node" (phys_equal id2 node_id) + | _ + -> assert_failure "Expected exactly two instructions with correct indices") ; let backward_node_id, _ = BackwardInstrCfg.id n1 in - begin - match BackwardInstrCfg.instr_ids n1 with - | [ (instr1, Some (id1, ProcCfg.Instr_index 1)); - (instr2, Some (id2, ProcCfg.Instr_index 0)); ] -> - assert_bool "First instr should be dummy_instr2" (phys_equal instr1 dummy_instr2); - assert_bool "Second instr should be dummy_instr1" (phys_equal instr2 dummy_instr1); - assert_bool "id1 should be id of underlying node" (phys_equal id1 backward_node_id); - assert_bool "id2 should be id of underlying node" (phys_equal id2 backward_node_id); - | _ -> assert_failure "Expected exactly two instructions with correct indices" - end; - assert_bool - "underlying_node should return node of underlying CFG type" + ( match BackwardInstrCfg.instr_ids n1 with + | [(instr1, Some (id1, ProcCfg.Instr_index 1)); (instr2, Some (id2, ProcCfg.Instr_index 0))] + -> assert_bool "First instr should be dummy_instr2" (phys_equal instr1 dummy_instr2) ; + assert_bool "Second instr should be dummy_instr1" (phys_equal instr2 dummy_instr1) ; + assert_bool "id1 should be id of underlying node" (phys_equal id1 backward_node_id) ; + assert_bool "id2 should be id of underlying node" (phys_equal id2 backward_node_id) + | _ + -> assert_failure "Expected exactly two instructions with correct indices" ) ; + assert_bool "underlying_node should return node of underlying CFG type" (Procdesc.Node.equal_id (Procdesc.Node.get_id (BackwardInstrCfg.underlying_node n1)) - (BackwardCfg.id n1)) in - "instr_test">::instr_test_ in - - let graph_tests = [ - (* test the succs of the normal cfg. forward... *) - ("succs_n1", ProcCfg.Normal.succs normal_proc_cfg n1, [n2]); - ("normal_succs_n1", ProcCfg.Normal.normal_succs normal_proc_cfg n1, [n2]); - ("succs_n2", ProcCfg.Normal.succs normal_proc_cfg n2, [n4]); - ("normal_succs_n2", ProcCfg.Normal.normal_succs normal_proc_cfg n2, [n4]); - ("succs_n3", ProcCfg.Normal.succs normal_proc_cfg n3, [n4]); - ("normal_succs_n3", ProcCfg.Normal.normal_succs normal_proc_cfg n3, [n4]); - (* ... and backward... *) - ("succs_n1_bw", BackwardCfg.preds backward_proc_cfg n1, [n2]); - ("normal_succs_n1_bw", BackwardCfg.normal_preds backward_proc_cfg n1, [n2]); - ("succs_n2_bw", BackwardCfg.preds backward_proc_cfg n2, [n4]); - ("normal_succs_n2_bw", BackwardCfg.normal_preds backward_proc_cfg n2, [n4]); - ("succs_n3_bw", BackwardCfg.preds backward_proc_cfg n3, [n4]); - ("normal_succs_n3_bw", BackwardCfg.normal_preds backward_proc_cfg n3, [n4]); - (* ...and make sure it all works when using backward + instr cfg *) - ("succs_n1_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n1, [n2]); - ("normal_succs_n1_bw_instrcfg", BackwardInstrCfg.normal_preds backward_instr_proc_cfg n1, [n2]); - ("succs_n2_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n2, [n4]); - ("normal_succs_n2_bw_instrcfg", BackwardInstrCfg.normal_preds backward_instr_proc_cfg n2, [n4]); - ("succs_n3_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n3, [n4]); - ("normal_succs_n3_bw_instrcfg", BackwardInstrCfg.normal_preds backward_instr_proc_cfg n3, [n4]); - - (* test the preds of the normal cfg... *) - ("preds_n2", ProcCfg.Normal.normal_preds normal_proc_cfg n2, [n1]); - ("normal_preds_n2", ProcCfg.Normal.normal_preds normal_proc_cfg n2, [n1]); - (* ...and the backward cfg... *) - ("preds_n2_bw", BackwardCfg.normal_succs backward_proc_cfg n2, [n1]); - ("normal_preds_n2_bw", BackwardCfg.normal_succs backward_proc_cfg n2, [n1]); - (* ...and again make sure it works with backward + instr cfg *) - ("preds_n2_bw_instr", BackwardInstrCfg.normal_succs backward_instr_proc_cfg n2, [n1]); - ("normal_preds_n2_bw_instr", BackwardInstrCfg.normal_succs backward_instr_proc_cfg n2, [n1]); - - (* we shouldn't see any exn succs or preds even though we added them *) - ("no_exn_succs_n1", ProcCfg.Normal.exceptional_succs normal_proc_cfg n1, []); - ("no_exn_preds_n3", ProcCfg.Normal.exceptional_preds normal_proc_cfg n3, []); - (* same in the backward cfg *) - ("no_exn_succs_n1_bw", BackwardCfg.exceptional_preds backward_proc_cfg n1, []); - ("no_exn_preds_n3_bw", BackwardCfg.exceptional_succs backward_proc_cfg n3, []); - (* same in backward + instr cfg *) - ("no_exn_succs_n1_bw_instr", BackwardInstrCfg.exceptional_preds backward_instr_proc_cfg n1, []); - ("no_exn_preds_n3_bw_instr", BackwardInstrCfg.exceptional_succs backward_instr_proc_cfg n3, []); - - - (* now, test the exceptional succs in the exceptional cfg. *) - ("exn_succs_n1", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n1, [n3]); - ("exn_succs_n2", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n2, [n3]); - ("exn_succs_n3", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n3, [n4]); - (* test exceptional pred links *) - ("exn_preds_n3", ProcCfg.Exceptional.exceptional_preds exceptional_proc_cfg n3, [n2; n1]); - (* succs should return both normal and exceptional successors *) - ("exn_all_succs_n1", ProcCfg.Exceptional.succs exceptional_proc_cfg n1, [n3; n2]); - (* but, should not return duplicates *) - ("exn_all_succs_n3", ProcCfg.Exceptional.succs exceptional_proc_cfg n3, [n4]); - (* similarly, preds should return both normal and exceptional predecessors *) - ("exn_all_preds_n3", ProcCfg.Exceptional.preds exceptional_proc_cfg n3, [n2; n1]); - ("exn_all_preds_n4", ProcCfg.Exceptional.preds exceptional_proc_cfg n4, [n3; n2]); - (* finally, normal_succs/normal_preds shouldn't return exceptional edges *) - ("exn_normal_succs_n1", ProcCfg.Exceptional.normal_succs exceptional_proc_cfg n1, [n2]); - ("exn_normal_preds_n2", ProcCfg.Exceptional.normal_preds exceptional_proc_cfg n2, [n1]); - ] - |> List.map ~f:(fun (name, test, expected) -> name>::create_test test expected) in + (BackwardCfg.id n1)) + in + "instr_test" >:: instr_test_ + in + let graph_tests = + [ (* test the succs of the normal cfg. forward... *) + ("succs_n1", ProcCfg.Normal.succs normal_proc_cfg n1, [n2]) + ; ("normal_succs_n1", ProcCfg.Normal.normal_succs normal_proc_cfg n1, [n2]) + ; ("succs_n2", ProcCfg.Normal.succs normal_proc_cfg n2, [n4]) + ; ("normal_succs_n2", ProcCfg.Normal.normal_succs normal_proc_cfg n2, [n4]) + ; ("succs_n3", ProcCfg.Normal.succs normal_proc_cfg n3, [n4]) + ; ("normal_succs_n3", ProcCfg.Normal.normal_succs normal_proc_cfg n3, [n4]) + ; (* ... and backward... *) + ("succs_n1_bw", BackwardCfg.preds backward_proc_cfg n1, [n2]) + ; ("normal_succs_n1_bw", BackwardCfg.normal_preds backward_proc_cfg n1, [n2]) + ; ("succs_n2_bw", BackwardCfg.preds backward_proc_cfg n2, [n4]) + ; ("normal_succs_n2_bw", BackwardCfg.normal_preds backward_proc_cfg n2, [n4]) + ; ("succs_n3_bw", BackwardCfg.preds backward_proc_cfg n3, [n4]) + ; ("normal_succs_n3_bw", BackwardCfg.normal_preds backward_proc_cfg n3, [n4]) + ; (* ...and make sure it all works when using backward + instr cfg *) + ("succs_n1_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n1, [n2]) + ; ( "normal_succs_n1_bw_instrcfg" + , BackwardInstrCfg.normal_preds backward_instr_proc_cfg n1 + , [n2] ) + ; ("succs_n2_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n2, [n4]) + ; ( "normal_succs_n2_bw_instrcfg" + , BackwardInstrCfg.normal_preds backward_instr_proc_cfg n2 + , [n4] ) + ; ("succs_n3_bw_instrcfg", BackwardInstrCfg.preds backward_instr_proc_cfg n3, [n4]) + ; ( "normal_succs_n3_bw_instrcfg" + , BackwardInstrCfg.normal_preds backward_instr_proc_cfg n3 + , [n4] ) + ; (* test the preds of the normal cfg... *) + ("preds_n2", ProcCfg.Normal.normal_preds normal_proc_cfg n2, [n1]) + ; ("normal_preds_n2", ProcCfg.Normal.normal_preds normal_proc_cfg n2, [n1]) + ; (* ...and the backward cfg... *) + ("preds_n2_bw", BackwardCfg.normal_succs backward_proc_cfg n2, [n1]) + ; ("normal_preds_n2_bw", BackwardCfg.normal_succs backward_proc_cfg n2, [n1]) + ; (* ...and again make sure it works with backward + instr cfg *) + ("preds_n2_bw_instr", BackwardInstrCfg.normal_succs backward_instr_proc_cfg n2, [n1]) + ; ("normal_preds_n2_bw_instr", BackwardInstrCfg.normal_succs backward_instr_proc_cfg n2, [n1]) + ; (* we shouldn't see any exn succs or preds even though we added them *) + ("no_exn_succs_n1", ProcCfg.Normal.exceptional_succs normal_proc_cfg n1, []) + ; ("no_exn_preds_n3", ProcCfg.Normal.exceptional_preds normal_proc_cfg n3, []) + ; (* same in the backward cfg *) + ("no_exn_succs_n1_bw", BackwardCfg.exceptional_preds backward_proc_cfg n1, []) + ; ("no_exn_preds_n3_bw", BackwardCfg.exceptional_succs backward_proc_cfg n3, []) + ; (* same in backward + instr cfg *) + ("no_exn_succs_n1_bw_instr", BackwardInstrCfg.exceptional_preds backward_instr_proc_cfg n1, []) + ; ( "no_exn_preds_n3_bw_instr" + , BackwardInstrCfg.exceptional_succs backward_instr_proc_cfg n3 + , [] ) + ; (* now, test the exceptional succs in the exceptional cfg. *) + ("exn_succs_n1", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n1, [n3]) + ; ("exn_succs_n2", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n2, [n3]) + ; ("exn_succs_n3", ProcCfg.Exceptional.exceptional_succs exceptional_proc_cfg n3, [n4]) + ; (* test exceptional pred links *) + ("exn_preds_n3", ProcCfg.Exceptional.exceptional_preds exceptional_proc_cfg n3, [n2; n1]) + ; (* succs should return both normal and exceptional successors *) + ("exn_all_succs_n1", ProcCfg.Exceptional.succs exceptional_proc_cfg n1, [n3; n2]) + ; (* but, should not return duplicates *) + ("exn_all_succs_n3", ProcCfg.Exceptional.succs exceptional_proc_cfg n3, [n4]) + ; (* similarly, preds should return both normal and exceptional predecessors *) + ("exn_all_preds_n3", ProcCfg.Exceptional.preds exceptional_proc_cfg n3, [n2; n1]) + ; ("exn_all_preds_n4", ProcCfg.Exceptional.preds exceptional_proc_cfg n4, [n3; n2]) + ; (* finally, normal_succs/normal_preds shouldn't return exceptional edges *) + ("exn_normal_succs_n1", ProcCfg.Exceptional.normal_succs exceptional_proc_cfg n1, [n2]) + ; ("exn_normal_preds_n2", ProcCfg.Exceptional.normal_preds exceptional_proc_cfg n2, [n1]) ] + |> List.map ~f:(fun (name, test, expected) -> name >:: create_test test expected) + in let tests = instr_test :: graph_tests in - "procCfgSuite">:::tests + "procCfgSuite" >::: tests diff --git a/infer/src/unit/schedulerTests.ml b/infer/src/unit/schedulerTests.ml index ceb0448bc..7bbb9a3db 100644 --- a/infer/src/unit/schedulerTests.ml +++ b/infer/src/unit/schedulerTests.ml @@ -8,49 +8,54 @@ *) open! IStd - module F = Format - - (** mocks for creating CFG's from adjacency lists *) module MockNode = struct type t = int + type id = int let instrs _ = [] + let instr_ids _ = [] + let hash = Hashtbl.hash + let to_instr_nodes _ = assert false + let id n = n + let loc _ = assert false + let underlying_node _ = assert false + let kind _ = Procdesc.Node.Stmt_node "" + let compare_id = Int.compare - let pp_id fmt i = - F.fprintf fmt "%i" i + + let pp_id fmt i = F.fprintf fmt "%i" i end module MockProcCfg = struct type node = int + include (MockNode : module type of MockNode with type t := node) + type t = (node * node list) list let equal_id = Int.equal let succs t n = let node_id = id n in - List.find - ~f:(fun (node, _) -> equal_id (id node) node_id) - t |> - Option.value_map ~f:snd ~default:[] + List.find ~f:(fun (node, _) -> equal_id (id node) node_id) t + |> Option.value_map ~f:snd ~default:[] let preds t n = try let node_id = id n in List.filter - ~f:(fun (_, succs) -> - List.exists ~f:(fun node -> equal_id (id node) node_id) succs) + ~f:(fun (_, succs) -> List.exists ~f:(fun node -> equal_id (id node) node_id) succs) t |> List.map ~f:fst with Not_found -> [] @@ -58,15 +63,24 @@ module MockProcCfg = struct let nodes t = List.map ~f:fst t let normal_succs = succs + let normal_preds = preds + let exceptional_succs _ _ = [] + let exceptional_preds _ _ = [] + let from_adjacency_list t = t + (* not called by the scheduler *) let start_node _ = assert false + let exit_node _ = assert false + let proc_desc _ = assert false + let from_pdesc _ = assert false + let is_loop_head _ = assert false end @@ -76,13 +90,17 @@ let create_test test_graph expected_result _ = (* keep popping and scheduling until the queue is empty, record the results *) let rec pop_schedule_record q visited_acc = match S.pop q with - | Some (n, _, q') -> - pop_schedule_record (S.schedule_succs q' n) (n :: visited_acc) - | None -> List.rev visited_acc in + | Some (n, _, q') + -> pop_schedule_record (S.schedule_succs q' n) (n :: visited_acc) + | None + -> List.rev visited_acc + in let pp_diff fmt (exp, actual) = let pp_sched fmt l = - F.pp_print_list ~pp_sep:F.pp_print_space (fun fmt i -> F.fprintf fmt "%d" i) fmt l in - F.fprintf fmt "Expected schedule %a but got schedule %a" pp_sched exp pp_sched actual in + F.pp_print_list ~pp_sep:F.pp_print_space (fun fmt i -> F.fprintf fmt "%d" i) fmt l + in + F.fprintf fmt "Expected schedule %a but got schedule %a" pp_sched exp pp_sched actual + in let cfg = MockProcCfg.from_adjacency_list test_graph in let q = S.schedule_succs (S.empty cfg) 1 in let result = pop_schedule_record q [1] in @@ -90,49 +108,19 @@ let create_test test_graph expected_result _ = let tests = let open OUnit2 in - let test_list = [ - ("straightline", - [(1, [2]); - (2, [3]); - (3, [4])], - [1; 2; 3; 4]); - ("if_then_else", - [(1, [2; 3]); - (2, [4]); - (3, [4]); - (4, [5])], - [1; 2; 3; 4; 5]); - ("if_then", - [(1, [2; 4]); - (2, [3]); - (3, [4]); - (4, [5])], - [1; 2; 3; 4; 5]); - ("diamond", - [(1, [2; 3]); - (2, [4]); - (3, [4]); - (4, [5; 6]); - (5, [7]); - (6, [7]); - (7, [8])], - [1; 2; 3; 4; 5; 6; 7; 8]); - ("switch", - [(1, [2; 3; 4; 5;]); - (2, [6]); - (3, [6]); - (4, [6]); - (5, [6]); - (6, [7])], - [1; 2; 3; 4; 5; 6; 7;]); - ("nums_order_irrelevant", - [(11, [10];); - (1, [7; 2]); - (2, [3; 11]); - (7, [11]); - (3, [7]);], - [1; 2; 3; 7; 11; 10]); - ] - |> List.map - ~f:(fun (name, test, expected) -> name>::create_test test expected) in - "scheduler_suite">:::test_list + let test_list = + [ ("straightline", [(1, [2]); (2, [3]); (3, [4])], [1; 2; 3; 4]) + ; ("if_then_else", [(1, [2; 3]); (2, [4]); (3, [4]); (4, [5])], [1; 2; 3; 4; 5]) + ; ("if_then", [(1, [2; 4]); (2, [3]); (3, [4]); (4, [5])], [1; 2; 3; 4; 5]) + ; ( "diamond" + , [(1, [2; 3]); (2, [4]); (3, [4]); (4, [5; 6]); (5, [7]); (6, [7]); (7, [8])] + , [1; 2; 3; 4; 5; 6; 7; 8] ) + ; ( "switch" + , [(1, [2; 3; 4; 5]); (2, [6]); (3, [6]); (4, [6]); (5, [6]); (6, [7])] + , [1; 2; 3; 4; 5; 6; 7] ) + ; ( "nums_order_irrelevant" + , [(11, [10]); (1, [7; 2]); (2, [3; 11]); (7, [11]); (3, [7])] + , [1; 2; 3; 7; 11; 10] ) ] + |> List.map ~f:(fun (name, test, expected) -> name >:: create_test test expected) + in + "scheduler_suite" >::: test_list diff --git a/infer/src/unit/stacktraceTests.ml b/infer/src/unit/stacktraceTests.ml index 028637ca4..0207fb778 100644 --- a/infer/src/unit/stacktraceTests.ml +++ b/infer/src/unit/stacktraceTests.ml @@ -8,84 +8,77 @@ *) open! IStd - module F = Format let tests = let open OUnit2 in - let empty_string_test = let empty_string_test_ _ = - assert_raises - (Failure "Empty stack trace") - (fun () -> Stacktrace.of_string "") in - "empty_string">::empty_string_test_ in - + assert_raises (Failure "Empty stack trace") (fun () -> Stacktrace.of_string "") + in + "empty_string" >:: empty_string_test_ + in let empty_trace_test = let empty_stack_trace_s = "Exception in thread \"main\" java.lang.NullPointerException" in let trace = Stacktrace.of_string empty_stack_trace_s in - let empty_trace_test_ _ = - assert_equal trace.frames [] in - "empty_trace">::empty_trace_test_ in - + let empty_trace_test_ _ = assert_equal trace.frames [] in + "empty_trace" >:: empty_trace_test_ + in let one_frame_trace_test = let one_frame_trace_test_s = - "Exception in thread \"main\" java.lang.NullPointerException\n" ^ - "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main" ^ - "(MinimalCrashTest.java:16)" in - let trace = Stacktrace.of_string - one_frame_trace_test_s in - let expected = Stacktrace.make - "java.lang.NullPointerException" - [Stacktrace.make_frame - "endtoend.java.checkers.crashcontext.MinimalCrashTest" - "main" - "MinimalCrashTest.java" - (Some 16)] in - let one_frame_trace_test_ _ = - assert_equal trace expected in - "one_frame_trace">::one_frame_trace_test_ in - + "Exception in thread \"main\" java.lang.NullPointerException\n" + ^ "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main" + ^ "(MinimalCrashTest.java:16)" + in + let trace = Stacktrace.of_string one_frame_trace_test_s in + let expected = + Stacktrace.make "java.lang.NullPointerException" + [ Stacktrace.make_frame "endtoend.java.checkers.crashcontext.MinimalCrashTest" "main" + "MinimalCrashTest.java" (Some 16) ] + in + let one_frame_trace_test_ _ = assert_equal trace expected in + "one_frame_trace" >:: one_frame_trace_test_ + in let multi_frame_trace_test = let multi_frame_trace_test_s = - "Exception in thread \"main\" java.lang.NullPointerException\n\t" ^ - "at endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.bar" ^ - "(MultiStackFrameCrashTest.java:16)\n" ^ - "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.foo" ^ - "(MultiStackFrameCrashTest.java:20)\n" ^ - "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.main" ^ - "(MultiStackFrameCrashTest.java:24)" in - let trace = Stacktrace.of_string - multi_frame_trace_test_s in - let class_name = - "endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest" in + "Exception in thread \"main\" java.lang.NullPointerException\n\t" + ^ "at endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.bar" + ^ "(MultiStackFrameCrashTest.java:16)\n" + ^ "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.foo" + ^ "(MultiStackFrameCrashTest.java:20)\n" + ^ "\tat endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest.main" + ^ "(MultiStackFrameCrashTest.java:24)" + in + let trace = Stacktrace.of_string multi_frame_trace_test_s in + let class_name = "endtoend.java.checkers.crashcontext.MultiStackFrameCrashTest" in let file_name = "MultiStackFrameCrashTest.java" in - let expected = Stacktrace.make - "java.lang.NullPointerException" - [Stacktrace.make_frame class_name "bar" file_name (Some 16); - Stacktrace.make_frame class_name "foo" file_name (Some 20); - Stacktrace.make_frame class_name "main" file_name (Some 24)] in - let multi_frame_trace_test_ _ = - assert_equal trace expected in - "multi_frame_trace_test">::multi_frame_trace_test_ in - + let expected = + Stacktrace.make "java.lang.NullPointerException" + [ Stacktrace.make_frame class_name "bar" file_name (Some 16) + ; Stacktrace.make_frame class_name "foo" file_name (Some 20) + ; Stacktrace.make_frame class_name "main" file_name (Some 24) ] + in + let multi_frame_trace_test_ _ = assert_equal trace expected in + "multi_frame_trace_test" >:: multi_frame_trace_test_ + in let missing_line_info_test = let missing_line_info_test_s = - "Exception in thread \"main\" java.lang.NullPointerException\n" ^ - "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main" ^ - "(MinimalCrashTest.java)" in + "Exception in thread \"main\" java.lang.NullPointerException\n" + ^ "\tat endtoend.java.checkers.crashcontext.MinimalCrashTest.main" + ^ "(MinimalCrashTest.java)" + in let trace = Stacktrace.of_string missing_line_info_test_s in - let expected = Stacktrace.make - "java.lang.NullPointerException" - [Stacktrace.make_frame - "endtoend.java.checkers.crashcontext.MinimalCrashTest" - "main" - "MinimalCrashTest.java" - None] in - let missing_line_info_test_ _ = - assert_equal trace expected in - "missing_line_info_test">::missing_line_info_test_ in - - "all_tests_suite">:::[empty_string_test; empty_trace_test; - one_frame_trace_test; multi_frame_trace_test; - missing_line_info_test] + let expected = + Stacktrace.make "java.lang.NullPointerException" + [ Stacktrace.make_frame "endtoend.java.checkers.crashcontext.MinimalCrashTest" "main" + "MinimalCrashTest.java" None ] + in + let missing_line_info_test_ _ = assert_equal trace expected in + "missing_line_info_test" >:: missing_line_info_test_ + in + "all_tests_suite" + >::: [ empty_string_test + ; empty_trace_test + ; one_frame_trace_test + ; multi_frame_trace_test + ; missing_line_info_test ] diff --git a/opam b/opam index 0bb481414..a789d7307 100644 --- a/opam +++ b/opam @@ -38,7 +38,6 @@ depends: [ "ounit" {="2.0.0"} "parmap" {>="1.0-rc8"} "ppx_deriving" {>="4.1"} - "reason" {="1.13.4"} "sawja" {>="1.5.2"} "xmlm" {>="1.2.0"} ] diff --git a/opam.lock b/opam.lock index 159945ace..83c49d919 100644 --- a/opam.lock +++ b/opam.lock @@ -74,8 +74,6 @@ ppx_typerep_conv = v0.9.0 ppx_variants_conv = v0.9.0 re = 1.7.1 react = 1.2.0 -reason = 1.13.4 -reason-parser = 1.13.4 result = 1.2 sawja = 1.5.2 sexplib = v0.9.1 diff --git a/scripts/reup.sh b/scripts/ocamlformat.sh similarity index 64% rename from scripts/reup.sh rename to scripts/ocamlformat.sh index 3e9c2f816..8a986e5c7 100755 --- a/scripts/reup.sh +++ b/scripts/ocamlformat.sh @@ -8,15 +8,17 @@ # of patent rights can be found in the PATENTS file in the same directory. -# re-format reason code +# ocamlformat wrapper to appease ArcanistExternalLinter set -e set -o pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -base=`basename $0` -tmpfile=`mktemp -t "${base}.XXXX"` +OCAMLFORMAT="$SCRIPT_DIR"/../facebook/dependencies/ocamlformat/src/_build/opt/ocamlformat.exe -"$SCRIPT_DIR/refmt.sh" --parse re --print re "$@" > "$tmpfile" -mv "$tmpfile" "${@: -1}" +TMPFILE=$(mktemp) + +"$OCAMLFORMAT" -o "$TMPFILE" "$@" + +cat "$TMPFILE" diff --git a/scripts/refmt.sh b/scripts/refmt.sh deleted file mode 100755 index 6c71e1426..000000000 --- a/scripts/refmt.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016 - present Facebook, Inc. -# All rights reserved. -# -# This source code is licensed under the BSD style license found in the -# LICENSE file in the root directory of this source tree. An additional grant -# of patent rights can be found in the PATENTS file in the same directory. - - -# refmt with infer options - -set -e -set -o pipefail - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -refmt \ - --assume-explicit-arity \ - --print-width 100 \ - --heuristics-file "$SCRIPT_DIR/unary.txt" \ - "$@" diff --git a/scripts/unary.txt b/scripts/unary.txt deleted file mode 100644 index 511af2815..000000000 --- a/scripts/unary.txt +++ /dev/null @@ -1,148 +0,0 @@ -Adiv0 -ArraySubscriptExpr -AttributedType -BinaryConditionalOperator -BinaryOperator -BlockDecl -BlockExpr -BlockPointerType -BreakStmt -BuiltinType -C -CapturedDecl -CStyleCastExpr -CXXBindTemporaryExpr -CXXBoolLiteralExpr -CXXCatchStmt -CXXConstCastExpr -CXXConstructExpr -CXXConstructorDecl -CXXConversionDecl -CXXDefaultArgExpr -CXXDefaultInitExpr -CXXDeleteExpr -CXXDestructorDecl -CXXDynamicCastExpr -CXXForRangeStmt -CXXFunctionalCastExpr -CXXMemberCallExpr -CXXMethodDecl -CXXNewExpr -CXXNullPtrLiteralExpr -CXXOperatorCallExpr -CXXRecordDecl -CXXReinterpretCastExpr -CXXScalarValueInitExpr -CXXStaticCastExpr -CXXStdInitializerListExpr -CXXTemporaryObjectExpr -CXXThisExpr -CXXThrowExpr -CXXTryStmt -CXXTypeidExpr -CallExpr -CaseStmt -CharacterLiteral -ClassTemplateDecl -ClassTemplateSpecializationDecl -CompoundAssignOperator -CompoundLiteralExpr -CompoundStmt -Condition_redundant -ConditionalOperator -ConstantArrayType -ContinueStmt -DecayedType -DeclRefExpr -DeclStmt -DefaultStmt -DependentSizedArrayType -DoStmt -EnumConstantDecl -EnumDecl -EnumType -Esub_entry -ExprWithCleanups -FieldDecl -FloatingLiteral -ForStmt -FunctionDecl -FunctionNoProtoType -FunctionProtoType -FunctionTemplateDecl -GCCAsmStmt -GNUNullExpr -GenericSelectionExpr -GotoStmt -IfStmt -ImplOK -ImplicitCastExpr -ImplicitValueInitExpr -IncompleteArrayType -InitListExpr -IntegerLiteral -LambdaExpr -LValueReferenceType -LabelStmt -LinkageSpecDecl -MaterializeTemporaryExpr -MemberExpr -NamespaceDecl -NonBottom -NullStmt -ObjCArrayLiteral -ObjCAtCatchStmt -ObjCAtFinallyStmt -ObjCAtSynchronizedStmt -ObjCAtThrowStmt -ObjCAtTryStmt -ObjCAutoreleasePoolStmt -ObjCBoolLiteralExpr -ObjCBoxedExpr -ObjCBridgedCastExpr -ObjCCategoryDecl -ObjCCategoryImplDecl -ObjCDictionaryLiteral -ObjCEncodeExpr -ObjCForCollectionStmt -ObjCImplementationDecl -ObjCIndirectCopyRestoreExpr -ObjCInterfaceDecl -ObjCInterfaceType -ObjCIvarDecl -ObjCIvarRefExpr -ObjCMessageExpr -ObjCMethodDecl -ObjCObjectPointerType -ObjCObjectType -ObjCPropertyDecl -ObjCPropertyImplDecl -ObjCPropertyRefExpr -ObjCProtocolDecl -ObjCProtocolExpr -ObjCSelectorExpr -ObjCStringLiteral -ObjCSubscriptRefExpr -OpaqueValueExpr -Parameter_annotation_inconsistent -ParenExpr -ParenType -ParmVarDecl -PointerType -PredefinedExpr -PseudoObjectExpr -RValueReferenceType -RecordDecl -RecordType -ReturnStmt -SizeOfPackExpr -StmtExpr -StringLiteral -SwitchStmt -TranslationUnitDecl -TypedefType -UnaryExprOrTypeTraitExpr -UnaryOperator -VarDecl -VariableArrayType -WhileStmt