1 // Copyright (c) 2019; Facebook; Inc.
2 // All rights reserved.
4 // This source code is licensed under the MIT license found in the
5 // LICENSE file in the "hack" directory of this source tree.
7 //! HackC emitter options.
9 //! Canonical name for each option is chosen to be JSON key, which often
10 //! differs from the ones passed as CLI arguments via `-v KEY=VALUE`. The
11 //! names for arguments (non-flags) are derived via the `serde` framework
12 //! by taking the name of the field (or the one given via serde(rename=new_name)),
13 //! and the names for flags (boolean options) are derived by downcasing bitflags names.
14 //! The options are grouped their common prefix in their canonical names,
15 //! which is specified via macros `prefix_all` or `prefixed_flags`, respectively.
16 //! E.g., `prefix_all("hhvm.")`` or `prefixed_flags(..., "hhvm.", ...)` ensure that
17 //! an argument "emit_meth_caller_func_pointers" or flag LOG_EXTERN_COMPILER_PERF gets the
18 //! canonical name "hhvm.emit_meth_caller_func_pointers" or "hhvm.log_extern_compiler_perf", respectively.
20 //! Non-canonical names (used when parsing from CLI) are specified by:
21 //! - `options_cli::CANON_BY_ALIAS.get("some_alias")`; and
22 //! - attribute `#[serde(alias = "some_alias")]`, for any non-flag argument.
23 //! The latter is mainly for convenience, so that JSON can be deserialized
24 //! even when the caller passes a CLI name (which would be understood for flags,
25 //! so it is also more consistent), but can also be handy for migration towards
26 //! consistent names between JSON and CLI.
30 //! let opts: Options = Options::default(); // JSON key
31 //! opts.doc_root.get(); // doc_root
32 //! opts.hhvm.emit_meth_caller_func_pointers.set(42); // hhvm.emit_meth_caller_func_pointers
33 //! opts.hhvm_flags.contains(
34 //! HhvmFlags::RX_IS_ENABLED); // hhvm.rx_is_enabled
35 //! opts.hhvm.hack_lang_flags.set(
36 //! LangFlags::ENABLE_ENUM_CLASSES); // hhvm.hack.lang.enable_enum_classes
41 use hhbc_by_ref_options_serde::prefix_all;
45 extern crate bitflags;
46 use bitflags::bitflags;
49 extern crate lazy_static;
51 use serde_derive::{Deserialize, Serialize};
52 use serde_json::{json, value::Value as Json};
54 use itertools::Either;
55 use std::{cell::RefCell, collections::BTreeMap, iter::empty};
57 /// Provides uniform access to bitflags-generated structs in JSON SerDe
63 + std::ops::BitOrAssign
64 + std::ops::BitAndAssign
65 + std::ops::Not<Output = Self>
67 const PREFIX: &'static str;
69 // these methods (or equivalents) are implemented by bitflags!
72 fn from_flags(flags: &Flags) -> Option<Self>;
73 fn contains(&self, other: Self) -> bool;
74 fn bits(&self) -> u64;
75 fn to_map() -> BTreeMap<String, Self>;
78 macro_rules! prefixed_flags {
79 ($class:ident, $prefix:expr, $($field:ident),*,) => { // require trailing comma
82 pub struct $class: u64 {
83 // TODO(leoo) expand RHS this into 1 << i, using equivalent of C++ index_sequence
84 $( const $field = Flags::$field.bits(); )*
87 impl PrefixedFlags for $class {
88 const PREFIX: &'static str = $prefix;
89 const EMPTY: Self = Self::empty();
90 const ALL: Self = Self::all();
92 // TODO(leoo) use proc_macro_hack and field_to_config_name!($field)
93 // to map ("some.prefix", SOME_FIELD) into "some.prefix.some_field"
94 // fn by_name(name: &'static str) -> Self {
96 // $( case field_to_config_name!($prefix, $field) => Flags::$field, )*
99 fn to_map() -> BTreeMap<String, Self> {{
100 let mut ret: BTreeMap<String, Self> = BTreeMap::new();
102 ret.insert(stringify!($field).to_lowercase(), Self::$field);
107 fn contains(&self, other: Self) -> bool {
111 fn bits(&self) -> u64 {
115 fn from_flags(flags: &Flags) -> Option<Self> {
116 Self::from_bits(flags.bits())
122 /// An option of non-boolean type T (i.e., not a flag)
123 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
128 pub fn get(&self) -> &T {
132 pub fn get_mut(&mut self) -> &mut T {
133 &mut self.global_value
136 pub fn new(global_value: T) -> Arg<T> {
141 // group options by JSON config prefix to avoid error-prone repetition & boilerplate in SerDe
147 OPTIMIZE_NULL_CHECKS,
150 impl Default for CompilerFlags {
151 fn default() -> CompilerFlags {
152 CompilerFlags::CONSTANT_FOLDING | CompilerFlags::RELABEL
160 EMIT_CLS_METH_POINTERS,
161 EMIT_INST_METH_POINTERS,
162 EMIT_METH_CALLER_FUNC_POINTERS,
163 ENABLE_INTRINSICS_EXTENSION,
164 FOLD_LAZY_CLASS_KEYS,
165 HACK_ARR_COMPAT_NOTICES,
167 JIT_ENABLE_RENAME_FUNCTION,
168 LOG_EXTERN_COMPILER_PERF,
171 impl Default for HhvmFlags {
172 fn default() -> HhvmFlags {
173 HhvmFlags::EMIT_CLS_METH_POINTERS
174 | HhvmFlags::EMIT_INST_METH_POINTERS
175 | HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS
176 | HhvmFlags::FOLD_LAZY_CLASS_KEYS
180 #[prefix_all("hhvm.")]
181 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
184 pub aliased_namespaces: Arg<BTreeMapOrEmptyVec<String, String>>,
187 pub include_roots: Arg<BTreeMap<String, String>>, // TODO(leoo) change to HashMap if order doesn't matter
189 #[serde(default = "defaults::emit_class_pointers")]
190 pub emit_class_pointers: Arg<String>,
194 serialize_with = "serialize_flags",
195 deserialize_with = "deserialize_flags"
197 pub flags: HhvmFlags,
199 #[serde(flatten, default)]
200 pub hack_lang: HackLang,
203 impl Default for Hhvm {
204 fn default() -> Self {
206 aliased_namespaces: Default::default(),
207 include_roots: Default::default(),
208 emit_class_pointers: defaults::emit_class_pointers(),
209 flags: Default::default(),
210 hack_lang: Default::default(),
216 pub fn aliased_namespaces_iter(&self) -> impl Iterator<Item = (&str, &str)> {
217 match self.aliased_namespaces.get() {
218 BTreeMapOrEmptyVec::Nonempty(m) => {
219 Either::Right(m.iter().map(|(x, y)| (x.as_str(), y.as_str())))
221 _ => Either::Left(empty()),
225 pub fn aliased_namespaces_cloned(&self) -> impl Iterator<Item = (String, String)> + '_ {
226 match self.aliased_namespaces.get() {
227 BTreeMapOrEmptyVec::Nonempty(m) => {
228 Either::Right(m.iter().map(|(x, y)| (x.clone(), y.clone())))
230 _ => Either::Left(empty()),
235 #[prefix_all("hhvm.hack.lang.")]
236 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
237 pub struct HackLang {
240 serialize_with = "serialize_flags",
241 deserialize_with = "deserialize_flags"
243 pub flags: LangFlags,
246 pub check_int_overflow: Arg<String>,
252 ABSTRACT_STATIC_PROPS,
253 ALLOW_NEW_ATTRIBUTE_SYNTAX,
254 ALLOW_UNSTABLE_FEATURES,
255 CONST_DEFAULT_FUNC_ARGS,
256 CONST_DEFAULT_LAMBDA_ARGS,
258 DISABLE_LEGACY_ATTRIBUTE_SYNTAX,
259 DISABLE_LEGACY_SOFT_TYPEHINTS,
260 DISABLE_LVAL_AS_AN_EXPRESSION,
261 DISABLE_UNSET_CLASS_CONST,
262 DISABLE_XHP_ELEMENT_MANGLING,
263 DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS,
265 DISALLOW_FUNC_PTRS_IN_CONSTANTS,
266 DISALLOW_HASH_COMMENTS,
267 DISALLOW_DYNAMIC_METH_CALLER_ARGS,
268 ENABLE_CLASS_LEVEL_WHERE_CLAUSES,
270 ENABLE_XHP_CLASS_MODIFIER,
272 DISABLE_ARRAY_TYPEHINT,
276 impl Default for LangFlags {
277 fn default() -> LangFlags {
278 LangFlags::DISABLE_LEGACY_SOFT_TYPEHINTS | LangFlags::ENABLE_ENUM_CLASSES
284 "hhvm.hack.lang.phpism.",
285 DISABLE_NONTOPLEVEL_DECLARATIONS,
287 impl Default for PhpismFlags {
288 fn default() -> PhpismFlags {
299 impl Default for Php7Flags {
300 fn default() -> Php7Flags {
310 impl Default for RepoFlags {
311 fn default() -> RepoFlags {
316 #[prefix_all("hhvm.server.")]
317 #[derive(Clone, Serialize, Deserialize, Default, PartialEq, Debug)]
320 pub include_search_paths: Arg<Vec<String>>,
323 #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
326 pub doc_root: Arg<String>,
330 serialize_with = "serialize_flags",
331 deserialize_with = "deserialize_flags"
333 pub hack_compiler_flags: CompilerFlags,
335 #[serde(flatten, default)]
338 #[serde(default = "defaults::max_array_elem_size_on_the_stack")]
339 pub max_array_elem_size_on_the_stack: Arg<isize>,
343 serialize_with = "serialize_flags",
344 deserialize_with = "deserialize_flags"
346 pub phpism_flags: PhpismFlags,
350 serialize_with = "serialize_flags",
351 deserialize_with = "deserialize_flags"
353 pub php7_flags: Php7Flags,
357 serialize_with = "serialize_flags",
358 deserialize_with = "deserialize_flags"
360 pub repo_flags: RepoFlags,
362 #[serde(flatten, default)]
367 pub fn log_extern_compiler_perf(&self) -> bool {
370 .contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF)
374 impl Default for Options {
375 fn default() -> Options {
377 max_array_elem_size_on_the_stack: defaults::max_array_elem_size_on_the_stack(),
378 hack_compiler_flags: CompilerFlags::default(),
379 hhvm: Hhvm::default(),
380 phpism_flags: PhpismFlags::default(),
381 php7_flags: Php7Flags::default(),
382 repo_flags: RepoFlags::default(),
383 server: Server::default(),
384 // the rest is zeroed out (cannot do ..Default::default() as it'd be recursive)
385 doc_root: Arg::new("".to_owned()),
390 /// Non-zero argument defaults for use in both Default::default & SerDe framework
394 pub fn max_array_elem_size_on_the_stack() -> Arg<isize> {
398 pub fn emit_class_pointers() -> Arg<String> {
399 Arg::new("0".to_string())
404 static CACHE: RefCell<Cache> = {
405 let hasher = fnv::FnvBuildHasher::default();
406 RefCell::new(LruCache::with_hasher(100, hasher))
410 pub(crate) type Cache = LruCache<(Vec<String>, Vec<String>), Options, fnv::FnvBuildHasher>;
413 pub fn to_string(&self) -> String {
414 serde_json::to_string_pretty(&self).expect("failed to parse JSON")
417 pub fn from_json(s: &str) -> Result<Self, String> {
418 let opts: serde_json::Result<Self> = serde_json::from_str(s);
419 opts.map_err(|e| format!("failed to load config JSON:\n{}", e))
422 fn from_cli_args(args: &[impl AsRef<str>]) -> Result<Json, String> {
423 let mut json = json!({});
425 let arg = arg.as_ref();
426 match arg.find('=') {
428 let (key, val) = arg.split_at(pos);
429 let val = &val[1..]; // strip '='
430 let key = key.to_ascii_lowercase();
431 let key = key.as_ref();
432 let key: &str = options_cli::CANON_BY_ALIAS.get(key).unwrap_or(&key);
433 if let Some(val) = options_cli::to_json(key)(&val) {
434 json.as_object_mut().map(|m| {
443 return Err(format!("Invalid format for CLI arg key: {}", key));
446 None => return Err(format!("Missing '=' key-value separator in: {}", arg)),
452 /// Merges src JSON into dst JSON, recursively adding or overwriting existing entries.
453 /// This method cleverly avoids the need to represent each option as Option<Type>,
454 /// since only the ones that are specified by JSON will be actually overridden.
455 fn merge(dst: &mut Json, src: &Json) {
457 (&mut Json::Object(ref mut dst), &Json::Object(ref src)) => {
459 Self::merge(dst.entry(k.clone()).or_insert(Json::Null), v);
468 pub fn from_configs<S: AsRef<str>>(jsons: &[S], clis: &[S]) -> Result<Self, String> {
470 let key: (Vec<String>, Vec<String>) = (
471 jsons.iter().map(|x| (*x).as_ref().into()).collect(),
472 clis.iter().map(|x| (*x).as_ref().into()).collect(),
474 let mut cache = cache.borrow_mut();
475 if let Some(o) = cache.get_mut(&key) {
478 let o = Options::from_configs_(&key.0, &key.1)?;
479 cache.put(key, o.clone());
485 fn from_configs_<S1, S2>(jsons: &[S1], cli_args: &[S2]) -> Result<Self, String>
490 let mut merged = json!({});
492 let json: &str = json.as_ref();
498 &serde_json::from_str(json).map_err(|e| e.to_string())?,
501 let overrides = Self::from_cli_args(cli_args)?;
502 Self::merge(&mut merged, &overrides);
503 let opts: serde_json::Result<Self> = serde_json::value::from_value(merged);
504 opts.map_err(|e| e.to_string())
507 pub fn array_provenance(&self) -> bool {
508 self.hhvm.flags.contains(HhvmFlags::ARRAY_PROVENANCE)
511 pub fn check_int_overflow(&self) -> bool {
517 .map_or(false, |x| x.is_positive())
520 pub fn emit_class_pointers(&self) -> i32 {
521 self.hhvm.emit_class_pointers.get().parse::<i32>().unwrap()
525 use serde::de::{self, Deserializer, MapAccess, Visitor};
526 use serde::{ser::SerializeMap, Serializer};
528 fn serialize_flags<S: Serializer, P: PrefixedFlags>(flags: &P, s: S) -> Result<S::Ok, S::Error> {
529 // TODO(leoo) iterate over each set bit: flags.bits() & ~(flags.bits() + 1)
530 let mut map = s.serialize_map(None)?;
531 for (key, value) in P::to_map().into_iter() {
532 let bool_val = flags.contains(value);
533 map.serialize_entry(&format!("{}{}", &P::PREFIX, key), &Arg::new(bool_val))?;
538 /// Expected JSON layouts for each field that is a valid Hack option
539 #[derive(Deserialize)]
546 MapStr(BTreeMap<String, String>),
547 Json(Json), // support HHVM options with arbitrary layout
548 // (A poorer alternative that risks silent HHVM breakage
549 // would be to explicitly enumerate all possible layouts.)
552 fn deserialize_flags<'de, D: Deserializer<'de>, P: PrefixedFlags>(
554 ) -> Result<P, D::Error> {
556 use std::marker::PhantomData;
557 struct Phantom<P>(PhantomData<P>);
559 impl<'de, P: PrefixedFlags> Visitor<'de> for Phantom<P> {
562 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
563 formatter.write_str("flag with string global_value")
566 fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
567 // TODO(leoo) proc macro to traverse Flags struct & iter over assoc. constants
568 let mut flags = P::default();
569 let by_name = P::to_map();
570 let prefix_len = P::PREFIX.len();
571 let from_str = |v: &str| match v {
573 "false" => Ok(false),
577 .map_err(de::Error::custom),
579 while let Some((ref k, ref v)) = map.next_entry::<String, Arg<GlobalValue>>()? {
580 let mut found = None;
582 let k: &str = options_cli::CANON_BY_ALIAS.get(k).unwrap_or(&k);
583 if k.starts_with(P::PREFIX) {
584 found = by_name.get(&k[prefix_len..]).cloned();
586 if let Some(flag) = found {
587 let truish = match v.get() {
588 GlobalValue::String(s) => {
595 GlobalValue::Bool(b) => *b,
596 GlobalValue::Int(n) => *n == 1,
597 _ => continue, // types such as VecStr aren't parsable as flags
610 deserializer.deserialize_map(Phantom(PhantomData::<P>))
613 /// Wrapper that serves as a workaround for a bug in the HHVM's serialization
614 /// empty JSON objects ({}) are silently converted to [] (darray vs varray):
615 /// - (de)serialize [] as Empty (instead of {});
616 /// - (de)serialize missing values as [].
617 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
619 pub enum BTreeMapOrEmptyVec<K: std::cmp::Ord, V> {
620 Nonempty(BTreeMap<K, V>),
623 impl<K: std::cmp::Ord, V> BTreeMapOrEmptyVec<K, V> {
624 pub fn as_map(&self) -> Option<&BTreeMap<K, V>> {
626 BTreeMapOrEmptyVec::Nonempty(m) => Some(m),
631 impl<K: std::cmp::Ord, V> Into<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
632 fn into(self) -> BTreeMap<K, V> {
634 BTreeMapOrEmptyVec::Nonempty(m) => m,
635 _ => BTreeMap::new(),
639 impl<K: std::cmp::Ord, V> From<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
640 fn from(m: BTreeMap<K, V>) -> Self {
642 BTreeMapOrEmptyVec::Empty(vec![])
644 BTreeMapOrEmptyVec::Nonempty(m)
648 impl<K: std::cmp::Ord, V> Default for BTreeMapOrEmptyVec<K, V> {
649 fn default() -> Self {
650 BTreeMapOrEmptyVec::Empty(vec![])
657 use pretty_assertions::assert_eq; // make assert_eq print huge diffs more human-readable
659 const HHVM_1: &'static str = r#"{
660 "hhvm.aliased_namespaces": {
666 "hhvm.array_provenance": {
667 "global_value": false
669 "hhvm.emit_class_pointers": {
672 "hhvm.emit_cls_meth_pointers": {
673 "global_value": false
675 "hhvm.emit_inst_meth_pointers": {
676 "global_value": false
678 "hhvm.emit_meth_caller_func_pointers": {
681 "hhvm.enable_intrinsics_extension": {
682 "global_value": false
684 "hhvm.fold_lazy_class_keys": {
687 "hhvm.hack.lang.abstract_static_props": {
688 "global_value": false
690 "hhvm.hack.lang.allow_new_attribute_syntax": {
691 "global_value": false
693 "hhvm.hack.lang.allow_unstable_features": {
694 "global_value": false
696 "hhvm.hack.lang.check_int_overflow": {
699 "hhvm.hack.lang.const_default_func_args": {
700 "global_value": false
702 "hhvm.hack.lang.const_default_lambda_args": {
703 "global_value": false
705 "hhvm.hack.lang.const_static_props": {
706 "global_value": false
708 "hhvm.hack.lang.disable_array": {
709 "global_value": false
711 "hhvm.hack.lang.disable_array_cast": {
712 "global_value": false
714 "hhvm.hack.lang.disable_array_typehint": {
715 "global_value": false
717 "hhvm.hack.lang.disable_legacy_attribute_syntax": {
718 "global_value": false
720 "hhvm.hack.lang.disable_legacy_soft_typehints": {
723 "hhvm.hack.lang.disable_lval_as_an_expression": {
724 "global_value": false
726 "hhvm.hack.lang.disable_unset_class_const": {
727 "global_value": false
729 "hhvm.hack.lang.disable_xhp_element_mangling": {
730 "global_value": false
732 "hhvm.hack.lang.disallow_dynamic_meth_caller_args": {
733 "global_value": false
735 "hhvm.hack.lang.disallow_fun_and_cls_meth_pseudo_funcs": {
736 "global_value": false
738 "hhvm.hack.lang.disallow_func_ptrs_in_constants": {
739 "global_value": false
741 "hhvm.hack.lang.disallow_hash_comments": {
742 "global_value": false
744 "hhvm.hack.lang.disallow_inst_meth": {
745 "global_value": false
747 "hhvm.hack.lang.enable_class_level_where_clauses": {
748 "global_value": false
750 "hhvm.hack.lang.enable_enum_classes": {
753 "hhvm.hack.lang.enable_xhp_class_modifier": {
754 "global_value": false
756 "hhvm.hack.lang.rust_emitter": {
757 "global_value": false
759 "hhvm.hack_arr_compat_notices": {
760 "global_value": false
762 "hhvm.hack_arr_dv_arrs": {
763 "global_value": false
765 "hhvm.include_roots": {
768 "hhvm.jit_enable_rename_function": {
769 "global_value": false
771 "hhvm.log_extern_compiler_perf": {
772 "global_value": false
774 "hhvm.rx_is_enabled": {
775 "global_value": false
780 fn test_hhvm_json_ser() {
781 let hhvm = json!(Hhvm {
782 aliased_namespaces: Arg::new({
783 let mut m = BTreeMap::new();
784 m.insert("foo".to_owned(), "bar".to_owned());
785 m.insert("bar".to_owned(), "baz".to_owned());
788 flags: HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS | HhvmFlags::FOLD_LAZY_CLASS_KEYS,
791 assert_eq!(HHVM_1, serde_json::to_string_pretty(&hhvm).unwrap(),);
795 fn test_hhvm_json_de() {
796 let j = serde_json::from_str(
798 "hhvm.aliased_namespaces": { "global_value": {"foo": "bar"} },
799 "hhvm.emit_meth_caller_func_pointers": { "global_value": "true" },
800 "hhvm.jit_enable_rename_function": { "global_value": 1 },
801 "hhvm.log_extern_compiler_perf": { "global_value": false },
802 "hhvm.array_provenance": { "global_value": "1" }
806 let hhvm: Hhvm = serde_json::from_value(j).unwrap();
807 assert!(hhvm.flags.contains(
808 HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS
809 | HhvmFlags::JIT_ENABLE_RENAME_FUNCTION
810 | HhvmFlags::ARRAY_PROVENANCE
812 assert!(!hhvm.flags.contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF));
816 fn test_hhvm_json_de_defaults_overrideable() {
817 let hhvm: Hhvm = serde_json::value::from_value(json!({})).unwrap();
818 assert_eq!(hhvm.flags, HhvmFlags::default());
821 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
824 // now override a true-by-default option with a false value
825 let hhvm: Hhvm = serde_json::value::from_value(json!({
826 "hhvm.emit_meth_caller_func_pointers": { "global_value": "false" },
832 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
837 fn test_hhvm_flags_alias_json_de() {
838 // sanity check for defaults (otherwise this test doesn't do much!)
839 assert!(!HhvmFlags::default().contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION));
840 assert!(HhvmFlags::default().contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS));
842 let hhvm: Hhvm = serde_json::from_str(
843 r#"{ "eval.jitenablerenamefunction": { "global_value": "true" },
844 "hhvm.emit_meth_caller_func_pointers": { "global_value": "false" } }"#,
848 hhvm // verify a false-by-default flag was parsed as true
850 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
854 !hhvm // verify a true-by-default flag was parsed as false
856 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
861 fn test_empty_flag_treated_as_false_json_de() {
862 // verify a true-by-default flag was parsed as false if ""
863 let hhvm: Hhvm = serde_json::from_str(
864 r#"{ "hhvm.emit_meth_caller_func_pointers": { "global_value": "" } }"#,
870 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
875 fn test_options_flat_arg_alias_json_de() {
876 let act: Options = serde_json::value::from_value(json!({
877 "eval.jitenablerenamefunction": {
878 "global_value": "true",
881 .expect("failed to deserialize");
885 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
890 fn test_options_nonzero_defaults_json_de() {
891 let act: Options = serde_json::value::from_value(json!({})).unwrap();
892 assert_eq!(act, Options::default());
896 fn test_options_map_str_str_json_de() {
897 let act: Options = serde_json::value::from_value(json!({
898 "hhvm.aliased_namespaces": { "global_value": {"ns1": "ns2"} }
901 assert_eq!(act.hhvm.aliased_namespaces.get().as_map().unwrap(), &{
902 let mut m = BTreeMap::new();
903 m.insert("ns1".to_owned(), "ns2".to_owned());
909 fn test_options_map_str_str_as_empty_array_json_de() {
910 let act: Options = serde_json::value::from_value(json!({
911 "hhvm.aliased_namespaces": { "global_value": [] }
914 assert_eq!(act.hhvm.aliased_namespaces.get().as_map(), None);
918 fn test_options_merge() {
919 let mut dst = json!({
920 "uniqueAtDst": "DST",
921 "person" : { "firstName": "John", "lastName": "Doe" },
922 "flat": [ "will", "be", "overridden" ],
925 "uniqueAtSrc": "SRC",
926 "person" : { "firstName" : "Jane (not John)" },
927 "flat": "overrides dst's field",
929 Options::merge(&mut dst, &src);
933 "flat": "overrides dst's field",
935 "firstName": "Jane (not John)",
938 "uniqueAtDst": "DST",
939 "uniqueAtSrc": "SRC",
944 const EMPTY_STRS: [&str; 0] = [];
947 fn test_options_de_multiple_jsons() {
948 let jsons: [String; 2] = [
950 // override an options from 1 to 0 in first JSON,
951 "hhvm.hack.lang.enable_enum_classes": { "global_value": false },
952 // but specify the default (0) on rx_is_enabled)
953 "hhvm.rx_is_enabled": { "global_value": false }
957 // override another option from 0 to 1 in second JSON for the first time
958 "hhvm.hack.lang.disable_xhp_element_mangling": { "global_value": true },
959 // and for the second time, respectively *)
960 "hhvm.rx_is_enabled": { "global_value": true }
964 let act = Options::from_configs_(&jsons, &EMPTY_STRS).unwrap();
969 .contains(LangFlags::DISABLE_XHP_ELEMENT_MANGLING)
975 .contains(LangFlags::ENABLE_ENUM_CLASSES)
977 assert!(act.hhvm.flags.contains(HhvmFlags::RX_IS_ENABLED));
981 fn test_hhvm_flags_cli_de_missing_equals() {
982 let args = ["eval.jitenablerenamefunction"];
983 let exp = Options::from_cli_args(args.as_ref());
984 assert!(exp.is_err());
985 let err = exp.unwrap_err();
986 assert!(err.starts_with("Missing '='"));
987 assert!(err.ends_with("function"));
991 fn test_hhvm_flags_cli_de_to_json() {
993 "eval.logexterncompilerperf=true",
994 "eval.jitenablerenamefunction=1",
996 let act = Options::from_cli_args(&args);
1000 "hhvm.jit_enable_rename_function": {
1001 "global_value": "1",
1003 "hhvm.log_extern_compiler_perf": {
1004 "global_value": "true",
1011 fn test_options_de_from_cli_override_json() {
1013 "eval.jitenablerenamefunction=1",
1014 "eval.hackarrcompatnotices=true",
1017 "hhvm.hack_arr_compat_notices": {
1018 "global_value": "0",
1020 "hhvm.log_extern_compiler_perf": {
1021 "global_value": "true",
1024 let act = Options::from_configs_(&[json.to_string()], &cli_args).unwrap();
1025 assert!(act.hhvm.flags.contains(HhvmFlags::HACK_ARR_COMPAT_NOTICES));
1029 fn test_options_de_from_cli_comma_separated_key_value() {
1030 let mut exp_include_roots = BTreeMap::<String, String>::new();
1031 exp_include_roots.insert("foo".into(), "bar".into());
1032 exp_include_roots.insert("bar".into(), "baz".into());
1033 const CLI_ARG: &str = "hhvm.include_roots=foo:bar,bar:baz";
1034 let act = Options::from_configs_(&EMPTY_STRS, &[CLI_ARG]).unwrap();
1035 assert_eq!(act.hhvm.include_roots.global_value, exp_include_roots,);
1039 fn test_options_de_regression_boolish_parse_on_unrelated_opt() {
1040 // Note: this fails if bool-looking options are too eagerly parsed
1041 // (i.e., before they're are looked by JSON key/name and match a flag)
1042 let _: Options = serde_json::value::from_value(json!({
1043 "hhvm.only.opt1": { "global_value": "12345678901234567890" },
1044 "hhvm.only.opt2": { "global_value": "" },
1046 .expect("boolish-parsing logic wrongly triggered");
1050 fn test_options_de_untyped_global_value_no_crash() {
1051 let res: Result<Options, String> = Options::from_configs(
1052 // try parsing an HHVM option whose layout is none of the
1053 // typed variants of GlobalValue, i.e., a string-to-int map
1055 "hhvm.semr_thread_overrides": {
1061 "local_value": { "4":300 },
1067 assert_eq!(res.err(), None);
1071 fn test_options_de_empty_configs_skipped_no_crash() {
1072 let res: Result<Options, String> = Options::from_configs_(
1073 // a subset (only 30/5K lines) of the real config passed by HHVM
1075 "", // this should be skipped (it's an invalid JSON)
1078 "hhvm.trusted_db_path": {
1088 "hhvm.php7.ltr_assign": {
1093 "hhvm.aliased_namespaces": {
1099 "Vec": "HH\\Lib\\Vec"
1106 "hhvm.include_roots": {
1113 assert_eq!(res.err(), None);
1117 // boilerplate code that could eventually be avoided via procedural macros
1121 const CONSTANT_FOLDING = 1 << 0;
1122 const OPTIMIZE_NULL_CHECKS = 1 << 1;
1123 // No longer using bit 2.
1125 const LTR_ASSIGN = 1 << 4;
1126 /// If true, then renumber labels after generating code for a method
1127 /// body. Semantic diff doesn't care about labels, but for visual diff against
1128 /// HHVM it's helpful to renumber in order that the labels match more closely
1129 const RELABEL = 1 << 5;
1130 // No longer using bit 6.
1131 const HACK_ARR_COMPAT_NOTICES = 1 << 7;
1132 const HACK_ARR_DV_ARRS = 1 << 8;
1133 const AUTHORITATIVE = 1 << 9;
1134 const JIT_ENABLE_RENAME_FUNCTION = 1 << 10;
1135 // No longer using bits 11-13.
1136 const LOG_EXTERN_COMPILER_PERF = 1 << 14;
1137 const ENABLE_INTRINSICS_EXTENSION = 1 << 15;
1138 // No longer using bits 16-21.
1139 const DISABLE_NONTOPLEVEL_DECLARATIONS = 1 << 22;
1140 // No longer using bits 23-25.
1141 const EMIT_CLS_METH_POINTERS = 1 << 26;
1142 const EMIT_INST_METH_POINTERS = 1 << 27;
1143 const EMIT_METH_CALLER_FUNC_POINTERS = 1 << 28;
1144 const RX_IS_ENABLED = 1 << 29;
1145 const DISABLE_LVAL_AS_AN_EXPRESSION = 1 << 30;
1146 // No longer using bits 31-32.
1147 const ARRAY_PROVENANCE = 1 << 33;
1148 // No longer using bit 34.
1149 const ENABLE_CLASS_LEVEL_WHERE_CLAUSES = 1 << 35;
1150 const DISABLE_LEGACY_SOFT_TYPEHINTS = 1 << 36;
1151 const ALLOW_NEW_ATTRIBUTE_SYNTAX = 1 << 37;
1152 const DISABLE_LEGACY_ATTRIBUTE_SYNTAX = 1 << 38;
1153 const CONST_DEFAULT_FUNC_ARGS = 1 << 39;
1154 const CONST_STATIC_PROPS = 1 << 40;
1155 const ABSTRACT_STATIC_PROPS = 1 << 41;
1156 const DISABLE_UNSET_CLASS_CONST = 1 << 42;
1157 const DISALLOW_FUNC_PTRS_IN_CONSTANTS = 1 << 43;
1158 // No longer using bit 44.
1159 const CONST_DEFAULT_LAMBDA_ARGS = 1 << 45;
1160 const ENABLE_XHP_CLASS_MODIFIER = 1 << 46;
1161 // No longer using bit 47.
1162 const ENABLE_ENUM_CLASSES = 1 << 48;
1163 const DISABLE_XHP_ELEMENT_MANGLING = 1 << 49;
1164 const DISABLE_ARRAY = 1 << 50;
1165 const RUST_EMITTER = 1 << 51;
1166 const DISABLE_ARRAY_CAST = 1 << 52;
1167 const DISABLE_ARRAY_TYPEHINT = 1 << 53;
1168 // No longer using bit 54
1169 const ALLOW_UNSTABLE_FEATURES = 1 << 55;
1170 const DISALLOW_HASH_COMMENTS = 1 << 56;
1171 const DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS = 1 << 57;
1172 const FOLD_LAZY_CLASS_KEYS = 1 << 58;
1173 const DISALLOW_DYNAMIC_METH_CALLER_ARGS = 1 << 59;
1174 const DISALLOW_INST_METH = 1 << 60;