[nullsafe] ThirdPartyAnnotationInfoStorage stores files names and line numbers for signatures

Summary:
In the follow up diff we will use it to render more informative
error message when type error is relevant to the fact that the code is
third-party

Reviewed By: artempyanykh

Differential Revision: D18569783

fbshipit-source-id: 4f6dcc404
master
Mitya Lyubarskiy 5 years ago committed by Facebook Github Bot
parent 71fd925d7f
commit a705373e02

@ -7,9 +7,13 @@
open! IStd open! IStd
module Hashtbl = Caml.Hashtbl module Hashtbl = Caml.Hashtbl
type storage = (ThirdPartyMethod.unique_repr, ThirdPartyMethod.nullability) Hashtbl.t type signature_info = {filename: string; line_number: int; nullability: ThirdPartyMethod.nullability}
let create_storage () = Hashtbl.create 1 type storage = {signature_map: signature_map; filenames: string list}
and signature_map = (ThirdPartyMethod.unique_repr, signature_info) Hashtbl.t
let create_storage () = {signature_map= Hashtbl.create 1; filenames= []}
type file_parsing_error = type file_parsing_error =
{line_number: int; unparsable_method: string; parsing_error: ThirdPartyMethod.parsing_error} {line_number: int; unparsable_method: string; parsing_error: ThirdPartyMethod.parsing_error}
@ -28,21 +32,25 @@ let bind_list_with_index ~init list ~f =
Result.bind acc ~f:(fun acc -> f acc index elem) ) Result.bind acc ~f:(fun acc -> f acc index elem) )
let parse_line_and_add_to_storage storage _line_index line = let parse_line_and_add_to_storage signature_map ~filename ~line_index line =
let open Result in let open Result in
ThirdPartyMethod.parse line ThirdPartyMethod.parse line
>>= fun (signature, nullability) -> >>= fun (signature, nullability) ->
Ok Ok
( Hashtbl.add storage signature nullability ; ( Hashtbl.add signature_map signature {filename; line_number= line_index + 1; nullability} ;
storage ) signature_map )
let add_from_signature_file storage ~lines = let add_from_signature_file storage ~filename ~lines =
(* each line in a file should represent a method signature *) (* each line in a file should represent a method signature *)
bind_list_with_index lines ~init:storage ~f:(fun storage index method_as_str -> let open Result in
parse_line_and_add_to_storage storage index method_as_str let new_filenames = storage.filenames @ [filename] in
bind_list_with_index lines ~init:storage.signature_map
~f:(fun signature_map line_index method_as_str ->
parse_line_and_add_to_storage signature_map ~filename ~line_index method_as_str
|> Result.map_error ~f:(fun parsing_error -> |> Result.map_error ~f:(fun parsing_error ->
{line_number= index + 1; unparsable_method= method_as_str; parsing_error} ) ) {line_number= line_index + 1; unparsable_method= method_as_str; parsing_error} ) )
>>= fun new_map -> Ok {signature_map= new_map; filenames= new_filenames}
let find_nullability_info storage unique_repr = Hashtbl.find_opt storage unique_repr let find_nullability_info {signature_map} unique_repr = Hashtbl.find_opt signature_map unique_repr

