source/configuration.ml (396 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
let default_python_major_version = 3
let default_python_minor_version = 10
let default_python_micro_version = 10
let default_shared_memory_heap_size = 8 * 1024 * 1024 * 1024 (* 8 GiB *)
let default_shared_memory_dependency_table_power = 27
let default_shared_memory_hash_table_power = 26
module Buck = struct
type t = {
mode: string option;
isolation_prefix: string option;
use_buck2: bool;
targets: string list;
(* This is the buck root of the source directory, i.e. output of `buck root`. *)
source_root: PyrePath.t;
(* This is the root of directory where built artifacts will be placed. *)
artifact_root: PyrePath.t;
}
[@@deriving sexp, compare, hash]
let of_yojson json =
let open JsonParsing in
try
let mode = optional_string_member "mode" json in
let isolation_prefix = optional_string_member "isolation_prefix" json in
let use_buck2 = bool_member ~default:false "use_buck2" json in
let targets = string_list_member "targets" json ~default:[] in
let source_root = path_member "source_root" json in
let artifact_root = path_member "artifact_root" json in
Result.Ok { mode; isolation_prefix; use_buck2; targets; source_root; artifact_root }
with
| Yojson.Safe.Util.Type_error (message, _)
| Yojson.Safe.Util.Undefined (message, _) ->
Result.Error message
| other_exception -> Result.Error (Exn.to_string other_exception)
let to_yojson { mode; isolation_prefix; use_buck2; targets; source_root; artifact_root } =
let result =
[
"use_buck2", `Bool use_buck2;
"targets", `List (List.map targets ~f:(fun target -> `String target));
"source_root", `String (PyrePath.absolute source_root);
"artifact_root", `String (PyrePath.absolute artifact_root);
]
in
let result =
match mode with
| None -> result
| Some mode -> ("mode", `String mode) :: result
in
let result =
match isolation_prefix with
| None -> result
| Some isolation_prefix -> ("isolation_prefix", `String isolation_prefix) :: result
in
`Assoc result
end
module ChangeIndicator = struct
type t = {
root: PyrePath.t;
relative: string;
}
[@@deriving sexp, compare, hash]
let of_yojson json =
let open JsonParsing in
try
let root = path_member "root" json in
let relative = string_member "relative" json in
Result.Ok { root; relative }
with
| Yojson.Safe.Util.Type_error (message, _)
| Yojson.Safe.Util.Undefined (message, _) ->
Result.Error message
| other_exception -> Result.Error (Exn.to_string other_exception)
let to_yojson { root; relative } =
`Assoc ["root", `String (PyrePath.absolute root); "relative", `String relative]
let to_path { root; relative } = PyrePath.create_relative ~root ~relative
end
module UnwatchedFiles = struct
type t = {
root: PyrePath.t;
checksum_path: string;
}
[@@deriving sexp, compare, hash]
let of_yojson json =
let open JsonParsing in
try
let root = path_member "root" json in
let checksum_path = string_member "checksum_path" json in
Result.Ok { root; checksum_path }
with
| Yojson.Safe.Util.Type_error (message, _)
| Yojson.Safe.Util.Undefined (message, _) ->
Result.Error message
| other_exception -> Result.Error (Exn.to_string other_exception)
let to_yojson { root; checksum_path } =
`Assoc ["root", `String (PyrePath.absolute root); "checksum_path", `String checksum_path]
end
module UnwatchedDependency = struct
type t = {
change_indicator: ChangeIndicator.t;
files: UnwatchedFiles.t;
}
[@@deriving sexp, compare, hash, yojson]
end
module SourcePaths = struct
type t =
| Simple of SearchPath.t list
| WithUnwatchedDependency of {
sources: SearchPath.t list;
unwatched_dependency: UnwatchedDependency.t;
}
| Buck of Buck.t
[@@deriving sexp, compare, hash]
let of_yojson json =
let open Yojson.Safe.Util in
let parsing_failed () =
let message = Format.sprintf "Malformed source path JSON: %s" (Yojson.Safe.to_string json) in
Result.Error message
in
let parse_search_path_jsons search_path_jsons =
try
Result.Ok (List.map search_path_jsons ~f:(fun json -> to_string json |> SearchPath.create))
with
| Type_error _ -> parsing_failed ()
in
let open Result in
match json with
| `List search_path_jsons ->
(* Recognize this as a shortcut for simple source paths. *)
parse_search_path_jsons search_path_jsons
>>= fun search_paths -> Result.Ok (Simple search_paths)
| `Assoc _ -> (
match member "kind" json with
| `String "simple" -> (
match member "paths" json with
| `List search_path_jsons ->
parse_search_path_jsons search_path_jsons
>>= fun search_paths -> Result.Ok (Simple search_paths)
| _ -> parsing_failed ())
| `String "buck" -> (
match Buck.of_yojson json with
| Result.Ok buck -> Result.Ok (Buck buck)
| Result.Error error -> Result.Error error)
| `String "with_unwatched_dependency" -> (
match member "paths" json, member "unwatched_dependency" json with
| `List search_path_jsons, (`Assoc _ as unwatched_dependency_json) ->
parse_search_path_jsons search_path_jsons
>>= fun sources ->
UnwatchedDependency.of_yojson unwatched_dependency_json
>>= fun unwatched_dependency ->
Result.Ok (WithUnwatchedDependency { sources; unwatched_dependency })
| _, _ -> parsing_failed ())
| _ -> parsing_failed ())
| _ -> parsing_failed ()
let to_yojson = function
| Simple search_paths ->
`Assoc
[
"kind", `String "simple";
"paths", [%to_yojson: string list] (List.map search_paths ~f:SearchPath.show);
]
| WithUnwatchedDependency { sources; unwatched_dependency } ->
`Assoc
[
"kind", `String "with_unwatched_dependency";
"paths", [%to_yojson: string list] (List.map sources ~f:SearchPath.show);
"unwatched_dependency", UnwatchedDependency.to_yojson unwatched_dependency;
]
| Buck buck -> Buck.to_yojson buck |> Yojson.Safe.Util.combine (`Assoc ["kind", `String "buck"])
let to_search_paths = function
| Simple sources
| WithUnwatchedDependency { sources; _ } ->
sources
| Buck { artifact_root; _ } -> [SearchPath.Root artifact_root]
end
module RemoteLogging = struct
type t = {
logger: string;
identifier: (string[@default ""]);
}
[@@deriving sexp, compare, hash, yojson]
end
module PythonVersion = struct
type t = {
major: int;
minor: int;
micro: int;
}
[@@deriving sexp, compare, hash, yojson]
let default =
{
major = default_python_major_version;
minor = default_python_minor_version;
micro = default_python_micro_version;
}
end
module SharedMemory = struct
type t = {
heap_size: int;
dependency_table_power: int;
hash_table_power: int;
}
[@@deriving sexp, compare, hash, yojson]
let default =
{
heap_size = default_shared_memory_heap_size;
dependency_table_power = default_shared_memory_dependency_table_power;
hash_table_power = default_shared_memory_hash_table_power;
}
let of_yojson json =
let open JsonParsing in
Ok
{
heap_size = int_member "heap_size" ~default:default.heap_size json;
dependency_table_power =
int_member "dependency_table_power" ~default:default.dependency_table_power json;
hash_table_power = int_member "hash_table_power" ~default:default.hash_table_power json;
}
end
module Extension = struct
type t = {
suffix: string;
include_suffix_in_module_qualifier: bool;
}
[@@deriving show, sexp, compare, hash]
let to_yojson { suffix; include_suffix_in_module_qualifier } =
`Assoc
[
"suffix", `String suffix;
"include_suffix_in_module_qualifier", `Bool include_suffix_in_module_qualifier;
]
let of_yojson = function
| `Assoc
[
("suffix", `String suffix);
("include_suffix_in_module_qualifier", `Bool include_suffix_in_module_qualifier);
] ->
Result.Ok { suffix; include_suffix_in_module_qualifier }
| _ as json ->
let message =
Format.sprintf "Malformed critical file JSON: %s" (Yojson.Safe.to_string json)
in
Result.Error message
let create_extension serialized =
match String.split serialized ~on:'$' with
| [suffix] -> { suffix; include_suffix_in_module_qualifier = false }
| [suffix; options] when String.equal options "include_suffix_in_module_qualifier" ->
{ suffix; include_suffix_in_module_qualifier = true }
| _ -> failwith (Format.asprintf "Unable to parse extension from %s" serialized)
let suffix { suffix; _ } = suffix
end
module Analysis = struct
type incremental_style =
| Shallow
| FineGrained
[@@deriving show]
type shared_memory = {
heap_size: int;
dependency_table_power: int;
hash_table_power: int;
}
[@@deriving show]
type constraint_solving_style =
| FunctionCallLevel
| ExpressionLevel
[@@deriving show]
let default_constraint_solving_style = FunctionCallLevel
type t = {
parallel: bool;
analyze_external_sources: bool;
filter_directories: PyrePath.t list option;
ignore_all_errors: PyrePath.t list option;
number_of_workers: int;
local_root: PyrePath.t;
debug: bool;
project_root: PyrePath.t;
source_paths: SearchPath.t list;
search_paths: SearchPath.t list;
taint_model_paths: PyrePath.t list;
strict: bool;
show_error_traces: bool;
excludes: Str.regexp list; [@opaque]
extensions: Extension.t list;
store_type_check_resolution: bool;
store_type_errors: bool;
incremental_style: incremental_style;
log_directory: PyrePath.t;
python_major_version: int;
python_minor_version: int;
python_micro_version: int;
shared_memory: shared_memory;
enable_type_comments: bool;
constraint_solving_style: constraint_solving_style;
}
[@@deriving show]
let create
?(parallel = true)
?(analyze_external_sources = false)
?filter_directories
?ignore_all_errors
?(number_of_workers = 4)
?(local_root = PyrePath.current_working_directory ())
?(project_root = PyrePath.create_absolute "/")
?(search_paths = [])
?(taint_model_paths = [])
?(strict = false)
?(debug = false)
?(show_error_traces = false)
?(excludes = [])
?(extensions = [])
?(store_type_check_resolution = true)
?(store_type_errors = true)
?(incremental_style = Shallow)
?log_directory
?(python_major_version = default_python_major_version)
?(python_minor_version = default_python_minor_version)
?(python_micro_version = default_python_micro_version)
?(shared_memory_heap_size = default_shared_memory_heap_size)
?(shared_memory_dependency_table_power = default_shared_memory_dependency_table_power)
?(shared_memory_hash_table_power = default_shared_memory_hash_table_power)
?(enable_type_comments = true)
?(constraint_solving_style = default_constraint_solving_style)
~source_paths
()
=
{
parallel;
analyze_external_sources;
filter_directories;
ignore_all_errors;
number_of_workers;
local_root;
debug;
project_root;
source_paths;
search_paths;
taint_model_paths;
strict;
show_error_traces;
excludes =
List.map excludes ~f:(fun exclude_regex ->
Str.global_substitute
(Str.regexp_string "${SOURCE_DIRECTORY}")
(fun _ -> PyrePath.absolute local_root)
exclude_regex
|> Str.regexp);
extensions;
store_type_check_resolution;
store_type_errors;
incremental_style;
log_directory =
(match log_directory with
| Some directory -> PyrePath.create_absolute directory
| None -> PyrePath.append local_root ~element:".pyre");
python_major_version;
python_minor_version;
python_micro_version;
shared_memory =
{
heap_size = shared_memory_heap_size;
dependency_table_power = shared_memory_dependency_table_power;
hash_table_power = shared_memory_hash_table_power;
};
enable_type_comments;
constraint_solving_style;
}
let log_directory { log_directory; _ } = log_directory
let search_paths { source_paths; search_paths; _ } =
(* Have an ordering of search_path > source_path with the parser. search_path precedes
* local_root due to the possibility of having a subdirectory of the root in the search path. *)
search_paths @ source_paths
let extension_suffixes { extensions; _ } = List.map ~f:Extension.suffix extensions
let find_extension { extensions; _ } path =
List.find extensions ~f:(fun extension ->
String.is_suffix ~suffix:(Extension.suffix extension) (PyrePath.absolute path))
end
module StaticAnalysis = struct
type t = {
result_json_path: PyrePath.t option;
dump_call_graph: PyrePath.t option;
verify_models: bool;
(* Analysis configuration *)
configuration: Analysis.t;
rule_filter: int list option;
find_missing_flows: string option;
dump_model_query_results: PyrePath.t option;
use_cache: bool;
maximum_trace_length: int option;
maximum_tito_depth: int option;
}
end