[racerd] on-annotations

Reviewed By: jvillard

Differential Revision: D14756737

fbshipit-source-id: 434859ce7
master
Nikos Gorogiannis 6 years ago committed by Facebook Github Bot
parent 1172e6de50
commit cf6ced0580

@ -57,16 +57,6 @@ let no_allocation = "NoAllocation"
let nullable = "Nullable" let nullable = "Nullable"
let on_bind = "OnBind"
let on_event = "OnEvent"
let on_mount = "OnMount"
let on_unbind = "OnUnbind"
let on_unmount = "OnUnmount"
let mainthread = "MainThread" let mainthread = "MainThread"
let nonblocking = "NonBlocking" let nonblocking = "NonBlocking"
@ -121,15 +111,13 @@ let ma_has_annotation_with ({return; params} : Annot.Method.t) (predicate : Anno
has_annot return || List.exists ~f:has_annot params has_annot return || List.exists ~f:has_annot params
let get_annot_ending ({class_name} : Annot.t) =
String.rsplit2 class_name ~on:'.' |> Option.value_map ~default:class_name ~f:snd
(** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package, (** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package,
is equal to [ann_name] *) is equal to [ann_name] *)
let annot_ends_with annot ann_name = let annot_ends_with annot ann_name = String.equal ann_name (get_annot_ending annot)
match String.rsplit2 annot.Annot.class_name ~on:'.' with
| None ->
String.equal annot.Annot.class_name ann_name
| Some (_, annot_class_name) ->
String.equal annot_class_name ann_name
let class_name_matches s ((annot : Annot.t), _) = String.equal s annot.class_name let class_name_matches s ((annot : Annot.t), _) = String.equal s annot.class_name
@ -234,20 +222,6 @@ let ia_is_inject ia = ia_ends_with ia inject
let ia_is_suppress_lint ia = ia_ends_with ia suppress_lint let ia_is_suppress_lint ia = ia_ends_with ia suppress_lint
let ia_is_on_event ia = ia_ends_with ia on_event
let ia_is_on_bind ia = ia_ends_with ia on_bind
let ia_is_on_mount ia = ia_ends_with ia on_mount
let ia_is_on_unbind ia = ia_ends_with ia on_unbind
let ia_is_on_unmount ia = ia_ends_with ia on_unmount
let ia_is_ui_thread ia = ia_ends_with ia ui_thread
let ia_is_mainthread ia = ia_ends_with ia mainthread
let ia_is_thread_confined ia = ia_ends_with ia thread_confined let ia_is_thread_confined ia = ia_ends_with ia thread_confined
let ia_is_worker_thread ia = ia_ends_with ia worker_thread let ia_is_worker_thread ia = ia_ends_with ia worker_thread

@ -41,12 +41,17 @@ val thread_confined : string
val thread_safe : string val thread_safe : string
val mainthread : string
val ui_thread : string val ui_thread : string
val visibleForTesting : string val visibleForTesting : string
val generated_graphql : string val generated_graphql : string
val get_annot_ending : Annot.t -> string
(** get the '.'-last component of an annotation *)
val annot_ends_with : Annot.t -> string -> bool val annot_ends_with : Annot.t -> string -> bool
(** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package, (** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package,
is equal to [ann_name] *) is equal to [ann_name] *)
@ -92,18 +97,6 @@ val ia_is_inject : Annot.Item.t -> bool
val ia_is_suppress_lint : Annot.Item.t -> bool val ia_is_suppress_lint : Annot.Item.t -> bool
val ia_is_on_event : Annot.Item.t -> bool
val ia_is_on_bind : Annot.Item.t -> bool
val ia_is_on_mount : Annot.Item.t -> bool
val ia_is_on_unbind : Annot.Item.t -> bool
val ia_is_on_unmount : Annot.Item.t -> bool
val ia_is_mainthread : Annot.Item.t -> bool
val ia_is_not_thread_safe : Annot.Item.t -> bool val ia_is_not_thread_safe : Annot.Item.t -> bool
val ia_is_nonblocking : Annot.Item.t -> bool val ia_is_nonblocking : Annot.Item.t -> bool
@ -118,8 +111,6 @@ val ia_is_thread_confined : Annot.Item.t -> bool
val ia_is_thrift_service : Annot.Item.t -> bool val ia_is_thrift_service : Annot.Item.t -> bool
val ia_is_ui_thread : Annot.Item.t -> bool
val ia_is_volatile : Annot.Item.t -> bool val ia_is_volatile : Annot.Item.t -> bool
val ia_is_worker_thread : Annot.Item.t -> bool val ia_is_worker_thread : Annot.Item.t -> bool

@ -312,12 +312,21 @@ let get_current_class_and_annotated_superclasses is_annot tenv pname =
None None
let find_annotated_or_overriden_annotated_method ~attrs_of_pname is_annot pname tenv = let is_class_or_superclasses_annotated is_annot tenv pname =
get_current_class_and_annotated_superclasses is_annot tenv pname
|> Option.exists ~f:(fun (_, annotated) -> not (List.is_empty annotated))
let find_method_or_override_annotated ~attrs_of_pname is_annot pname tenv =
PatternMatch.override_find PatternMatch.override_find
(fun pn -> Annotations.pname_has_return_annot pn ~attrs_of_pname is_annot) (fun pn -> Annotations.pname_has_return_annot pn ~attrs_of_pname is_annot)
tenv pname tenv pname
let is_method_or_override_annotated ~attrs_of_pname is_annot pname tenv =
find_method_or_override_annotated ~attrs_of_pname is_annot pname tenv |> Option.is_some
let ui_matcher_records = let ui_matcher_records =
let open MethodMatcher in let open MethodMatcher in
let fragment_methods = let fragment_methods =
@ -362,57 +371,64 @@ let is_ui_method =
fun tenv pname -> MethodMatcher.of_list matchers tenv pname [] fun tenv pname -> MethodMatcher.of_list matchers tenv pname []
let runs_on_ui_thread = let if_pred_evalopt ~pred ~f x =
(* assume that methods annotated with @UiThread, @OnEvent, @OnBind, @OnMount, @OnUnbind, IOption.if_none_evalopt x ~f:(fun () -> if pred () then Some (f ()) else None)
@OnUnmount always run on the UI thread *)
let annotation_matchers =
[ Annotations.ia_is_mainthread (* assume that methods annotated with @MainThread, @UiThread,
; Annotations.ia_is_ui_thread and any string starting with "On" always run on the UI thread.
; Annotations.ia_is_on_bind We do the latter because there are too many to precisely list. *)
; Annotations.ia_is_on_event let is_uithread annots =
; Annotations.ia_is_on_mount let f (annot, _) =
; Annotations.ia_is_on_unbind let ending = Annotations.get_annot_ending annot in
; Annotations.ia_is_on_unmount ] String.equal ending Annotations.mainthread
|| String.equal ending Annotations.ui_thread
|| String.is_prefix ~prefix:"On" ending
in in
let is_annot annot = List.exists annotation_matchers ~f:(fun m -> m annot) in List.exists annots ~f
let mono_pname = MF.wrap_monospaced Typ.Procname.pp in
fun ~attrs_of_pname tenv proc_desc ->
let pname = Procdesc.get_proc_name proc_desc in let mono_pname = MF.wrap_monospaced Typ.Procname.pp
if
Annotations.pdesc_has_return_annot proc_desc Annotations.ia_is_worker_thread let runs_on_ui_thread ~attrs_of_pname tenv proc_desc =
|| find_annotated_or_overriden_annotated_method ~attrs_of_pname let pname = Procdesc.get_proc_name proc_desc in
Annotations.ia_is_worker_thread pname tenv if
|> Option.is_some is_method_or_override_annotated ~attrs_of_pname Annotations.ia_is_worker_thread pname tenv
|| get_current_class_and_annotated_superclasses Annotations.ia_is_worker_thread tenv pname || is_class_or_superclasses_annotated Annotations.ia_is_worker_thread tenv pname
|> Option.value_map ~default:false ~f:(function _, [] -> false | _ -> true) then None
then None else
else if is_ui_method tenv pname then None
Some (F.asprintf "%a is a standard UI-thread method" mono_pname pname) |> if_pred_evalopt
else if Annotations.pdesc_has_return_annot proc_desc is_annot then ~pred:(fun () -> is_ui_method tenv pname)
Some ~f:(fun () -> F.asprintf "%a is a standard UI-thread method" mono_pname pname)
(F.asprintf "%a is annotated %s" mono_pname pname |> if_pred_evalopt
(MF.monospaced_to_string Annotations.ui_thread)) ~pred:(fun () -> Annotations.pdesc_has_return_annot proc_desc is_uithread)
else ~f:(fun () ->
match find_annotated_or_overriden_annotated_method ~attrs_of_pname is_annot pname tenv with F.asprintf "%a is annotated %s" mono_pname pname
| Some override_pname -> (MF.monospaced_to_string Annotations.ui_thread) )
Some |> IOption.if_none_evalopt ~f:(fun () ->
(F.asprintf "class %a overrides %a, which is annotated %s" mono_pname pname mono_pname find_method_or_override_annotated ~attrs_of_pname is_uithread pname tenv
override_pname |> Option.map ~f:(fun override_pname ->
(MF.monospaced_to_string Annotations.ui_thread)) F.asprintf "class %a overrides %a, which is annotated %s" mono_pname pname
| None -> ( mono_pname override_pname
match get_current_class_and_annotated_superclasses is_annot tenv pname with (MF.monospaced_to_string Annotations.ui_thread) ) )
| Some (current_class, (super_class :: _ as super_classes)) -> |> IOption.if_none_evalopt ~f:(fun () ->
let middle = get_current_class_and_annotated_superclasses is_uithread tenv pname
if List.exists super_classes ~f:(Typ.Name.equal current_class) then "" |> Option.bind ~f:(function
else F.asprintf " extends %a, which" (MF.wrap_monospaced Typ.Name.pp) super_class | current_class, (super_class :: _ as super_classes) ->
in let middle =
Some if List.exists super_classes ~f:(Typ.Name.equal current_class) then ""
(F.asprintf "class %s%s is annotated %s" else
(MF.monospaced_to_string (Typ.Name.name current_class)) F.asprintf " extends %a, which" (MF.wrap_monospaced Typ.Name.pp)
middle super_class
(MF.monospaced_to_string Annotations.ui_thread)) in
| _ -> Some
None ) (F.asprintf "class %s%s is annotated %s"
(MF.monospaced_to_string (Typ.Name.name current_class))
middle
(MF.monospaced_to_string Annotations.ui_thread))
| _ ->
None ) )
let cpp_lock_types_matcher = Clang.lock_types_matcher let cpp_lock_types_matcher = Clang.lock_types_matcher

@ -47,7 +47,7 @@ val runs_on_ui_thread :
val get_current_class_and_annotated_superclasses : val get_current_class_and_annotated_superclasses :
(Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> (Typ.name * Typ.name list) option (Annot.Item.t -> bool) -> Tenv.t -> Typ.Procname.t -> (Typ.name * Typ.name list) option
val find_annotated_or_overriden_annotated_method : val find_method_or_override_annotated :
attrs_of_pname:(Typ.Procname.t -> ProcAttributes.t option) attrs_of_pname:(Typ.Procname.t -> ProcAttributes.t option)
-> (Annot.Item.t -> bool) -> (Annot.Item.t -> bool)
-> Typ.Procname.t -> Typ.Procname.t

@ -480,18 +480,14 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
{astate' with accesses} {astate' with accesses}
let if_none_then = IOption.value_default_f
let if_none_do ~f x = match x with None -> f () | Some _ -> x
let exec_instr (astate : Domain.t) ({ProcData.pdesc} as proc_data) _ (instr : HilInstr.t) = let exec_instr (astate : Domain.t) ({ProcData.pdesc} as proc_data) _ (instr : HilInstr.t) =
match instr with match instr with
| Call (ret_base, Direct callee_pname, actuals, call_flags, loc) -> | Call (ret_base, Direct callee_pname, actuals, call_flags, loc) ->
let astate = add_reads actuals loc astate proc_data in let astate = add_reads actuals loc astate proc_data in
treat_call_acquiring_ownership ret_base callee_pname actuals loc proc_data astate () treat_call_acquiring_ownership ret_base callee_pname actuals loc proc_data astate ()
|> if_none_do |> IOption.if_none_evalopt
~f:(treat_container_accesses ret_base callee_pname actuals loc proc_data astate) ~f:(treat_container_accesses ret_base callee_pname actuals loc proc_data astate)
|> if_none_then |> IOption.if_none_eval
~f:(do_proc_call ret_base callee_pname actuals call_flags loc proc_data astate) ~f:(do_proc_call ret_base callee_pname actuals call_flags loc proc_data astate)
| Call (_, Indirect _, _, _, _) -> | Call (_, Indirect _, _, _, _) ->
if Typ.Procname.is_java (Procdesc.get_proc_name pdesc) then if Typ.Procname.is_java (Procdesc.get_proc_name pdesc) then

@ -342,8 +342,8 @@ let is_thread_safe_class pname tenv =
let is_thread_safe_method pname tenv = let is_thread_safe_method pname tenv =
find_annotated_or_overriden_annotated_method ~attrs_of_pname:Summary.proc_resolve_attributes find_method_or_override_annotated ~attrs_of_pname:Summary.proc_resolve_attributes is_thread_safe
is_thread_safe pname tenv pname tenv
|> Option.is_some |> Option.is_some

@ -10,3 +10,7 @@ open! IStd
let find_value_exn = function None -> raise Caml.Not_found | Some v -> v let find_value_exn = function None -> raise Caml.Not_found | Some v -> v
let value_default_f ~f = function None -> f () | Some v -> v let value_default_f ~f = function None -> f () | Some v -> v
let if_none_evalopt ~f x = match x with None -> f () | Some _ -> x
let if_none_eval = value_default_f

@ -12,3 +12,13 @@ val find_value_exn : 'a option -> 'a
val value_default_f : f:(unit -> 'a) -> 'a option -> 'a val value_default_f : f:(unit -> 'a) -> 'a option -> 'a
(** Like [Option.value ~default:(f ())] but [f] is called only if [None]. *) (** Like [Option.value ~default:(f ())] but [f] is called only if [None]. *)
val if_none_evalopt : f:(unit -> 'a option) -> 'a option -> 'a option
(** [if_none_evalopt ~f x] evaluates to [f ()] if [x = None], otherwise returns [x].
Useful for chaining matchers where the first returning non-[None] determines
the result. *)
val if_none_eval : f:(unit -> 'a) -> 'a option -> 'a
(** [if_none_eval ~f x] evaluates to [y] if [x=Some y] else to [f ()].
Useful for terminating chains built with [if_none_evalopt].
This is exactly the same as [value_default_f] but with a better name. *)

@ -439,3 +439,18 @@ class ExtendsClassOnUiThread extends AllMethodsOnUiThread {
return super.bar(); return super.bar();
} }
} }
// All annotations that start with "On" are assumed to be on the main thread
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface OnXYZ {}
@ThreadSafe
class WeirdAnnotation {
int f;
@OnXYZ
void fooOk() {
f = 0;
}
}

Loading…
Cancel
Save