|
|
@ -51,8 +51,17 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
| Unlock
|
|
|
|
| Unlock
|
|
|
|
| NoEffect
|
|
|
|
| NoEffect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let is_thread_utils_method method_name_str = function
|
|
|
|
|
|
|
|
| Typ.Procname.Java java_pname ->
|
|
|
|
|
|
|
|
String.is_suffix ~suffix:"ThreadUtils" (Typ.Procname.java_get_class_name java_pname)
|
|
|
|
|
|
|
|
&& String.equal (Typ.Procname.java_get_method java_pname) method_name_str
|
|
|
|
|
|
|
|
| _ -> false
|
|
|
|
|
|
|
|
|
|
|
|
let get_lock_model = function
|
|
|
|
let get_lock_model = function
|
|
|
|
| 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
|
|
|
|
begin
|
|
|
|
match Typ.Procname.java_get_class_name java_pname, Typ.Procname.java_get_method java_pname with
|
|
|
|
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.Lock"
|
|
|
@ -355,7 +364,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
AccessDomain.empty
|
|
|
|
AccessDomain.empty
|
|
|
|
| None ->
|
|
|
|
| None ->
|
|
|
|
AccessDomain.empty in
|
|
|
|
AccessDomain.empty in
|
|
|
|
Some (false, callee_accesses, AttributeSetDomain.empty)
|
|
|
|
Some (false, false, callee_accesses, AttributeSetDomain.empty)
|
|
|
|
| _ ->
|
|
|
|
| _ ->
|
|
|
|
failwithf
|
|
|
|
failwithf
|
|
|
|
"Call to %a is marked as a container write, but has no receiver"
|
|
|
|
"Call to %a is marked as a container write, but has no receiver"
|
|
|
@ -414,6 +423,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
| Sil.Call (ret_opt, Const (Cfun callee_pname), actuals, loc, _) ->
|
|
|
|
| Sil.Call (ret_opt, Const (Cfun callee_pname), actuals, loc, _) ->
|
|
|
|
let astate_callee =
|
|
|
|
let astate_callee =
|
|
|
|
(* assuming that modeled procedures do not have useful summaries *)
|
|
|
|
(* assuming that modeled procedures do not have useful summaries *)
|
|
|
|
|
|
|
|
if is_thread_utils_method "assertMainThread" callee_pname then
|
|
|
|
|
|
|
|
{ astate with threads = true; }
|
|
|
|
|
|
|
|
else
|
|
|
|
match get_lock_model callee_pname with
|
|
|
|
match get_lock_model callee_pname with
|
|
|
|
| Lock ->
|
|
|
|
| Lock ->
|
|
|
|
{ astate with locks = true; }
|
|
|
|
{ astate with locks = true; }
|
|
|
@ -422,7 +434,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
| NoEffect ->
|
|
|
|
| NoEffect ->
|
|
|
|
match
|
|
|
|
match
|
|
|
|
get_summary pdesc callee_pname actuals ~f_resolve_id loc tenv with
|
|
|
|
get_summary pdesc callee_pname actuals ~f_resolve_id loc tenv with
|
|
|
|
| Some (callee_locks, callee_accesses, return_attributes) ->
|
|
|
|
| Some ( callee_threads, callee_locks, callee_accesses, return_attributes) ->
|
|
|
|
let call_site = CallSite.make callee_pname loc in
|
|
|
|
let call_site = CallSite.make callee_pname loc in
|
|
|
|
let combine_accesses_for_pre pre ~caller_accesses ~callee_accesses =
|
|
|
|
let combine_accesses_for_pre pre ~caller_accesses ~callee_accesses =
|
|
|
|
let combined_accesses =
|
|
|
|
let combined_accesses =
|
|
|
@ -436,6 +448,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
~caller_accesses:astate.accesses
|
|
|
|
~caller_accesses:astate.accesses
|
|
|
|
~callee_accesses in
|
|
|
|
~callee_accesses in
|
|
|
|
let locks' = callee_locks || astate.locks in
|
|
|
|
let locks' = callee_locks || astate.locks in
|
|
|
|
|
|
|
|
let threads' = callee_threads || astate.threads in
|
|
|
|
let astate' =
|
|
|
|
let astate' =
|
|
|
|
if is_unprotected locks' pdesc
|
|
|
|
if is_unprotected locks' pdesc
|
|
|
|
then
|
|
|
|
then
|
|
|
@ -519,7 +532,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
|
|
astate.attribute_map
|
|
|
|
astate.attribute_map
|
|
|
|
~f_resolve_id
|
|
|
|
~f_resolve_id
|
|
|
|
extras in
|
|
|
|
extras in
|
|
|
|
{ astate' with locks = locks'; attribute_map; }
|
|
|
|
{ astate' with locks = locks'; threads = threads'; attribute_map; }
|
|
|
|
| None ->
|
|
|
|
| None ->
|
|
|
|
if is_box callee_pname
|
|
|
|
if is_box callee_pname
|
|
|
|
then
|
|
|
|
then
|
|
|
@ -751,8 +764,9 @@ let analyze_procedure callback =
|
|
|
|
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
|
|
|
|
let open ThreadSafetyDomain in
|
|
|
|
let has_lock = false in
|
|
|
|
let has_lock = false in
|
|
|
|
|
|
|
|
let known_on_ui_thread = false in
|
|
|
|
let return_attrs = AttributeSetDomain.empty in
|
|
|
|
let return_attrs = AttributeSetDomain.empty in
|
|
|
|
let empty = has_lock, AccessDomain.empty, return_attrs in
|
|
|
|
let empty = known_on_ui_thread, has_lock, AccessDomain.empty, return_attrs in
|
|
|
|
(* convert the abstract state to a summary by dropping the id map *)
|
|
|
|
(* convert the abstract state to a summary by dropping the id map *)
|
|
|
|
let compute_post ({ ProcData.pdesc; tenv; extras; } as proc_data) =
|
|
|
|
let compute_post ({ ProcData.pdesc; tenv; extras; } as proc_data) =
|
|
|
|
if should_analyze_proc pdesc tenv
|
|
|
|
if should_analyze_proc pdesc tenv
|
|
|
@ -785,7 +799,7 @@ let analyze_procedure callback =
|
|
|
|
ThreadSafetyDomain.empty in
|
|
|
|
ThreadSafetyDomain.empty in
|
|
|
|
|
|
|
|
|
|
|
|
match Analyzer.compute_post proc_data ~initial with
|
|
|
|
match Analyzer.compute_post proc_data ~initial with
|
|
|
|
| Some { locks; accesses; attribute_map; } ->
|
|
|
|
| Some { threads; locks; accesses; attribute_map; } ->
|
|
|
|
let return_var_ap =
|
|
|
|
let return_var_ap =
|
|
|
|
AccessPath.of_pvar
|
|
|
|
AccessPath.of_pvar
|
|
|
|
(Pvar.get_ret_pvar (Procdesc.get_proc_name pdesc))
|
|
|
|
(Pvar.get_ret_pvar (Procdesc.get_proc_name pdesc))
|
|
|
@ -793,7 +807,7 @@ let analyze_procedure callback =
|
|
|
|
let return_attributes =
|
|
|
|
let return_attributes =
|
|
|
|
try AttributeMapDomain.find return_var_ap attribute_map
|
|
|
|
try AttributeMapDomain.find return_var_ap attribute_map
|
|
|
|
with Not_found -> AttributeSetDomain.empty in
|
|
|
|
with Not_found -> AttributeSetDomain.empty in
|
|
|
|
Some (locks, accesses, return_attributes)
|
|
|
|
Some (threads, locks, accesses, return_attributes)
|
|
|
|
| None ->
|
|
|
|
| None ->
|
|
|
|
None
|
|
|
|
None
|
|
|
|
end
|
|
|
|
end
|
|
|
@ -817,7 +831,10 @@ let make_results_table get_proc_desc file_env =
|
|
|
|
(* make a Map sending each element e of list l to (f e) *)
|
|
|
|
(* make a Map sending each element e of list l to (f e) *)
|
|
|
|
let map_post_computation_over_procs f l =
|
|
|
|
let map_post_computation_over_procs f l =
|
|
|
|
List.fold
|
|
|
|
List.fold
|
|
|
|
~f:(fun m p -> ResultsTableType.add p (f p) m)
|
|
|
|
~f:(fun m p -> match f p with
|
|
|
|
|
|
|
|
| (true,_,_,_) -> m (* Don't put a post there *)
|
|
|
|
|
|
|
|
| _ -> ResultsTableType.add p (f p) m
|
|
|
|
|
|
|
|
)
|
|
|
|
~init:ResultsTableType.empty
|
|
|
|
~init:ResultsTableType.empty
|
|
|
|
l in
|
|
|
|
l in
|
|
|
|
let compute_post_for_procedure = (* takes proc_env as arg *)
|
|
|
|
let compute_post_for_procedure = (* takes proc_env as arg *)
|
|
|
@ -918,7 +935,7 @@ let filter_conflicting_sinks sink trace =
|
|
|
|
let collect_conflicting_writes sink tab =
|
|
|
|
let collect_conflicting_writes sink tab =
|
|
|
|
let procs_and_writes =
|
|
|
|
let procs_and_writes =
|
|
|
|
List.map
|
|
|
|
List.map
|
|
|
|
~f:(fun (key, (_, accesses, _)) ->
|
|
|
|
~f:(fun (key, (_, _, accesses, _)) ->
|
|
|
|
let conflicting_writes =
|
|
|
|
let conflicting_writes =
|
|
|
|
filter_conflicting_sinks sink (get_all_accesses Write accesses) in
|
|
|
|
filter_conflicting_sinks sink (get_all_accesses Write accesses) in
|
|
|
|
key, conflicting_writes
|
|
|
|
key, conflicting_writes
|
|
|
@ -989,7 +1006,7 @@ let report_thread_safety_violations ( _, tenv, pname, pdesc) make_description tr
|
|
|
|
let open ThreadSafetyDomain in
|
|
|
|
let open ThreadSafetyDomain in
|
|
|
|
let trace_of_pname callee_pname =
|
|
|
|
let trace_of_pname callee_pname =
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
match Summary.read_summary pdesc callee_pname with
|
|
|
|
| Some (_, accesses, _) -> get_possibly_unsafe_writes accesses
|
|
|
|
| Some (_, _, accesses, _) -> get_possibly_unsafe_writes accesses
|
|
|
|
| _ -> PathDomain.empty in
|
|
|
|
| _ -> PathDomain.empty in
|
|
|
|
let report_one_path ((_, sinks) as path) =
|
|
|
|
let report_one_path ((_, sinks) as path) =
|
|
|
|
let initial_sink, _ = List.last_exn sinks in
|
|
|
|
let initial_sink, _ = List.last_exn sinks in
|
|
|
@ -1104,7 +1121,7 @@ let process_results_table file_env tab =
|
|
|
|
(should_report_on_all_procs || is_thread_safe_method pdesc tenv)
|
|
|
|
(should_report_on_all_procs || is_thread_safe_method pdesc tenv)
|
|
|
|
&& should_report_on_proc proc_env in
|
|
|
|
&& should_report_on_proc proc_env in
|
|
|
|
ResultsTableType.iter (* report errors for each method *)
|
|
|
|
ResultsTableType.iter (* report errors for each method *)
|
|
|
|
(fun proc_env (_, accesses, _) ->
|
|
|
|
(fun proc_env (_, _, accesses, _) ->
|
|
|
|
if should_report proc_env
|
|
|
|
if should_report proc_env
|
|
|
|
then
|
|
|
|
then
|
|
|
|
let open ThreadSafetyDomain in
|
|
|
|
let open ThreadSafetyDomain in
|
|
|
|