[starvation] warn on SettableFuture.set under lock

Summary:
`SettableFuture.set` invokes callbacks registered prior to the call, which may also try to acquire extra locks.  If the called of `set` already holds a lock this creates lock dependencies which may lead to deadlocks.

Here we warn whenever `set` is called under a lock taken in a different source file. This avoids reporting when a class internally manages locks and calls `set`, reasoning that developers will be aware this is happening.

Reviewed By: jvillard

Differential Revision: D25562190

fbshipit-source-id: d1b5cb69c
master
Nikos Gorogiannis 4 years ago committed by Facebook GitHub Bot
parent f6f47cd7c9
commit 7f6798999a

@ -0,0 +1,24 @@
A call which may execute arbitrary code (such as registered, or chained, callbacks) is made while a lock is held.
This code may deadlock whenever the callbacks obtain locks themselves, so it is an unsafe pattern.
This warning is issued only at the innermost lock acquisition around the final call.
Example:
```java
public class NotUnderLock {
SettableFuture future = null;
public void callFutureSetOk() {
future.set(null);
}
public synchronized void firstAcquisitionBad() {
callFutureSetOk();
}
public void secondAcquisitionOk(Object o) {
synchronized (o) {
firstAcquisitionBad();
}
}
}
```

@ -382,7 +382,8 @@ OPTIONS
disabling issue types does not make the corresponding checker not disabling issue types does not make the corresponding checker not
run. Available issue types are as follows: run. Available issue types are as follows:
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default), ARBITRARY_CODE_EXECUTION_UNDER_LOCK (enabled by default),
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L2 (disabled by default), ARRAY_OUT_OF_BOUNDS_L2 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L3 (disabled by default), ARRAY_OUT_OF_BOUNDS_L3 (disabled by default),
ASSIGN_POINTER_WARNING (enabled by default), ASSIGN_POINTER_WARNING (enabled by default),

@ -85,7 +85,8 @@ OPTIONS
disabling issue types does not make the corresponding checker not disabling issue types does not make the corresponding checker not
run. Available issue types are as follows: run. Available issue types are as follows:
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default), ARBITRARY_CODE_EXECUTION_UNDER_LOCK (enabled by default),
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L2 (disabled by default), ARRAY_OUT_OF_BOUNDS_L2 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L3 (disabled by default), ARRAY_OUT_OF_BOUNDS_L3 (disabled by default),
ASSIGN_POINTER_WARNING (enabled by default), ASSIGN_POINTER_WARNING (enabled by default),

@ -382,7 +382,8 @@ OPTIONS
disabling issue types does not make the corresponding checker not disabling issue types does not make the corresponding checker not
run. Available issue types are as follows: run. Available issue types are as follows:
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default), ARBITRARY_CODE_EXECUTION_UNDER_LOCK (enabled by default),
ARRAY_OUT_OF_BOUNDS_L1 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L2 (disabled by default), ARRAY_OUT_OF_BOUNDS_L2 (disabled by default),
ARRAY_OUT_OF_BOUNDS_L3 (disabled by default), ARRAY_OUT_OF_BOUNDS_L3 (disabled by default),
ASSIGN_POINTER_WARNING (enabled by default), ASSIGN_POINTER_WARNING (enabled by default),

@ -268,6 +268,12 @@ let abduction_case_not_implemented =
register_hidden ~id:"Abduction_case_not_implemented" Error Biabduction register_hidden ~id:"Abduction_case_not_implemented" Error Biabduction
let arbitrary_code_execution_under_lock =
register ~id:"ARBITRARY_CODE_EXECUTION_UNDER_LOCK" ~hum:"Arbitrary Code Execution Under lock"
Error Starvation
~user_documentation:[%blob "../../documentation/issues/ARBITRARY_CODE_EXECUTION_UNDER_LOCK.md"]
let array_of_pointsto = register_hidden ~id:"Array_of_pointsto" Error Biabduction let array_of_pointsto = register_hidden ~id:"Array_of_pointsto" Error Biabduction
let array_out_of_bounds_l1 = let array_out_of_bounds_l1 =

@ -69,6 +69,8 @@ val set_enabled : t -> bool -> unit
val abduction_case_not_implemented : t val abduction_case_not_implemented : t
val arbitrary_code_execution_under_lock : t
val array_of_pointsto : t val array_of_pointsto : t
val array_out_of_bounds_l1 : t val array_out_of_bounds_l1 : t

