You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
7.3 KiB

(*
* 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.
*)
open! IStd
(** Annotations. *)
let any_thread = "AnyThread"
let auto_cleanup = "AutoCleanup"
let bind = "Bind"
let bind_view = "BindView"
let bind_array = "BindArray"
let bind_bitmap = "BindBitmap"
let bind_drawable = "BindDrawable"
let bind_string = "BindString"
let camel_nonnull = "NonNull"
let cleanup = "Cleanup"
let expensive = "Expensive"
let false_on_null = "FalseOnNull"
let for_ui_thread = "ForUiThread"
let for_non_ui_thread = "ForNonUiThread"
let functional = "Functional"
let guarded_by = "GuardedBy"
let ignore_allocations = "IgnoreAllocations"
let initializer_ = "Initializer"
let inject = "Inject"
let inject_prop = "InjectProp"
let inject_view = "InjectView"
let json_field = "JsonField"
let lockless = "Lockless"
let nonnull = "Nonnull"
let no_allocation = "NoAllocation"
let nullable = "Nullable"
let nullsafe_strict = "NullsafeStrict"
let nullsafe = "Nullsafe"
let mainthread = "MainThread"
let nonblocking = "NonBlocking"
let notnull = "NotNull"
let not_thread_safe = "NotThreadSafe"
let on_bind = "OnBind"
let on_event = "OnEvent"
let on_mount = "OnMount"
let on_unbind = "OnUnbind"
let on_unmount = "OnUnmount"
let performance_critical = "PerformanceCritical"
let prop = "Prop"
let propagates_nullable = "PropagatesNullable"
let returns_ownership = "ReturnsOwnership"
let synchronized_collection = "SynchronizedCollection"
let generated_graphql = "GeneratedGraphQL"
let suppress_lint = "SuppressLint"
let suppress_view_nullability = "SuppressViewNullability"
let recently_nullable = "RecentlyNullable"
let thread_confined = "ThreadConfined"
let thread_safe = "ThreadSafe"
let thrift_service = "ThriftService"
let true_on_null = "TrueOnNull"
let ui_thread = "UiThread"
let visibleForTesting = "VisibleForTesting"
let volatile = "volatile"
let worker_thread = "WorkerThread"
let ia_has_annotation_with (ia : Annot.Item.t) (predicate : Annot.t -> bool) : bool =
List.exists ~f:(fun (a, _) -> predicate a) ia
let ma_has_annotation_with ({return; params} : Annot.Method.t) (predicate : Annot.t -> bool) : bool
=
let has_annot a = ia_has_annotation_with a predicate in
has_annot return || List.exists ~f:has_annot params
(** [annot_ends_with annot ann_name] returns true if the class name of [annot], without the package,
is equal to [ann_name] *)
let annot_ends_with ({class_name} : Annot.t) ann_name =
String.is_suffix class_name ~suffix:ann_name
&&
(* here, [class_name] ends with [ann_name] but it could be that it's just a suffix
of the last dot-component of [class_name], eg [class_name="x.y.z.a.bcd"] and
[ann_name="cd"]; in that case we want to fail the check, so we check that the
character just before the match is indeed a dot (or there is no dot at all). *)
let dot_pos = String.(length class_name - length ann_name - 1) in
Int.is_negative dot_pos || Char.equal '.' class_name.[dot_pos]
let class_name_matches s ((annot : Annot.t), _) = String.equal s annot.class_name
let ia_ends_with ia ann_name = List.exists ~f:(fun (a, _) -> annot_ends_with a ann_name) ia
let find_ia_ends_with ia ann_name = List.find ~f:(fun (a, _) -> annot_ends_with a ann_name) ia
let ia_contains ia ann_name = List.exists ~f:(class_name_matches ann_name) ia
let pdesc_get_return_annot pdesc =
(Procdesc.get_attributes pdesc).ProcAttributes.method_annotation.return
let pdesc_has_return_annot pdesc predicate = predicate (pdesc_get_return_annot pdesc)
let pdesc_return_annot_ends_with pdesc annot =
pdesc_has_return_annot pdesc (fun ia -> ia_ends_with ia annot)
(* note: we would use Summary.proc_resolve_attributes directly instead of requiring [attrs_of_pname],
but doing so creates a circular dependency *)
let pname_has_return_annot pname ~attrs_of_pname predicate =
match attrs_of_pname pname with
| Some attributes ->
predicate attributes.ProcAttributes.method_annotation.return
| None ->
false
let field_has_annot fieldname (struct_typ : Struct.t) predicate =
let fld_has_taint_annot (fname, _, annot) = Fieldname.equal fieldname fname && predicate annot in
List.exists ~f:fld_has_taint_annot struct_typ.fields
|| List.exists ~f:fld_has_taint_annot struct_typ.statics
let struct_typ_has_annot (struct_typ : Struct.t) predicate = predicate struct_typ.annots
let ia_is_not_thread_safe ia = ia_ends_with ia not_thread_safe
let ia_is_propagates_nullable ia = ia_ends_with ia propagates_nullable
let ia_is_nullable ia =
ia_ends_with ia nullable
(* @RecentlyNullable is a special annotation that was added to solve backward compatibility issues
for Android SDK migration.
See https://android-developers.googleblog.com/2018/08/android-pie-sdk-is-now-more-kotlin.html for details.
From nullsafe point of view, such annotations should be treated exactly as normal @Nullable annotation is treated.
(Actually, IDEs might even show it as @Nullable)
*)
|| ia_ends_with ia recently_nullable
|| ia_is_propagates_nullable ia
let ia_is_nonnull ia = List.exists ~f:(ia_ends_with ia) [nonnull; notnull; camel_nonnull]
let ia_is_nullsafe_strict ia = ia_ends_with ia nullsafe_strict
let ia_find_nullsafe ia = Option.map (find_ia_ends_with ia nullsafe) ~f:fst
let ia_is_false_on_null ia = ia_ends_with ia false_on_null
let ia_is_returns_ownership ia = ia_ends_with ia returns_ownership
let ia_is_synchronized_collection ia = ia_ends_with ia synchronized_collection
let ia_is_thread_safe ia = ia_ends_with ia thread_safe
let ia_is_thrift_service ia = ia_ends_with ia thrift_service
let ia_is_true_on_null ia = ia_ends_with ia true_on_null
let ia_is_nonblocking ia = ia_ends_with ia nonblocking
let ia_is_initializer ia = ia_ends_with ia initializer_
let ia_is_cleanup ia = ia_ends_with ia cleanup
let ia_is_volatile ia = ia_contains ia volatile
let field_injector_readwrite_list =
[ inject_view
; bind
; bind_view
; bind_array
; bind_bitmap
; bind_drawable
; bind_string
; suppress_view_nullability ]
let field_injector_readonly_list = inject :: field_injector_readwrite_list
(** Annotations for readonly injectors. The injector framework initializes the field but does not
write null into it. *)
let ia_is_field_injector_readonly ia = List.exists ~f:(ia_ends_with ia) field_injector_readonly_list
(** Annotations for read-write injectors. The injector framework initializes the field and can write
null into it. *)
let ia_is_field_injector_readwrite ia =
List.exists ~f:(ia_ends_with ia) field_injector_readwrite_list
let ia_is_expensive ia = ia_ends_with ia expensive
let ia_is_functional ia = ia_ends_with ia functional
let ia_is_ignore_allocations ia = ia_ends_with ia ignore_allocations
let ia_is_inject ia = ia_ends_with ia inject
let ia_is_json_field ia = ia_ends_with ia json_field
let ia_is_suppress_lint ia = ia_ends_with ia suppress_lint
let ia_is_thread_confined ia = ia_ends_with ia thread_confined
let ia_is_worker_thread ia = ia_ends_with ia worker_thread
(* methods annotated with the annotations below always run on the UI thread. *)
let ia_is_uithread_equivalent =
let annotations = [mainthread; ui_thread; on_bind; on_event; on_mount; on_unbind; on_unmount] in
fun ia -> List.exists annotations ~f:(ia_ends_with ia)