diff --git a/infer/src/.merlin b/infer/src/.merlin index 972d8ad97..2b96746ad 100644 --- a/infer/src/.merlin +++ b/infer/src/.merlin @@ -3,6 +3,7 @@ B _build/default/atd B _build/default/istd PKG ANSITerminal PKG atdgen +PKG base PKG cmdliner PKG core PKG javalib diff --git a/infer/src/IR/Attributes.ml b/infer/src/IR/Attributes.ml index 4de264884..8ef63ccac 100644 --- a/infer/src/IR/Attributes.ml +++ b/infer/src/IR/Attributes.ml @@ -11,84 +11,109 @@ module L = Logging type attributes_kind = ProcUndefined | ProcObjCAccessor | ProcDefined [@@deriving compare] -let least_relevant_up_to_proc_kind_exclusive = function - | ProcUndefined - -> [] - | ProcObjCAccessor - -> [ProcUndefined] - | ProcDefined - -> [ProcUndefined; ProcObjCAccessor] - -let most_relevant_down_to_proc_kind_inclusive = function - | ProcUndefined - -> [ProcDefined; ProcObjCAccessor; ProcUndefined] - | ProcObjCAccessor - -> [ProcDefined; ProcObjCAccessor] - | ProcDefined - -> [ProcDefined] +let int64_of_attributes_kind = + (* only allocate this once *) + let int64_two = Int64.of_int 2 in + function ProcUndefined -> Int64.zero | ProcObjCAccessor -> Int64.one | ProcDefined -> int64_two let proc_kind_of_attr (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 should_override_attr attr1 attr2 = - (* use the source file to be more deterministic in case the same procedure name is defined in several files *) - [%compare : attributes_kind * SourceFile.t] - (proc_kind_of_attr attr1, attr1.ProcAttributes.loc.file) - (proc_kind_of_attr attr2, attr2.ProcAttributes.loc.file) - > 0 +module type Data = sig + val of_pname : Typ.Procname.t -> Sqlite3.Data.t -module Table = struct - type key = string + val of_source_file : SourceFile.t -> Sqlite3.Data.t - type value = ProcAttributes.t + val of_proc_attr : ProcAttributes.t -> Sqlite3.Data.t - let table = ResultsDir.attributes_table + val to_proc_attr : Sqlite3.Data.t -> ProcAttributes.t end -module Store = KeyValue.Make (Table) +module Data : Data = struct + let pname_to_key = Base.Hashtbl.create (module Typ.Procname) () -let string_of_pkind = function - | ProcUndefined - -> "U" - | ProcObjCAccessor - -> "O" - | ProcDefined - -> "D" + let of_pname pname = + let default () = Sqlite3.Data.TEXT (Typ.Procname.to_filename pname) in + Base.Hashtbl.find_or_add pname_to_key pname ~default -module KeyHashtbl = Caml.Hashtbl.Make (struct - type t = Typ.Procname.t * attributes_kind + let of_source_file file = Sqlite3.Data.TEXT (SourceFile.to_string file) - let equal = [%compare.equal : Typ.Procname.t * attributes_kind] + let to_proc_attr = function[@warning "-8"] Sqlite3.Data.BLOB b -> Marshal.from_string b 0 - let hash = Hashtbl.hash -end) - -let pname_to_key = KeyHashtbl.create 16 - -let key_of_pname_pkind (pname, pkind as p) = - try KeyHashtbl.find pname_to_key p - with Not_found -> - let key = Typ.Procname.to_filename pname ^ string_of_pkind pkind |> Store.blob_of_key in - KeyHashtbl.replace pname_to_key p key ; key - -let load_aux ?(min_kind= ProcUndefined) pname = - List.find_map (most_relevant_down_to_proc_kind_inclusive min_kind) ~f:(fun pkind -> - key_of_pname_pkind (pname, pkind) |> Store.find ) + let of_proc_attr x = Sqlite3.Data.BLOB (Marshal.to_string x []) +end -let load pname : ProcAttributes.t option = load_aux pname +let get_replace_statement = + (* The innermost SELECT returns either the current attributes_kind and source_file associated with + the given proc name, or default values of (-1,""). These default values have the property that + they are always "less than" any legit value. More precisely, MAX ensures that some value is + returned even if there is no row satisfying WHERE (we'll get NULL in that case, the value in + the row otherwise). COALESCE then returns the first non-NULL value, which will be either the + value of the row corresponding to that pname in the DB, or the default if no such row exists. + + The next (second-outermost) SELECT filters out that value if it is "more defined" than the ones + we would like to insert (which will never be the case if the default values are returned). If + not, it returns a trivial row (consisting solely of NULL since we don't use its values) and the + INSERT OR REPLACE will proceed and insert or update the values stored into the DB for that + pname. *) + (* TRICK: use the source file to be more deterministic in case the same procedure name is defined + in several files *) + (* TRICK: older versions of sqlite (prior to version 3.15.0 (2016-10-14)) do not support row + values so the lexicographic ordering for (:akind, :sfile) is done by hand *) + (* TODO (optim): it might be worth not generating the source file everytime we do a store, but + only generate it if the attribute needs updating (which should be orders of magnitude less + frequent) *) + ResultsDir.register_statement + {| +INSERT OR REPLACE INTO attributes +SELECT :pname, :akind, :sfile, :pattr +FROM ( + SELECT NULL + FROM ( + SELECT COALESCE(MAX(attr_kind),-1) AS attr_kind, + COALESCE(MAX(source_file),"") AS source_file + FROM attributes + WHERE proc_name = :pname ) + WHERE attr_kind < :akind + OR (attr_kind = :akind AND source_file < :sfile) )|} + +let replace pname_blob akind loc_file attr_blob = + let replace_stmt = get_replace_statement () in + Sqlite3.bind replace_stmt 1 (* :pname *) pname_blob + |> SqliteUtils.check_sqlite_error ~log:"replace bind pname" ; + Sqlite3.bind replace_stmt 2 (* :akind *) (Sqlite3.Data.INT (int64_of_attributes_kind akind)) + |> SqliteUtils.check_sqlite_error ~log:"replace bind attribute kind" ; + Sqlite3.bind replace_stmt 3 (* :sfile *) loc_file + |> SqliteUtils.check_sqlite_error ~log:"replace bind source file" ; + Sqlite3.bind replace_stmt 4 (* :pattr *) attr_blob + |> SqliteUtils.check_sqlite_error ~log:"replace bind proc attributes" ; + SqliteUtils.sqlite_unit_step ~finalize:false ~log:"Attributes.replace" replace_stmt + +let get_select_statement = + ResultsDir.register_statement "SELECT proc_attributes FROM attributes WHERE proc_name = :k" + +let get_select_defined_statement = + ResultsDir.register_statement + "SELECT proc_attributes FROM attributes WHERE proc_name = :k AND attr_kind = %Ld" + (int64_of_attributes_kind ProcDefined) + +let find ~defined pname_blob = + let select_stmt = if defined then get_select_defined_statement () else get_select_statement () in + Sqlite3.bind select_stmt 1 pname_blob + |> SqliteUtils.check_sqlite_error ~log:"find bind proc name" ; + SqliteUtils.sqlite_result_step ~finalize:false ~log:"Attributes.find" select_stmt + |> Option.map ~f:Data.to_proc_attr + +let load pname = Data.of_pname pname |> find ~defined:false let store (attr: ProcAttributes.t) = let pkind = proc_kind_of_attr attr in - if load attr.proc_name |> Option.value_map ~default:true ~f:(should_override_attr attr) then - (* NOTE: We need to do this dance of adding the proc_kind to the key because there's a race condition between the time we load the attributes from the db and the time we write possibly better ones. We could avoid this by making the db schema richer than just key/value and turning the SELECT + REPLACE into an atomic transaction. *) - let key = key_of_pname_pkind (attr.proc_name, pkind) in - Store.replace key (Store.blob_of_value attr) ; - least_relevant_up_to_proc_kind_exclusive pkind - |> List.iter ~f:(fun k -> key_of_pname_pkind (attr.proc_name, k) |> Store.delete) - -let load_defined pname = load_aux ~min_kind:ProcDefined pname + let key = Data.of_pname attr.proc_name in + replace key pkind (Data.of_source_file attr.loc.Location.file) (Data.of_proc_attr attr) + +let load_defined pname = Data.of_pname pname |> find ~defined:true 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: diff --git a/infer/src/IR/Attributes.mli b/infer/src/IR/Attributes.mli index 528518546..5f38feb75 100644 --- a/infer/src/IR/Attributes.mli +++ b/infer/src/IR/Attributes.mli @@ -11,12 +11,6 @@ open! IStd -type attributes_kind - -module Table : KeyValue.Table with type key = string and type value = ProcAttributes.t - -module Store : KeyValue.S with module Table = Table - val store : ProcAttributes.t -> unit (** Save .attr file for the procedure into the attributes database. *) diff --git a/infer/src/IR/Typ.ml b/infer/src/IR/Typ.ml index d5b5b903c..d7e4056ba 100644 --- a/infer/src/IR/Typ.ml +++ b/infer/src/IR/Typ.ml @@ -547,6 +547,8 @@ module Procname = struct let equal = [%compare.equal : t] + let hash = Hashtbl.hash + (** Level of verbosity of some to_string functions. *) type detail_level = Verbose | Non_verbose | Simple [@@deriving compare] @@ -949,6 +951,8 @@ module Procname = struct | Linters_dummy_method -> to_unique_id p + let sexp_of_t p = Sexp.Atom (to_string p) + (** Convenient representation of a procname for external tools (e.g. eclipse plugin) *) let to_simplified_string ?(withclass= false) p = match p with diff --git a/infer/src/IR/Typ.mli b/infer/src/IR/Typ.mli index 4d076ae13..56e55c4b7 100644 --- a/infer/src/IR/Typ.mli +++ b/infer/src/IR/Typ.mli @@ -281,7 +281,10 @@ module Procname : sig [@@deriving compare] val equal : t -> t -> bool - (** Equality for proc names. *) + + val hash : t -> int + + val sexp_of_t : t -> Sexp.t type java_type = string option * string diff --git a/infer/src/base/KeyValue.ml b/infer/src/base/KeyValue.ml deleted file mode 100644 index 4328274ee..000000000 --- a/infer/src/base/KeyValue.ml +++ /dev/null @@ -1,137 +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 -module L = Logging - -module type Table = sig - type key - - type value - - val table : string -end - -module type BlobInternal = sig - module Table : Table - - type key_blob = Sqlite3.Data.t - - type value_blob = Sqlite3.Data.t - - val blob_of_key : Table.key -> key_blob - - val blob_of_value : Table.value -> value_blob - - val value_of_blob : value_blob -> Table.value option -end - -module type S = sig - module Table : Table - - type key_blob - - type value_blob - - val blob_of_key : Table.key -> key_blob - - val blob_of_value : Table.value -> value_blob - - external key_blob_of_data : Sqlite3.Data.t -> key_blob = "%identity" - - external value_blob_of_data : Sqlite3.Data.t -> value_blob = "%identity" - - val value_of_blob : value_blob -> Table.value option - - val replace : key_blob -> value_blob -> unit - - val find : key_blob -> Table.value option - - val delete : key_blob -> unit -end - -(* The functor is mostly here to provide a modicum of type safety around blobing/unblobing *) -module Make (Table : Table) : S with module Table = Table = struct - module Unsafe : BlobInternal with module Table = Table = struct - module Table = Table - - type key_blob = Sqlite3.Data.t - - type value_blob = Sqlite3.Data.t - - let blob x = Sqlite3.Data.BLOB (Marshal.to_string x []) - - let unblob = function - | Sqlite3.Data.BLOB b - -> Some (Marshal.from_string b 0) - | Sqlite3.Data.NULL - -> None - | _ - -> assert false - - let blob_of_key = blob - - let blob_of_value = blob - - let value_of_blob = unblob - end - - (* cannot mix, e.g., blob_key and blob_value now *) - include Unsafe - - external key_blob_of_data : Sqlite3.Data.t -> key_blob = "%identity" - - external value_blob_of_data : Sqlite3.Data.t -> value_blob = "%identity" - - let register_statement stmt_fmt = - let k stmt0 = - let stmt_ref = ref None in - let new_statement db = - let stmt = Sqlite3.prepare db stmt0 in - ResultsDir.on_close_database ~f:(fun _ -> - Option.iter !stmt_ref ~f:(SqliteUtils.finalize ~log:"db close callback") ) ; - stmt_ref := Some stmt - in - ResultsDir.on_new_database_connection ~f:new_statement ; - fun () -> - match !stmt_ref with - | None - -> L.(die InternalError) "database not initialized" - | Some stmt - -> Sqlite3.reset stmt |> SqliteUtils.check_sqlite_error ~log:"reset prepared statement" ; - Sqlite3.clear_bindings stmt - |> SqliteUtils.check_sqlite_error ~log:"clear bindings of prepared statement" ; - stmt - in - Printf.ksprintf k stmt_fmt - - let get_replace_statement = - register_statement "REPLACE INTO %s(key, value) VALUES(:k, :v)" Table.table - - let replace key_blob value_blob = - let replace_stmt = get_replace_statement () in - Sqlite3.bind replace_stmt 1 key_blob |> SqliteUtils.check_sqlite_error ~log:"replace bind key" ; - Sqlite3.bind replace_stmt 2 value_blob - |> SqliteUtils.check_sqlite_error ~log:"replace bind value" ; - SqliteUtils.sqlite_unit_step ~finalize:false ~log:"KeyValue.replace" replace_stmt - - let get_select_statement = register_statement "SELECT value FROM %s WHERE key = :k" Table.table - - let find key_blob = - let select_stmt = get_select_statement () in - Sqlite3.bind select_stmt 1 key_blob |> SqliteUtils.check_sqlite_error ~log:"insert bind key" ; - SqliteUtils.sqlite_result_step ~finalize:false ~log:"KeyValue.find" select_stmt - |> Option.bind ~f:value_of_blob - - let get_delete_statement = register_statement "DELETE FROM %s WHERE key = :k" Table.table - - let delete key_blob = - let delete_stmt = get_delete_statement () in - Sqlite3.bind delete_stmt 1 key_blob |> SqliteUtils.check_sqlite_error ~log:"delete bind key" ; - SqliteUtils.sqlite_unit_step ~finalize:false ~log:"KeyValue.delete" delete_stmt -end diff --git a/infer/src/base/KeyValue.mli b/infer/src/base/KeyValue.mli deleted file mode 100644 index 7b0691bff..000000000 --- a/infer/src/base/KeyValue.mli +++ /dev/null @@ -1,48 +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 - -(** key/value database information *) -module type Table = sig - type key - - type value - - val table : string -end - -(** Key/value store backed by the ResultsDir database *) -module type S = sig - module Table : Table - - type key_blob - - type value_blob - - val blob_of_key : Table.key -> key_blob - (** turn a key into a [Sqlite3.Data.BLOB] *) - - val blob_of_value : Table.value -> value_blob - (** turn a value into a [Sqlite3.Data.BLOB] *) - - external key_blob_of_data : Sqlite3.Data.t -> key_blob = "%identity" - - external value_blob_of_data : Sqlite3.Data.t -> value_blob = "%identity" - - val value_of_blob : value_blob -> Table.value option - - val replace : key_blob -> value_blob -> unit - - val find : key_blob -> Table.value option - - val delete : key_blob -> unit -end - -module Make (Table : Table) : S with module Table = Table diff --git a/infer/src/base/MergeResults.ml b/infer/src/base/MergeResults.ml index 0236bfced..c924976ae 100644 --- a/infer/src/base/MergeResults.ml +++ b/infer/src/base/MergeResults.ml @@ -9,34 +9,46 @@ open! IStd module L = Logging -let merge_attributes_table ~into ~db_name ~db_file = - (* no need to wrap this in a single transaction (to batch writes) because we open the table with - synchronous=OFF *) - (* do not go through Attributes so as not to deserialize and reserialize objects pointlessly, and - so as not to fill the cache with all the attributes (especially since this function will be - called before forking all the analysis processes. *) +let merge_attributes_table ~db_file = + (* Do the merge purely in SQL for great speed. The query works by doing a left join between the + sub-table and the main one, and applying the same "more defined" logic as in Attributes in the + cases where a proc_name is present in both the sub-table and the main one (main_attr_kind != + NULL). All the rows that pass this filter are inserted/updated into the main table. *) let copy_stmt = - Sqlite3.prepare into - (Printf.sprintf "REPLACE INTO %s SELECT * FROM %s.%s" ResultsDir.attributes_table db_name - ResultsDir.attributes_table) + Sqlite3.prepare (ResultsDir.get_database ()) + {| +INSERT OR REPLACE INTO attributes +SELECT proc_name, attr_kind, source_file, proc_attributes +FROM ( + SELECT sub.proc_name, sub.attr_kind, sub.source_file, sub.proc_attributes, + main.attr_kind AS main_attr_kind, main.source_file AS main_source_file + FROM ( + attached.attributes AS sub + LEFT JOIN attributes AS main + ON sub.proc_name = main.proc_name ) ) +WHERE + main_attr_kind is NULL + OR main_attr_kind < attr_kind + OR (main_attr_kind = attr_kind AND main_source_file < source_file) +|} in SqliteUtils.sqlite_unit_step ~log:(Printf.sprintf "copying contents of database '%s'" db_file) copy_stmt -let merge ~into db_file = - let db_name = "db" in +let merge ~db_file = + (* no need to wrap all the individual table merges in a single transaction (to batch writes) + because we open the table with synchronous=OFF *) + let main_db = ResultsDir.get_database () in SqliteUtils.check_sqlite_error ~fatal:true ~log:(Printf.sprintf "attaching database '%s'" db_file) - (Sqlite3.exec into (Printf.sprintf "ATTACH '%s' AS %s" db_file db_name)) ; - let do_merge () = merge_attributes_table ~into ~db_name ~db_file in - Utils.without_gc ~f:do_merge ; + (Sqlite3.exec main_db (Printf.sprintf "ATTACH '%s' AS attached" db_file)) ; + merge_attributes_table ~db_file ; SqliteUtils.check_sqlite_error ~fatal:true ~log:(Printf.sprintf "detaching database '%s'" db_file) - (Sqlite3.exec into (Printf.sprintf "DETACH %s" db_name)) ; + (Sqlite3.exec main_db "DETACH attached") ; () let merge_buck_flavors_results infer_deps_file = - let into = ResultsDir.get_database () in let one_line line = match String.split ~on:'\t' line with | [_; _; target_results_dir] @@ -45,7 +57,7 @@ let merge_buck_flavors_results infer_deps_file = Filename.dirname (Config.project_root ^/ "buck-out") ^/ target_results_dir else target_results_dir in - merge ~into (infer_out_src ^/ ResultsDir.database_filename) + merge ~db_file:(infer_out_src ^/ ResultsDir.database_filename) | _ -> assert false in diff --git a/infer/src/base/MergeResults.mli b/infer/src/base/MergeResults.mli index 43e419388..b798b1f3c 100644 --- a/infer/src/base/MergeResults.mli +++ b/infer/src/base/MergeResults.mli @@ -10,4 +10,5 @@ open! IStd val merge_buck_flavors_results : string -> unit -(** Merge the results from sub-invocations of infer inside buck-out/. Takes as argument the infer_deps file. *) +(** Merge the results from sub-invocations of infer inside buck-out/. Takes as argument the + infer_deps file. *) diff --git a/infer/src/base/ResultsDir.ml b/infer/src/base/ResultsDir.ml index abc34a73b..1841e8036 100644 --- a/infer/src/base/ResultsDir.ml +++ b/infer/src/base/ResultsDir.ml @@ -16,8 +16,6 @@ let database_filename = "results.db" let database_fullpath = Config.results_dir ^/ database_filename -let attributes_table = "attributes" - let results_dir_dir_markers = List.map ~f:(Filename.concat Config.results_dir) [Config.captured_dir_name; Config.specs_dir_name] @@ -48,10 +46,16 @@ let remove_results_dir () = Utils.rmtree Config.results_dir ) let create_attributes_table db = + (* it would be nice to use "WITHOUT ROWID" here but ancient versions of sqlite do not support + it *) SqliteUtils.exec db ~log:"initializing results DB" ~stmt: - (Printf.sprintf "CREATE TABLE IF NOT EXISTS %s (key BLOB PRIMARY KEY ,value BLOB)" - attributes_table) + {| +CREATE TABLE IF NOT EXISTS attributes + ( proc_name TEXT PRIMARY KEY + , attr_kind INTEGER NOT NULL + , source_file TEXT NOT NULL + , proc_attributes BLOB NOT NULL )|} let create_db () = let temp_db = Filename.temp_file ~in_dir:Config.results_dir database_filename ".tmp" in @@ -112,8 +116,7 @@ let get_database () = Option.value_exn !database let reset_attributes_table () = let db = get_database () in - SqliteUtils.exec db ~log:"drop attributes table" - ~stmt:(Printf.sprintf "DROP TABLE %s" attributes_table) ; + SqliteUtils.exec db ~log:"drop attributes table" ~stmt:"DROP TABLE attributes" ; create_attributes_table db let delete_capture_and_analysis_data () = @@ -126,3 +129,29 @@ let delete_capture_and_analysis_data () = let canonicalize_db () = let db = get_database () in SqliteUtils.exec db ~log:"running VACUUM" ~stmt:"VACUUM" + +let register_statement stmt_fmt = + let k stmt0 = + let stmt_ref = ref None in + let new_statement db = + let stmt = + try Sqlite3.prepare db stmt0 + with Sqlite3.Error error -> + L.die InternalError "Could not prepare the following statement:@\n%s@\nReason: %s" stmt0 + error + in + on_close_database ~f:(fun _ -> SqliteUtils.finalize ~log:"db close callback" stmt) ; + stmt_ref := Some stmt + in + on_new_database_connection ~f:new_statement ; + fun () -> + match !stmt_ref with + | None + -> L.(die InternalError) "database not initialized" + | Some stmt + -> Sqlite3.reset stmt |> SqliteUtils.check_sqlite_error ~log:"reset prepared statement" ; + Sqlite3.clear_bindings stmt + |> SqliteUtils.check_sqlite_error ~log:"clear bindings of prepared statement" ; + stmt + in + Printf.ksprintf k stmt_fmt diff --git a/infer/src/base/ResultsDir.mli b/infer/src/base/ResultsDir.mli index 93592f722..528ce3d4e 100644 --- a/infer/src/base/ResultsDir.mli +++ b/infer/src/base/ResultsDir.mli @@ -15,9 +15,6 @@ val database_filename : string val get_database : unit -> Sqlite3.db (** The results database. You should always use this function to access the database, as the connection to it may change during the execution (see [new_database_connection]). *) -val attributes_table : string -(** the name of the table of proc names to their proc attributes *) - val reset_attributes_table : unit -> unit (** zero out the attributes table *) @@ -39,6 +36,11 @@ val delete_capture_and_analysis_data : unit -> unit val canonicalize_db : unit -> unit (** put the database on disk in deterministic form *) -val on_new_database_connection : f:(Sqlite3.db -> unit) -> unit +val register_statement : ('a, unit, string, (unit -> Sqlite3.stmt)) Base.format4 -> 'a +(** Return a function unit -> Sqlite3.stmt that can be called (once the DB has been initialized) to + get the prepared statement corresponding to the current DB connection. Use this to prepare + statements only once per DB connection. -val on_close_database : f:(Sqlite3.db -> unit) -> unit + In particular, clients of this need not worry about calling [Sqlite3.finalize] on the returned + statement, or about generating new statements when the connection to the DB changes: this is all + handled internally. *) diff --git a/infer/src/jbuild.common.in b/infer/src/jbuild.common.in index 95cf78b3b..cef9194db 100644 --- a/infer/src/jbuild.common.in +++ b/infer/src/jbuild.common.in @@ -54,6 +54,7 @@ let common_libraries = (if java then ["javalib"; "ptrees"; "sawja"] else []) @ [ "ANSITerminal" ; "atdgen" + ; "base" ; "cmdliner" ; "core" ; "extlib" diff --git a/infer/tests/build_systems/buck_flavors/Makefile b/infer/tests/build_systems/buck_flavors/Makefile index 3e790436f..d8ce934bf 100644 --- a/infer/tests/build_systems/buck_flavors/Makefile +++ b/infer/tests/build_systems/buck_flavors/Makefile @@ -10,7 +10,7 @@ ROOT_DIR = $(TESTS_DIR)/../.. ANALYZER = infer BUCK_TARGET = //src:hello @buck_target.txt -SOURCES = $(wildcard src/hello.c) +SOURCES = $(wildcard src/*.c) $(wildcard src/subtarget1/*.c) $(wildcard src/subtarget2/*.c) OBJECTS = buck-out/gen/src/hello\#compile-hello.c.o1f717d69,default/hello.c.o INFER_OPTIONS = --report-custom-error --developer-mode --project-root $(TESTS_DIR) INFERPRINT_OPTIONS = --project-root $(TESTS_DIR) --issues-tests diff --git a/infer/tests/build_systems/buck_flavors/issues.exp b/infer/tests/build_systems/buck_flavors/issues.exp index 29a2fd6a6..84b553855 100644 --- a/infer/tests/build_systems/buck_flavors/issues.exp +++ b/infer/tests/build_systems/buck_flavors/issues.exp @@ -1,3 +1,5 @@ src/hello.c, test, 2, NULL_DEREFERENCE, [start of procedure test()] src/hello2.c, test2, 2, NULL_DEREFERENCE, [start of procedure test2()] src/hello3.c, test3, 2, NULL_DEREFERENCE, [start of procedure test3()] +src/subtarget1/z_filename_greater_than_subhello1.c, foo_defined_in_subtarget1, 2, NULL_DEREFERENCE, [start of procedure foo_defined_in_subtarget1()] +src/subtarget2/subhello2.c, goo, 3, NULL_DEREFERENCE, [start of procedure goo(),Skipping foo_defined_in_subtarget1(): function or method not found] diff --git a/infer/tests/build_systems/buck_flavors/src/BUCK b/infer/tests/build_systems/buck_flavors/src/BUCK index 3c6ddad73..8e7b8a860 100644 --- a/infer/tests/build_systems/buck_flavors/src/BUCK +++ b/infer/tests/build_systems/buck_flavors/src/BUCK @@ -3,6 +3,10 @@ cxx_library( srcs = [ 'hello.c', 'hello2.c', ], + deps = [ + "//src/subtarget1:subtarget1", + "//src/subtarget2:subtarget2", + ] ) cxx_library( diff --git a/infer/tests/build_systems/buck_flavors/src/subtarget1/BUCK b/infer/tests/build_systems/buck_flavors/src/subtarget1/BUCK new file mode 100644 index 000000000..e9368c055 --- /dev/null +++ b/infer/tests/build_systems/buck_flavors/src/subtarget1/BUCK @@ -0,0 +1,10 @@ +cxx_library( + name = 'subtarget1', + visibility = [ + 'PUBLIC', + ], + srcs = [ + 'z_filename_greater_than_subhello1.c', + 'subhello1.c', + ], +) diff --git a/infer/tests/build_systems/buck_flavors/src/subtarget1/subhello1.c b/infer/tests/build_systems/buck_flavors/src/subtarget1/subhello1.c new file mode 100644 index 000000000..c442da89a --- /dev/null +++ b/infer/tests/build_systems/buck_flavors/src/subtarget1/subhello1.c @@ -0,0 +1,15 @@ +/* + * 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. + */ + +#include + +void foo_defined_in_subtarget1() { + int* s = NULL; + *s = 42; +} diff --git a/infer/tests/build_systems/buck_flavors/src/subtarget1/z_filename_greater_than_subhello1.c b/infer/tests/build_systems/buck_flavors/src/subtarget1/z_filename_greater_than_subhello1.c new file mode 100644 index 000000000..1e05f1ec6 --- /dev/null +++ b/infer/tests/build_systems/buck_flavors/src/subtarget1/z_filename_greater_than_subhello1.c @@ -0,0 +1,15 @@ +/* + * 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. + */ + +#include + +void foo_defined_in_subtarget1() { + int* t = NULL; + *t = 42; +} diff --git a/infer/tests/build_systems/buck_flavors/src/subtarget2/BUCK b/infer/tests/build_systems/buck_flavors/src/subtarget2/BUCK new file mode 100644 index 000000000..6df812b6d --- /dev/null +++ b/infer/tests/build_systems/buck_flavors/src/subtarget2/BUCK @@ -0,0 +1,9 @@ +cxx_library( + name = 'subtarget2', + visibility = [ + 'PUBLIC', + ], + srcs = [ + 'subhello2.c', + ], +) diff --git a/infer/tests/build_systems/buck_flavors/src/subtarget2/subhello2.c b/infer/tests/build_systems/buck_flavors/src/subtarget2/subhello2.c new file mode 100644 index 000000000..2adf4a414 --- /dev/null +++ b/infer/tests/build_systems/buck_flavors/src/subtarget2/subhello2.c @@ -0,0 +1,18 @@ +/* + * 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. + */ + +#include + +void foo_defined_in_subtarget1(); + +void goo() { + foo_defined_in_subtarget1(); + int* s = NULL; + *s = 42; +}