@ -1179,7 +1179,8 @@ let report_thread_safety_violation tenv pdesc ~make_description ~report_kind acc
let exn =
let exn =
Exceptions . Checkers ( issue_type . IssueType . unique_id , Localise . verbatim_desc error_message )
Exceptions . Checkers ( issue_type . IssueType . unique_id , Localise . verbatim_desc error_message )
in
in
Reporting . log_error_deprecated ~ store_summary : true pname ~ loc ~ ltr exn
let access = B64 . encode ( Marshal . to_string ( pname , access ) [] ) in
Reporting . log_error_deprecated ~ store_summary : true pname ~ loc ~ ltr ~ access exn
in
in
let trace_of_pname = trace_of_pname access pdesc 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 )
Option . iter ~ f : report_one_path ( PathDomain . get_reportable_sink_path access ~ trace_of_pname )
@ -1244,22 +1245,6 @@ let make_read_write_race_description ~read_is_sync conflicts pname final_sink_si
pp_access final_sink conflicts_description
pp_access final_sink conflicts_description
(* * 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 }
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 }
(* return true if procedure is at an abstraction boundary or reporting has been explicitly
(* return true if procedure is at an abstraction boundary or reporting has been explicitly
requested via @ ThreadSafe * )
requested via @ ThreadSafe * )
let should_report_on_proc proc_desc tenv =
let should_report_on_proc proc_desc tenv =
@ -1308,154 +1293,112 @@ let report_unsafe_accesses
list
list
AccessListMap . t ) =
AccessListMap . t ) =
let open RacerDDomain in
let open RacerDDomain in
let is_duplicate_report access pname { reported_sites ; reported_writes ; reported_reads } =
let report_unsafe_access ( access , pre , thread , tenv , pdesc ) accesses =
if Config . filtering then CallSite . Set . mem ( TraceElem . call_site access ) reported_sites
| |
match TraceElem . kind access with
| Access . Write _ | Access . ContainerWrite _ ->
Typ . Procname . Set . mem pname reported_writes
| Access . Read _ | Access . ContainerRead _ ->
Typ . Procname . Set . mem pname reported_reads
| Access . InterfaceCall _ ->
false
else false
in
let update_reported access pname reported =
if Config . filtering then
let reported_sites = CallSite . Set . add ( TraceElem . call_site access ) reported . reported_sites in
match TraceElem . kind access with
| Access . Write _ | Access . ContainerWrite _ ->
let reported_writes = Typ . Procname . Set . add pname reported . reported_writes in
{ reported with reported_writes ; reported_sites }
| Access . Read _ | Access . ContainerRead _ ->
let reported_reads = Typ . Procname . Set . add pname reported . reported_reads in
{ reported with reported_reads ; reported_sites }
| Access . InterfaceCall _ ->
reported
else reported
in
let report_unsafe_access ( access , pre , thread , tenv , pdesc ) accesses reported_acc =
let pname = Procdesc . get_proc_name pdesc in
let pname = Procdesc . get_proc_name pdesc in
if is_duplicate_report access pname reported_acc then reported_acc
match ( TraceElem . kind access , pre ) with
else
| ( Access . InterfaceCall unannoted_call_pname
match ( TraceElem . kind access , pre ) with
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) ->
| ( Access . InterfaceCall unannoted_call_pname
if ThreadsDomain . is_any thread && is_marked_thread_safe pdesc tenv then
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) ->
(* un-annotated interface call + no lock in method marked thread-safe. warn *)
if ThreadsDomain . is_any thread && is_marked_thread_safe pdesc tenv then (
report_unannotated_interface_violation tenv pdesc access thread unannoted_call_pname
(* un-annotated interface call + no lock in method marked thread-safe. warn *)
| Access . InterfaceCall _ , AccessPrecondition . Protected _ ->
report_unannotated_interface_violation tenv pdesc access thread unannoted_call_pname ;
(* un-annotated interface call, but it's protected by a lock/thread. don't report *)
update_reported access pname reported_acc )
()
else reported_acc
| ( ( Access . Write _ | ContainerWrite _ )
| Access . InterfaceCall _ , AccessPrecondition . Protected _ ->
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) -> (
(* un-annotated interface call, but it's protected by a lock/thread. don't report *)
match Procdesc . get_proc_name pdesc with
reported_acc
| Java _ ->
| ( ( Access . Write _ | ContainerWrite _ )
let writes_on_background_thread =
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) -> (
if ThreadsDomain . is_any thread then
match Procdesc . get_proc_name pdesc with
(* unprotected write in method that may run in parallel with itself. warn *)
| Java _ ->
[]
let writes_on_background_thread =
else
if ThreadsDomain . is_any thread then
(* unprotected write, but not on a method that may run in parallel with itself
(* unprotected write in method that may run in parallel with itself. warn *)
[]
else
(* unprotected write, but not on a method that may run in parallel with itself
( i . e . , not a self race ) . find accesses on a background thread this access might
( i . e . , not a self race ) . find accesses on a background thread this access might
conflict with and report them * )
conflict with and report them * )
List . filter_map
List . filter_map
~ f : ( fun ( other_access , _ , other_thread , _ , _ ) ->
~ f : ( fun ( other_access , _ , other_thread , _ , _ ) ->
if TraceElem . is_write other_access && ThreadsDomain . is_any other_thread then
if TraceElem . is_write other_access && ThreadsDomain . is_any other_thread then
Some other_access
Some other_access
else None )
else None )
accesses
accesses
in
if List . is_empty writes_on_background_thread && not ( ThreadsDomain . is_any thread ) then
reported_acc
else (
report_thread_safety_violation tenv pdesc
~ make_description : make_unprotected_write_description
~ report_kind : ( WriteWriteRace writes_on_background_thread ) access thread ;
update_reported access pname reported_acc )
| _ ->
(* Do not report unprotected writes when an access can't run in parallel with itself, or
for ObjC_Cpp * )
reported_acc )
| ( Access . Write _ | ContainerWrite _ ) , AccessPrecondition . Protected _ ->
(* protected write, do nothing *)
reported_acc
| ( ( Access . Read _ | ContainerRead _ )
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) ->
(* 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 _ | TotallyUnprotected ->
Typ . Procname . is_java pname
| AccessPrecondition . Protected _ ->
true
in
let is_conflict other_access pre other_thread =
TraceElem . is_write other_access
&&
if Typ . Procname . is_java pname then ThreadsDomain . is_any thread
| | ThreadsDomain . is_any other_thread
else is_cpp_protected_write pre
in
in
let all_writes =
if not ( List . is_empty writes_on_background_thread && not ( ThreadsDomain . is_any thread ) )
List . filter
then
~ f : ( fun ( other_access , pre , other_thread , _ , _ ) ->
is_conflict other_access pre other_thread )
accesses
in
if List . is_empty all_writes then reported_acc
else (
report_thread_safety_violation tenv pdesc
report_thread_safety_violation tenv pdesc
~ make_description : ( make_read_write_race_description ~ read_is_sync : false all_writes )
~ make_description : make_unprotected_write_description
~ report_kind :
~ report_kind : ( WriteWriteRace writes_on_background_thread ) access thread
( ReadWriteRace ( List . map ~ f : ( fun ( access , _ , _ , _ , _ ) -> access ) all_writes ) )
| _ ->
access thread ;
(* Do not report unprotected writes when an access can't run in parallel with itself, or
update_reported access pname reported_acc )
for ObjC_Cpp * )
| ( Access . Read _ | ContainerRead _ ) , AccessPrecondition . Protected excl ->
() )
(* protected read. report unprotected writes and opposite protected writes as conflicts
| ( Access . Write _ | ContainerWrite _ ) , AccessPrecondition . Protected _ ->
(* protected write, do nothing *)
()
| ( ( Access . Read _ | ContainerRead _ )
, ( AccessPrecondition . Unprotected _ | AccessPrecondition . TotallyUnprotected ) ) ->
(* 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 _ | TotallyUnprotected ->
Typ . Procname . is_java pname
| AccessPrecondition . Protected _ ->
true
in
let is_conflict other_access pre other_thread =
TraceElem . is_write other_access
&&
if Typ . Procname . is_java pname then ThreadsDomain . is_any thread
| | ThreadsDomain . is_any other_thread
else is_cpp_protected_write pre
in
let all_writes =
List . filter
~ f : ( fun ( other_access , pre , other_thread , _ , _ ) ->
is_conflict other_access pre other_thread )
accesses
in
if not ( List . is_empty all_writes ) then
report_thread_safety_violation tenv pdesc
~ make_description : ( make_read_write_race_description ~ read_is_sync : false all_writes )
~ report_kind :
( ReadWriteRace ( List . map ~ f : ( fun ( access , _ , _ , _ , _ ) -> access ) all_writes ) )
access thread
| ( Access . Read _ | ContainerRead _ ) , 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 * )
Thread and Lock are opposites of one another , and Both has no opposite * )
let is_opposite = function
let is_opposite = function
| Excluder . Lock , Excluder . Thread ->
| Excluder . Lock , Excluder . Thread ->
true
true
| Excluder . Thread , Excluder . Lock ->
| Excluder . Thread , Excluder . Lock ->
true
true
| _ , _ ->
| _ , _ ->
false
false
in
in
let conflicting_writes =
let conflicting_writes =
List . filter
List . filter
~ f : ( fun ( access , pre , other_thread , _ , _ ) ->
~ f : ( fun ( access , pre , other_thread , _ , _ ) ->
match pre with
match pre with
| AccessPrecondition . Unprotected _ ->
| AccessPrecondition . Unprotected _ ->
TraceElem . is_write access && ThreadsDomain . is_any other_thread
TraceElem . is_write access && ThreadsDomain . is_any other_thread
| AccessPrecondition . Protected other_excl when is_opposite ( excl , other_excl ) ->
| AccessPrecondition . Protected other_excl when is_opposite ( excl , other_excl ) ->
TraceElem . is_write access
TraceElem . is_write access
| _ ->
| _ ->
false )
false )
accesses
accesses
in
in
if List . is_empty conflicting_writes then reported_acc
if not ( List . is_empty conflicting_writes ) then
else (
(* protected read with conflicting unprotected write ( s ) . warn. *)
(* protected read with conflicting unprotected write ( s ) . warn. *)
report_thread_safety_violation tenv pdesc
report_thread_safety_violation tenv pdesc
~ make_description :
~ make_description :
( make_read_write_race_description ~ read_is_sync : true conflicting_writes )
( make_read_write_race_description ~ read_is_sync : true conflicting_writes )
~ report_kind :
~ report_kind :
( ReadWriteRace ( List . map ~ f : ( fun ( access , _ , _ , _ , _ ) -> access ) conflicting_writes ) )
( ReadWriteRace
access thread
( List . map ~ f : ( fun ( access , _ , _ , _ , _ ) -> access ) conflicting_writes ) )
access thread ;
update_reported access pname reported_acc )
in
in
AccessListMap . fold
AccessListMap . iter
( fun _ grouped_accesses reported_acc ->
( fun _ grouped_accesses ->
(* 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 class_has_mutex_member objc_cpp tenv =
let class_has_mutex_member objc_cpp tenv =
let class_name = Typ . Procname . objc_cpp_get_class_type_name objc_cpp in
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
let matcher = QualifiedCppName . Match . of_fuzzy_qual_names [ " std::mutex " ] in
@ -1487,10 +1430,10 @@ let report_unsafe_accesses
let reportable_accesses =
let reportable_accesses =
List . filter ~ f : ( fun ( _ , _ , _ , tenv , pdesc ) -> should_report pdesc tenv ) grouped_accesses
List . filter ~ f : ( fun ( _ , _ , _ , tenv , pdesc ) -> should_report pdesc tenv ) grouped_accesses
in
in
List . fold
List . iter
~ f : ( fun acc acc ess -> report_unsafe_access access reportable_accesses acc )
~ f : ( fun acc ess -> report_unsafe_access access reportable_accesses )
reportable_accesses ~ init : reported )
reportable_accesses )
aggregated_access_map empty_reported
aggregated_access_map
| > ignore
| > ignore