[racerd] don't report INTERFACE_NOT_THREAD_SAFE if the receiver of the interface call is marked `ThreadConfined`

Summary: We already suppress race reports if the field is marked in this way; makes sense to do the same thing for these reports.

Reviewed By: ngorogiannis

Differential Revision: D7589275

fbshipit-source-id: 8f0aeab
master
Sam Blackshear 7 years ago committed by Facebook Github Bot
parent 346589f1fb
commit ce8f958522

@ -54,11 +54,12 @@ module Raw = struct
let equal = [%compare.equal : t] let equal = [%compare.equal : t]
let truncate = function let truncate ((base, accesses) as t) =
| base, [] | base, [_] -> match List.rev accesses with
(base, []) | [] ->
| base, accesses -> (t, None)
(base, List.rev (List.tl_exn (List.rev accesses))) | last_access :: accesses ->
((base, List.rev accesses), Some last_access)
let lookup_field_type_annot tenv base_typ field_name = let lookup_field_type_annot tenv base_typ field_name =

@ -22,9 +22,9 @@ type access =
representedas (x, [f; g]) *) representedas (x, [f; g]) *)
and t = base * access list [@@deriving compare] and t = base * access list [@@deriving compare]
val truncate : t -> t val truncate : t -> t * access option
(** remove the last access of the access path if the access list is non-empty. returns the (** remove and return the last access of the access path if the access list is non-empty. returns
original access path if the access list is empty *) the original access path * None if the access list is empty *)
val get_last_access : t -> access option val get_last_access : t -> access option
(** get the last access in the list. returns None if the list is empty *) (** get the last access in the list. returns None if the list is empty *)

@ -214,9 +214,13 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
(remove_call_sites ap aps, updated_pnames) (remove_call_sites ap aps, updated_pnames)
let rec longest_nullable_prefix ap ((nulable_aps, _) as astate) = let rec longest_nullable_prefix ap ((nullable_aps, _) as astate) =
try Some (ap, NullableAP.find ap nulable_aps) with Not_found -> try Some (ap, NullableAP.find ap nullable_aps) with Not_found ->
match ap with _, [] -> None | p -> longest_nullable_prefix (AccessPath.truncate p) astate match ap with
| _, [] ->
None
| p ->
longest_nullable_prefix (fst (AccessPath.truncate p)) astate
let check_ap proc_data loc ap astate = let check_ap proc_data loc ap astate =

@ -89,18 +89,48 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
(ownership', attribute_map') (ownership', attribute_map')
let add_unannotated_call_access pname (call_flags: CallFlags.t) loc tenv ~locks ~threads (* we don't want to warn on accesses to the field if it is (a) thread-confined, or
(b) volatile *)
let is_safe_access access prefix_path tenv =
match (access, AccessPath.get_typ prefix_path tenv) with
| ( AccessPath.FieldAccess fieldname
, Some ({Typ.desc= Tstruct typename} | {desc= Tptr ({desc= Tstruct typename}, _)}) ) -> (
match Tenv.lookup tenv typename with
| Some struct_typ ->
Annotations.struct_typ_has_annot struct_typ Annotations.ia_is_thread_confined
|| Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_thread_confined
|| Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_volatile
| None ->
false )
| _ ->
false
let add_unannotated_call_access pname actuals (call_flags: CallFlags.t) loc tenv ~locks ~threads
attribute_map (proc_data: extras ProcData.t) = attribute_map (proc_data: extras ProcData.t) =
let open RacerDConfig in let open RacerDConfig in
let thread_safe_or_thread_confined annot = let thread_safe_or_thread_confined annot =
Annotations.ia_is_thread_safe annot || Annotations.ia_is_thread_confined annot Annotations.ia_is_thread_safe annot || Annotations.ia_is_thread_confined annot
in in
let is_receiver_safe = function
| (HilExp.AccessExpression receiver_access_exp) :: _
-> (
let receiver_access_path = AccessExpression.to_access_path receiver_access_exp in
match AccessPath.truncate receiver_access_path with
| receiver_prefix, Some receiver_field ->
is_safe_access receiver_field receiver_prefix tenv
| _ ->
false )
| _ ->
false
in
if call_flags.cf_interface && Typ.Procname.is_java pname if call_flags.cf_interface && Typ.Procname.is_java pname
&& not (Models.is_java_library pname || Models.is_builder_function pname) && not (Models.is_java_library pname || Models.is_builder_function pname)
(* can't ask anyone to annotate interfaces in library code, and Builder's should always be (* can't ask anyone to annotate interfaces in library code, and Builder's should always be
thread-safe (would be unreasonable to ask everyone to annotate them) *) thread-safe (would be unreasonable to ask everyone to annotate them) *)
&& not (PatternMatch.check_class_attributes thread_safe_or_thread_confined tenv pname) && not (PatternMatch.check_class_attributes thread_safe_or_thread_confined tenv pname)
&& not (Models.has_return_annot thread_safe_or_thread_confined pname) && not (Models.has_return_annot thread_safe_or_thread_confined pname)
&& not (is_receiver_safe actuals)
then then
let open Domain in let open Domain in
let pre = AccessData.make locks threads False proc_data.pdesc in let pre = AccessData.make locks threads False proc_data.pdesc in
@ -117,22 +147,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| _ -> | _ ->
false false
in in
(* we don't want to warn on accesses to the field if it is (a) thread-confined, or (* TODO: simplify this with AccessPath.truncate *)
(b) volatile *)
let is_safe_access access prefix_path tenv =
match (access, AccessPath.get_typ prefix_path tenv) with
| ( AccessPath.FieldAccess fieldname
, Some ({Typ.desc= Tstruct typename} | {desc= Tptr ({desc= Tstruct typename}, _)}) ) -> (
match Tenv.lookup tenv typename with
| Some struct_typ ->
Annotations.struct_typ_has_annot struct_typ Annotations.ia_is_thread_confined
|| Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_thread_confined
|| Annotations.field_has_annot fieldname struct_typ Annotations.ia_is_volatile
| None ->
false )
| _ ->
false
in
let rec add_field_accesses prefix_path access_acc = function let rec add_field_accesses prefix_path access_acc = function
| [] -> | [] ->
access_acc access_acc
@ -411,7 +426,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) | Call (ret_opt, Direct callee_pname, actuals, call_flags, loc)
-> ( -> (
let accesses_with_unannotated_calls = let accesses_with_unannotated_calls =
add_unannotated_call_access callee_pname call_flags loc tenv ~locks:astate.locks add_unannotated_call_access callee_pname actuals call_flags loc tenv ~locks:astate.locks
~threads:astate.threads astate.accesses proc_data ~threads:astate.threads astate.accesses proc_data
in in
let accesses = let accesses =
@ -980,7 +995,7 @@ let get_contaminated_race_message access wobbly_paths =
(* Access paths rooted in static variables are always race-prone, (* Access paths rooted in static variables are always race-prone,
hence do not complain about contamination. *) hence do not complain about contamination. *)
when not (access_path |> fst |> fst |> ignore_var) -> when not (access_path |> fst |> fst |> ignore_var) ->
let proper_prefix_path = AccessPath.truncate access_path in let proper_prefix_path, _ = AccessPath.truncate access_path in
let base, accesses = proper_prefix_path in let base, accesses = proper_prefix_path in
let rec prefix_in_wobbly_paths prefix = function let rec prefix_in_wobbly_paths prefix = function
| [] -> | [] ->

@ -89,3 +89,17 @@ class Some {
d.callUnderLock(i); d.callUnderLock(i);
} }
} }
@ThreadSafe
class ThreadConfinedField {
@ThreadConfined(ThreadConfined.ANY) UnannotatedInterface mThreadConfined;
UnannotatedInterface mNormal;
void interfaceCallOnThreadConfinedFieldOk() {
mThreadConfined.foo();
}
void interfaceCallOnNormalFieldBad() {
mNormal.foo();
}
}

