[nullsafe] Directly model nullability of values from third-party code

Summary:
We need to be able to differentiate `UncheckedNonnull`s in internal vs
third-party code. Previously, those were under one `UncheckedNonnull`
nullability which led to hacks for optmistic third-party parameter
checks in `eradicateChecks.ml` and lack of third-party enforcement in
`Nullsafe(LOCAL, trust=all)` mode (i.e. we want to trust internal
unchecked code, but don't want to trust unvetted third-party).

Now such values are properly modelled and can be accounted for
regularly within rules.

Also, various whitelists are refactored using
`Nullability.is_considered_nonnull ~nullsafe_mode nullability`.

`ErrorRenderingUtils` became a tad more convoluted, but oh well, one
step at a time.

Reviewed By: mityal

Differential Revision: D19977086

fbshipit-source-id: 8337a47b9
master
Artem Pianykh 5 years ago committed by Facebook Github Bot
parent b50f13eb18
commit 2572819a5b

@ -39,3 +39,7 @@ let pp fmt = function
let package {package} = package let package {package} = package
let classname {classname} = classname let classname {classname} = classname
let is_external_via_config t =
let package = package t in
Option.exists ~f:Config.java_package_is_external package

@ -18,3 +18,6 @@ val pp : Format.formatter -> t -> unit
val package : t -> string option val package : t -> string option
val classname : t -> string val classname : t -> string
val is_external_via_config : t -> bool
(** Considered external based on config flags. *)

@ -43,11 +43,17 @@ let get tenv field_name class_typ =
Typ.name class_typ Typ.name class_typ
|> Option.value_map ~f:(NullsafeMode.of_class tenv) ~default:NullsafeMode.Default |> Option.value_map ~f:(NullsafeMode.of_class tenv) ~default:NullsafeMode.Default
in in
let is_third_party =
ThirdPartyAnnotationInfo.is_third_party_typ
(ThirdPartyAnnotationGlobalRepo.get_repo ())
class_typ
in
Struct.get_field_info ~lookup field_name class_typ Struct.get_field_info ~lookup field_name class_typ
|> Option.map ~f:(fun (Struct.{typ= field_typ; annotations} as field_info) -> |> Option.map ~f:(fun (Struct.{typ= field_typ; annotations} as field_info) ->
let is_enum_value = is_enum_value tenv ~class_typ field_info in let is_enum_value = is_enum_value tenv ~class_typ field_info in
let nullability = let nullability =
AnnotatedNullability.of_type_and_annotation field_typ annotations ~nullsafe_mode AnnotatedNullability.of_type_and_annotation ~nullsafe_mode ~is_third_party field_typ
annotations
in in
let corrected_nullability = let corrected_nullability =
match nullability with match nullability with

@ -14,9 +14,11 @@ module F = Format
nullsafe omits Nullability information in types used for local variable declarations: this nullsafe omits Nullability information in types used for local variable declarations: this
information is inferred according to flow-sensitive inferrence rule. *) information is inferred according to flow-sensitive inferrence rule. *)
(** See {!Nullability.t} for explanation *)
type t = type t =
| Nullable of nullable_origin | Nullable of nullable_origin
| UncheckedNonnull of unchecked_nonnull_origin (** See {!Nullability.t} for explanation *) | ThirdPartyNonnull
| UncheckedNonnull of unchecked_nonnull_origin
| LocallyCheckedNonnull | LocallyCheckedNonnull
| StrictNonnull of strict_nonnull_origin | StrictNonnull of strict_nonnull_origin
[@@deriving compare] [@@deriving compare]
@ -51,6 +53,8 @@ and strict_nonnull_origin =
let get_nullability = function let get_nullability = function
| Nullable _ -> | Nullable _ ->
Nullability.Nullable Nullability.Nullable
| ThirdPartyNonnull ->
Nullability.ThirdPartyNonnull
| UncheckedNonnull _ -> | UncheckedNonnull _ ->
Nullability.UncheckedNonnull Nullability.UncheckedNonnull
| LocallyCheckedNonnull -> | LocallyCheckedNonnull ->
@ -88,6 +92,8 @@ let pp fmt t =
match t with match t with
| Nullable origin -> | Nullable origin ->
F.fprintf fmt "Nullable[%s]" (string_of_nullable_origin origin) F.fprintf fmt "Nullable[%s]" (string_of_nullable_origin origin)
| ThirdPartyNonnull ->
F.fprintf fmt "ThirdPartyNonnull"
| UncheckedNonnull origin -> | UncheckedNonnull origin ->
F.fprintf fmt "UncheckedNonnull[%s]" (string_of_declared_nonnull_origin origin) F.fprintf fmt "UncheckedNonnull[%s]" (string_of_declared_nonnull_origin origin)
| LocallyCheckedNonnull -> | LocallyCheckedNonnull ->
@ -96,7 +102,7 @@ let pp fmt t =
F.fprintf fmt "StrictNonnull[%s]" (string_of_nonnull_origin origin) F.fprintf fmt "StrictNonnull[%s]" (string_of_nonnull_origin origin)
let of_type_and_annotation ~(nullsafe_mode : NullsafeMode.t) typ annotations = let of_type_and_annotation ~nullsafe_mode ~is_third_party typ annotations =
if not (PatternMatch.type_is_class typ) then StrictNonnull PrimitiveType if not (PatternMatch.type_is_class typ) then StrictNonnull PrimitiveType
else if Annotations.ia_is_nullable annotations then else if Annotations.ia_is_nullable annotations then
let nullable_origin = let nullable_origin =
@ -111,6 +117,7 @@ let of_type_and_annotation ~(nullsafe_mode : NullsafeMode.t) typ annotations =
| NullsafeMode.Local _ -> | NullsafeMode.Local _ ->
LocallyCheckedNonnull LocallyCheckedNonnull
| NullsafeMode.Default -> | NullsafeMode.Default ->
if Annotations.ia_is_nonnull annotations then UncheckedNonnull AnnotatedNonnull if is_third_party then ThirdPartyNonnull
else if Annotations.ia_is_nonnull annotations then UncheckedNonnull AnnotatedNonnull
(* Currently, we treat not annotated types as nonnull *) (* Currently, we treat not annotated types as nonnull *)
else UncheckedNonnull ImplicitlyNonnull else UncheckedNonnull ImplicitlyNonnull

@ -16,9 +16,11 @@ open! IStd
user-provided annotations for local types, so annotated nullability applies only for types user-provided annotations for local types, so annotated nullability applies only for types
declared at methods and field level. *) declared at methods and field level. *)
(** See {!Nullability.t} for explanation *)
type t = type t =
| Nullable of nullable_origin | Nullable of nullable_origin
| UncheckedNonnull of unchecked_nonnull_origin (** See {!Nullability.t} for explanation *) | ThirdPartyNonnull
| UncheckedNonnull of unchecked_nonnull_origin
| LocallyCheckedNonnull | LocallyCheckedNonnull
| StrictNonnull of strict_nonnull_origin | StrictNonnull of strict_nonnull_origin
[@@deriving compare] [@@deriving compare]
@ -52,7 +54,8 @@ and strict_nonnull_origin =
val get_nullability : t -> Nullability.t val get_nullability : t -> Nullability.t
val of_type_and_annotation : nullsafe_mode:NullsafeMode.t -> Typ.t -> Annot.Item.t -> t val of_type_and_annotation :
nullsafe_mode:NullsafeMode.t -> is_third_party:bool -> Typ.t -> Annot.Item.t -> t
(** Given the type and its annotations, returns its nullability. NOTE: it does not take into account (** Given the type and its annotations, returns its nullability. NOTE: it does not take into account
models etc., so this is intended to be used as a helper function for more high-level annotation models etc., so this is intended to be used as a helper function for more high-level annotation
processing. *) processing. *)

