[sql] let sqlite handle the attributes update logic

Summary:
This adds more structure to the SQL schema backing attributes. With that, we
can transfer the logic for updating attributes in SQLite, instead of doing
optimistic concurrency in the client.

Reviewed By: jberdine

Differential Revision: D5891038

fbshipit-source-id: 6577ba2
master
Jules Villard 7 years ago committed by Facebook Github Bot
parent b00451d23e
commit ee1b688e6d

@ -3,6 +3,7 @@ B _build/default/atd
B _build/default/istd B _build/default/istd
PKG ANSITerminal PKG ANSITerminal
PKG atdgen PKG atdgen
PKG base
PKG cmdliner PKG cmdliner
PKG core PKG core
PKG javalib PKG javalib

@ -11,84 +11,109 @@ module L = Logging
type attributes_kind = ProcUndefined | ProcObjCAccessor | ProcDefined [@@deriving compare] type attributes_kind = ProcUndefined | ProcObjCAccessor | ProcDefined [@@deriving compare]
let least_relevant_up_to_proc_kind_exclusive = function let int64_of_attributes_kind =
| ProcUndefined (* only allocate this once *)
-> [] let int64_two = Int64.of_int 2 in
| ProcObjCAccessor function ProcUndefined -> Int64.zero | ProcObjCAccessor -> Int64.one | ProcDefined -> int64_two
-> [ProcUndefined]
| ProcDefined
-> [ProcUndefined; ProcObjCAccessor]
let most_relevant_down_to_proc_kind_inclusive = function
| ProcUndefined
-> [ProcDefined; ProcObjCAccessor; ProcUndefined]
| ProcObjCAccessor
-> [ProcDefined; ProcObjCAccessor]
| ProcDefined
-> [ProcDefined]
let proc_kind_of_attr (proc_attributes: ProcAttributes.t) = let proc_kind_of_attr (proc_attributes: ProcAttributes.t) =
if proc_attributes.is_defined then ProcDefined if proc_attributes.is_defined then ProcDefined
else if Option.is_some proc_attributes.objc_accessor then ProcObjCAccessor else if Option.is_some proc_attributes.objc_accessor then ProcObjCAccessor
else ProcUndefined else ProcUndefined
let should_override_attr attr1 attr2 = module type Data = sig
(* use the source file to be more deterministic in case the same procedure name is defined in several files *) val of_pname : Typ.Procname.t -> Sqlite3.Data.t
[%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 Table = struct val of_source_file : SourceFile.t -> Sqlite3.Data.t
type key = string
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 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 let of_pname pname =
| ProcUndefined let default () = Sqlite3.Data.TEXT (Typ.Procname.to_filename pname) in
-> "U" Base.Hashtbl.find_or_add pname_to_key pname ~default
| ProcObjCAccessor
-> "O"
| ProcDefined
-> "D"
module KeyHashtbl = Caml.Hashtbl.Make (struct let of_source_file file = Sqlite3.Data.TEXT (SourceFile.to_string file)
type t = Typ.Procname.t * attributes_kind
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 let of_proc_attr x = Sqlite3.Data.BLOB (Marshal.to_string x [])
end) 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 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 store (attr: ProcAttributes.t) =
let pkind = proc_kind_of_attr attr in let pkind = proc_kind_of_attr attr in
if load attr.proc_name |> Option.value_map ~default:true ~f:(should_override_attr attr) then let key = Data.of_pname attr.proc_name in
(* 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. *) replace key pkind (Data.of_source_file attr.loc.Location.file) (Data.of_proc_attr attr)
let key = key_of_pname_pkind (attr.proc_name, pkind) in
Store.replace key (Store.blob_of_value attr) ; let load_defined pname = Data.of_pname pname |> find ~defined:true
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 get_correct_type_from_objc_class_name type_name = 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: (* ToDo: this function should return a type that includes a reference to the tenv computed by:

@ -11,12 +11,6 @@
open! IStd 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 val store : ProcAttributes.t -> unit
(** Save .attr file for the procedure into the attributes database. *) (** Save .attr file for the procedure into the attributes database. *)

@ -547,6 +547,8 @@ module Procname = struct
let equal = [%compare.equal : t] let equal = [%compare.equal : t]
let hash = Hashtbl.hash
(** Level of verbosity of some to_string functions. *) (** Level of verbosity of some to_string functions. *)
type detail_level = Verbose | Non_verbose | Simple [@@deriving compare] type detail_level = Verbose | Non_verbose | Simple [@@deriving compare]
@ -949,6 +951,8 @@ module Procname = struct
| Linters_dummy_method | Linters_dummy_method
-> to_unique_id p -> 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) *) (** Convenient representation of a procname for external tools (e.g. eclipse plugin) *)
let to_simplified_string ?(withclass= false) p = let to_simplified_string ?(withclass= false) p =
match p with match p with