@ -50,6 +50,7 @@ codetoanalyze/java/racerd/DeepOwnership.java, void DeepOwnership.globalNotOwnedB
codetoanalyze/java/racerd/DeepOwnership.java, void DeepOwnership.reassignBaseToGlobalBad(), 3, THREAD_SAFETY_VIOLATION, ERROR, [access to `x.DeepOwnership.next`] codetoanalyze/java/racerd/DeepOwnership.java, void DeepOwnership.reassignBaseToGlobalBad(), 3, THREAD_SAFETY_VIOLATION, ERROR, [access to `x.DeepOwnership.next`]
codetoanalyze/java/racerd/Dispatch.java, void Dispatch.callUnannotatedInterfaceBad(UnannotatedInterface), 1, INTERFACE_NOT_THREAD_SAFE, ERROR, [Call to un-annotated interface method void UnannotatedInterface.foo()] codetoanalyze/java/racerd/Dispatch.java, void Dispatch.callUnannotatedInterfaceBad(UnannotatedInterface), 1, INTERFACE_NOT_THREAD_SAFE, ERROR, [Call to un-annotated interface method void UnannotatedInterface.foo()]
codetoanalyze/java/racerd/Dispatch.java, void Dispatch.callUnannotatedInterfaceIndirectBad(NotThreadSafe,UnannotatedInterface), 1, INTERFACE_NOT_THREAD_SAFE, ERROR, [call to void NotThreadSafe.notThreadSafeOk(UnannotatedInterface),Call to un-annotated interface method void UnannotatedInterface.foo()] codetoanalyze/java/racerd/Dispatch.java, void Dispatch.callUnannotatedInterfaceIndirectBad(NotThreadSafe,UnannotatedInterface), 1, INTERFACE_NOT_THREAD_SAFE, ERROR, [call to void NotThreadSafe.notThreadSafeOk(UnannotatedInterface),Call to un-annotated interface method void UnannotatedInterface.foo()]
codetoanalyze/java/racerd/Dispatch.java, void ThreadConfinedField.interfaceCallOnNormalFieldBad(), 1, INTERFACE_NOT_THREAD_SAFE, ERROR, [Call to un-annotated interface method void UnannotatedInterface.foo()]
codetoanalyze/java/racerd/Inference.java, int Inference.read4OutsideSyncBad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField4`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField4`] codetoanalyze/java/racerd/Inference.java, int Inference.read4OutsideSyncBad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField4`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField4`]
codetoanalyze/java/racerd/Inference.java, int Inference.unprotectedRead1Bad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField1`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField1`] codetoanalyze/java/racerd/Inference.java, int Inference.unprotectedRead1Bad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField1`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField1`]
codetoanalyze/java/racerd/Inference.java, int Inference.unprotectedRead2Bad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField2`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField2`] codetoanalyze/java/racerd/Inference.java, int Inference.unprotectedRead2Bad(), 1, THREAD_SAFETY_VIOLATION, ERROR, [<Read trace>,access to `this.codetoanalyze.java.checkers.Inference.mField2`,<Write trace>,access to `this.codetoanalyze.java.checkers.Inference.mField2`]

Loading…
Cancel
Save