@ -35,10 +35,11 @@ and model_source = InternalModel | ThirdPartyRepo of {filename: string; line_num
[@@deriving compare] [@@deriving compare]
(* get nullability of method's return type given its annotations and information about its params *) (* get nullability of method's return type given its annotations and information about its params *)
let nullability_for_return ret_type ret_annotations ~nullsafe_mode ~has_propagates_nullable_in_param let nullability_for_return ret_type ret_annotations ~nullsafe_mode ~is_third_party
= ~has_propagates_nullable_in_param =
let nullability = let nullability =
AnnotatedNullability.of_type_and_annotation ~nullsafe_mode ret_type ret_annotations AnnotatedNullability.of_type_and_annotation ~nullsafe_mode ~is_third_party ret_type
ret_annotations
in in
(* if any param is annotated with propagates nullable, then the result is nullable *) (* if any param is annotated with propagates nullable, then the result is nullable *)
match nullability with match nullability with
@ -53,10 +54,12 @@ let nullability_for_return ret_type ret_annotations ~nullsafe_mode ~has_propagat
(* Given annotations for method signature, extract nullability information (* Given annotations for method signature, extract nullability information
for return type and params *) for return type and params *)
let extract_nullability ~nullsafe_mode ret_type ret_annotations param_annotated_types = let extract_nullability ~nullsafe_mode ~is_third_party ret_type ret_annotations
param_annotated_types =
let params_nullability = let params_nullability =
List.map param_annotated_types ~f:(fun (typ, annotations) -> List.map param_annotated_types ~f:(fun (typ, annotations) ->
AnnotatedNullability.of_type_and_annotation typ annotations ~nullsafe_mode ) AnnotatedNullability.of_type_and_annotation typ annotations ~nullsafe_mode ~is_third_party
)
in in
let has_propagates_nullable_in_param = let has_propagates_nullable_in_param =
List.exists params_nullability ~f:(function List.exists params_nullability ~f:(function
@ -66,7 +69,8 @@ let extract_nullability ~nullsafe_mode ret_type ret_annotations param_annotated_
false ) false )
in in
let return_nullability = let return_nullability =
nullability_for_return ret_type ret_annotations ~nullsafe_mode ~has_propagates_nullable_in_param nullability_for_return ret_type ret_annotations ~nullsafe_mode ~is_third_party
~has_propagates_nullable_in_param
in in
(return_nullability, params_nullability) (return_nullability, params_nullability)
@ -77,6 +81,12 @@ let get ~nullsafe_mode proc_attributes : t =
in in
let formals = proc_attributes.ProcAttributes.formals in let formals = proc_attributes.ProcAttributes.formals in
let ret_type = proc_attributes.ProcAttributes.ret_type in let ret_type = proc_attributes.ProcAttributes.ret_type in
let procname = proc_attributes.ProcAttributes.proc_name in
let is_third_party =
ThirdPartyAnnotationInfo.is_third_party_proc
(ThirdPartyAnnotationGlobalRepo.get_repo ())
procname
in
(* zip formal params with annotation *) (* zip formal params with annotation *)
let params_with_annotations = let params_with_annotations =
let rec zip_params ial parl = let rec zip_params ial parl =
@ -100,7 +110,7 @@ let get ~nullsafe_mode proc_attributes : t =
List.map params_with_annotations ~f:(fun ((_, typ), annotations) -> (typ, annotations)) List.map params_with_annotations ~f:(fun ((_, typ), annotations) -> (typ, annotations))
in in
let return_nullability, params_nullability = let return_nullability, params_nullability =
extract_nullability ~nullsafe_mode ret_type ret_annotation param_annotated_types extract_nullability ~nullsafe_mode ~is_third_party ret_type ret_annotation param_annotated_types
in in
let ret = let ret =
{ ret_annotation_deprecated= ret_annotation { ret_annotation_deprecated= ret_annotation

@ -22,38 +22,22 @@ and function_info =
; param_position: int ; param_position: int
; function_procname: Procname.t } ; function_procname: Procname.t }
(** For better adoption we allow certain conversions. Otherwise using code checked under different
nullsafe modes becomes a pain because of extra warnings. *)
module AssignmentWhitelist = struct
let all_whitelisted =
[ (Nullability.StrictNonnull, Nullability.UncheckedNonnull)
; (Nullability.LocallyCheckedNonnull, Nullability.UncheckedNonnull)
; (Nullability.StrictNonnull, Nullability.LocallyCheckedNonnull) ]
let all_whitelisted_in_mode = function
| NullsafeMode.Default | NullsafeMode.Local NullsafeMode.Trust.All ->
all_whitelisted
| NullsafeMode.Local (NullsafeMode.Trust.Only ([] as _classes))
| NullsafeMode.Local (NullsafeMode.Trust.Only _classes) ->
(* TODO(T61473665): case with specified non-empty classes not supported now, defaulting to trust=none *)
[(Nullability.StrictNonnull, Nullability.LocallyCheckedNonnull)]
| NullsafeMode.Strict ->
[]
let is_allowed_in_mode ~nullsafe_mode ~lhs ~rhs =
List.exists (all_whitelisted_in_mode nullsafe_mode) ~f:(Nullability.equal_pair (lhs, rhs))
let is_potentially_allowed ~lhs ~rhs =
List.exists all_whitelisted ~f:(Nullability.equal_pair (lhs, rhs))
end
let check ~(nullsafe_mode : NullsafeMode.t) ~lhs ~rhs = let check ~(nullsafe_mode : NullsafeMode.t) ~lhs ~rhs =
let falls_under_optimistic_third_party =
match nullsafe_mode with
| NullsafeMode.Default when Config.nullsafe_optimistic_third_party_params_in_non_strict -> (
match lhs with Nullability.ThirdPartyNonnull -> true | _ -> false )
| _ ->
false
in
let is_allowed_assignment = let is_allowed_assignment =
Nullability.is_subtype ~subtype:rhs ~supertype:lhs Nullability.is_subtype ~supertype:lhs ~subtype:rhs
|| AssignmentWhitelist.is_allowed_in_mode ~nullsafe_mode ~lhs ~rhs || falls_under_optimistic_third_party
(* For better adoption we allow certain conversions. Otherwise using code checked under
different nullsafe modes becomes a pain because of extra warnings. *)
(* TODO(T62521386): consider using caller context when determining nullability to get rid of
white-lists. *)
|| Nullability.is_considered_nonnull ~nullsafe_mode rhs
in in
Result.ok_if_true is_allowed_assignment ~error:{nullsafe_mode; lhs; rhs} Result.ok_if_true is_allowed_assignment ~error:{nullsafe_mode; lhs; rhs}
@ -95,17 +79,17 @@ let mk_description_for_bad_param_passed
"`null`" "`null`"
| Nullability.Nullable -> | Nullability.Nullable ->
"nullable" "nullable"
| Nullability.StrictNonnull | other ->
| Nullability.UncheckedNonnull Logging.die InternalError
| Nullability.LocallyCheckedNonnull -> "mk_description_for_bad_param:: invariant violation: unexpected nullability %a"
Logging.die InternalError "Invariant violation: unexpected nullability" Nullability.pp other
in in
Format.asprintf "%a is %s" MF.pp_monospaced actual_param_expression nullability_descr Format.asprintf "%a is %s" MF.pp_monospaced actual_param_expression nullability_descr
in in
let suggested_file_to_add_third_party = let suggested_file_to_add_third_party =
match model_source with match model_source with
| None -> | None ->
ThirdPartyAnnotationInfo.lookup_related_sig_file_by_package ThirdPartyAnnotationInfo.lookup_related_sig_file_for_proc
(ThirdPartyAnnotationGlobalRepo.get_repo ()) (ThirdPartyAnnotationGlobalRepo.get_repo ())
function_procname function_procname
| Some _ -> | Some _ ->
@ -158,61 +142,69 @@ let get_issue_type = function
IssueType.eradicate_return_not_nullable IssueType.eradicate_return_not_nullable
let violation_description {nullsafe_mode; lhs; rhs} ~assignment_location assignment_type ~rhs_origin let violation_description {nullsafe_mode; rhs} ~assignment_location assignment_type ~rhs_origin =
= let special_message =
if AssignmentWhitelist.is_potentially_allowed ~lhs ~rhs then if not (NullsafeMode.equal NullsafeMode.Default nullsafe_mode) then
(* This type of violation is more subtle than the normal case, so it should ErrorRenderingUtils.mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability:rhs
be rendered in a special way. An 'impossible case' is checked in the ~bad_usage_location:assignment_location rhs_origin
following call and will cause infer to die. *) else None
ErrorRenderingUtils.mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability:rhs in
~bad_usage_location:assignment_location rhs_origin match special_message with
else | Some desc ->
let nullability_evidence = desc
get_origin_opt assignment_type rhs_origin | _ ->
|> Option.bind ~f:(fun origin -> TypeOrigin.get_description origin) let nullability_evidence =
in get_origin_opt assignment_type rhs_origin
let nullability_evidence_as_suffix = |> Option.bind ~f:(fun origin -> TypeOrigin.get_description origin)
Option.value_map nullability_evidence ~f:(fun evidence -> ": " ^ evidence) ~default:"" in
in let nullability_evidence_as_suffix =
let module MF = MarkupFormatter in Option.value_map nullability_evidence ~f:(fun evidence -> ": " ^ evidence) ~default:""
let error_message = in
match assignment_type with let module MF = MarkupFormatter in
| PassingParamToFunction function_info -> let error_message =
mk_description_for_bad_param_passed function_info nullability_evidence match assignment_type with
~param_nullability:rhs | PassingParamToFunction function_info ->
| AssigningToField field_name -> mk_description_for_bad_param_passed function_info nullability_evidence
let rhs_description = ~param_nullability:rhs
Nullability.( | AssigningToField field_name ->
match rhs with let rhs_description =
| Null -> Nullability.(
"`null`" match rhs with
| Nullable -> | Null ->
"a nullable" "`null`"
| StrictNonnull | UncheckedNonnull | LocallyCheckedNonnull -> | Nullable ->
Logging.die InternalError "Invariant violation: unexpected nullability") "a nullable"
in | other ->
Format.asprintf "%a is declared non-nullable but is assigned %s%s." MF.pp_monospaced Logging.die InternalError
(Fieldname.get_field_name field_name) "violation_description(assign_field):: invariant violation: unexpected \
rhs_description nullability_evidence_as_suffix nullability %a"
| ReturningFromFunction function_proc_name -> Nullability.pp other)
let return_description = in
Nullability.( Format.asprintf "%a is declared non-nullable but is assigned %s%s." MF.pp_monospaced
match rhs with (Fieldname.get_field_name field_name)
| Null -> rhs_description nullability_evidence_as_suffix
(* Return `null` in all_whitelisted branches *) | ReturningFromFunction function_proc_name ->
"`null`" let return_description =
| Nullable -> Nullability.(
"a nullable value" match rhs with
| StrictNonnull | UncheckedNonnull | LocallyCheckedNonnull -> | Null ->
Logging.die InternalError "Invariant violation: unexpected nullability") (* Return `null` in all_whitelisted branches *)
in "`null`"
Format.asprintf "%a: return type is declared non-nullable but the method returns %s%s." | Nullable ->
MF.pp_monospaced "a nullable value"
(Procname.to_simplified_string ~withclass:false function_proc_name) | other ->
return_description nullability_evidence_as_suffix Logging.die InternalError
in "violation_description(ret_fun):: invariant violation: unexpected \
let issue_type = get_issue_type assignment_type in nullability %a"
(error_message, issue_type, assignment_location) Nullability.pp other)
in
Format.asprintf "%a: return type is declared non-nullable but the method returns %s%s."
MF.pp_monospaced
(Procname.to_simplified_string ~withclass:false function_proc_name)
return_description nullability_evidence_as_suffix
in
let issue_type = get_issue_type assignment_type in
(error_message, issue_type, assignment_location)
let violation_severity {nullsafe_mode} = NullsafeMode.severity nullsafe_mode let violation_severity {nullsafe_mode} = NullsafeMode.severity nullsafe_mode

@ -19,22 +19,9 @@ let check ~nullsafe_mode nullability =
match nullability with match nullability with
| Nullability.Nullable | Nullability.Null -> | Nullability.Nullable | Nullability.Null ->
Error {nullsafe_mode; nullability} Error {nullsafe_mode; nullability}
| Nullability.UncheckedNonnull -> ( | other when not (Nullability.is_considered_nonnull ~nullsafe_mode other) ->
match nullsafe_mode with Error {nullsafe_mode; nullability}
| NullsafeMode.Strict | NullsafeMode.Local (NullsafeMode.Trust.Only []) -> | _ ->
Error {nullsafe_mode; nullability}
| NullsafeMode.Local (NullsafeMode.Trust.Only _typ_names) ->
(* TODO(T61473665). For now treat as trust=none. *)
Error {nullsafe_mode; nullability}
| NullsafeMode.Local NullsafeMode.Trust.All | NullsafeMode.Default ->
Ok () )
| Nullability.LocallyCheckedNonnull -> (
match nullsafe_mode with
| NullsafeMode.Default | NullsafeMode.Local _ ->
Ok ()
| NullsafeMode.Strict ->
Error {nullsafe_mode; nullability} )
| Nullability.StrictNonnull ->
Ok () Ok ()
@ -52,12 +39,15 @@ let get_origin_opt ~nullable_object_descr origin =
let violation_description {nullsafe_mode; nullability} ~dereference_location dereference_type let violation_description {nullsafe_mode; nullability} ~dereference_location dereference_type
~nullable_object_descr ~nullable_object_origin = ~nullable_object_descr ~nullable_object_origin =
let module MF = MarkupFormatter in let module MF = MarkupFormatter in
match nullability with let special_message =
| Nullability.UncheckedNonnull | Nullability.LocallyCheckedNonnull -> if not (NullsafeMode.equal NullsafeMode.Default nullsafe_mode) then
(* These types of violations are possible in non-default nullsafe mode. For them we
provide tailored error messages. *)
ErrorRenderingUtils.mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability:nullability ErrorRenderingUtils.mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability:nullability
~bad_usage_location:dereference_location nullable_object_origin ~bad_usage_location:dereference_location nullable_object_origin
else None
in
match special_message with
| Some desc ->
desc
| _ -> | _ ->
let what_is_dereferred_str = let what_is_dereferred_str =
match dereference_type with match dereference_type with
@ -104,10 +94,10 @@ let violation_description {nullsafe_mode; nullability} ~dereference_location der
| Nullability.Nullable -> | Nullability.Nullable ->
Format.sprintf "%s is nullable and is not locally checked for null when %s%s." Format.sprintf "%s is nullable and is not locally checked for null when %s%s."
what_is_dereferred_str action_descr suffix what_is_dereferred_str action_descr suffix
| Nullability.UncheckedNonnull | other ->
| Nullability.LocallyCheckedNonnull Logging.die InternalError
| Nullability.StrictNonnull -> "violation_description:: invariant violation: unexpected nullability %a"
Logging.die InternalError "Invariant violation: unexpected nullability" Nullability.pp other
in in
(description, IssueType.eradicate_nullable_dereference, dereference_location) (description, IssueType.eradicate_nullable_dereference, dereference_location)

@ -6,6 +6,7 @@
*) *)
open! IStd open! IStd
module F = Format
let is_object_nullability_self_explanatory ~object_expression (object_origin : TypeOrigin.t) = let is_object_nullability_self_explanatory ~object_expression (object_origin : TypeOrigin.t) =
(* Fundamentally, object can be of two kinds: (* Fundamentally, object can be of two kinds:
@ -85,41 +86,52 @@ let get_field_class_name field_name =
let mk_coming_from nullsafe_mode nullability = let mk_coming_from nullsafe_mode nullability =
match (nullsafe_mode, nullability) with match (nullsafe_mode, nullability) with
| NullsafeMode.Strict, Nullability.ThirdPartyNonnull
| NullsafeMode.Strict, Nullability.UncheckedNonnull -> | NullsafeMode.Strict, Nullability.UncheckedNonnull ->
"non-strict classes" Some "non-strict classes"
| NullsafeMode.Strict, Nullability.LocallyCheckedNonnull -> | NullsafeMode.Strict, Nullability.LocallyCheckedNonnull ->
"nullsafe-local classes" Some "nullsafe-local classes"
| NullsafeMode.Local _, Nullability.ThirdPartyNonnull ->
Some "third-party classes"
| NullsafeMode.Local _, Nullability.UncheckedNonnull -> | NullsafeMode.Local _, Nullability.UncheckedNonnull ->
"non-nullsafe classes" Some "non-nullsafe classes"
| (_ as mode), nullability -> | _ ->
Logging.die InternalError "Unexpected: using %s in %a should not be a violation" None
(Nullability.to_string nullability)
NullsafeMode.pp mode
let mk_recommendation nullsafe_mode nullability what = let mk_recommendation nullsafe_mode nullability what =
match (nullsafe_mode, nullability) with match (nullsafe_mode, nullability) with
| NullsafeMode.Strict, Nullability.ThirdPartyNonnull
| NullsafeMode.Strict, Nullability.UncheckedNonnull | NullsafeMode.Strict, Nullability.UncheckedNonnull
| NullsafeMode.Strict, Nullability.LocallyCheckedNonnull -> | NullsafeMode.Strict, Nullability.LocallyCheckedNonnull ->
Format.sprintf "make %s nullsafe strict" what Some (F.sprintf "make %s nullsafe strict" what)
| NullsafeMode.Local _, Nullability.UncheckedNonnull -> | NullsafeMode.Local _, Nullability.UncheckedNonnull ->
Format.sprintf "make %s nullsafe" what Some (F.sprintf "make %s nullsafe" what)
| (_ as mode), nullability -> | _ ->
Logging.die InternalError "Unexpected: using %s in %a should not be a violation" None
(Nullability.to_string nullability)
NullsafeMode.pp mode
let mk_recommendation_for_third_party_field nullsafe_mode nullability field =
match (nullsafe_mode, nullability) with
| NullsafeMode.Strict, Nullability.ThirdPartyNonnull ->
Some (F.sprintf "access %s via a nullsafe strict getter" field)
| NullsafeMode.Local _, Nullability.ThirdPartyNonnull ->
Some (F.sprintf "access %s via a nullsafe getter" field)
| _ ->
None
let get_info object_origin nullsafe_mode bad_nullability = let get_info object_origin nullsafe_mode bad_nullability =
let open IOption.Let_syntax in
match object_origin with match object_origin with
| TypeOrigin.MethodCall {pname; call_loc} -> | TypeOrigin.MethodCall {pname; call_loc} ->
let offending_object = let offending_object =
Format.asprintf "%a" MarkupFormatter.pp_monospaced F.asprintf "%a" MarkupFormatter.pp_monospaced
(Procname.to_simplified_string ~withclass:true pname) (Procname.to_simplified_string ~withclass:true pname)
in in
let object_loc = call_loc in let object_loc = call_loc in
let suggested_third_party_sig_file = let suggested_third_party_sig_file =
ThirdPartyAnnotationInfo.lookup_related_sig_file_by_package ThirdPartyAnnotationInfo.lookup_related_sig_file_for_proc
(ThirdPartyAnnotationGlobalRepo.get_repo ()) (ThirdPartyAnnotationGlobalRepo.get_repo ())
pname pname
in in
@ -127,21 +139,29 @@ let get_info object_origin nullsafe_mode bad_nullability =
(* Two main cases: it is either FB owned code or third party. (* Two main cases: it is either FB owned code or third party.
We determine the difference based on presense of suggested_third_party_sig_file. We determine the difference based on presense of suggested_third_party_sig_file.
*) *)
let coming_from_explanation, recommendation, issue_type = let+ coming_from_explanation, recommendation, issue_type =
let what_to_strictify =
Option.value (get_method_class_name pname) ~default:offending_object
in
match suggested_third_party_sig_file with match suggested_third_party_sig_file with
| None ->
( mk_coming_from nullsafe_mode bad_nullability
, mk_recommendation nullsafe_mode bad_nullability what_to_strictify
, IssueType.eradicate_unchecked_usage_in_nullsafe )
| Some sig_file_name -> | Some sig_file_name ->
( "not vetted third party methods" (* Dereferences or assignment violations with regular Nullables
, Format.sprintf "add the correct signature to %s" should not be special cased *)
(ThirdPartyAnnotationGlobalRepo.get_user_friendly_third_party_sig_file_name if Nullability.equal Nullable bad_nullability then None
~filename:sig_file_name) else
, IssueType.eradicate_unvetted_third_party_in_nullsafe ) return
( "not vetted third party methods"
, F.sprintf "add the correct signature to %s"
(ThirdPartyAnnotationGlobalRepo.get_user_friendly_third_party_sig_file_name
~filename:sig_file_name)
, IssueType.eradicate_unvetted_third_party_in_nullsafe )
| None ->
let* from = mk_coming_from nullsafe_mode bad_nullability in
let+ recommendation =
let what_to_strictify =
Option.value (get_method_class_name pname) ~default:offending_object
in
mk_recommendation nullsafe_mode bad_nullability what_to_strictify
in
let issue_type = IssueType.eradicate_unchecked_usage_in_nullsafe in
(from, recommendation, issue_type)
in in
{ offending_object { offending_object
; object_loc ; object_loc
@ -150,41 +170,45 @@ let get_info object_origin nullsafe_mode bad_nullability =
; recommendation ; recommendation
; issue_type } ; issue_type }
| TypeOrigin.Field {field_name; access_loc} -> | TypeOrigin.Field {field_name; access_loc} ->
let offending_object = let qualified_name =
Format.asprintf "%a" MarkupFormatter.pp_monospaced F.asprintf "%a" MarkupFormatter.pp_monospaced (Fieldname.to_simplified_string field_name)
(Fieldname.to_simplified_string field_name) in
let unqualified_name =
F.asprintf "%a" MarkupFormatter.pp_monospaced (Fieldname.get_field_name field_name)
in in
let object_loc = access_loc in let object_loc = access_loc in
(* TODO: currently we do not support third-party annotations for fields. Because of this, (* TODO: currently we do not support third-party annotations for fields. Because of this,
render error like it is a non-stict class. *) render error like it is a non-stict class. *)
let what_is_used = "This field" in let what_is_used = "This field" in
let coming_from_explanation = mk_coming_from nullsafe_mode bad_nullability in let* coming_from_explanation = mk_coming_from nullsafe_mode bad_nullability in
let recommendation = let+ recommendation =
mk_recommendation nullsafe_mode bad_nullability (get_field_class_name field_name) Option.first_some
(mk_recommendation_for_third_party_field nullsafe_mode bad_nullability unqualified_name)
(mk_recommendation nullsafe_mode bad_nullability (get_field_class_name field_name))
in in
let issue_type = IssueType.eradicate_unchecked_usage_in_nullsafe in let issue_type = IssueType.eradicate_unchecked_usage_in_nullsafe in
{ offending_object { offending_object= qualified_name
; object_loc ; object_loc
; coming_from_explanation ; coming_from_explanation
; what_is_used ; what_is_used
; recommendation ; recommendation
; issue_type } ; issue_type }
| _ -> | _ ->
Logging.die Logging.InternalError None
"Invariant violation: unexpected origin of declared non-nullable value"
let mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability ~bad_usage_location object_origin = let mk_special_nullsafe_issue ~nullsafe_mode ~bad_nullability ~bad_usage_location object_origin =
let { offending_object let open IOption.Let_syntax in
; object_loc let+ { offending_object
; coming_from_explanation ; object_loc
; what_is_used ; coming_from_explanation
; recommendation ; what_is_used
; issue_type } = ; recommendation
; issue_type } =
get_info object_origin nullsafe_mode bad_nullability get_info object_origin nullsafe_mode bad_nullability
in in
let description = let description =
Format.asprintf F.asprintf
"%s: `@Nullsafe%a` mode prohibits using values coming from %s without a check. %s is used at \ "%s: `@Nullsafe%a` mode prohibits using values coming from %s without a check. %s is used at \
line %d. Either add a local check for null or assertion, or %s." line %d. Either add a local check for null or assertion, or %s."
offending_object NullsafeMode.pp nullsafe_mode coming_from_explanation what_is_used offending_object NullsafeMode.pp nullsafe_mode coming_from_explanation what_is_used

@ -18,7 +18,7 @@ val mk_special_nullsafe_issue :
-> bad_nullability:Nullability.t -> bad_nullability:Nullability.t
-> bad_usage_location:Location.t -> bad_usage_location:Location.t
-> TypeOrigin.t -> TypeOrigin.t
-> string * IssueType.t * Location.t -> (string * IssueType.t * Location.t) option
(** Situation when we tried to use nonnull values of incompatible modes. This is disallowed in (** Situation when we tried to use nonnull values of incompatible modes. This is disallowed in
strict and local mode. Returns a tuple (error message, issue type, error location). NOTE: strict and local mode. Returns a tuple (error message, issue type, error location). NOTE:
Location of the error will be NOT in the place when the value is used (that is Location of the error will be NOT in the place when the value is used (that is

@ -13,11 +13,9 @@ let create origin = {nullability= TypeOrigin.get_nullability origin; origin}
let get_nullability {nullability} = nullability let get_nullability {nullability} = nullability
let is_nonnull_or_declared_nonnull {nullability} = let is_nonnullish {nullability} = Nullability.is_nonnullish nullability
match nullability with StrictNonnull | UncheckedNonnull -> true | _ -> false
let pp fmt {nullability} = Nullability.pp fmt nullability
let to_string {nullability} = Printf.sprintf "[%s]" (Nullability.to_string nullability)
let join t1 t2 = let join t1 t2 =
let joined_nullability = Nullability.join t1.nullability t2.nullability in let joined_nullability = Nullability.join t1.nullability t2.nullability in

@ -20,7 +20,8 @@ val get_nullability : t -> Nullability.t
val create : TypeOrigin.t -> t val create : TypeOrigin.t -> t
val is_nonnull_or_declared_nonnull : t -> bool val is_nonnullish : t -> bool
(** Check whether corresponding [Nullability] is [Nullability.is_nonnullish] *)
val get_origin : t -> TypeOrigin.t val get_origin : t -> TypeOrigin.t
(** The simple explanation of how was nullability inferred. *) (** The simple explanation of how was nullability inferred. *)
@ -39,4 +40,4 @@ val join : t -> t -> t
val origin_is_fun_library : t -> bool val origin_is_fun_library : t -> bool
val to_string : t -> string val pp : Format.formatter -> t -> unit

@ -12,15 +12,13 @@ type violation = {nullsafe_mode: NullsafeMode.t; base: Nullability.t; overridden
type type_role = Param | Ret type type_role = Param | Ret
let is_whitelisted_violation ~subtype ~supertype = let is_whitelisted_violation ~subtype ~supertype =
match (subtype, supertype) with (* When both nullabilities are kind-of non-nullable we don't want to raise the
| Nullability.UncheckedNonnull, Nullability.StrictNonnull -> issue. Without this suppression there will be a lot of non-actionable issues
(* It is a violation that we are currently willing to ignore because raised for classes in one [NullsafeMode] inheriting from classes in the other
it is hard to obey in practice. [NullsafeMode]. *)
It might lead to unsoundness issues, so this might be reconsidered. (* TODO(T62521386): consider using caller context when determining nullability to get
*) rid of white-lists. *)
true Nullability.is_nonnullish subtype && Nullability.is_nonnullish supertype
| _ ->
false
let check ~nullsafe_mode type_role ~base ~overridden = let check ~nullsafe_mode type_role ~base ~overridden =

@ -6,10 +6,14 @@
*) *)
open! IStd open! IStd
module F = Format
type t = type t =
| Null (** The only possible value for that type is null *) | Null (** The only possible value for that type is null *)
| Nullable (** No guarantees on the nullability *) | Nullable (** No guarantees on the nullability *)
| ThirdPartyNonnull
(** Values coming from third-party methods and fields not explictly annotated as [@Nullable].
We still consider those as non-nullable but with the least level of confidence. *)
| UncheckedNonnull | UncheckedNonnull
(** The type comes from a signature that is annotated (explicitly or implicitly according to (** The type comes from a signature that is annotated (explicitly or implicitly according to
conventions) as non-nullable. However, it might still contain null since the truthfulness conventions) as non-nullable. However, it might still contain null since the truthfulness
@ -43,6 +47,8 @@ let join x y =
Nullable Nullable
| Nullable, _ | _, Nullable -> | Nullable, _ | _, Nullable ->
Nullable Nullable
| ThirdPartyNonnull, _ | _, ThirdPartyNonnull ->
ThirdPartyNonnull
| UncheckedNonnull, _ | _, UncheckedNonnull -> | UncheckedNonnull, _ | _, UncheckedNonnull ->
UncheckedNonnull UncheckedNonnull
| LocallyCheckedNonnull, _ | _, LocallyCheckedNonnull -> | LocallyCheckedNonnull, _ | _, LocallyCheckedNonnull ->
@ -53,14 +59,37 @@ let join x y =
let is_subtype ~subtype ~supertype = equal (join subtype supertype) supertype let is_subtype ~subtype ~supertype = equal (join subtype supertype) supertype
let is_considered_nonnull ~nullsafe_mode nullability =
let least_required =
match nullsafe_mode with
| NullsafeMode.Strict ->
StrictNonnull
| NullsafeMode.Local (NullsafeMode.Trust.Only _classes) ->
(* TODO(T61473665). For now treat trust with specified classes as trust=none. *)
LocallyCheckedNonnull
| NullsafeMode.Local NullsafeMode.Trust.All ->
UncheckedNonnull
| NullsafeMode.Default ->
ThirdPartyNonnull
in
is_subtype ~subtype:nullability ~supertype:least_required
let is_nonnullish t = is_considered_nonnull ~nullsafe_mode:NullsafeMode.Default t
let to_string = function let to_string = function
| Null -> | Null ->
"Null" "Null"
| Nullable -> | Nullable ->
"Nullable" "Nullable"
| ThirdPartyNonnull ->
"ThirdPartyNonnull"
| UncheckedNonnull -> | UncheckedNonnull ->
"UncheckedNonnull" "UncheckedNonnull"
| LocallyCheckedNonnull -> | LocallyCheckedNonnull ->
"LocallyCheckedNonnull" "LocallyCheckedNonnull"
| StrictNonnull -> | StrictNonnull ->
"StrictNonnull" "StrictNonnull"
let pp fmt t = F.fprintf fmt "%s" (to_string t)

@ -6,6 +6,7 @@
*) *)
open! IStd open! IStd
module F = Format
(** Nullability is a central concept for Nullsafe type-checker. Informally, nullability is a "type" (** Nullability is a central concept for Nullsafe type-checker. Informally, nullability is a "type"
\- set of values together with some additional context. All nullsafe is interested about if \- set of values together with some additional context. All nullsafe is interested about if
@ -18,6 +19,9 @@ open! IStd
type t = type t =
| Null (** The only possible value for that type is null *) | Null (** The only possible value for that type is null *)
| Nullable (** No guarantees on the nullability *) | Nullable (** No guarantees on the nullability *)
| ThirdPartyNonnull
(** Values coming from third-party methods and fields not explictly annotated as [@Nullable].
We still consider those as non-nullable but with the least level of confidence. *)
| UncheckedNonnull | UncheckedNonnull
(** The type comes from a signature that is annotated (explicitly or implicitly according to (** The type comes from a signature that is annotated (explicitly or implicitly according to
conventions) as non-nullable. Hovewer, it might still contain null since the truthfullness conventions) as non-nullable. Hovewer, it might still contain null since the truthfullness
@ -48,4 +52,10 @@ val join : t -> t -> t
(** Unique upper bound over two types: the most precise type that is a supertype of both. (** Unique upper bound over two types: the most precise type that is a supertype of both.
Practically, joins occur e.g. when two branches of execution flow are getting merged. *) Practically, joins occur e.g. when two branches of execution flow are getting merged. *)
val to_string : t -> string val is_considered_nonnull : nullsafe_mode:NullsafeMode.t -> t -> bool
(** Check whether a given nullability is considered non-nullable within a given [nullsafe_mode]. *)
val is_nonnullish : t -> bool
(** Check whether a given nullability is one of the non-nullable types with no regards to the mode. *)
val pp : F.formatter -> t -> unit

@ -71,14 +71,12 @@ let does_package_match_file ~package sig_filename =
let lookup_related_sig_file {filenames} ~package = let lookup_related_sig_file {filenames} ~package =
List.filter filenames ~f:(does_package_match_file ~package) List.filter filenames ~f:(does_package_match_file ~package)
|> List.max_elt (* In case two different files match the package, we choose the most specific;
~compare:(fun (* If two different files match the package, we choose the most specific; it will have the biggest length *) it will have the longest length *)
filename1 |> List.max_elt ~compare:(fun name1 name2 -> String.length name1 - String.length name2)
filename2
-> String.length filename1 - String.length filename2 )
let lookup_related_sig_file_by_package storage procname = let lookup_related_sig_file_for_proc storage procname =
let package = let package =
match procname with match procname with
| Procname.Java java_pname -> | Procname.Java java_pname ->
@ -87,3 +85,33 @@ let lookup_related_sig_file_by_package storage procname =
None None
in in
Option.bind package ~f:(fun package -> lookup_related_sig_file storage ~package) Option.bind package ~f:(fun package -> lookup_related_sig_file storage ~package)
let is_third_party_proc storage procname =
let is_from_config =
match procname with
| Procname.Java java_pname ->
Procname.Java.is_external java_pname
| _ ->
false
in
let lookup_sig_file _ = lookup_related_sig_file_for_proc storage procname in
is_from_config || Option.is_some (lookup_sig_file ())
(* There is a bit of duplication relative to [is_third_party_proc] due to mismatch between
[Typ.Name.Java] and [JavaClassName]. When those types are consolidated would be a good
idea to refactor this function. *)
let is_third_party_typ storage typ =
IOption.Let_syntax.(
let* typ_name = Typ.name typ in
let+ class_name =
match typ_name with Typ.JavaClass class_name -> return class_name | _ -> None
in
let is_from_config = JavaClassName.is_external_via_config class_name in
let lookup_sig_file _ =
let* package = JavaClassName.package class_name in
lookup_related_sig_file storage ~package
in
is_from_config || Option.is_some (lookup_sig_file ()))
|> Option.value ~default:false

@ -33,6 +33,13 @@ val find_nullability_info : storage -> ThirdPartyMethod.unique_repr -> signature
val lookup_related_sig_file : storage -> package:string -> string option val lookup_related_sig_file : storage -> package:string -> string option
(** If the package is third-party, return the relevant .sig file to add signatures for this package. *) (** If the package is third-party, return the relevant .sig file to add signatures for this package. *)
val lookup_related_sig_file_by_package : storage -> Procname.t -> string option val lookup_related_sig_file_for_proc : storage -> Procname.t -> string option
(** If the function is third-party (based on its package), return relevant .sig file to add the (** If the function is third-party (based on its package), return relevant .sig file *)
signature NOTE: this DOES NOT look if the function is is already *)
val is_third_party_proc : storage -> Procname.t -> bool
(** Checks whether a required procname comes from third-party code based on available .sig files and
config flags. NOTE: considering config flags is done for compatibility with the legacy behaviour
and will be removed in the future *)
val is_third_party_typ : storage -> Typ.t -> bool
(** See [is_third_party_proc]. *)

@ -97,7 +97,7 @@ let check_condition_for_redundancy tenv ~is_always_true find_canonical_duplicate
*) *)
(* NOTE: We don't report the opposite case, namely when the expression is known to be always `null`. Consider changing this. (* NOTE: We don't report the opposite case, namely when the expression is known to be always `null`. Consider changing this.
*) *)
InferredNullability.is_nonnull_or_declared_nonnull inferred_nullability InferredNullability.is_nonnullish inferred_nullability
&& Config.eradicate_condition_redundant && (not is_temp) && PatternMatch.type_is_class typ && Config.eradicate_condition_redundant && (not is_temp) && PatternMatch.type_is_class typ
&& (not (from_try_with_resources ())) && (not (from_try_with_resources ()))
&& not (InferredNullability.origin_is_fun_library inferred_nullability) && not (InferredNullability.origin_is_fun_library inferred_nullability)
@ -168,22 +168,15 @@ let check_field_assignment ~nullsafe_mode tenv find_canonical_duplicate curr_pde
(Some instr_ref) loc curr_pdesc ) (Some instr_ref) loc curr_pdesc )
let is_declared_nonnull AnnotatedField.{annotated_type} = (* Check if the field declared as not nullable (implicitly or explicitly). If the field is
match annotated_type.nullability with absent, we optimistically assume it is not nullable.
| AnnotatedNullability.Nullable _ ->
false
| AnnotatedNullability.UncheckedNonnull _
| AnnotatedNullability.LocallyCheckedNonnull
| AnnotatedNullability.StrictNonnull _ ->
true
TODO(T54687014) investigate if this leads to unsoundness issues in practice *)
(* Is field declared as non-nullable (implicitly or explicitly)? *)
let is_field_declared_as_nonnull annotated_field_opt = let is_field_declared_as_nonnull annotated_field_opt =
(* If the field is not present, we optimistically assume it is not nullable. let is_nonnullish AnnotatedField.{annotated_type= {nullability}} =
TODO(T54687014) investigate if this leads to unsoundness issues in practice Nullability.is_nonnullish (AnnotatedNullability.get_nullability nullability)
*) in
Option.exists annotated_field_opt ~f:is_declared_nonnull Option.exists annotated_field_opt ~f:is_nonnullish
let lookup_field_in_typestate pname field_name typestate = let lookup_field_in_typestate pname field_name typestate =
@ -429,32 +422,6 @@ type resolved_param =
; actual: Exp.t * InferredNullability.t ; actual: Exp.t * InferredNullability.t
; is_formal_propagates_nullable: bool } ; is_formal_propagates_nullable: bool }
let is_third_party_via_sig_files proc_name =
Option.is_some
(ThirdPartyAnnotationInfo.lookup_related_sig_file_by_package
(ThirdPartyAnnotationGlobalRepo.get_repo ())
proc_name)
let is_marked_third_party_in_config proc_name =
match proc_name with
| Procname.Java java_pname ->
(* TODO: migrate to the new way of checking for third party: use
signatures repository instead of looking it up in config params.
*)
Procname.Java.is_external java_pname
| _ ->
false
(* if this method belongs to a third party code, but is not modelled neigher internally nor externally *)
let is_third_party_without_model proc_name model_source =
let is_third_party =
is_third_party_via_sig_files proc_name || is_marked_third_party_in_config proc_name
in
is_third_party && Option.is_none model_source
(** Check the parameters of a call. *) (** Check the parameters of a call. *)
let check_call_parameters ~nullsafe_mode ~callee_annotated_signature tenv find_canonical_duplicate let check_call_parameters ~nullsafe_mode ~callee_annotated_signature tenv find_canonical_duplicate
curr_pdesc node callee_attributes resolved_params loc instr_ref : unit = curr_pdesc node callee_attributes resolved_params loc instr_ref : unit =
@ -490,17 +457,7 @@ let check_call_parameters ~nullsafe_mode ~callee_annotated_signature tenv find_c
let rhs = InferredNullability.get_nullability nullability_actual in let rhs = InferredNullability.get_nullability nullability_actual in
Result.iter_error (AssignmentRule.check ~nullsafe_mode ~lhs ~rhs) ~f:report Result.iter_error (AssignmentRule.check ~nullsafe_mode ~lhs ~rhs) ~f:report
in in
let should_ignore_parameters_check = List.iter ~f:check resolved_params
(* TODO(T52947663) model params in third-party non modelled method as a dedicated nullability type,
so this logic can be moved to [AssignmentRule.check] *)
NullsafeMode.equal nullsafe_mode NullsafeMode.Default
&& Config.nullsafe_optimistic_third_party_params_in_non_strict
&& is_third_party_without_model callee_pname
callee_annotated_signature.AnnotatedSignature.model_source
in
if not should_ignore_parameters_check then
(* left to right to avoid guessing the different lengths *)
List.iter ~f:check resolved_params
let check_inheritance_rule_for_return find_canonical_duplicate tenv loc ~nullsafe_mode let check_inheritance_rule_for_return find_canonical_duplicate tenv loc ~nullsafe_mode

@ -450,7 +450,7 @@ let do_preconditions_check_not_null instr_ref tenv find_canonical_duplicate node
let should_report = let should_report =
Config.eradicate_condition_redundant Config.eradicate_condition_redundant
(* TODO: This condition should be extracted into a dedicated rule *) (* TODO: This condition should be extracted into a dedicated rule *)
&& InferredNullability.is_nonnull_or_declared_nonnull nullability && InferredNullability.is_nonnullish nullability
&& not (InferredNullability.origin_is_fun_library nullability) && not (InferredNullability.origin_is_fun_library nullability)
in in
( if checks.eradicate && should_report then ( if checks.eradicate && should_report then

@ -129,6 +129,7 @@ let get_method_ret_description pname call_loc
match nullability with match nullability with
| AnnotatedNullability.Nullable _ -> | AnnotatedNullability.Nullable _ ->
"nullable" "nullable"
| AnnotatedNullability.ThirdPartyNonnull
| AnnotatedNullability.UncheckedNonnull _ | AnnotatedNullability.UncheckedNonnull _
| AnnotatedNullability.LocallyCheckedNonnull | AnnotatedNullability.LocallyCheckedNonnull
| AnnotatedNullability.StrictNonnull _ -> | AnnotatedNullability.StrictNonnull _ ->

@ -27,10 +27,9 @@ let empty = M.empty
let pp fmt typestate = let pp fmt typestate =
let pp_one exp (typ, ta) = let pp_one exp (typ, ta) =
F.fprintf fmt " %a -> [%s] %s %a@\n" Exp.pp exp F.fprintf fmt " %a -> [%s] %a %a@\n" Exp.pp exp
(TypeOrigin.to_string (InferredNullability.get_origin ta)) (TypeOrigin.to_string (InferredNullability.get_origin ta))
(InferredNullability.to_string ta) InferredNullability.pp ta (Typ.pp_full Pp.text) typ
(Typ.pp_full Pp.text) typ
in in
let pp_map map = M.iter pp_one map in let pp_map map = M.iter pp_one map in
pp_map typestate pp_map typestate

@ -10,6 +10,7 @@ package codetoanalyze.java.nullsafe_default;
import com.facebook.infer.annotation.Nullsafe; import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.NullsafeStrict; import com.facebook.infer.annotation.NullsafeStrict;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import some.test.pckg.ThirdPartyTestClass;
public class NullsafeMode { public class NullsafeMode {
abstract class VariousMethods { abstract class VariousMethods {
@ -35,6 +36,11 @@ public class NullsafeMode {
String OK_passUncheckedToStrict(String arg) { String OK_passUncheckedToStrict(String arg) {
return new StrictNullsafe().acceptVal(arg); return new StrictNullsafe().acceptVal(arg);
} }
void OK_passNullableToThirdPartyParam() {
new ThirdPartyTestClass().paramUnspecified(returnNull());
return;
}
} }
class AnotherNonNullsafe extends VariousMethods {} class AnotherNonNullsafe extends VariousMethods {}
@ -55,6 +61,18 @@ public class NullsafeMode {
return (new NonNullsafe()).returnNull(); return (new NonNullsafe()).returnNull();
} }
String BAD_returnFromUnvettedThirdParty() {
return new ThirdPartyTestClass().returnUnspecified();
}
String BAD_returnNullableFieldFromThirdParty() {
return new ThirdPartyTestClass().nullableField;
}
String BAD_returnNonNullableFieldFromThirdParty() {
return new ThirdPartyTestClass().nonNullableField;
}
String OK_passLocalToStrictMode(String arg) { String OK_passLocalToStrictMode(String arg) {
return new NullsafeWithStrictMode().acceptVal(arg); return new NullsafeWithStrictMode().acceptVal(arg);
} }

@ -45,6 +45,10 @@ public class StrictModeForThirdParty {
obj.returnSpecifiedAsNullable().toString(); obj.returnSpecifiedAsNullable().toString();
} }
public void dereferenceFieldIsBAD() {
obj.nonNullableField.toString();
}
public void dereferenceSpecifiedAsNonnullIsOK() { public void dereferenceSpecifiedAsNonnullIsOK() {
obj.returnSpecifiedAsNonnull().toString(); obj.returnSpecifiedAsNonnull().toString();
} }

@ -147,15 +147,17 @@ codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.null
codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.testSystemGetenvBad():int, 1, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`envValue` is nullable and is not locally checked for null when calling `length()`: call to System.getenv(...) at line 240 (nullable according to nullsafe internal models).] codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.testSystemGetenvBad():int, 1, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`envValue` is nullable and is not locally checked for null when calling `length()`: call to System.getenv(...) at line 240 (nullable according to nullsafe internal models).]
codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.withConditionalAssignemnt(codetoanalyze.java.nullsafe_default.NullMethodCall$AnotherI,boolean,java.lang.Object,java.lang.Object):void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`i` is nullable and is not locally checked for null when calling `withObjectParameter(...)`.] codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.withConditionalAssignemnt(codetoanalyze.java.nullsafe_default.NullMethodCall$AnotherI,boolean,java.lang.Object,java.lang.Object):void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`i` is nullable and is not locally checked for null when calling `withObjectParameter(...)`.]
codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.withConjuction(codetoanalyze.java.nullsafe_default.NullMethodCall$AnotherI,boolean,boolean):void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`i` is nullable and is not locally checked for null when calling `withBooleanParameter(...)`.] codetoanalyze/java/nullsafe-default/NullMethodCall.java, codetoanalyze.java.nullsafe_default.NullMethodCall.withConjuction(codetoanalyze.java.nullsafe_default.NullMethodCall$AnotherI,boolean,boolean):void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, WARNING, [`i` is nullable and is not locally checked for null when calling `withBooleanParameter(...)`.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$NullsafeWithStrictMode.BAD_returnFromNonStrict():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeStrict` mode prohibits using values coming from non-strict classes without a check. Result of this call is used at line 115. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe strict.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$NullsafeWithStrictMode.BAD_returnFromNonStrict():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeStrict` mode prohibits using values coming from non-strict classes without a check. Result of this call is used at line 133. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe strict.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$StrictNullsafe.BAD_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeStrict` mode prohibits using values coming from non-strict classes without a check. Result of this call is used at line 135. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe strict.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$StrictNullsafe.BAD_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeStrict` mode prohibits using values coming from non-strict classes without a check. Result of this call is used at line 153. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe strict.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustAllNullsafe.BAD_returnNullFromNonNulsafe():java.lang.String, 0, ERADICATE_RETURN_NOT_NULLABLE, no_bucket, ERROR, [`BAD_returnNullFromNonNulsafe()`: return type is declared non-nullable but the method returns a nullable value: call to returnNull() at line 55.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustAllNullsafe.BAD_returnFromUnvettedThirdParty():java.lang.String, 0, ERADICATE_UNVETTED_THIRD_PARTY_IN_NULLSAFE, no_bucket, ERROR, [`ThirdPartyTestClass.returnUnspecified()`: `@NullsafeLocal(trust=all)` mode prohibits using values coming from not vetted third party methods without a check. Result of this call is used at line 65. Either add a local check for null or assertion, or add the correct signature to nullsafe-default/third-party-signatures/some.test.pckg.sig.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustNoneNullsafe.BAD_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 95. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustAllNullsafe.BAD_returnNonNullableFieldFromThirdParty():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`ThirdPartyTestClass.nonNullableField`: `@NullsafeLocal(trust=all)` mode prohibits using values coming from third-party classes without a check. This field is used at line 73. Either add a local check for null or assertion, or access `nonNullableField` via a nullsafe getter.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.BAD_returnFromAnotherNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 79. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustAllNullsafe.BAD_returnNullFromNonNulsafe():java.lang.String, 0, ERADICATE_RETURN_NOT_NULLABLE, no_bucket, ERROR, [`BAD_returnNullFromNonNulsafe()`: return type is declared non-nullable but the method returns a nullable value: call to returnNull() at line 61.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.BAD_returnNullFromNonNulsafe():java.lang.String, 0, ERADICATE_RETURN_NOT_NULLABLE, no_bucket, ERROR, [`BAD_returnNullFromNonNulsafe()`: return type is declared non-nullable but the method returns a nullable value: call to returnNull() at line 88.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustAllNullsafe.BAD_returnNullableFieldFromThirdParty():java.lang.String, 0, ERADICATE_RETURN_NOT_NULLABLE, no_bucket, ERROR, [`BAD_returnNullableFieldFromThirdParty()`: return type is declared non-nullable but the method returns a nullable value: field nullableField at line 69.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.FP_OK_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 75. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustNoneNullsafe.BAD_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 113. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.BAD_returnFromAnotherNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 97. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.BAD_returnNullFromNonNulsafe():java.lang.String, 0, ERADICATE_RETURN_NOT_NULLABLE, no_bucket, ERROR, [`BAD_returnNullFromNonNulsafe()`: return type is declared non-nullable but the method returns a nullable value: call to returnNull() at line 106.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.FP_OK_returnFromNonNullsafe():java.lang.String, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`NullsafeMode$VariousMethods.returnVal()`: `@NullsafeLocal(trust=none)` mode prohibits using values coming from non-nullsafe classes without a check. Result of this call is used at line 93. Either add a local check for null or assertion, or make NullsafeMode$VariousMethods nullsafe.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.OK_returnFromAnotherNonNullsafeAsNullable():java.lang.String, 0, ERADICATE_RETURN_OVER_ANNOTATED, no_bucket, WARNING, [Method `OK_returnFromAnotherNonNullsafeAsNullable()` is annotated with `@Nullable` but never returns null.] codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.OK_returnFromAnotherNonNullsafeAsNullable():java.lang.String, 0, ERADICATE_RETURN_OVER_ANNOTATED, no_bucket, WARNING, [Method `OK_returnFromAnotherNonNullsafeAsNullable()` is annotated with `@Nullable` but never returns null.]
codetoanalyze/java/nullsafe-default/NullsafeMode.java, codetoanalyze.java.nullsafe_default.NullsafeMode$TrustSomeNullsafe.returnVal():java.lang.String, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, ERROR, [0th parameter `this` of method `NullsafeMode$TrustSomeNullsafe.returnVal()` is missing `@Nullable` declaration when overriding `NullsafeMode$VariousMethods.returnVal()`. The parent method declared it can handle `null` for this param, so the child should also declare that.]
codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable$ConstructorCall.<init>(codetoanalyze.java.nullsafe_default.ParameterNotNullable,int), 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable$ConstructorCall(...)`: parameter #2(`s`) is declared non-nullable but the argument is `null`.] codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable$ConstructorCall.<init>(codetoanalyze.java.nullsafe_default.ParameterNotNullable,int), 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable$ConstructorCall(...)`: parameter #2(`s`) is declared non-nullable but the argument is `null`.]
codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable.callNull():void, 1, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable.test(...)`: parameter #1(`s`) is declared non-nullable but the argument `s` is `null`: null constant at line 39.] codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable.callNull():void, 1, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable.test(...)`: parameter #1(`s`) is declared non-nullable but the argument `s` is `null`: null constant at line 39.]
codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable.callNullable(java.lang.String):void, 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable.test(...)`: parameter #1(`s`) is declared non-nullable but the argument `s` is nullable.] codetoanalyze/java/nullsafe-default/ParameterNotNullable.java, codetoanalyze.java.nullsafe_default.ParameterNotNullable.callNullable(java.lang.String):void, 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`ParameterNotNullable.test(...)`: parameter #1(`s`) is declared non-nullable but the argument `s` is nullable.]
@ -245,6 +247,7 @@ codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe
codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableFieldIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`__new(...).nullable` is nullable and is not locally checked for null when calling `toString()`.] codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableFieldIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`__new(...).nullable` is nullable and is not locally checked for null when calling `toString()`.]
codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableMethodIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`__new(...).getNullable()` is nullable and is not locally checked for null when calling `toString()`.] codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableMethodIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`__new(...).getNullable()` is nullable and is not locally checked for null when calling `toString()`.]
codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableStaticMethodIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`staticNullable()` is nullable and is not locally checked for null when calling `toString()`.] codetoanalyze/java/nullsafe-default/StrictMode.java, codetoanalyze.java.nullsafe_default.Strict.strictClass_dereferenceNullableStaticMethodIsBad():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`staticNullable()` is nullable and is not locally checked for null when calling `toString()`.]
codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.dereferenceFieldIsBAD():void, 0, ERADICATE_UNCHECKED_USAGE_IN_NULLSAFE, no_bucket, ERROR, [`ThirdPartyTestClass.nonNullableField`: `@NullsafeStrict` mode prohibits using values coming from non-strict classes without a check. This field is used at line 49. Either add a local check for null or assertion, or access `nonNullableField` via a nullsafe strict getter.]
codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.dereferenceSpecifiedAsNullableIsBAD():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`StrictModeForThirdParty.obj.returnSpecifiedAsNullable()` is nullable and is not locally checked for null when calling `toString()`: call to ThirdPartyTestClass.returnSpecifiedAsNullable() at line 45 (declared nullable in nullsafe-default/third-party-signatures/some.test.pckg.sig at line 2).] codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.dereferenceSpecifiedAsNullableIsBAD():void, 0, ERADICATE_NULLABLE_DEREFERENCE, no_bucket, ERROR, [`StrictModeForThirdParty.obj.returnSpecifiedAsNullable()` is nullable and is not locally checked for null when calling `toString()`: call to ThirdPartyTestClass.returnSpecifiedAsNullable() at line 45 (declared nullable in nullsafe-default/third-party-signatures/some.test.pckg.sig at line 2).]
codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.dereferenceUnspecifiedIsBAD():void, 0, ERADICATE_UNVETTED_THIRD_PARTY_IN_NULLSAFE, no_bucket, ERROR, [`ThirdPartyTestClass.returnUnspecified()`: `@NullsafeStrict` mode prohibits using values coming from not vetted third party methods without a check. Result of this call is used at line 41. Either add a local check for null or assertion, or add the correct signature to nullsafe-default/third-party-signatures/some.test.pckg.sig.] codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.dereferenceUnspecifiedIsBAD():void, 0, ERADICATE_UNVETTED_THIRD_PARTY_IN_NULLSAFE, no_bucket, ERROR, [`ThirdPartyTestClass.returnUnspecified()`: `@NullsafeStrict` mode prohibits using values coming from not vetted third party methods without a check. Result of this call is used at line 41. Either add a local check for null or assertion, or add the correct signature to nullsafe-default/third-party-signatures/some.test.pckg.sig.]
codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.passingNullableParamToUnspecifiedIsBAD():void, 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, ERROR, [Third-party `ThirdPartyTestClass.paramUnspecified(...)` is missing a signature that would allow passing a nullable to param #1(`param`). Actual argument `getNullable()` is nullable. Consider adding the correct signature of `ThirdPartyTestClass.paramUnspecified(...)` to nullsafe-default/third-party-signatures/some.test.pckg.sig.] codetoanalyze/java/nullsafe-default/StrictModeForThirdParty.java, codetoanalyze.java.nullsafe_default.StrictModeForThirdParty.passingNullableParamToUnspecifiedIsBAD():void, 0, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, ERROR, [Third-party `ThirdPartyTestClass.paramUnspecified(...)` is missing a signature that would allow passing a nullable to param #1(`param`). Actual argument `getNullable()` is nullable. Consider adding the correct signature of `ThirdPartyTestClass.paramUnspecified(...)` to nullsafe-default/third-party-signatures/some.test.pckg.sig.]

@ -6,12 +6,24 @@
*/ */
package some.test.pckg; package some.test.pckg;
import javax.annotation.Nullable;
/** /**
* A test third party class. We specify its annotations outside of this class, in a third-party * A test third party class. We specify its annotations outside of this class, in a third-party
* repository. * repository.
*/ */
public class ThirdPartyTestClass { public class ThirdPartyTestClass {
// Fields.
public String nonNullableField;
@Nullable public String nullableField;
public ThirdPartyTestClass() {
nonNullableField = "OK";
}
// Return values. // Return values.
// No information in 3rd party repo // No information in 3rd party repo

Loading…
Cancel
Save