@ -281,7 +281,10 @@ module Procname : sig
[@@deriving compare] [@@deriving compare]
val equal : t -> t -> bool 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 type java_type = string option * string

@ -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

@ -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

@ -9,34 +9,46 @@
open! IStd open! IStd
module L = Logging module L = Logging
let merge_attributes_table ~into ~db_name ~db_file = let merge_attributes_table ~db_file =
(* no need to wrap this in a single transaction (to batch writes) because we open the table with (* Do the merge purely in SQL for great speed. The query works by doing a left join between the
synchronous=OFF *) sub-table and the main one, and applying the same "more defined" logic as in Attributes in the
(* do not go through Attributes so as not to deserialize and reserialize objects pointlessly, and cases where a proc_name is present in both the sub-table and the main one (main_attr_kind !=
so as not to fill the cache with all the attributes (especially since this function will be NULL). All the rows that pass this filter are inserted/updated into the main table. *)
called before forking all the analysis processes. *)
let copy_stmt = let copy_stmt =
Sqlite3.prepare into Sqlite3.prepare (ResultsDir.get_database ())
(Printf.sprintf "REPLACE INTO %s SELECT * FROM %s.%s" ResultsDir.attributes_table db_name {|
ResultsDir.attributes_table) 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 in
SqliteUtils.sqlite_unit_step ~log:(Printf.sprintf "copying contents of database '%s'" db_file) SqliteUtils.sqlite_unit_step ~log:(Printf.sprintf "copying contents of database '%s'" db_file)
copy_stmt copy_stmt
let merge ~into db_file = let merge ~db_file =
let db_name = "db" in (* 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 SqliteUtils.check_sqlite_error ~fatal:true
~log:(Printf.sprintf "attaching database '%s'" db_file) ~log:(Printf.sprintf "attaching database '%s'" db_file)
(Sqlite3.exec into (Printf.sprintf "ATTACH '%s' AS %s" db_file db_name)) ; (Sqlite3.exec main_db (Printf.sprintf "ATTACH '%s' AS attached" db_file)) ;
let do_merge () = merge_attributes_table ~into ~db_name ~db_file in merge_attributes_table ~db_file ;
Utils.without_gc ~f:do_merge ;
SqliteUtils.check_sqlite_error ~fatal:true SqliteUtils.check_sqlite_error ~fatal:true
~log:(Printf.sprintf "detaching database '%s'" db_file) ~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 merge_buck_flavors_results infer_deps_file =
let into = ResultsDir.get_database () in
let one_line line = let one_line line =
match String.split ~on:'\t' line with match String.split ~on:'\t' line with
| [_; _; target_results_dir] | [_; _; 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 Filename.dirname (Config.project_root ^/ "buck-out") ^/ target_results_dir
else target_results_dir else target_results_dir
in in
merge ~into (infer_out_src ^/ ResultsDir.database_filename) merge ~db_file:(infer_out_src ^/ ResultsDir.database_filename)
| _ | _
-> assert false -> assert false
in in

@ -10,4 +10,5 @@
open! IStd open! IStd
val merge_buck_flavors_results : string -> unit 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. *)

@ -16,8 +16,6 @@ let database_filename = "results.db"
let database_fullpath = Config.results_dir ^/ database_filename let database_fullpath = Config.results_dir ^/ database_filename
let attributes_table = "attributes"
let results_dir_dir_markers = let results_dir_dir_markers =
List.map ~f:(Filename.concat Config.results_dir) [Config.captured_dir_name; Config.specs_dir_name] 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 ) Utils.rmtree Config.results_dir )
let create_attributes_table db = 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" SqliteUtils.exec db ~log:"initializing results DB"
~stmt: ~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 create_db () =
let temp_db = Filename.temp_file ~in_dir:Config.results_dir database_filename ".tmp" in 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 reset_attributes_table () =
let db = get_database () in let db = get_database () in
SqliteUtils.exec db ~log:"drop attributes table" SqliteUtils.exec db ~log:"drop attributes table" ~stmt:"DROP TABLE attributes" ;
~stmt:(Printf.sprintf "DROP TABLE %s" attributes_table) ;
create_attributes_table db create_attributes_table db
let delete_capture_and_analysis_data () = let delete_capture_and_analysis_data () =
@ -126,3 +129,29 @@ let delete_capture_and_analysis_data () =
let canonicalize_db () = let canonicalize_db () =
let db = get_database () in let db = get_database () in
SqliteUtils.exec db ~log:"running VACUUM" ~stmt:"VACUUM" 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

@ -15,9 +15,6 @@ val database_filename : string
val get_database : unit -> Sqlite3.db 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]). *) (** 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 val reset_attributes_table : unit -> unit
(** zero out the attributes table *) (** zero out the attributes table *)
@ -39,6 +36,11 @@ val delete_capture_and_analysis_data : unit -> unit
val canonicalize_db : unit -> unit val canonicalize_db : unit -> unit
(** put the database on disk in deterministic form *) (** 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. *)

@ -54,6 +54,7 @@ let common_libraries =
(if java then ["javalib"; "ptrees"; "sawja"] else []) (if java then ["javalib"; "ptrees"; "sawja"] else [])
@ [ "ANSITerminal" @ [ "ANSITerminal"
; "atdgen" ; "atdgen"
; "base"
; "cmdliner" ; "cmdliner"
; "core" ; "core"
; "extlib" ; "extlib"

@ -10,7 +10,7 @@ ROOT_DIR = $(TESTS_DIR)/../..
ANALYZER = infer ANALYZER = infer
BUCK_TARGET = //src:hello @buck_target.txt 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 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) INFER_OPTIONS = --report-custom-error --developer-mode --project-root $(TESTS_DIR)
INFERPRINT_OPTIONS = --project-root $(TESTS_DIR) --issues-tests INFERPRINT_OPTIONS = --project-root $(TESTS_DIR) --issues-tests

@ -1,3 +1,5 @@
src/hello.c, test, 2, NULL_DEREFERENCE, [start of procedure test()] src/hello.c, test, 2, NULL_DEREFERENCE, [start of procedure test()]
src/hello2.c, test2, 2, NULL_DEREFERENCE, [start of procedure test2()] src/hello2.c, test2, 2, NULL_DEREFERENCE, [start of procedure test2()]
src/hello3.c, test3, 2, NULL_DEREFERENCE, [start of procedure test3()] 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]

@ -3,6 +3,10 @@ cxx_library(
srcs = [ srcs = [
'hello.c', 'hello2.c', 'hello.c', 'hello2.c',
], ],
deps = [
"//src/subtarget1:subtarget1",
"//src/subtarget2:subtarget2",
]
) )
cxx_library( cxx_library(

@ -0,0 +1,10 @@
cxx_library(
name = 'subtarget1',
visibility = [
'PUBLIC',
],
srcs = [
'z_filename_greater_than_subhello1.c',
'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 <stdlib.h>
void foo_defined_in_subtarget1() {
int* s = NULL;
*s = 42;
}

@ -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 <stdlib.h>
void foo_defined_in_subtarget1() {
int* t = NULL;
*t = 42;
}

@ -0,0 +1,9 @@
cxx_library(
name = 'subtarget2',
visibility = [
'PUBLIC',
],
srcs = [
'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 <stdlib.h>
void foo_defined_in_subtarget1();
void goo() {
foo_defined_in_subtarget1();
int* s = NULL;
*s = 42;
}
Loading…
Cancel
Save