@ -29,7 +29,9 @@ let container_write_string = "infer.dummy.__CONTAINERWRITE__"
(* return ( name of container, name of mutating function call ) pair *)
let get_container_write_desc sink =
let ( base_var , _ ) , access_list = fst ( ThreadSafetyDomain . TraceElem . kind sink ) in
match ThreadSafetyDomain . TraceElem . kind sink with
| Write ( ( base_var , _ ) , access_list )
-> (
let get_container_write_desc_ call_name container_name =
match
String . chop_prefix ( Typ . Fieldname . to_string call_name ) ~ prefix : container_write_string
@ -45,7 +47,10 @@ let get_container_write_desc sink =
| [ ( FieldAccess call_name ) ]
-> get_container_write_desc_ call_name ( F . asprintf " %a " Var . pp base_var )
| _
-> None
-> None )
| Read _
-> (* TODO: support Read *)
None
let is_container_write_sink sink = Option . is_some ( get_container_write_desc sink )
@ -259,7 +264,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let is_unprotected is_locked is_threaded 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
let add_access exp loc ~ is_write_ access 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
@ -282,9 +287,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| []
-> access_acc
| access :: access_list'
-> let kind =
if List . is_empty access_list' then access_kind else ThreadSafetyDomain . Access . Read
in
-> let is_write = if List . is_empty access_list' then is_write_access else false in
let access_path = ( fst prefix_path , snd prefix_path @ [ access ] ) in
let access_acc' =
if is_owned prefix_path attribute_map
@ -293,7 +296,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
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
let accesses' =
PathDomain . add_sink ( make_access access_path ~ is_write loc ) accesses
in
AccessDomain . add pre accesses' access_acc
in
add_field_accesses pre access_path access_acc' access_list'
@ -467,7 +472,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
( fst receiver_ap , snd receiver_ap @ [ AccessPath . FieldAccess dummy_fieldname ] )
in
AccessDomain . add_access ( Unprotected ( Some 0 ) )
( make_access dummy_access_ap Writ e callee_loc ) AccessDomain . empty
( make_access dummy_access_ap ~ is_write : tru e callee_loc ) AccessDomain . empty
in
(* TODO: for now all formals escape *)
(* we need a more intelligent escape analysis, that branches on whether
@ -505,7 +510,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
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 )
~ f : ( fun acc exp ->
add_access exp loc ~ is_write_access : false acc locks threads attribute_map proc_data )
exps ~ init : accesses
let add_escapees_from_exp rhs_exp extras escapees =
@ -726,7 +732,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
-> astate_callee )
| Assign ( lhs_access_path , rhs_exp , loc )
-> let rhs_accesses =
add_access rhs_exp loc Read astate . accesses astate . locks astate . threads
add_access rhs_exp loc ~ is_write_access : false astate . accesses astate . locks astate . threads
astate . attribute_map proc_data
in
let rhs_access_paths = HilExp . get_access_paths rhs_exp in
@ -743,8 +749,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
report spurious read / write races * )
rhs_accesses
else
add_access ( AccessPath lhs_access_path ) loc Write rhs_accesses astate . lock s
astate . threads astate . attribute_map proc_data
add_access ( AccessPath lhs_access_path ) loc ~ is_write_access : true rhs_accesse s
astate . locks astate . threads astate . attribute_map proc_data
in
let attribute_map =
propagate_attributes lhs_access_path rhs_exp astate . attribute_map extras
@ -794,8 +800,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
{ acc with threads }
in
let accesses =
add_access assume_exp loc Read astate . accesses astate . locks astate . threads
astate . attribute_map proc_data
add_access assume_exp loc ~ is_write_access : false astate . accesses astate . locks
astate . threads astate . attribute_map proc_data
in
let astate' =
match HilExp . get_access_paths assume_exp with
@ -993,11 +999,12 @@ let analyze_procedure {Callbacks.proc_desc; tenv; summary} =
else Summary . update_summary empty_post summary
module AccessListMap = Caml . Map . Make ( struct
type t = AccessPath . Raw . t
type t = AccessPath . Raw . t option
(* TODO -- keep this compare to satisfy the order of tests, consider using Raw.compare *)
let compare access_path1 access_path2 =
List . compare AccessPath . compare_access ( snd access_path1 ) ( snd access_path2 )
let compare =
Option . compare ( fun access_path1 access_path2 ->
List . compare AccessPath . compare_access ( snd access_path1 ) ( snd access_path2 ) )
end )
let get_current_class_and_threadsafe_superclasses tenv pname =
@ -1051,7 +1058,10 @@ let pp_access fmt sink =
| Some container_write_desc
-> pp_container_access fmt container_write_desc
| None
-> let access_path , _ = ThreadSafetyDomain . PathDomain . Sink . kind sink in
-> let access_path =
match ThreadSafetyDomain . PathDomain . Sink . kind sink
with Read access_path | Write access_path -> access_path
in
F . fprintf fmt " %a " ( MF . wrap_monospaced AccessPath . pp_access_list ) ( snd access_path )
let desc_of_sink sink =
@ -1216,19 +1226,19 @@ let report_unsafe_accesses aggregated_access_map =
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
( match TraceElem . kind access with
| Access . Write _
-> reported_writes
| Access . Read
| 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
match 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
| Access . Read _
-> let reported_reads = Typ . Procname . Set . add pname reported . reported_reads in
{ reported with reported_reads ; reported_sites }
in
@ -1236,8 +1246,8 @@ let report_unsafe_accesses aggregated_access_map =
let pname = Procdesc . get_proc_name pdesc in
if is_duplicate_report access pname reported_acc then reported_acc
else
match ( snd ( TraceElem . kind access ) , pre ) with
| Access . Write , AccessPrecondition . Unprotected _ -> (
match ( TraceElem . kind access , pre ) with
| Access . Write _ , AccessPrecondition . Unprotected _ -> (
match Procdesc . get_proc_name pdesc with
| Java _
-> if threaded then reported_acc
@ -1249,10 +1259,10 @@ let report_unsafe_accesses aggregated_access_map =
| _
-> (* Do not report unprotected writes for ObjC_Cpp *)
reported_acc )
| Access . Write , AccessPrecondition . Protected _
| Access . Write _ , AccessPrecondition . Protected _
-> (* protected write, do nothing *)
reported_acc
| Access . Read , AccessPrecondition . Unprotected _
| 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 =
@ -1276,7 +1286,7 @@ let report_unsafe_accesses aggregated_access_map =
~ conflicts : ( List . map ~ f : ( fun ( access , _ , _ , _ , _ ) -> access ) all_writes )
access ;
update_reported access pname reported_acc )
| Access . Read , AccessPrecondition . Protected excl
| 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
@ -1403,16 +1413,31 @@ let quotient_access_map acc_map =
let rec aux acc m =
if AccessListMap . is_empty m then acc
else
let k , vals = AccessListMap . choose m in
let k _opt , vals = AccessListMap . choose m in
let _ , _ , _ , tenv , _ =
List . find_exn vals ~ f : ( fun ( elem , _ , _ , _ , _ ) ->
AccessPath . Raw . equal k ( ThreadSafetyDomain . TraceElem . kind elem | > fst ) )
Option . equal
( fun e1 e2 -> AccessPath . Raw . equal e1 e2 )
k_opt
( ThreadSafetyDomain . Access . get_access_path ( ThreadSafetyDomain . TraceElem . kind elem ) )
)
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
let k_part , non_k_part =
AccessListMap . partition
( fun k_opt' _ ->
match ( k_opt' , k_opt ) with
| Some k' , Some k
-> may_alias tenv k k'
| None , None
-> true
| _
-> false )
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
let new_acc = AccessListMap . add k _opt k_accesses acc in
aux new_acc non_k_part
in
aux AccessListMap . empty acc_map
@ -1439,14 +1464,14 @@ let make_results_table file_env =
( 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
let access_path _opt = Access . get_access_path ( TraceElem . kind access ) in
if Option . exists ~ f : should_filter_access access_path _opt then acc
else
let grouped_accesses =
try AccessListMap . find access_path acc
try AccessListMap . find access_path _opt acc
with Not_found -> []
in
AccessListMap . add access_path
AccessListMap . add access_path _opt
( ( access , pre , threaded , tenv , pdesc ) :: grouped_accesses ) acc )
( PathDomain . sinks accesses ) acc )
accesses acc