@ -8,6 +8,11 @@ open! IStd
(** In-memory storage the information about nullability annotation of third-party methods. *) (** In-memory storage the information about nullability annotation of third-party methods. *)
type signature_info =
{ filename: string (** File where the particular signature is stored *)
; line_number: int (** Line number with this signature *)
; nullability: ThirdPartyMethod.nullability }
type storage type storage
val create_storage : unit -> storage val create_storage : unit -> storage
@ -17,11 +22,11 @@ type file_parsing_error =
val pp_parsing_error : Format.formatter -> file_parsing_error -> unit val pp_parsing_error : Format.formatter -> file_parsing_error -> unit
val add_from_signature_file : storage -> lines:string list -> (storage, file_parsing_error) result val add_from_signature_file :
storage -> filename:string -> lines:string list -> (storage, file_parsing_error) result
(** Parse the information from the signature file, and add it to the storage *) (** Parse the information from the signature file, and add it to the storage *)
val find_nullability_info : val find_nullability_info : storage -> ThirdPartyMethod.unique_repr -> signature_info option
storage -> ThirdPartyMethod.unique_repr -> ThirdPartyMethod.nullability option
(** The main method. Do we have an information about the third-party method? (** The main method. Do we have an information about the third-party method?
If we do not, or it is not a third-party method, returns None. If we do not, or it is not a third-party method, returns None.
Otherwise returns the nullability information. Otherwise returns the nullability information.

@ -15,9 +15,8 @@ let pp_load_error fmt {filename; parsing_error} =
let add_from_file storage ~path_to_repo_dir ~sig_file = let add_from_file storage ~path_to_repo_dir ~sig_file =
let lines = In_channel.read_lines (path_to_repo_dir ^ "/" ^ sig_file) in let lines = In_channel.read_lines (path_to_repo_dir ^ "/" ^ sig_file) in
ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines ThirdPartyAnnotationInfo.add_from_signature_file storage ~filename:sig_file ~lines
|> Result.map_error ~f:(fun parsing_error -> {filename= sig_file; parsing_error}) |> Result.map_error ~f:(fun parsing_error -> {filename= sig_file; parsing_error})
|> Result.bind ~f:(fun _ -> Ok storage)
let load ~path_to_repo_dir = let load ~path_to_repo_dir =

@ -85,7 +85,8 @@ let get_modelled_annotated_signature tenv proc_attributes =
~f: ~f:
(ThirdPartyAnnotationInfo.find_nullability_info (ThirdPartyAnnotationInfo.find_nullability_info
(ThirdPartyAnnotationGlobalRepo.get_repo ())) (ThirdPartyAnnotationGlobalRepo.get_repo ()))
|> Option.map ~f:to_modelled_nullability |> Option.map ~f:(fun ThirdPartyAnnotationInfo.{nullability} ->
to_modelled_nullability nullability )
|> Option.value_map |> Option.value_map
(* If we found information in third-party repo, overwrite annotated signature *) (* If we found information in third-party repo, overwrite annotated signature *)
~f:(AnnotatedSignature.set_modelled_nullability proc_name ann_sig) ~f:(AnnotatedSignature.set_modelled_nullability proc_name ann_sig)

@ -9,32 +9,37 @@ open! IStd
open OUnit2 open OUnit2
module F = Format module F = Format
let assert_has_nullability_info storage unique_repr ~expected_nullability = let assert_has_nullability_info ?expected_file ?expected_line storage unique_repr
~expected_nullability =
match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with
| None -> | None ->
assert_failure assert_failure
(F.asprintf "Expected to find info for %a, but it was not found" (F.asprintf "Expected to find info for %a, but it was not found"
ThirdPartyMethod.pp_unique_repr unique_repr) ThirdPartyMethod.pp_unique_repr unique_repr)
| Some nullability -> | Some {filename; line_number; nullability} ->
assert_equal expected_nullability nullability assert_equal expected_nullability nullability
~msg: ~msg:
(F.asprintf "Nullability info for %a does not match" ThirdPartyMethod.pp_unique_repr (F.asprintf "Nullability info for %a does not match" ThirdPartyMethod.pp_unique_repr
unique_repr) unique_repr)
~printer:(Pp.string_of_pp ThirdPartyMethod.pp_nullability) ~printer:(Pp.string_of_pp ThirdPartyMethod.pp_nullability) ;
Option.iter expected_file ~f:(fun expected_file ->
assert_equal expected_file filename ~msg:"Filename does not match" ) ;
Option.iter expected_line ~f:(fun expected_line ->
assert_equal expected_line line_number ~msg:"Line number does not match" )
let assert_no_info storage unique_repr = let assert_no_info storage unique_repr =
match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with
| None -> | None ->
() ()
| Some nullability -> | Some {nullability} ->
assert_failure assert_failure
(F.asprintf "Did not expect to find nullability info for method %a, but found %a" (F.asprintf "Did not expect to find nullability info for method %a, but found %a"
ThirdPartyMethod.pp_unique_repr unique_repr ThirdPartyMethod.pp_nullability nullability) ThirdPartyMethod.pp_unique_repr unique_repr ThirdPartyMethod.pp_nullability nullability)
let add_from_annot_file_and_check_success storage ~lines = let add_from_annot_file_and_check_success storage ~filename ~lines =
match ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines with match ThirdPartyAnnotationInfo.add_from_signature_file storage ~filename ~lines with
| Ok storage -> | Ok storage ->
storage storage
| Error parsing_error -> | Error parsing_error ->
@ -43,8 +48,8 @@ let add_from_annot_file_and_check_success storage ~lines =
ThirdPartyAnnotationInfo.pp_parsing_error parsing_error) ThirdPartyAnnotationInfo.pp_parsing_error parsing_error)
let add_from_annot_file_and_check_failure storage ~lines ~expected_error_line_number = let add_from_annot_file_and_check_failure storage ~filename ~lines ~expected_error_line_number =
match ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines with match ThirdPartyAnnotationInfo.add_from_signature_file storage ~filename ~lines with
| Ok _ -> | Ok _ ->
assert_failure assert_failure
"Expected to not be able to parse the file, but it was successfully parsed instead" "Expected to not be able to parse the file, but it was successfully parsed instead"
@ -60,15 +65,19 @@ let basic_find =
let lines = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in let lines = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in
(* Load some functions from the file *) (* Load some functions from the file *)
let storage = let storage =
add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines add_from_annot_file_and_check_success ~filename:"test.sig"
(ThirdPartyAnnotationInfo.create_storage ())
~lines
in in
(* Make sure we can find what we just stored *) (* Make sure we can find what we just stored *)
assert_has_nullability_info storage assert_has_nullability_info storage
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]} {class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ; ~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]}
~expected_file:"test.sig" ~expected_line:1 ;
assert_has_nullability_info storage assert_has_nullability_info storage
{class_name= "b.B"; method_name= Method "bar"; param_types= ["c.C"; "d.D"]} {class_name= "b.B"; method_name= Method "bar"; param_types= ["c.C"; "d.D"]}
~expected_nullability:{ret_nullability= Nullable; param_nullability= [Nonnull; Nullable]} ; ~expected_nullability:{ret_nullability= Nullable; param_nullability= [Nonnull; Nullable]}
~expected_file:"test.sig" ~expected_line:2 ;
(* Make sure we can not find stuff we did not store *) (* Make sure we can not find stuff we did not store *)
(* Wrong class name *) (* Wrong class name *)
assert_no_info storage {class_name= "a.AB"; method_name= Method "foo"; param_types= ["b.B"]} ; assert_no_info storage {class_name= "a.AB"; method_name= Method "foo"; param_types= ["b.B"]} ;
@ -97,7 +106,9 @@ let overload_resolution =
in in
(* Load some functions from the file *) (* Load some functions from the file *)
let storage = let storage =
add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines add_from_annot_file_and_check_success ~filename:"test.sig"
(ThirdPartyAnnotationInfo.create_storage ())
~lines
in in
(* Make sure we can find what we just stored *) (* Make sure we can find what we just stored *)
(* a.b.SomeClass.foo with 1 param *) (* a.b.SomeClass.foo with 1 param *)
@ -153,21 +164,26 @@ let can_add_several_files =
(* 1. Add file and check if we added info *) (* 1. Add file and check if we added info *)
let file1 = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in let file1 = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in
let storage = let storage =
add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines:file1 add_from_annot_file_and_check_success
(ThirdPartyAnnotationInfo.create_storage ())
~filename:"file1.sig" ~lines:file1
in in
assert_has_nullability_info storage assert_has_nullability_info storage
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]} {class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ; ~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]}
~expected_file:"file1.sig" ~expected_line:1 ;
(* 2. Add another file and check if we added info *) (* 2. Add another file and check if we added info *)
let file2 = ["e.E#baz(f.F)"; "g.G#<init>(h.H, @Nullable i.I) @Nullable"] in let file2 = ["e.E#baz(f.F)"; "g.G#<init>(h.H, @Nullable i.I) @Nullable"] in
let storage = add_from_annot_file_and_check_success storage ~lines:file2 in let storage = add_from_annot_file_and_check_success storage ~filename:"file2.sig" ~lines:file2 in
assert_has_nullability_info storage assert_has_nullability_info storage
{class_name= "e.E"; method_name= Method "baz"; param_types= ["f.F"]} {class_name= "e.E"; method_name= Method "baz"; param_types= ["f.F"]}
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ; ~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]}
~expected_file:"file2.sig" ~expected_line:1 ;
(* 3. Ensure we did not forget the content from the first file *) (* 3. Ensure we did not forget the content from the first file *)
assert_has_nullability_info storage assert_has_nullability_info storage
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]} {class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]}
~expected_file:"file1.sig" ~expected_line:1
let should_not_forgive_unparsable_strings = let should_not_forgive_unparsable_strings =
@ -183,7 +199,7 @@ let should_not_forgive_unparsable_strings =
(* Ensure we can add the good file, but can not add the bad one *) (* Ensure we can add the good file, but can not add the bad one *)
add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines:file_ok add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines:file_ok
|> ignore ; |> ignore ;
add_from_annot_file_and_check_failure add_from_annot_file_and_check_failure ~filename:"test.sig"
(ThirdPartyAnnotationInfo.create_storage ()) (ThirdPartyAnnotationInfo.create_storage ())
~lines:file_bad ~expected_error_line_number:2 ~lines:file_bad ~expected_error_line_number:2

Loading…
Cancel
Save