@ -416,3 +416,9 @@ let is_java_main_method (pname : Procname.t) =
&& String.equal "main" (Procname.get_method pname) && String.equal "main" (Procname.get_method pname)
&& Typ.equal StdTyp.void (Procname.Java.get_return_typ java_pname) && Typ.equal StdTyp.void (Procname.Java.get_return_typ java_pname)
&& check_main_args (Procname.Java.get_parameters java_pname) && check_main_args (Procname.Java.get_parameters java_pname)
let may_execute_arbitrary_code =
let open MethodMatcher in
of_record
{default with classname= "com.google.common.util.concurrent.SettableFuture"; methods= ["set"]}

@ -79,3 +79,6 @@ val is_assume_true : Tenv.t -> Procname.t -> HilExp.t list -> bool
val is_java_main_method : Procname.t -> bool val is_java_main_method : Procname.t -> bool
(** does the method look like a Java [main] *) (** does the method look like a Java [main] *)
val may_execute_arbitrary_code : Tenv.t -> Procname.t -> HilExp.t list -> bool
(** for example [com.google.common.util.concurrent.SettableFuture.set] *)

@ -206,6 +206,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
List.hd actuals |> Option.map ~f:(fun exp -> do_assume exp astate) List.hd actuals |> Option.map ~f:(fun exp -> do_assume exp astate)
else None else None
in in
let treat_arbitrary_code_exec () =
if StarvationModels.may_execute_arbitrary_code tenv callee actuals then
StarvationDomain.arbitrary_code_execution ~callee ~loc astate |> Option.some
else None
in
(* constructor calls are special-cased because they side-effect the receiver and do not (* constructor calls are special-cased because they side-effect the receiver and do not
return anything *) return anything *)
let treat_modeled_summaries () = let treat_modeled_summaries () =
@ -221,7 +226,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
Domain.integrate_summary ~tenv ~lhs ~subst callsite astate summary ) Domain.integrate_summary ~tenv ~lhs ~subst callsite astate summary )
in in
IList.eval_until_first_some IList.eval_until_first_some
[treat_handler_constructor; treat_thread_constructor; treat_assume; treat_modeled_summaries] [ treat_handler_constructor
; treat_thread_constructor
; treat_assume
; treat_arbitrary_code_exec
; treat_modeled_summaries ]
|> Option.value ~default:astate |> Option.value ~default:astate
@ -458,6 +467,8 @@ module ReportMap : sig
val add_lockless_violation : report_add_t val add_lockless_violation : report_add_t
val add_arbitrary_code_execution_under_lock : report_add_t
val issue_log_of : t -> IssueLog.t val issue_log_of : t -> IssueLog.t
val store_multi_file : t -> unit val store_multi_file : t -> unit
@ -469,6 +480,7 @@ end = struct
| Deadlock of int | Deadlock of int
| StrictModeViolation of int | StrictModeViolation of int
| LocklessViolation of int | LocklessViolation of int
| ArbitraryCodeExecutionUnderLock of int
let issue_type_of_problem = function let issue_type_of_problem = function
| Deadlock _ -> | Deadlock _ ->
@ -479,6 +491,8 @@ end = struct
IssueType.strict_mode_violation IssueType.strict_mode_violation
| LocklessViolation _ -> | LocklessViolation _ ->
IssueType.lockless_violation IssueType.lockless_violation
| ArbitraryCodeExecutionUnderLock _ ->
IssueType.arbitrary_code_execution_under_lock
type report_t = {problem: problem; pname: Procname.t; ltr: Errlog.loc_trace; message: string} type report_t = {problem: problem; pname: Procname.t; ltr: Errlog.loc_trace; message: string}
@ -522,6 +536,14 @@ end = struct
add tenv pattrs loc report map add tenv pattrs loc report map
let add_arbitrary_code_execution_under_lock tenv pattrs loc ltr message (map : t) =
let pname = ProcAttributes.get_proc_name pattrs in
let report =
{problem= ArbitraryCodeExecutionUnderLock (-List.length ltr); pname; ltr; message}
in
add tenv pattrs loc report map
let issue_log_of loc_map = let issue_log_of loc_map =
let log_report ~issue_log loc {problem; pname; ltr; message} = let log_report ~issue_log loc {problem; pname; ltr; message} =
let issue_type = issue_type_of_problem problem in let issue_type = issue_type_of_problem problem in
@ -565,6 +587,12 @@ end = struct
| _ -> | _ ->
None None
in in
let filter_arbitrary_code_execution_under_lock = function
| {problem= ArbitraryCodeExecutionUnderLock l} as r ->
Some (l, r)
| _ ->
None
in
let compare_reports weight_compare (w, r) (w', r') = let compare_reports weight_compare (w, r) (w', r') =
match weight_compare w w' with 0 -> String.compare r.message r'.message | result -> result match weight_compare w w' with 0 -> String.compare r.message r'.message | result -> result
in in
@ -573,10 +601,14 @@ end = struct
let starvations = List.filter_map problems ~f:filter_map_starvation in let starvations = List.filter_map problems ~f:filter_map_starvation in
let strict_mode_violations = List.filter_map problems ~f:filter_map_strict_mode_violation in let strict_mode_violations = List.filter_map problems ~f:filter_map_strict_mode_violation in
let lockless_violations = List.filter_map problems ~f:filter_map_lockless_violation in let lockless_violations = List.filter_map problems ~f:filter_map_lockless_violation in
let arbitrary_code_executions_under_lock =
List.filter_map problems ~f:filter_arbitrary_code_execution_under_lock
in
log_reports (compare_reports Int.compare) loc deadlocks issue_log log_reports (compare_reports Int.compare) loc deadlocks issue_log
|> log_reports (compare_reports Int.compare) loc lockless_violations |> log_reports (compare_reports Int.compare) loc lockless_violations
|> log_reports (compare_reports StarvationModels.compare_severity) loc starvations |> log_reports (compare_reports StarvationModels.compare_severity) loc starvations
|> log_reports (compare_reports Int.compare) loc strict_mode_violations |> log_reports (compare_reports Int.compare) loc strict_mode_violations
|> log_reports (compare_reports Int.compare) loc arbitrary_code_executions_under_lock
in in
Location.Map.fold log_location loc_map IssueLog.empty Location.Map.fold log_location loc_map IssueLog.empty
@ -601,8 +633,8 @@ let should_report_deadlock_on_current_proc current_elem endpoint_elem =
(not Config.deduplicate) (not Config.deduplicate)
|| ||
match (endpoint_elem.CriticalPair.elem.event, current_elem.CriticalPair.elem.event) with match (endpoint_elem.CriticalPair.elem.event, current_elem.CriticalPair.elem.event) with
| _, (MayBlock _ | StrictModeCall _ | MonitorWait _) | _, (StrictModeCall _ | MayBlock _ | MonitorWait _ | MustNotOccurUnderLock _)
| (MayBlock _ | StrictModeCall _ | MonitorWait _), _ -> | (StrictModeCall _ | MayBlock _ | MonitorWait _ | MustNotOccurUnderLock _), _ ->
(* should never happen *) (* should never happen *)
L.die InternalError "Deadlock cannot occur without two lock events: %a" CriticalPair.pp L.die InternalError "Deadlock cannot occur without two lock events: %a" CriticalPair.pp
current_elem current_elem
@ -619,8 +651,6 @@ let should_report_deadlock_on_current_proc current_elem endpoint_elem =
let should_report attrs = let should_report attrs =
(not (PredSymb.equal_access (ProcAttributes.get_access attrs) Private))
&&
match ProcAttributes.get_proc_name attrs with match ProcAttributes.get_proc_name attrs with
| Procname.Java java_pname -> | Procname.Java java_pname ->
(not (Procname.Java.is_autogen_method java_pname)) (not (Procname.Java.is_autogen_method java_pname))
@ -648,6 +678,8 @@ let fold_reportable_summaries analyze_ondemand tenv clazz ~init ~f =
List.fold methods ~init ~f List.fold methods ~init ~f
let is_private attrs = PredSymb.equal_access (ProcAttributes.get_access attrs) Private
(* Note about how many times we report a deadlock: normally twice, at each trace starting point. (* Note about how many times we report a deadlock: normally twice, at each trace starting point.
Due to the fact we look for deadlocks in the summaries of the class at the root of a path, Due to the fact we look for deadlocks in the summaries of the class at the root of a path,
this will fail when (a) the lock is of class type (ie as used in static sync methods), because this will fail when (a) the lock is of class type (ie as used in static sync methods), because
@ -660,55 +692,60 @@ let fold_reportable_summaries analyze_ondemand tenv clazz ~init ~f =
[should_report_starvation] means [pair] is on the UI thread and not on a constructor *) [should_report_starvation] means [pair] is on the UI thread and not on a constructor *)
let report_on_parallel_composition ~should_report_starvation tenv pattrs pair lock other_pname let report_on_parallel_composition ~should_report_starvation tenv pattrs pair lock other_pname
other_pair report_map = other_pair report_map =
let open Domain in if
let pname = ProcAttributes.get_proc_name pattrs in is_private pattrs
let make_trace_and_loc () = || AnalysisCallbacks.proc_resolve_attributes other_pname |> Option.exists ~f:is_private
let first_trace = CriticalPair.make_trace ~header:"[Trace 1] " pname pair in then report_map
let second_trace = CriticalPair.make_trace ~header:"[Trace 2] " other_pname other_pair in else
let ltr = first_trace @ second_trace in let open Domain in
let loc = CriticalPair.get_earliest_lock_or_call_loc ~procname:pname pair in let pname = ProcAttributes.get_proc_name pattrs in
(ltr, loc) let make_trace_and_loc () =
in let first_trace = CriticalPair.make_trace ~header:"[Trace 1] " pname pair in
if CriticalPair.can_run_in_parallel pair other_pair then let second_trace = CriticalPair.make_trace ~header:"[Trace 2] " other_pname other_pair in
let acquisitions = other_pair.CriticalPair.elem.acquisitions in let ltr = first_trace @ second_trace in
match other_pair.CriticalPair.elem.event with let loc = CriticalPair.get_earliest_lock_or_call_loc ~procname:pname pair in
| MayBlock {severity} as event (ltr, loc)
when should_report_starvation in
&& Acquisitions.lock_is_held_in_other_thread tenv lock acquisitions -> if CriticalPair.can_run_in_parallel pair other_pair then
let error_message = let acquisitions = other_pair.CriticalPair.elem.acquisitions in
Format.asprintf match other_pair.CriticalPair.elem.event with
"Method %a runs on UI thread and%a, which may be held by another thread which %a." | MayBlock {severity} as event
pname_pp pname Lock.pp_locks lock Event.describe event when should_report_starvation
in && Acquisitions.lock_is_held_in_other_thread tenv lock acquisitions ->
let ltr, loc = make_trace_and_loc () in let error_message =
ReportMap.add_starvation severity tenv pattrs loc ltr error_message report_map Format.asprintf
| MonitorWait {lock= monitor_lock} "Method %a runs on UI thread and%a, which may be held by another thread which %a."
when should_report_starvation pname_pp pname Lock.pp_locks lock Event.describe event
&& Acquisitions.lock_is_held_in_other_thread tenv lock acquisitions in
&& not (Lock.equal lock monitor_lock) -> let ltr, loc = make_trace_and_loc () in
let error_message = ReportMap.add_starvation severity tenv pattrs loc ltr error_message report_map
Format.asprintf | MonitorWait {lock= monitor_lock}
"Method %a runs on UI thread and%a, which may be held by another thread which %a." when should_report_starvation
pname_pp pname Lock.pp_locks lock Event.describe other_pair.CriticalPair.elem.event && Acquisitions.lock_is_held_in_other_thread tenv lock acquisitions
in && not (Lock.equal lock monitor_lock) ->
let ltr, loc = make_trace_and_loc () in
ReportMap.add_starvation High tenv pattrs loc ltr error_message report_map
| LockAcquire _ -> (
match CriticalPair.may_deadlock tenv ~lhs:pair ~lhs_lock:lock ~rhs:other_pair with
| Some other_lock when should_report_deadlock_on_current_proc pair other_pair ->
let error_message = let error_message =
Format.asprintf Format.asprintf
"Potential deadlock. %a (Trace 1) and %a (Trace 2) acquire locks %a and %a in \ "Method %a runs on UI thread and%a, which may be held by another thread which %a."
reverse orders." pname_pp pname Lock.pp_locks lock Event.describe other_pair.CriticalPair.elem.event
pname_pp pname pname_pp other_pname Lock.describe lock Lock.describe other_lock
in in
let ltr, loc = make_trace_and_loc () in let ltr, loc = make_trace_and_loc () in
ReportMap.add_deadlock tenv pattrs loc ltr error_message report_map ReportMap.add_starvation High tenv pattrs loc ltr error_message report_map
| LockAcquire _ -> (
match CriticalPair.may_deadlock tenv ~lhs:pair ~lhs_lock:lock ~rhs:other_pair with
| Some other_lock when should_report_deadlock_on_current_proc pair other_pair ->
let error_message =
Format.asprintf
"Potential deadlock. %a (Trace 1) and %a (Trace 2) acquire locks %a and %a in \
reverse orders."
pname_pp pname pname_pp other_pname Lock.describe lock Lock.describe other_lock
in
let ltr, loc = make_trace_and_loc () in
ReportMap.add_deadlock tenv pattrs loc ltr error_message report_map
| _ ->
report_map )
| _ -> | _ ->
report_map ) report_map
| _ -> else report_map
report_map
else report_map
let report_on_pair ~analyze_ondemand tenv pattrs (pair : Domain.CriticalPair.t) report_map = let report_on_pair ~analyze_ondemand tenv pattrs (pair : Domain.CriticalPair.t) report_map =
@ -718,34 +755,62 @@ let report_on_pair ~analyze_ondemand tenv pattrs (pair : Domain.CriticalPair.t)
let should_report_starvation = let should_report_starvation =
CriticalPair.is_uithread pair && not (Procname.is_constructor pname) CriticalPair.is_uithread pair && not (Procname.is_constructor pname)
in in
let is_not_private = not (is_private pattrs) in
let make_trace_and_loc () = let make_trace_and_loc () =
let loc = CriticalPair.get_loc pair in let loc = CriticalPair.get_loc pair in
let ltr = CriticalPair.make_trace ~include_acquisitions:false pname pair in let ltr = CriticalPair.make_trace ~include_acquisitions:false pname pair in
(ltr, loc) (ltr, loc)
in in
match event with match event with
| MayBlock {severity} when should_report_starvation -> | MayBlock {severity} when is_not_private && should_report_starvation ->
let error_message = let error_message =
Format.asprintf "Method %a runs on UI thread and may block; %a." pname_pp pname Format.asprintf "Method %a runs on UI thread and may block; %a." pname_pp pname
Event.describe event Event.describe event
in in
let ltr, loc = make_trace_and_loc () in let ltr, loc = make_trace_and_loc () in
ReportMap.add_starvation severity tenv pattrs loc ltr error_message report_map ReportMap.add_starvation severity tenv pattrs loc ltr error_message report_map
| MonitorWait _ when should_report_starvation -> | MonitorWait _ when is_not_private && should_report_starvation ->
let error_message = let error_message =
Format.asprintf "Method %a runs on UI thread and may block; %a." pname_pp pname Format.asprintf "Method %a runs on UI thread and may block; %a." pname_pp pname
Event.describe event Event.describe event
in in
let ltr, loc = make_trace_and_loc () in let ltr, loc = make_trace_and_loc () in
ReportMap.add_starvation High tenv pattrs loc ltr error_message report_map ReportMap.add_starvation High tenv pattrs loc ltr error_message report_map
| StrictModeCall _ when should_report_starvation -> | StrictModeCall _ when is_not_private && should_report_starvation ->
let error_message = let error_message =
Format.asprintf "Method %a runs on UI thread and may violate Strict Mode; %a." pname_pp Format.asprintf "Method %a runs on UI thread and may violate Strict Mode; %a." pname_pp
pname Event.describe event pname Event.describe event
in in
let ltr, loc = make_trace_and_loc () in let ltr, loc = make_trace_and_loc () in
ReportMap.add_strict_mode_violation tenv pattrs loc ltr error_message report_map ReportMap.add_strict_mode_violation tenv pattrs loc ltr error_message report_map
| LockAcquire _ when StarvationModels.is_annotated_lockless tenv pname -> | MustNotOccurUnderLock _ when not (Acquisitions.is_empty pair.elem.acquisitions) -> (
(* warn only at the innermost procedure taking a lock around the final call *)
let procs_with_acquisitions =
Acquisitions.fold
(fun (acquisition : Acquisition.t) acc -> Procname.Set.add acquisition.procname acc)
pair.elem.acquisitions Procname.Set.empty
in
match Procname.Set.is_singleton_or_more procs_with_acquisitions with
| IContainer.Empty ->
L.die InternalError "Found empty set of acquisitions after checking for non-emptiness.@\n"
| IContainer.More ->
(* acquisitions found in more than one proc, ignore *)
report_map
| IContainer.Singleton acquiring_pname when not (Procname.equal acquiring_pname pname) ->
(* we are at a caller of the acquiring procname, so ignore *)
report_map
| IContainer.Singleton _ ->
let error_message =
Format.asprintf
"Method %a %a under a lock; executed code may acquire arbitrary locks leading to \
potential deadlock."
pname_pp pname Event.describe event
in
let loc = CriticalPair.get_earliest_lock_or_call_loc ~procname:pname pair in
let ltr = CriticalPair.make_trace pname pair in
ReportMap.add_arbitrary_code_execution_under_lock tenv pattrs loc ltr error_message
report_map )
| LockAcquire _ when is_not_private && StarvationModels.is_annotated_lockless tenv pname ->
let error_message = let error_message =
Format.asprintf "Method %a is annotated %s but%a." pname_pp pname Format.asprintf "Method %a is annotated %s but%a." pname_pp pname
(MF.monospaced_to_string Annotations.lockless) (MF.monospaced_to_string Annotations.lockless)
@ -754,7 +819,7 @@ let report_on_pair ~analyze_ondemand tenv pattrs (pair : Domain.CriticalPair.t)
let loc = CriticalPair.get_earliest_lock_or_call_loc ~procname:pname pair in let loc = CriticalPair.get_earliest_lock_or_call_loc ~procname:pname pair in
let ltr = CriticalPair.make_trace pname pair in let ltr = CriticalPair.make_trace pname pair in
ReportMap.add_lockless_violation tenv pattrs loc ltr error_message report_map ReportMap.add_lockless_violation tenv pattrs loc ltr error_message report_map
| LockAcquire {locks} -> ( | LockAcquire {locks} when is_not_private -> (
match match
List.find locks ~f:(fun lock -> Acquisitions.lock_is_held lock pair.elem.acquisitions) List.find locks ~f:(fun lock -> Acquisitions.lock_is_held lock pair.elem.acquisitions)
with with

@ -191,8 +191,9 @@ module Event = struct
type t = type t =
| LockAcquire of {locks: Lock.t list; thread: ThreadDomain.t} | LockAcquire of {locks: Lock.t list; thread: ThreadDomain.t}
| MayBlock of {callee: Procname.t; severity: StarvationModels.severity; thread: ThreadDomain.t} | MayBlock of {callee: Procname.t; severity: StarvationModels.severity; thread: ThreadDomain.t}
| StrictModeCall of {callee: Procname.t; thread: ThreadDomain.t}
| MonitorWait of {lock: Lock.t; thread: ThreadDomain.t} | MonitorWait of {lock: Lock.t; thread: ThreadDomain.t}
| MustNotOccurUnderLock of {callee: Procname.t; thread: ThreadDomain.t}
| StrictModeCall of {callee: Procname.t; thread: ThreadDomain.t}
[@@deriving compare] [@@deriving compare]
let pp fmt = function let pp fmt = function
@ -207,20 +208,26 @@ module Event = struct
F.fprintf fmt "StrictModeCall(%a, %a)" Procname.pp callee ThreadDomain.pp thread F.fprintf fmt "StrictModeCall(%a, %a)" Procname.pp callee ThreadDomain.pp thread
| MonitorWait {lock; thread} -> | MonitorWait {lock; thread} ->
F.fprintf fmt "MonitorWait(%a, %a)" Lock.pp lock ThreadDomain.pp thread F.fprintf fmt "MonitorWait(%a, %a)" Lock.pp lock ThreadDomain.pp thread
| MustNotOccurUnderLock {callee; thread} ->
F.fprintf fmt "MustNotOccurUnderLock(%a, %a)" Procname.pp callee ThreadDomain.pp thread
let describe fmt elem = let describe fmt elem =
match elem with match elem with
| LockAcquire {locks} -> | LockAcquire {locks} ->
Pp.comma_seq Lock.pp_locks fmt locks Pp.comma_seq Lock.pp_locks fmt locks
| MayBlock {callee} | StrictModeCall {callee} -> | MayBlock {callee} | StrictModeCall {callee} | MustNotOccurUnderLock {callee} ->
F.fprintf fmt "calls %a" describe_pname callee F.fprintf fmt "calls %a" describe_pname callee
| MonitorWait {lock} -> | MonitorWait {lock} ->
F.fprintf fmt "calls `wait` on %a" Lock.describe lock F.fprintf fmt "calls `wait` on %a" Lock.describe lock
let get_thread = function let get_thread = function
| LockAcquire {thread} | MayBlock {thread} | StrictModeCall {thread} | MonitorWait {thread} -> | LockAcquire {thread}
| MayBlock {thread}
| MonitorWait {thread}
| MustNotOccurUnderLock {thread}
| StrictModeCall {thread} ->
thread thread
@ -232,10 +239,12 @@ module Event = struct
LockAcquire {lock_acquire with thread} LockAcquire {lock_acquire with thread}
| MayBlock may_block -> | MayBlock may_block ->
MayBlock {may_block with thread} MayBlock {may_block with thread}
| StrictModeCall strict_mode_call ->
StrictModeCall {strict_mode_call with thread}
| MonitorWait monitor_wait -> | MonitorWait monitor_wait ->
MonitorWait {monitor_wait with thread} MonitorWait {monitor_wait with thread}
| MustNotOccurUnderLock not_under_lock ->
MustNotOccurUnderLock {not_under_lock with thread}
| StrictModeCall strict_mode_call ->
StrictModeCall {strict_mode_call with thread}
let apply_caller_thread caller_thread event = let apply_caller_thread caller_thread event =
@ -254,11 +263,13 @@ module Event = struct
let make_object_wait lock thread = MonitorWait {lock; thread} let make_object_wait lock thread = MonitorWait {lock; thread}
let make_arbitrary_code_exec callee thread = MustNotOccurUnderLock {callee; thread}
let get_acquired_locks = function LockAcquire {locks} -> locks | _ -> [] let get_acquired_locks = function LockAcquire {locks} -> locks | _ -> []
let apply_subst subst event = let apply_subst subst event =
match event with match event with
| MayBlock _ | StrictModeCall _ -> | MayBlock _ | StrictModeCall _ | MustNotOccurUnderLock _ ->
Some event Some event
| MonitorWait {lock; thread} -> ( | MonitorWait {lock; thread} -> (
match Lock.apply_subst subst lock with match Lock.apply_subst subst lock with
@ -806,6 +817,11 @@ let strict_mode_call ~callee ~loc astate =
make_call_with_event new_event ~loc astate make_call_with_event new_event ~loc astate
let arbitrary_code_execution ~callee ~loc astate =
let new_event = Event.make_arbitrary_code_exec callee astate.thread in
make_call_with_event new_event ~loc astate
let release ({lock_state} as astate) locks = let release ({lock_state} as astate) locks =
{ astate with { astate with
lock_state= List.fold locks ~init:lock_state ~f:(fun acc l -> LockState.release l acc) } lock_state= List.fold locks ~init:lock_state ~f:(fun acc l -> LockState.release l acc) }

@ -59,8 +59,9 @@ module Event : sig
type t = type t =
| LockAcquire of {locks: Lock.t list; thread: ThreadDomain.t} | LockAcquire of {locks: Lock.t list; thread: ThreadDomain.t}
| MayBlock of {callee: Procname.t; severity: StarvationModels.severity; thread: ThreadDomain.t} | MayBlock of {callee: Procname.t; severity: StarvationModels.severity; thread: ThreadDomain.t}
| StrictModeCall of {callee: Procname.t; thread: ThreadDomain.t}
| MonitorWait of {lock: Lock.t; thread: ThreadDomain.t} | MonitorWait of {lock: Lock.t; thread: ThreadDomain.t}
| MustNotOccurUnderLock of {callee: Procname.t; thread: ThreadDomain.t}
| StrictModeCall of {callee: Procname.t; thread: ThreadDomain.t}
[@@deriving compare] [@@deriving compare]
val describe : F.formatter -> t -> unit val describe : F.formatter -> t -> unit
@ -198,6 +199,8 @@ val future_get : callee:Procname.t -> loc:Location.t -> HilExp.t list -> t -> t
val strict_mode_call : callee:Procname.t -> loc:Location.t -> t -> t val strict_mode_call : callee:Procname.t -> loc:Location.t -> t -> t
val arbitrary_code_execution : callee:Procname.t -> loc:Location.t -> t -> t
val add_guard : val add_guard :
acquire_now:bool acquire_now:bool
-> procname:Procname.t -> procname:Procname.t

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import com.google.common.util.concurrent.SettableFuture;
public class NotUnderLock {
SettableFuture future = null;
private void callFutureSetOk() {
future.set(null);
}
private synchronized void firstAcquisitionBad() {
callFutureSetOk();
}
private void secondAcquisitionOk(Object o) {
synchronized (o) {
firstAcquisitionBad();
}
}
}

@ -41,6 +41,7 @@ codetoanalyze/java/starvation/MyActivity.java, MyActivity.onStart():void, 33, ST
codetoanalyze/java/starvation/MyActivity.java, MyActivity.onStop():void, 53, STARVATION, no_bucket, ERROR, [`void MyActivity.onStop()`,Method call: `void MyActivity.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation/MyActivity.java, MyActivity.onStop():void, 53, STARVATION, no_bucket, ERROR, [`void MyActivity.onStop()`,Method call: `void MyActivity.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`]
codetoanalyze/java/starvation/NonBlk.java, NonBlk.deadlockABBad():void, 33, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void NonBlk.deadlockABBad()`, locks `this` in `class NonBlk`, locks `this.future` in `class NonBlk`,[Trace 2] `void NonBlk.deadlockBABad()`, locks `this.future` in `class NonBlk`, locks `this` in `class NonBlk`] codetoanalyze/java/starvation/NonBlk.java, NonBlk.deadlockABBad():void, 33, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void NonBlk.deadlockABBad()`, locks `this` in `class NonBlk`, locks `this.future` in `class NonBlk`,[Trace 2] `void NonBlk.deadlockBABad()`, locks `this.future` in `class NonBlk`, locks `this` in `class NonBlk`]
codetoanalyze/java/starvation/NonBlk.java, NonBlk.deadlockBABad():void, 40, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void NonBlk.deadlockBABad()`, locks `this.future` in `class NonBlk`, locks `this` in `class NonBlk`,[Trace 2] `void NonBlk.deadlockABBad()`, locks `this` in `class NonBlk`, locks `this.future` in `class NonBlk`] codetoanalyze/java/starvation/NonBlk.java, NonBlk.deadlockBABad():void, 40, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void NonBlk.deadlockBABad()`, locks `this.future` in `class NonBlk`, locks `this` in `class NonBlk`,[Trace 2] `void NonBlk.deadlockABBad()`, locks `this` in `class NonBlk`, locks `this.future` in `class NonBlk`]
codetoanalyze/java/starvation/NotUnderLock.java, NotUnderLock.firstAcquisitionBad():void, 17, ARBITRARY_CODE_EXECUTION_UNDER_LOCK, no_bucket, ERROR, [`void NotUnderLock.firstAcquisitionBad()`, locks `this` in `class NotUnderLock`,Method call: `void NotUnderLock.callFutureSetOk()`,calls `boolean SettableFuture.set(Object)`]
codetoanalyze/java/starvation/ObjWait.java, ObjWait.indirectWaitOnMainWithoutTimeoutBad():void, 46, STARVATION, no_bucket, ERROR, [[Trace 1] `void ObjWait.indirectWaitOnMainWithoutTimeoutBad()`, locks `this.lock` in `class ObjWait`,[Trace 2] `void ObjWait.lockAndWaitOnAnyWithoutTimeoutBad()`, locks `this.lock` in `class ObjWait`, locks `this.x` in `class ObjWait`,calls `wait` on `this.x` in `class ObjWait`] codetoanalyze/java/starvation/ObjWait.java, ObjWait.indirectWaitOnMainWithoutTimeoutBad():void, 46, STARVATION, no_bucket, ERROR, [[Trace 1] `void ObjWait.indirectWaitOnMainWithoutTimeoutBad()`, locks `this.lock` in `class ObjWait`,[Trace 2] `void ObjWait.lockAndWaitOnAnyWithoutTimeoutBad()`, locks `this.lock` in `class ObjWait`, locks `this.x` in `class ObjWait`,calls `wait` on `this.x` in `class ObjWait`]
codetoanalyze/java/starvation/ObjWait.java, ObjWait.waitOnMainWithExcessiveTimeout1Bad():void, 31, STARVATION, no_bucket, ERROR, [`void ObjWait.waitOnMainWithExcessiveTimeout1Bad()`,calls `wait` on `this.o` in `class ObjWait`] codetoanalyze/java/starvation/ObjWait.java, ObjWait.waitOnMainWithExcessiveTimeout1Bad():void, 31, STARVATION, no_bucket, ERROR, [`void ObjWait.waitOnMainWithExcessiveTimeout1Bad()`,calls `wait` on `this.o` in `class ObjWait`]
codetoanalyze/java/starvation/ObjWait.java, ObjWait.waitOnMainWithExcessiveTimeout2Bad():void, 38, STARVATION, no_bucket, ERROR, [`void ObjWait.waitOnMainWithExcessiveTimeout2Bad()`,calls `wait` on `this.o` in `class ObjWait`] codetoanalyze/java/starvation/ObjWait.java, ObjWait.waitOnMainWithExcessiveTimeout2Bad():void, 38, STARVATION, no_bucket, ERROR, [`void ObjWait.waitOnMainWithExcessiveTimeout2Bad()`,calls `wait` on `this.o` in `class ObjWait`]

Loading…
Cancel
Save