diff --git a/infer/man/man1/infer-full.txt b/infer/man/man1/infer-full.txt index 97ae4bed2..fff337b99 100644 --- a/infer/man/man1/infer-full.txt +++ b/infer/man/man1/infer-full.txt @@ -1538,6 +1538,14 @@ INTERNAL OPTIONS Activates: Warn when containers are used with nullable keys or values (Conversely: --no-nullsafe-strict-containers) + --nullsafe-third-party-signatures string + Path to a folder with annotated signatures of third-party methods + to be taken into account by nullsafe. Path is relative to + .inferconfig folder. + + --nullsafe-third-party-signatures-reset + Cancel the effect of --nullsafe-third-party-signatures. + --no-only-cheap-debug Deactivates: Disable expensive debugging output (Conversely: --only-cheap-debug) diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index a5f65435e..2f5a75242 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -1758,6 +1758,12 @@ and nullable_annotation = CLOpt.mk_string_opt ~long:"nullable-annotation-name" "Specify custom nullable annotation name" +and nullsafe_third_party_signatures = + CLOpt.mk_string_opt ~long:"nullsafe-third-party-signatures" + "Path to a folder with annotated signatures of third-party methods to be taken into account \ + by nullsafe. Path is relative to .inferconfig folder." + + and nullsafe_strict_containers = CLOpt.mk_bool ~long:"nullsafe-strict-containers" ~default:false "Warn when containers are used with nullable keys or values" @@ -2516,7 +2522,7 @@ and (_ : bool ref) = (** Parse Command Line Args *) -let inferconfig_file = +let inferconfig_dir = let rec find dir = match Sys.file_exists ~follow_symlinks:false (dir ^/ CommandDoc.inferconfig_file) with | `Yes -> @@ -2526,11 +2532,15 @@ let inferconfig_file = let is_root = String.equal dir parent in if is_root then None else find parent in + find (Sys.getcwd ()) + + +let inferconfig_file = match Sys.getenv CommandDoc.inferconfig_env_var with | Some _ as env_path -> env_path | None -> - find (Sys.getcwd ()) |> Option.map ~f:(fun dir -> dir ^/ CommandDoc.inferconfig_file) + Option.map inferconfig_dir ~f:(fun dir -> dir ^/ CommandDoc.inferconfig_file) let post_parsing_initialization command_opt = @@ -2999,6 +3009,8 @@ and nelseg = !nelseg and nullable_annotation = !nullable_annotation +and nullsafe_third_party_signatures = !nullsafe_third_party_signatures + and nullsafe_strict_containers = !nullsafe_strict_containers and no_translate_libs = not !headers diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 0428aa6e2..fce55ac49 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -404,6 +404,8 @@ val implicit_sdk_root : string option val inferconfig_file : string option +val inferconfig_dir : string option + val iphoneos_target_sdk_version : string option val iphoneos_target_sdk_version_path_regex : iphoneos_target_sdk_version_path_regex list @@ -496,6 +498,8 @@ val nullable_annotation : string option val nullsafe : bool +val nullsafe_third_party_signatures : string option + val nullsafe_strict_containers : bool val oom_threshold : int option diff --git a/infer/src/infer.ml b/infer/src/infer.ml index 47fd86f1a..e49ae3965 100644 --- a/infer/src/infer.ml +++ b/infer/src/infer.ml @@ -63,6 +63,7 @@ let setup () = | Events -> ResultsDir.assert_results_dir "have you run infer before?" ) ; db_start () ; + NullsafeInit.init () ; if CLOpt.is_originator then ( RunState.add_run_to_sequence () ; RunState.store () ) ; () diff --git a/infer/src/nullsafe/NullsafeInit.ml b/infer/src/nullsafe/NullsafeInit.ml new file mode 100644 index 000000000..ce5f01a3d --- /dev/null +++ b/infer/src/nullsafe/NullsafeInit.ml @@ -0,0 +1,45 @@ +(* + * 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 load_third_party_repo ~absolute_path_to_repo_dir = + match Sys.is_directory absolute_path_to_repo_dir with + | `Yes -> ( + match ThirdPartyAnnotationInfoLoader.load ~path_to_repo_dir:absolute_path_to_repo_dir with + | Ok storage -> + storage + | Error error -> + Logging.die Logging.InternalError "Error while reading 3rd party annotation repo: %a" + ThirdPartyAnnotationInfoLoader.pp_load_error error ) + | _ -> + Logging.die Logging.InternalError + "Could not locate 3rd party annotation repository: expected location %s" + absolute_path_to_repo_dir + + +let get_absolute_path_to_repo_dir relative_path_to_repo_dir = + match Config.inferconfig_dir with + | None -> + Logging.die Logging.InternalError + "Could not locate .inferconfig directory, which is required for resolving the path to \ + third party annotation repository" + | Some inferconfig_dir -> + inferconfig_dir ^/ relative_path_to_repo_dir + + +let create_global_storage () = + match Config.nullsafe_third_party_signatures with + | Some dir -> + load_third_party_repo ~absolute_path_to_repo_dir:(get_absolute_path_to_repo_dir dir) + (* Create empty *) + | None -> + ThirdPartyAnnotationInfo.create_storage () + + +let init () = + let global_storage = create_global_storage () in + ThirdPartyAnnotationGlobalRepo.initialize global_storage diff --git a/infer/src/nullsafe/NullsafeInit.mli b/infer/src/nullsafe/NullsafeInit.mli new file mode 100644 index 000000000..91a1c4624 --- /dev/null +++ b/infer/src/nullsafe/NullsafeInit.mli @@ -0,0 +1,10 @@ +(* + * 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 + +val init : unit -> unit +(** This function should be called once before nullsafe checker is called *) diff --git a/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.ml b/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.ml new file mode 100644 index 000000000..f2bdc5481 --- /dev/null +++ b/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.ml @@ -0,0 +1,25 @@ +(* + * 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 repo = ref None + +let initialize storage = + match !repo with + | None -> + repo := Some storage + | Some _ -> + Logging.die Logging.InternalError "Attempt to initialize global 3rd party repository twice" + + +let get_repo () = + match !repo with + | None -> + Logging.die Logging.InternalError "Attempt to access not initialized 3rd party repository" + | Some repo -> + repo diff --git a/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.mli b/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.mli new file mode 100644 index 000000000..ed0c872a4 --- /dev/null +++ b/infer/src/nullsafe/ThirdPartyAnnotationGlobalRepo.mli @@ -0,0 +1,16 @@ +(* + * 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 + +(** A singleton. Should be initialized once prior to start of nullsafe. *) + +val initialize : ThirdPartyAnnotationInfo.storage -> unit +(** Should be initialized exactly once before access. *) + +val get_repo : unit -> ThirdPartyAnnotationInfo.storage +(** Can be accessed only when initialization was done *) diff --git a/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.ml b/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.ml new file mode 100644 index 000000000..670ae8a57 --- /dev/null +++ b/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.ml @@ -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 + +type load_error = {filename: string; parsing_error: ThirdPartyAnnotationInfo.file_parsing_error} + +let pp_load_error fmt {filename; parsing_error} = + Format.fprintf fmt "Could not parse %s: %a" filename ThirdPartyAnnotationInfo.pp_parsing_error + parsing_error + + +let add_from_file storage ~path_to_repo_dir ~sig_file = + let lines = In_channel.read_lines (path_to_repo_dir ^ "/" ^ sig_file) in + ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines + |> Result.map_error ~f:(fun parsing_error -> {filename= sig_file; parsing_error}) + |> Result.bind ~f:(fun _ -> Ok storage) + + +let load ~path_to_repo_dir = + (* Sequentally load information from all .sig files *) + Sys.ls_dir path_to_repo_dir + |> List.filter ~f:(String.is_suffix ~suffix:".sig") + |> List.fold_result ~init:(ThirdPartyAnnotationInfo.create_storage ()) + ~f:(fun accumulated_storage sig_file -> + add_from_file accumulated_storage ~path_to_repo_dir ~sig_file ) diff --git a/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.mli b/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.mli new file mode 100644 index 000000000..018cc08cb --- /dev/null +++ b/infer/src/nullsafe/ThirdPartyAnnotationInfoLoader.mli @@ -0,0 +1,17 @@ +(* + * 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 load_error + +val pp_load_error : Format.formatter -> load_error -> unit + +val load : path_to_repo_dir:string -> (ThirdPartyAnnotationInfo.storage, load_error) result +(** Given a path to a repo with 3rd annotation info, loads it from a disk to + in-memory representation. + After this is done, information can be requested via [ThirdPartyAnnotationInfo]. + *) diff --git a/infer/tests/codetoanalyze/java/nullsafe-default/.inferconfig b/infer/tests/codetoanalyze/java/nullsafe-default/.inferconfig index 9914a46a4..61cf77b67 100644 --- a/infer/tests/codetoanalyze/java/nullsafe-default/.inferconfig +++ b/infer/tests/codetoanalyze/java/nullsafe-default/.inferconfig @@ -2,5 +2,6 @@ "external-java-packages": [ "external." ], - "nullsafe-strict-containers": true + "nullsafe-strict-containers": true, + "nullsafe-third-party-signatures": "third-party-annots" } diff --git a/infer/tests/codetoanalyze/java/nullsafe-default/third-party-annots/other.test.pckg.sig b/infer/tests/codetoanalyze/java/nullsafe-default/third-party-annots/other.test.pckg.sig new file mode 100644 index 000000000..e69de29bb diff --git a/infer/tests/codetoanalyze/java/nullsafe-default/third-party-annots/some.test.pckg.sig b/infer/tests/codetoanalyze/java/nullsafe-default/third-party-annots/some.test.pckg.sig new file mode 100644 index 000000000..c50e04614 --- /dev/null +++ b/infer/tests/codetoanalyze/java/nullsafe-default/third-party-annots/some.test.pckg.sig @@ -0,0 +1,2 @@ +some.test.pckg.SomeClass#getNullable() @Nullable +some.test.pckg.SomeClass#getNonnull()