hphp/hack/src/server/serverConfig.ml (522 lines of code) (raw):

(* * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the "hack" directory of this source tree. * *) (** * Parses and gathers information from the .hhconfig in the repo. *) open Hh_prelude open Config_file.Getters open Reordered_argument_collections open ServerLocalConfig type t = { version: Config_file.version; [@printer (fun fmt _ -> fprintf fmt "version")] load_script_timeout: int; (* in seconds *) (* Configures only the workers. Workers can have more relaxed GC configs as * they are short-lived processes *) gc_control: Gc.control; [@printer (fun fmt _ -> fprintf fmt "control")] sharedmem_config: SharedMem.config; tc_options: TypecheckerOptions.t; parser_options: ParserOptions.t; glean_options: GleanOptions.t; symbol_write_options: SymbolWriteOptions.t; formatter_override: Path.t option; config_hash: string option; (* A list of regexps for paths to ignore *) ignored_paths: string list; (* A list of extra paths to search for declarations *) extra_paths: Path.t list; warn_on_non_opt_build: bool; } [@@deriving show] let filename = Relative_path.from_root ~suffix:Config_file.file_path_relative_to_repo_root let is_compatible c1 c2 = (* This comparison can eventually be made more complex; we may not always * need to restart hh_server, e.g. changing the path to the load script * is immaterial*) Poly.equal c1 c2 let make_gc_control config = let { Gc.Control.minor_heap_size; space_overhead; _ } = GlobalConfig.gc_control in let minor_heap_size = int_ "gc_minor_heap_size" ~default:minor_heap_size config in let space_overhead = int_ "gc_space_overhead" ~default:space_overhead config in { GlobalConfig.gc_control with Gc.Control.minor_heap_size; space_overhead } let make_sharedmem_config config options local_config = let { SharedMem.global_size; heap_size; shm_min_avail; _ } = SharedMem.default_config in let shm_dirs = local_config.ServerLocalConfig.shm_dirs in let global_size = int_ "sharedmem_global_size" ~default:global_size config in let heap_size = int_ "sharedmem_heap_size" ~default:heap_size config in let hash_table_pow = int_ "sharedmem_hash_table_pow" ~default:18 config in let log_level = int_ "sharedmem_log_level" ~default:0 config in let sample_rate = float_ "sharedmem_sample_rate" ~default:0.0 config in let compression = int_ "sharedmem_compression" ~default:0 config in let shm_dirs = string_list "sharedmem_dirs" ~default:shm_dirs config in let shm_use_sharded_hashtbl = bool_ "shm_use_sharded_hashtbl" ~default:local_config.ServerLocalConfig.shm_use_sharded_hashtbl config in let shm_cache_size = int_ "shm_cache_size" ~default:local_config.ServerLocalConfig.shm_cache_size config in let shm_min_avail = int_ "sharedmem_minimum_available" ~default:shm_min_avail config in let config = { SharedMem.global_size; heap_size; hash_table_pow; log_level; sample_rate; shm_dirs; shm_use_sharded_hashtbl; shm_cache_size; shm_min_avail; compression; } in match ServerArgs.ai_mode options with | None -> config | Some ai_options -> Ai_options.modify_shared_mem ai_options config let config_list_regexp = Str.regexp "[, \t]+" let process_experimental sl = match List.map sl ~f:String.lowercase with | ["false"] -> SSet.empty | ["true"] -> TypecheckerOptions.experimental_all | features -> List.fold_left features ~f:SSet.add ~init:SSet.empty let config_experimental_tc_features config = match Config_file.Getters.string_opt "enable_experimental_tc_features" config with | None -> SSet.empty | Some s -> let sl = Str.split config_list_regexp s in process_experimental sl let process_migration_flags sl = match sl with | ["false"] -> SSet.empty | ["true"] -> TypecheckerOptions.migration_flags_all | flags -> List.iter flags ~f:(fun s -> if not (SSet.mem TypecheckerOptions.migration_flags_all s) then failwith ("invalid migration flag: " ^ s)); List.fold_left flags ~f:SSet.add ~init:SSet.empty let config_tc_migration_flags config = Config_file.Getters.string_opt "enable_tc_migration_flags" config |> Option.value_map ~f:(Str.split config_list_regexp) ~default:[] |> List.map ~f:String.lowercase |> process_migration_flags let convert_paths str = let json = Hh_json.json_of_string ~strict:true str in let l = Hh_json.get_array_exn json in List.filter_map ~f:(fun s -> match s with | Hh_json.JSON_String path -> Some path | _ -> None) l let process_ignored_paths config = Config_file.Getters.string_opt "ignored_paths" config |> Option.value_map ~f:convert_paths ~default:[] let maybe_relative_path fn = (* Note: this is not the same as calling realpath; the cwd is not * necessarily the same as hh_server's root!!! *) Path.make begin if Filename.is_relative fn then Relative_path.(to_absolute (from_root ~suffix:fn)) else fn end let process_extra_paths config = match Config_file.Getters.string_opt "extra_paths" config with | Some s -> Str.split config_list_regexp s |> List.map ~f:maybe_relative_path | _ -> [] let process_untrusted_mode config = match Config_file.Getters.string_opt "untrusted_mode" config with | Some s -> if bool_of_string s then let blacklist = [ (* out of tree file access*) "extra_paths"; (* potential resource abuse *) "language_feature_logging"; ] in let prefix_blacklist = [(* potential resource abuse *) "gc_"; "sharedmem_"] in let invalid_keys = List.filter (Config_file.keys config) ~f:(fun ck -> let ck = String.lowercase ck in let exact_match = List.find ~f:(fun bli -> String.equal bli ck) blacklist in let prefix_match = List.find ~f:(fun blp -> String_utils.string_starts_with ck blp) prefix_blacklist in match (exact_match, prefix_match) with | (None, None) -> false | _ -> true) in if not (List.is_empty invalid_keys) then failwith ("option not permitted in untrusted_mode: " ^ String.concat ~sep:", " invalid_keys) else failwith "untrusted_mode can only be enabled, not disabled" | _ -> () let extract_auto_namespace_element ns_map element = match element with | (source, Hh_json.JSON_String target) -> (source, target) :: ns_map | _ -> (* This means the JSON we received is incorrect *) ns_map let convert_auto_namespace_to_map map = let json = Hh_json.json_of_string ~strict:true map in let pairs = Hh_json.get_object_exn json in (* We do a fold instead of a map to filter * out the incorrect entrie as we look at each item *) List.fold_left ~init:[] ~f:extract_auto_namespace_element pairs let prepare_auto_namespace_map config = Option.value_map (Config_file.Getters.string_opt "auto_namespace_map" config) ~default:[] ~f:convert_auto_namespace_to_map let extract_log_level = function | (log_key, Hh_json.JSON_Number log_level) -> begin match int_of_string_opt log_level with | Some log_level -> (log_key, log_level) | None -> failwith "non-integer log level value" end | _ -> failwith "non-integer log level value" let convert_log_levels_to_map map = let json = Hh_json.json_of_string ~strict:true map in let pairs = Hh_json.get_object_exn json in List.map ~f:extract_log_level pairs |> SMap.of_list let prepare_log_levels config = Option.value_map (Config_file.Getters.string_opt "log_levels" config) ~default:SMap.empty ~f:convert_log_levels_to_map let prepare_iset config config_name initial_values = Config_file.Getters.string_opt config_name config |> Option.value_map ~f:(Str.split config_list_regexp) ~default:[] |> List.map ~f:int_of_string |> List.fold_right ~init:initial_values ~f:ISet.add let prepare_error_codes_treated_strictly config = prepare_iset config "error_codes_treated_strictly" (ISet.of_list []) let prepare_allowed_decl_fixme_codes config = prepare_iset config "allowed_decl_fixme_codes" (ISet.of_list []) let load ~silent config_filename options : t * ServerLocalConfig.t = let config_overrides = Config_file.of_list @@ ServerArgs.config options in let (config_hash, config) = Config_file.parse_hhconfig (Relative_path.to_absolute config_filename) in let config = Config_file.apply_overrides ~from:None ~config ~overrides:config_overrides in process_untrusted_mode config; let version = Config_file.parse_version (Config_file.Getters.string_opt "version" config) in let local_config = ServerLocalConfig.load ~silent ~current_version:version config_overrides in let local_config = if Option.is_some (ServerArgs.ai_mode options) then let open ServerLocalConfig in { local_config with watchman = { local_config.watchman with Watchman.enabled = false; subscribe = false; }; interrupt_on_watchman = false; interrupt_on_client = false; trace_parsing = false; } else local_config in let ignored_paths = process_ignored_paths config in let extra_paths = process_extra_paths config in (* Since we use the unix alarm() for our timeouts, a timeout value of 0 means * to wait indefinitely *) let load_script_timeout = int_ "load_script_timeout" ~default:0 config in let warn_on_non_opt_build = bool_ "warn_on_non_opt_build" ~default:false config in let formatter_override = Option.map (Config_file.Getters.string_opt "formatter_override" config) ~f:maybe_relative_path in let global_opts = GlobalOptions.make ?po_deregister_php_stdlib:(bool_opt "deregister_php_stdlib" config) ?tco_num_local_workers:local_config.num_local_workers ~tco_parallel_type_checking_threshold: local_config.parallel_type_checking_threshold ?tco_max_typechecker_worker_memory_mb: local_config.max_typechecker_worker_memory_mb ?tco_defer_class_declaration_threshold: local_config.defer_class_declaration_threshold ?tco_prefetch_deferred_files:(Some local_config.prefetch_deferred_files) ?tco_remote_type_check_threshold: ServerLocalConfig.RemoteTypeCheck.( local_config.remote_type_check.recheck_threshold) ?tco_remote_type_check: ServerLocalConfig.RemoteTypeCheck.( Some local_config.remote_type_check.enabled) ?tco_remote_worker_key:local_config.remote_worker_key ?tco_remote_check_id:local_config.remote_check_id ?tco_remote_max_batch_size: ServerLocalConfig.RemoteTypeCheck.( Some local_config.remote_type_check.max_batch_size) ?tco_remote_min_batch_size: ServerLocalConfig.RemoteTypeCheck.( Some local_config.remote_type_check.min_batch_size) ?tco_num_remote_workers: ServerLocalConfig.RemoteTypeCheck.( Some local_config.remote_type_check.num_workers) ?so_remote_version_specifier:local_config.remote_version_specifier ?so_remote_worker_vfs_checkout_threshold: ServerLocalConfig.RemoteTypeCheck.( Some local_config.remote_type_check.worker_vfs_checkout_threshold) ?so_naming_sqlite_path:local_config.naming_sqlite_path ?tco_language_feature_logging:(bool_opt "language_feature_logging" config) ?tco_timeout:(int_opt "timeout" config) ?tco_disallow_invalid_arraykey: (bool_opt "disallow_invalid_arraykey" config) ?tco_disallow_byref_dynamic_calls: (bool_opt "disallow_byref_dynamic_calls" config) ?tco_disallow_byref_calls:(bool_opt "disallow_byref_calls" config) ?po_disable_lval_as_an_expression: (bool_opt "disable_lval_as_an_expression" config) ~allowed_fixme_codes_strict: (prepare_iset config "allowed_fixme_codes_strict" ISet.empty) ~allowed_fixme_codes_partial: (prepare_iset config "allowed_fixme_codes_partial" ISet.empty) ~codes_not_raised_partial: (prepare_iset config "codes_not_raised_partial" ISet.empty) ~po_auto_namespace_map:(prepare_auto_namespace_map config) ~tco_experimental_features:(config_experimental_tc_features config) ~tco_log_inference_constraints: (ServerArgs.log_inference_constraints options) ~tco_migration_flags:(config_tc_migration_flags config) ~tco_shallow_class_decl:local_config.ServerLocalConfig.shallow_class_decl ~tco_force_shallow_decl_fanout: local_config.ServerLocalConfig.force_shallow_decl_fanout ~tco_force_load_hot_shallow_decls: local_config.ServerLocalConfig.force_load_hot_shallow_decls ~tco_fetch_remote_old_decls: local_config.ServerLocalConfig.fetch_remote_old_decls ~tco_populate_member_heaps: local_config.ServerLocalConfig.populate_member_heaps ~tco_skip_hierarchy_checks: local_config.ServerLocalConfig.skip_hierarchy_checks ~po_allow_unstable_features: local_config.ServerLocalConfig.allow_unstable_features ?tco_like_type_hints:(bool_opt "like_type_hints" config) ?tco_union_intersection_type_hints: (bool_opt "union_intersection_type_hints" config) ?tco_coeffects:(bool_opt "call_coeffects" config) ?tco_coeffects_local:(bool_opt "local_coeffects" config) ?tco_like_casts:(bool_opt "like_casts" config) ?tco_simple_pessimize:(float_opt "simple_pessimize" config) ?tco_complex_coercion:(bool_opt "complex_coercion" config) ~error_codes_treated_strictly: (prepare_error_codes_treated_strictly config) ?tco_check_xhp_attribute:(bool_opt "check_xhp_attribute" config) ?tco_check_redundant_generics:(bool_opt "check_redundant_generics" config) ?tco_disallow_unresolved_type_variables: (bool_opt "disallow_unresolved_type_variables" config) ?po_enable_class_level_where_clauses: (bool_opt "class_level_where_clauses" config) ?po_disable_legacy_soft_typehints: (bool_opt "disable_legacy_soft_typehints" config) ?po_disallow_toplevel_requires: (bool_opt "disallow_toplevel_requires" config) ~po_allowed_decl_fixme_codes:(prepare_allowed_decl_fixme_codes config) ?po_allow_new_attribute_syntax: (bool_opt "allow_new_attribute_syntax" config) ?po_disable_legacy_attribute_syntax: (bool_opt "disable_legacy_attribute_syntax" config) ?tco_const_attribute:(bool_opt "const_attribute" config) ?po_const_default_func_args:(bool_opt "const_default_func_args" config) ?po_const_default_lambda_args: (bool_opt "const_default_lambda_args" config) ?po_disallow_silence:(bool_opt "disallow_silence" config) ?tco_global_inference:(bool_opt "global_inference" config) ?tco_gi_reinfer_types:(string_list_opt "reinfer_types" config) ?tco_const_static_props:(bool_opt "const_static_props" config) ?po_abstract_static_props:(bool_opt "abstract_static_props" config) ~po_parser_errors_only:(Option.is_some (ServerArgs.ai_mode options)) ?tco_check_attribute_locations: (bool_opt "check_attribute_locations" config) ?glean_service:(string_opt "glean_service" config) ?glean_hostname:(string_opt "glean_hostname" config) ?glean_port:(int_opt "glean_port" config) ?glean_reponame:(string_opt "glean_reponame" config) ?symbol_write_ownership:(bool_opt "symbol_write_ownership" config) ?symbol_write_root_path:(string_opt "symbol_write_root_path" config) ?symbol_write_hhi_path:(string_opt "symbol_write_hhi_path" config) ?symbol_write_ignore_paths: (string_list_opt "symbol_write_ignore_paths" config) ?symbol_write_index_paths: (string_list_opt "symbol_write_index_paths" config) ?symbol_write_index_paths_file: (string_opt "symbol_write_index_paths_file" config) ?symbol_write_index_paths_file_output: (string_opt "symbol_write_index_paths_file_output" config) ?symbol_write_include_hhi:(bool_opt "symbol_write_include_hhi" config) ?po_disallow_func_ptrs_in_constants: (bool_opt "disallow_func_ptrs_in_constants" config) ?tco_error_php_lambdas:(bool_opt "error_php_lambdas" config) ?tco_disallow_discarded_nullable_awaitables: (bool_opt "disallow_discarded_nullable_awaitables" config) ?po_disable_xhp_element_mangling: (bool_opt "disable_xhp_element_mangling" config) ?po_disable_xhp_children_declarations: (bool_opt "disable_xhp_children_declarations" config) ?po_enable_xhp_class_modifier: (bool_opt "enable_xhp_class_modifier" config) ?po_disable_hh_ignore_error:(bool_opt "disable_hh_ignore_error" config) ?tco_method_call_inference:(bool_opt "method_call_inference" config) ?tco_report_pos_from_reason:(bool_opt "report_pos_from_reason" config) ?tco_typecheck_sample_rate:(float_opt "typecheck_sample_rate" config) ?tco_enable_sound_dynamic:(bool_opt "enable_sound_dynamic_type" config) ?tco_enable_modules:(bool_opt "enable_modules" config) ?po_disallow_fun_and_cls_meth_pseudo_funcs: (bool_opt "disallow_fun_and_cls_meth_pseudo_funcs" config) ?po_disallow_inst_meth:(bool_opt "disallow_inst_meth" config) ~tco_use_direct_decl_parser: local_config.ServerLocalConfig.use_direct_decl_parser ~tco_ifc_enabled:(ServerArgs.enable_ifc options) ~tco_global_write_check_enabled: (ServerArgs.enable_global_write_check options) ~tco_global_write_check_functions_enabled: (ServerArgs.enable_global_write_check_functions options) ?po_enable_enum_classes:(bool_opt "enable_enum_classes" config) ?po_enable_enum_supertyping:(bool_opt "enable_enum_supertyping" config) ?po_interpret_soft_types_as_like_types: (bool_opt "interpret_soft_types_as_like_types" config) ?tco_enable_strict_string_concat_interp: (bool_opt "enable_strict_string_concat_interp" config) ?tco_ignore_unsafe_cast:(bool_opt "ignore_unsafe_cast" config) ?tco_allowed_expression_tree_visitors: (Option.map (string_list_opt "allowed_expression_tree_visitors" config) ~f:(fun l -> List.map l ~f:Utils.add_ns)) ?tco_math_new_code:(bool_opt "math_new_code" config) ?tco_typeconst_concrete_concrete_error: (bool_opt "typeconst_concrete_concrete_error" config) ?tco_enable_strict_const_semantics: (bool_opt "enable_strict_const_semantics" config) ?tco_meth_caller_only_public_visibility: (bool_opt "meth_caller_only_public_visibility" config) ?tco_require_extends_implements_ancestors: (bool_opt "require_extends_implements_ancestors" config) ?tco_strict_value_equality:(bool_opt "strict_value_equality" config) ?tco_enforce_sealed_subclasses: (bool_opt "enforce_sealed_subclasses" config) ?tco_everything_sdt:(bool_opt "everything_sdt" config) ?tco_pessimise_builtins:(bool_opt "pessimise_builtins" config) ~tco_enable_disk_heap:local_config.ServerLocalConfig.enable_disk_heap ?tco_explicit_consistent_constructors: (int_opt "explicit_consistent_constructors" config) ?tco_require_types_class_consts: (int_opt "require_types_tco_require_types_class_consts" config) ?tco_type_printer_fuel:(int_opt "type_printer_fuel" config) ?tco_saved_state_manifold_api_key: (Some local_config.ServerLocalConfig.saved_state_manifold_api_key) ~tco_log_saved_state_age_and_distance: local_config.ServerLocalConfig.log_saved_state_age_and_distance ~log_levels:(prepare_log_levels config) () in Errors.allowed_fixme_codes_strict := GlobalOptions.allowed_fixme_codes_strict global_opts; Errors.allowed_fixme_codes_partial := GlobalOptions.allowed_fixme_codes_partial global_opts; Errors.codes_not_raised_partial := GlobalOptions.codes_not_raised_partial global_opts; Errors.error_codes_treated_strictly := GlobalOptions.error_codes_treated_strictly global_opts; Errors.report_pos_from_reason := GlobalOptions.tco_report_pos_from_reason global_opts; ( { version; load_script_timeout; gc_control = make_gc_control config; sharedmem_config = make_sharedmem_config config options local_config; tc_options = global_opts; parser_options = global_opts; glean_options = global_opts; symbol_write_options = global_opts; formatter_override; config_hash = Some config_hash; ignored_paths; extra_paths; warn_on_non_opt_build; }, local_config ) (* useful in testing code *) let default_config = { version = Config_file.Opaque_version None; load_script_timeout = 0; gc_control = GlobalConfig.gc_control; sharedmem_config = SharedMem.default_config; tc_options = TypecheckerOptions.default; glean_options = GleanOptions.default; symbol_write_options = SymbolWriteOptions.default; parser_options = ParserOptions.default; formatter_override = None; config_hash = None; ignored_paths = []; extra_paths = []; warn_on_non_opt_build = false; } let set_parser_options config popt = { config with parser_options = popt } let set_tc_options config tcopt = { config with tc_options = tcopt } let set_glean_options config gleanopt = { config with glean_options = gleanopt } let set_symbol_write_options config swriteopt = { config with symbol_write_options = swriteopt } let gc_control config = config.gc_control let sharedmem_config config = config.sharedmem_config let typechecker_options config = config.tc_options let parser_options config = config.parser_options let glean_options config = config.glean_options let symbol_write_options config = config.symbol_write_options let formatter_override config = config.formatter_override let config_hash config = config.config_hash let ignored_paths config = config.ignored_paths |> List.map ~f:Str.regexp let extra_paths config = config.extra_paths let version config = config.version let warn_on_non_opt_build config = config.warn_on_non_opt_build