Summary: Currently, we have NullsafeRules.ml responsible for detecting the violation fact. All other logic: what should be the error type, severity, and error message, is in TypeErr.ml. In this diff, we move logic from NullsafeRules.ml and TypeErr.ml to dedicated modules like AssignmentRule.ml etc. Each such module is responsible for: - detecting the violation fact (this is moved from NullsafeRules.ml) - rendering the violation error (this is moved from TypeErr.ml). This approach makes sense for two reasons: 1. The violation fact and the way we show error are logically related to each other. 2. In future diffs, we will support more features guiding rule behavior, such as a) decision whether to hide or show the error depending on type information and mode; b) the way we render error depending on type information and role. Having dedicated modules incapsulating knowledge about rules is a natural way to support 2. Reviewed By: artempyanykh Differential Revision: D17977891 fbshipit-source-id: a53d916d3master
parent
92f765a948
commit
681f853b20
@ -0,0 +1,40 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
type violation = {lhs: Nullability.t; rhs: Nullability.t} [@@deriving compare]
|
||||
|
||||
type assignment_type =
|
||||
| PassingParamToFunction of
|
||||
{ param_description: string
|
||||
; param_position: int
|
||||
; function_procname: Typ.Procname.t }
|
||||
| AssigningToField of Typ.Fieldname.t
|
||||
| ReturningFromFunction of Typ.Procname.t
|
||||
[@@deriving compare]
|
||||
|
||||
let check ~lhs ~rhs =
|
||||
Result.ok_if_true (Nullability.is_subtype ~subtype:rhs ~supertype:lhs) ~error:{lhs; rhs}
|
||||
|
||||
|
||||
let violation_description _ assignment_type ~rhs_origin_descr =
|
||||
let module MF = MarkupFormatter in
|
||||
match assignment_type with
|
||||
| PassingParamToFunction {param_description; param_position; function_procname} ->
|
||||
Format.asprintf "%a needs a non-null value in parameter %d but argument %a can be null. %s"
|
||||
MF.pp_monospaced
|
||||
(Typ.Procname.to_simplified_string ~withclass:true function_procname)
|
||||
param_position MF.pp_monospaced param_description rhs_origin_descr
|
||||
| AssigningToField field_name ->
|
||||
Format.asprintf "Field %a can be null but is not declared %a. %s" MF.pp_monospaced
|
||||
(Typ.Fieldname.to_simplified_string field_name)
|
||||
MF.pp_monospaced "@Nullable" rhs_origin_descr
|
||||
| ReturningFromFunction function_proc_name ->
|
||||
Format.asprintf "Method %a may return null but it is not annotated with %a. %s"
|
||||
MF.pp_monospaced
|
||||
(Typ.Procname.to_simplified_string function_proc_name)
|
||||
MF.pp_monospaced "@Nullable" rhs_origin_descr
|
@ -0,0 +1,27 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** Assignment rule should be checked when a value is assigned to a location.
|
||||
Assignment can be explicit (lhs = rhs) or implicit (e.g. returning from a function).
|
||||
This rule checks if null can be passed to a place that does not expect null.
|
||||
*)
|
||||
|
||||
type violation [@@deriving compare]
|
||||
|
||||
val check : lhs:Nullability.t -> rhs:Nullability.t -> (unit, violation) result
|
||||
|
||||
type assignment_type =
|
||||
| PassingParamToFunction of
|
||||
{ param_description: string
|
||||
; param_position: int
|
||||
; function_procname: Typ.Procname.t }
|
||||
| AssigningToField of Typ.Fieldname.t
|
||||
| ReturningFromFunction of Typ.Procname.t
|
||||
[@@deriving compare]
|
||||
|
||||
val violation_description : violation -> assignment_type -> rhs_origin_descr:string -> string
|
@ -0,0 +1,58 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
type violation = Nullability.t [@@deriving compare]
|
||||
|
||||
type dereference_type =
|
||||
| MethodCall of Typ.Procname.t
|
||||
| AccessToField of Typ.Fieldname.t
|
||||
| AccessByIndex of {index_desc: string}
|
||||
| ArrayLengthAccess
|
||||
[@@deriving compare]
|
||||
|
||||
let check = function
|
||||
| Nullability.Nullable as nullability ->
|
||||
Error nullability
|
||||
| Nullability.Nonnull ->
|
||||
Ok ()
|
||||
|
||||
|
||||
let violation_description _ dereference_type ~nullable_object_descr ~origin_descr =
|
||||
let module MF = MarkupFormatter in
|
||||
let nullable_object_descr =
|
||||
match dereference_type with
|
||||
| MethodCall _ | AccessToField _ -> (
|
||||
match nullable_object_descr with
|
||||
| None ->
|
||||
"Object"
|
||||
(* Just describe an object itself *)
|
||||
| Some descr ->
|
||||
MF.monospaced_to_string descr )
|
||||
| ArrayLengthAccess | AccessByIndex _ -> (
|
||||
(* In Java, those operations can be applied only to arrays *)
|
||||
match nullable_object_descr with
|
||||
| None ->
|
||||
"Array"
|
||||
| Some descr ->
|
||||
Format.sprintf "Array %s" (MF.monospaced_to_string descr) )
|
||||
in
|
||||
let action_descr =
|
||||
match dereference_type with
|
||||
| MethodCall method_name ->
|
||||
Format.sprintf "calling %s"
|
||||
(MF.monospaced_to_string (Typ.Procname.to_simplified_string method_name))
|
||||
| AccessToField field_name ->
|
||||
Format.sprintf "accessing field %s"
|
||||
(MF.monospaced_to_string (Typ.Fieldname.to_simplified_string field_name))
|
||||
| AccessByIndex {index_desc} ->
|
||||
Format.sprintf "accessing at index %s" (MF.monospaced_to_string index_desc)
|
||||
| ArrayLengthAccess ->
|
||||
"accessing its length"
|
||||
in
|
||||
Format.sprintf "%s is nullable and is not locally checked for null when %s. %s"
|
||||
nullable_object_descr action_descr origin_descr
|
@ -0,0 +1,29 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** Dereference rule should be checked every type an object is dereferenced.
|
||||
The rule checks if the reference is nullable.
|
||||
*)
|
||||
|
||||
type violation [@@deriving compare]
|
||||
|
||||
val check : Nullability.t -> (unit, violation) result
|
||||
|
||||
type dereference_type =
|
||||
| MethodCall of Typ.Procname.t
|
||||
| AccessToField of Typ.Fieldname.t
|
||||
| AccessByIndex of {index_desc: string}
|
||||
| ArrayLengthAccess
|
||||
[@@deriving compare]
|
||||
|
||||
val violation_description :
|
||||
violation
|
||||
-> dereference_type
|
||||
-> nullable_object_descr:string option
|
||||
-> origin_descr:string
|
||||
-> string
|
@ -0,0 +1,59 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
type violation = {base: Nullability.t; overridden: Nullability.t} [@@deriving compare]
|
||||
|
||||
type type_role = Param | Ret
|
||||
|
||||
let check type_role ~base ~overridden =
|
||||
let subtype, supertype =
|
||||
match type_role with
|
||||
| Ret ->
|
||||
(* covariance for ret *)
|
||||
(overridden, base)
|
||||
| Param ->
|
||||
(* contravariance for param *)
|
||||
(base, overridden)
|
||||
in
|
||||
Result.ok_if_true (Nullability.is_subtype ~subtype ~supertype) ~error:{base; overridden}
|
||||
|
||||
|
||||
type violation_type =
|
||||
| InconsistentParam of {param_description: string; param_position: int}
|
||||
| InconsistentReturn
|
||||
[@@deriving compare]
|
||||
|
||||
let violation_description _ violation_type ~base_proc_name ~overridden_proc_name =
|
||||
let module MF = MarkupFormatter in
|
||||
let nullable_annotation = "@Nullable" in
|
||||
let base_method_descr = Typ.Procname.to_simplified_string ~withclass:true base_proc_name in
|
||||
let overridden_method_descr =
|
||||
Typ.Procname.to_simplified_string ~withclass:true overridden_proc_name
|
||||
in
|
||||
match violation_type with
|
||||
| InconsistentReturn ->
|
||||
Format.asprintf "Method %a is annotated with %a but overrides unannotated method %a."
|
||||
MF.pp_monospaced overridden_method_descr MF.pp_monospaced nullable_annotation
|
||||
MF.pp_monospaced base_method_descr
|
||||
| InconsistentParam {param_description; param_position} ->
|
||||
let translate_position = function
|
||||
| 1 ->
|
||||
"First"
|
||||
| 2 ->
|
||||
"Second"
|
||||
| 3 ->
|
||||
"Third"
|
||||
| n ->
|
||||
string_of_int n ^ "th"
|
||||
in
|
||||
Format.asprintf
|
||||
"%s parameter %a of method %a is not %a but is declared %ain the parent class method %a."
|
||||
(translate_position param_position)
|
||||
MF.pp_monospaced param_description MF.pp_monospaced overridden_method_descr
|
||||
MF.pp_monospaced nullable_annotation MF.pp_monospaced nullable_annotation MF.pp_monospaced
|
||||
base_method_descr
|
@ -0,0 +1,35 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** Inheritance rule:
|
||||
a) Return type for an overridden method is covariant:
|
||||
overridden method is allowed to narrow down the return value to a subtype of the one from the
|
||||
base method; this means it is OK to make the return value non-null when it was nullable in the base)
|
||||
b) Parameter type for an overridden method is contravariant.
|
||||
It is OK for a derived method to accept nullable in the params even if the base does not accept nullable.
|
||||
NOTE: Rule a) is based on Java covariance rule for the return type.
|
||||
In contrast, rule b) is nullsafe specific as Java does not support type contravariance for method params.
|
||||
*)
|
||||
|
||||
type violation [@@deriving compare]
|
||||
|
||||
type violation_type =
|
||||
| InconsistentParam of {param_description: string; param_position: int}
|
||||
| InconsistentReturn
|
||||
[@@deriving compare]
|
||||
|
||||
type type_role = Param | Ret
|
||||
|
||||
val check : type_role -> base:Nullability.t -> overridden:Nullability.t -> (unit, violation) result
|
||||
|
||||
val violation_description :
|
||||
violation
|
||||
-> violation_type
|
||||
-> base_proc_name:Typ.Procname.t
|
||||
-> overridden_proc_name:Typ.Procname.t
|
||||
-> string
|
@ -1,35 +0,0 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
let passes_assignment_rule ~lhs ~rhs = Nullability.is_subtype ~subtype:rhs ~supertype:lhs
|
||||
|
||||
let passes_dereference_rule = function
|
||||
| Nullability.Nullable ->
|
||||
false
|
||||
| Nullability.Nonnull ->
|
||||
true
|
||||
|
||||
|
||||
type type_role = Param | Ret
|
||||
|
||||
let passes_inheritance_rule type_role ~base ~overridden =
|
||||
let subtype, supertype =
|
||||
match type_role with
|
||||
| Ret ->
|
||||
(* covariance for ret *)
|
||||
(overridden, base)
|
||||
| Param ->
|
||||
(* contravariance for param *)
|
||||
(base, overridden)
|
||||
in
|
||||
Nullability.is_subtype ~subtype ~supertype
|
||||
|
||||
|
||||
let is_overannotated ~lhs ~rhs_upper_bound =
|
||||
Nullability.is_strict_subtype ~subtype:rhs_upper_bound ~supertype:lhs
|
@ -1,57 +0,0 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** This is a single place consolidating core rules driving Nullsafe type checking.
|
||||
Nullsafe enforces similar rules in different places (e.g. places dealing with fields,
|
||||
function calls, assignments, local variables etc.).
|
||||
Those places might have additional specifics, but core checks should be done through this class.
|
||||
If you are writing a new or modifying an existing check, ask yourself if you can directly
|
||||
use already existng rules from this module.
|
||||
If you feel you need a rule of a completely new nature, add it to this module.
|
||||
As a rule of thumb, every different "check" that is responsible for detecting issues, should query
|
||||
this module instead of doing things on their own.
|
||||
*)
|
||||
|
||||
val passes_assignment_rule : lhs:Nullability.t -> rhs:Nullability.t -> bool
|
||||
(** Assignment rule: No expression of nullable type is ever assigned to a location
|
||||
of non-nullable type.
|
||||
*)
|
||||
|
||||
val passes_dereference_rule : Nullability.t -> bool
|
||||
(** Dereference rule: an object can be dereferenced only when it is not nullable (or believed to be so).
|
||||
*)
|
||||
|
||||
type type_role = Param | Ret
|
||||
|
||||
val passes_inheritance_rule : type_role -> base:Nullability.t -> overridden:Nullability.t -> bool
|
||||
(** Inheritance rule:
|
||||
a) Return type for an overridden method is covariant:
|
||||
overridden method is allowed to narrow down the return value to a subtype of the one from the
|
||||
base method; this means it is OK to make the return value non-null when it was nullable in the base)
|
||||
b) Parameter type for an overridden method is contravariant.
|
||||
It is OK for a derived method to accept nullable in the params even if the base does not accept nullable.
|
||||
NOTE: Rule a) is based on Java covariance rule for the return type.
|
||||
In contrast, rule b) is nullsafe specific as Java does not support type contravariance for method params.
|
||||
*)
|
||||
|
||||
val is_overannotated : lhs:Nullability.t -> rhs_upper_bound:Nullability.t -> bool
|
||||
(** Check if a type in signature (e.g. return value) can be made more specific.
|
||||
If an upper bound of `rhs_i` over ALL assignents `lhs = rhs_i` that exist in the program
|
||||
is a _strict_ subtype of lhs, `lhs`'s type can be narrowed to be that upper bound.
|
||||
NOTE: This rule is complementatary to assignment rule.
|
||||
While assignment rule checks a single assignment `lhs = rhs`, this rule
|
||||
checks checks ALL assignments to `lhs` in the program.
|
||||
NOTE: Violation of this rule is not a type violation, hence it should never be surfaced as error:
|
||||
`lhs`'s type can be intentionally made broad by code author
|
||||
(e.g. to anticipate future changes in the implementation).
|
||||
Additional heuristits are required to correctly surface overannotated rule to the user.
|
||||
This rule is useful for some scenarios, especially for nullability code conversions
|
||||
when it is expected that some signatures were annotated with @Nullable defensively, so
|
||||
surfacing such cases can improve API and make migration smooth.
|
||||
*)
|
@ -0,0 +1,34 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
type violation = {lhs: Nullability.t; rhs_upper_bound: Nullability.t} [@@deriving compare]
|
||||
|
||||
let check ~lhs ~rhs_upper_bound =
|
||||
if Nullability.is_strict_subtype ~subtype:rhs_upper_bound ~supertype:lhs then
|
||||
Error {lhs; rhs_upper_bound}
|
||||
else Ok ()
|
||||
|
||||
|
||||
type violation_type =
|
||||
| FieldOverAnnoted of Typ.Fieldname.t
|
||||
| ReturnOverAnnotated of Typ.Procname.t (** Return value of a method can be made non-nullable *)
|
||||
[@@deriving compare]
|
||||
|
||||
let violation_description _ violation_type =
|
||||
let module MF = MarkupFormatter in
|
||||
let nullable_annotation = "@Nullable" in
|
||||
match violation_type with
|
||||
| FieldOverAnnoted field_name ->
|
||||
Format.asprintf "Field %a is always initialized in the constructor but is declared %a"
|
||||
MF.pp_monospaced
|
||||
(Typ.Fieldname.to_simplified_string field_name)
|
||||
MF.pp_monospaced nullable_annotation
|
||||
| ReturnOverAnnotated proc_name ->
|
||||
Format.asprintf "Method %a is annotated with %a but never returns null." MF.pp_monospaced
|
||||
(Typ.Procname.to_simplified_string proc_name)
|
||||
MF.pp_monospaced nullable_annotation
|
@ -0,0 +1,34 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** Checks if a type in signature (e.g. return value) can be made more specific.
|
||||
NOTE: This rule is complementatary to assignment rule.
|
||||
While assignment rule checks a single assignment `lhs = rhs`, this rule
|
||||
checks checks ALL assignments to `lhs` in the program.
|
||||
NOTE: Violation of this rule is not a type violation, hence it should never be surfaced as error:
|
||||
`lhs`'s type can be intentionally made broad by code author
|
||||
(e.g. to anticipate future changes in the implementation).
|
||||
Heuristits are required to correctly surface overannotated rule to the user.
|
||||
This rule is useful for some scenarios, especially for nullability code conversions
|
||||
when it is expected that some signatures were annotated with @Nullable defensively, so
|
||||
surfacing such cases can improve API and make migration smooth.
|
||||
*)
|
||||
|
||||
type violation [@@deriving compare]
|
||||
|
||||
val check : lhs:Nullability.t -> rhs_upper_bound:Nullability.t -> (unit, violation) result
|
||||
(** If an upper bound of `rhs_i` over ALL assignents `lhs = rhs_i` that exist in the program
|
||||
is a _strict_ subtype of lhs, `lhs`'s type can be narrowed to be that upper bound.
|
||||
*)
|
||||
|
||||
type violation_type =
|
||||
| FieldOverAnnoted of Typ.Fieldname.t
|
||||
| ReturnOverAnnotated of Typ.Procname.t (** Return value of a method can be made non-nullable *)
|
||||
[@@deriving compare]
|
||||
|
||||
val violation_description : violation -> violation_type -> string
|
Loading…
Reference in new issue