Tweak `HhasTypeConstant` C++: Replace Option with Maybe
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / options.rs
blob86fc7bc7649cda3e0fe334545568113e1d9fb26d
1 // Copyright (c) 2019; Facebook; Inc.
2 // All rights reserved.
3 //
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.
8 //!
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.
19 //!
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.
27 //!
28 //! Example:
29 //! ```
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::ENABLE_IMPLICIT_CONTEXT);          // hhvm.enable_implicit_context
35 //! opts.hhvm.hack_lang_flags.set(
36 //!     LangFlags::ENABLE_ENUM_CLASSES);      // hhvm.hack.lang.enable_enum_classes
37 //! ```
39 mod options_cli;
41 use hhbc_by_ref_options_serde::prefix_all;
43 use lru::LruCache;
45 extern crate bitflags;
46 use bitflags::bitflags;
48 #[macro_use]
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
58 trait PrefixedFlags:
59     Sized
60     + Copy
61     + Default
62     + std::fmt::Debug
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!
70     const EMPTY: Self;
71     const ALL: Self;
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
81         bitflags! {
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(); )*
85             }
86         }
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 {
95             //     match name {
96             //         $( case field_to_config_name!($prefix, $field) => Flags::$field, )*
97             //     }
98             // }
99             fn to_map() -> BTreeMap<String, Self> {{
100                 let mut ret: BTreeMap<String, Self> = BTreeMap::new();
101                 $(
102                     ret.insert(stringify!($field).to_lowercase(), Self::$field);
103                 )*
104                 ret
105             }}
107             fn contains(&self, other: Self) -> bool {
108                 self.contains(other)
109             }
111             fn bits(&self) -> u64 {
112                 self.bits()
113             }
115             fn from_flags(flags: &Flags) -> Option<Self> {
116                 Self::from_bits(flags.bits())
117             }
118         }
119     }
122 /// An option of non-boolean type T (i.e., not a flag)
123 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
124 pub struct Arg<T> {
125     global_value: T,
127 impl<T> Arg<T> {
128     pub fn get(&self) -> &T {
129         &self.global_value
130     }
132     pub fn get_mut(&mut self) -> &mut T {
133         &mut self.global_value
134     }
136     pub fn new(global_value: T) -> Arg<T> {
137         Arg { global_value }
138     }
141 // group options by JSON config prefix to avoid error-prone repetition & boilerplate in SerDe
143 prefixed_flags!(
144     CompilerFlags,
145     "hack.compiler.",
146     CONSTANT_FOLDING,
147     OPTIMIZE_NULL_CHECKS,
148     RELABEL,
150 impl Default for CompilerFlags {
151     fn default() -> CompilerFlags {
152         CompilerFlags::CONSTANT_FOLDING | CompilerFlags::RELABEL
153     }
156 prefixed_flags!(
157     HhvmFlags,
158     "hhvm.",
159     ARRAY_PROVENANCE,
160     EMIT_CLS_METH_POINTERS,
161     EMIT_METH_CALLER_FUNC_POINTERS,
162     ENABLE_INTRINSICS_EXTENSION,
163     FOLD_LAZY_CLASS_KEYS,
164     JIT_ENABLE_RENAME_FUNCTION,
165     LOG_EXTERN_COMPILER_PERF,
166     ENABLE_IMPLICIT_CONTEXT,
168 impl Default for HhvmFlags {
169     fn default() -> HhvmFlags {
170         HhvmFlags::EMIT_CLS_METH_POINTERS
171             | HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS
172             | HhvmFlags::FOLD_LAZY_CLASS_KEYS
173     }
176 #[prefix_all("hhvm.")]
177 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
178 pub struct Hhvm {
179     #[serde(default)]
180     pub aliased_namespaces: Arg<BTreeMapOrEmptyVec<String, String>>,
182     #[serde(default)]
183     pub include_roots: Arg<BTreeMap<String, String>>, // TODO(leoo) change to HashMap if order doesn't matter
185     #[serde(default = "defaults::emit_class_pointers")]
186     pub emit_class_pointers: Arg<String>,
188     #[serde(
189         flatten,
190         serialize_with = "serialize_flags",
191         deserialize_with = "deserialize_flags"
192     )]
193     pub flags: HhvmFlags,
195     #[serde(flatten, default)]
196     pub hack_lang: HackLang,
199 impl Default for Hhvm {
200     fn default() -> Self {
201         Self {
202             aliased_namespaces: Default::default(),
203             include_roots: Default::default(),
204             emit_class_pointers: defaults::emit_class_pointers(),
205             flags: Default::default(),
206             hack_lang: Default::default(),
207         }
208     }
211 impl Hhvm {
212     pub fn aliased_namespaces_iter(&self) -> impl Iterator<Item = (&str, &str)> {
213         match self.aliased_namespaces.get() {
214             BTreeMapOrEmptyVec::Nonempty(m) => {
215                 Either::Right(m.iter().map(|(x, y)| (x.as_str(), y.as_str())))
216             }
217             _ => Either::Left(empty()),
218         }
219     }
221     pub fn aliased_namespaces_cloned(&self) -> impl Iterator<Item = (String, String)> + '_ {
222         match self.aliased_namespaces.get() {
223             BTreeMapOrEmptyVec::Nonempty(m) => {
224                 Either::Right(m.iter().map(|(x, y)| (x.clone(), y.clone())))
225             }
226             _ => Either::Left(empty()),
227         }
228     }
231 #[prefix_all("hhvm.hack.lang.")]
232 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
233 pub struct HackLang {
234     #[serde(
235         flatten,
236         serialize_with = "serialize_flags",
237         deserialize_with = "deserialize_flags"
238     )]
239     pub flags: LangFlags,
241     #[serde(default)]
242     pub check_int_overflow: Arg<String>,
245 prefixed_flags!(
246     LangFlags,
247     "hhvm.hack.lang.",
248     ABSTRACT_STATIC_PROPS,
249     ALLOW_NEW_ATTRIBUTE_SYNTAX,
250     ALLOW_UNSTABLE_FEATURES,
251     CONST_DEFAULT_FUNC_ARGS,
252     CONST_DEFAULT_LAMBDA_ARGS,
253     CONST_STATIC_PROPS,
254     DISABLE_LEGACY_ATTRIBUTE_SYNTAX,
255     DISABLE_LEGACY_SOFT_TYPEHINTS,
256     DISABLE_LVAL_AS_AN_EXPRESSION,
257     DISABLE_UNSET_CLASS_CONST,
258     DISABLE_XHP_ELEMENT_MANGLING,
259     DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS,
260     DISALLOW_INST_METH,
261     DISALLOW_FUNC_PTRS_IN_CONSTANTS,
262     DISALLOW_HASH_COMMENTS,
263     DISALLOW_DYNAMIC_METH_CALLER_ARGS,
264     ENABLE_CLASS_LEVEL_WHERE_CLAUSES,
265     ENABLE_ENUM_CLASSES,
266     ENABLE_READONLY_ENFORCEMENT,
267     ENABLE_XHP_CLASS_MODIFIER,
268     ESCAPE_BRACE,
269     DISABLE_ARRAY_CAST,
270     DISABLE_ARRAY_TYPEHINT,
271     DISABLE_ARRAY,
272     RUST_EMITTER,
274 impl Default for LangFlags {
275     fn default() -> LangFlags {
276         LangFlags::DISABLE_LEGACY_SOFT_TYPEHINTS | LangFlags::ENABLE_ENUM_CLASSES
277     }
280 prefixed_flags!(
281     PhpismFlags,
282     "hhvm.hack.lang.phpism.",
283     DISABLE_NONTOPLEVEL_DECLARATIONS,
285 impl Default for PhpismFlags {
286     fn default() -> PhpismFlags {
287         PhpismFlags::empty()
288     }
291 prefixed_flags!(
292     Php7Flags,
293     "hhvm.php7.",
294     LTR_ASSIGN, //
295     UVS,        //
297 impl Default for Php7Flags {
298     fn default() -> Php7Flags {
299         Php7Flags::empty()
300     }
303 prefixed_flags!(
304     RepoFlags,
305     "hhvm.repo.",
306     AUTHORITATIVE, //
308 impl Default for RepoFlags {
309     fn default() -> RepoFlags {
310         RepoFlags::empty()
311     }
314 #[prefix_all("hhvm.server.")]
315 #[derive(Clone, Serialize, Deserialize, Default, PartialEq, Debug)]
316 pub struct Server {
317     #[serde(default)]
318     pub include_search_paths: Arg<Vec<String>>,
321 #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
322 pub struct Options {
323     #[serde(default)]
324     pub doc_root: Arg<String>,
326     #[serde(
327         flatten,
328         serialize_with = "serialize_flags",
329         deserialize_with = "deserialize_flags"
330     )]
331     pub hack_compiler_flags: CompilerFlags,
333     #[serde(flatten, default)]
334     pub hhvm: Hhvm,
336     #[serde(default = "defaults::max_array_elem_size_on_the_stack")]
337     pub max_array_elem_size_on_the_stack: Arg<isize>,
339     #[serde(
340         flatten,
341         serialize_with = "serialize_flags",
342         deserialize_with = "deserialize_flags"
343     )]
344     pub phpism_flags: PhpismFlags,
346     #[serde(
347         flatten,
348         serialize_with = "serialize_flags",
349         deserialize_with = "deserialize_flags"
350     )]
351     pub php7_flags: Php7Flags,
353     #[serde(
354         flatten,
355         serialize_with = "serialize_flags",
356         deserialize_with = "deserialize_flags"
357     )]
358     pub repo_flags: RepoFlags,
360     #[serde(flatten, default)]
361     pub server: Server,
364 impl Options {
365     pub fn log_extern_compiler_perf(&self) -> bool {
366         self.hhvm
367             .flags
368             .contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF)
369     }
372 impl Default for Options {
373     fn default() -> Options {
374         Options {
375             max_array_elem_size_on_the_stack: defaults::max_array_elem_size_on_the_stack(),
376             hack_compiler_flags: CompilerFlags::default(),
377             hhvm: Hhvm::default(),
378             phpism_flags: PhpismFlags::default(),
379             php7_flags: Php7Flags::default(),
380             repo_flags: RepoFlags::default(),
381             server: Server::default(),
382             // the rest is zeroed out (cannot do ..Default::default() as it'd be recursive)
383             doc_root: Arg::new("".to_owned()),
384         }
385     }
388 /// Non-zero argument defaults for use in both Default::default & SerDe framework
389 mod defaults {
390     use super::*;
392     pub fn max_array_elem_size_on_the_stack() -> Arg<isize> {
393         Arg::new(64)
394     }
396     pub fn emit_class_pointers() -> Arg<String> {
397         Arg::new("0".to_string())
398     }
401 thread_local! {
402     static CACHE: RefCell<Cache> = {
403         let hasher = fnv::FnvBuildHasher::default();
404         RefCell::new(LruCache::with_hasher(100, hasher))
405     }
408 pub(crate) type Cache = LruCache<(Vec<String>, Vec<String>), Options, fnv::FnvBuildHasher>;
410 impl Options {
411     pub fn to_string(&self) -> String {
412         serde_json::to_string_pretty(&self).expect("failed to parse JSON")
413     }
415     pub fn from_json(s: &str) -> Result<Self, String> {
416         let opts: serde_json::Result<Self> = serde_json::from_str(s);
417         opts.map_err(|e| format!("failed to load config JSON:\n{}", e))
418     }
420     fn from_cli_args(args: &[impl AsRef<str>]) -> Result<Json, String> {
421         let mut json = json!({});
422         for arg in args {
423             let arg = arg.as_ref();
424             match arg.find('=') {
425                 Some(pos) => {
426                     let (key, val) = arg.split_at(pos);
427                     let val = &val[1..]; // strip '='
428                     let key = key.to_ascii_lowercase();
429                     let key = key.as_ref();
430                     let key: &str = options_cli::CANON_BY_ALIAS.get(key).unwrap_or(&key);
431                     if let Some(val) = options_cli::to_json(key)(&val) {
432                         json.as_object_mut().map(|m| {
433                             m.insert(
434                                 key.to_owned(),
435                                 json!({
436                                     "global_value": val,
437                                 }),
438                             )
439                         });
440                     } else {
441                         return Err(format!("Invalid format for CLI arg key: {}", key));
442                     }
443                 }
444                 None => return Err(format!("Missing '=' key-value separator in: {}", arg)),
445             }
446         }
447         Ok(json)
448     }
450     /// Merges src JSON into dst JSON, recursively adding or overwriting existing entries.
451     /// This method cleverly avoids the need to represent each option as Option<Type>,
452     /// since only the ones that are specified by JSON will be actually overridden.
453     fn merge(dst: &mut Json, src: &Json) {
454         match (dst, src) {
455             (&mut Json::Object(ref mut dst), &Json::Object(ref src)) => {
456                 for (k, v) in src {
457                     Self::merge(dst.entry(k.clone()).or_insert(Json::Null), v);
458                 }
459             }
460             (dst, src) => {
461                 *dst = src.clone();
462             }
463         }
464     }
466     pub fn from_configs<S: AsRef<str>>(jsons: &[S], clis: &[S]) -> Result<Self, String> {
467         CACHE.with(|cache| {
468             let key: (Vec<String>, Vec<String>) = (
469                 jsons.iter().map(|x| (*x).as_ref().into()).collect(),
470                 clis.iter().map(|x| (*x).as_ref().into()).collect(),
471             );
472             let mut cache = cache.borrow_mut();
473             if let Some(o) = cache.get_mut(&key) {
474                 Ok(o.clone())
475             } else {
476                 let o = Options::from_configs_(&key.0, &key.1)?;
477                 cache.put(key, o.clone());
478                 Ok(o)
479             }
480         })
481     }
483     fn from_configs_<S1, S2>(jsons: &[S1], cli_args: &[S2]) -> Result<Self, String>
484     where
485         S1: AsRef<str>,
486         S2: AsRef<str>,
487     {
488         let mut merged = json!({});
489         for json in jsons {
490             let json: &str = json.as_ref();
491             if json.is_empty() {
492                 continue;
493             }
494             Self::merge(
495                 &mut merged,
496                 &serde_json::from_str(json).map_err(|e| e.to_string())?,
497             );
498         }
499         let overrides = Self::from_cli_args(cli_args)?;
500         Self::merge(&mut merged, &overrides);
501         let opts: serde_json::Result<Self> = serde_json::value::from_value(merged);
502         opts.map_err(|e| e.to_string())
503     }
505     pub fn array_provenance(&self) -> bool {
506         self.hhvm.flags.contains(HhvmFlags::ARRAY_PROVENANCE)
507     }
509     pub fn check_int_overflow(&self) -> bool {
510         self.hhvm
511             .hack_lang
512             .check_int_overflow
513             .get()
514             .parse::<i32>()
515             .map_or(false, |x| x.is_positive())
516     }
518     pub fn emit_class_pointers(&self) -> i32 {
519         self.hhvm.emit_class_pointers.get().parse::<i32>().unwrap()
520     }
523 use serde::de::{self, Deserializer, MapAccess, Visitor};
524 use serde::{ser::SerializeMap, Serializer};
526 fn serialize_flags<S: Serializer, P: PrefixedFlags>(flags: &P, s: S) -> Result<S::Ok, S::Error> {
527     // TODO(leoo) iterate over each set bit: flags.bits() & ~(flags.bits() + 1)
528     let mut map = s.serialize_map(None)?;
529     for (key, value) in P::to_map().into_iter() {
530         let bool_val = flags.contains(value);
531         map.serialize_entry(&format!("{}{}", &P::PREFIX, key), &Arg::new(bool_val))?;
532     }
533     map.end()
536 /// Expected JSON layouts for each field that is a valid Hack option
537 #[derive(Deserialize)]
538 #[serde(untagged)]
539 enum GlobalValue {
540     String(String),
541     Bool(bool),
542     Int(isize),
543     VecStr(Vec<String>),
544     MapStr(BTreeMap<String, String>),
545     Json(Json), // support HHVM options with arbitrary layout
546     // (A poorer alternative that risks silent HHVM breakage
547     // would be to explicitly enumerate all possible layouts.)
550 fn deserialize_flags<'de, D: Deserializer<'de>, P: PrefixedFlags>(
551     deserializer: D,
552 ) -> Result<P, D::Error> {
553     use std::fmt;
554     use std::marker::PhantomData;
555     struct Phantom<P>(PhantomData<P>);
557     impl<'de, P: PrefixedFlags> Visitor<'de> for Phantom<P> {
558         type Value = P;
560         fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
561             formatter.write_str("flag with string global_value")
562         }
564         fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
565             // TODO(leoo) proc macro to traverse Flags struct & iter over assoc. constants
566             let mut flags = P::default();
567             let by_name = P::to_map();
568             let prefix_len = P::PREFIX.len();
569             let from_str = |v: &str| match v {
570                 "true" => Ok(true),
571                 "false" => Ok(false),
572                 num => num
573                     .parse::<i32>()
574                     .map(|v| v == 1)
575                     .map_err(de::Error::custom),
576             };
577             while let Some((ref k, ref v)) = map.next_entry::<String, Arg<GlobalValue>>()? {
578                 let mut found = None;
579                 let k: &str = &k;
580                 let k: &str = options_cli::CANON_BY_ALIAS.get(k).unwrap_or(&k);
581                 if k.starts_with(P::PREFIX) {
582                     found = by_name.get(&k[prefix_len..]).cloned();
583                 }
584                 if let Some(flag) = found {
585                     let truish = match v.get() {
586                         GlobalValue::String(s) => {
587                             if s.is_empty() {
588                                 false
589                             } else {
590                                 from_str(s)?
591                             }
592                         }
593                         GlobalValue::Bool(b) => *b,
594                         GlobalValue::Int(n) => *n == 1,
595                         _ => continue, // types such as VecStr aren't parsable as flags
596                     };
597                     if truish {
598                         flags |= flag
599                     } else {
600                         flags &= !flag
601                     }
602                 }
603             }
604             Ok(flags)
605         }
606     }
608     deserializer.deserialize_map(Phantom(PhantomData::<P>))
611 /// Wrapper that serves as a workaround for a bug in the HHVM's serialization
612 /// empty JSON objects ({}) are silently converted to [] (darray vs varray):
613 /// - (de)serialize [] as Empty (instead of {});
614 /// - (de)serialize missing values as [].
615 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
616 #[serde(untagged)]
617 pub enum BTreeMapOrEmptyVec<K: std::cmp::Ord, V> {
618     Nonempty(BTreeMap<K, V>),
619     Empty(Vec<V>),
621 impl<K: std::cmp::Ord, V> BTreeMapOrEmptyVec<K, V> {
622     pub fn as_map(&self) -> Option<&BTreeMap<K, V>> {
623         match self {
624             BTreeMapOrEmptyVec::Nonempty(m) => Some(m),
625             _ => None,
626         }
627     }
629 impl<K: std::cmp::Ord, V> Into<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
630     fn into(self) -> BTreeMap<K, V> {
631         match self {
632             BTreeMapOrEmptyVec::Nonempty(m) => m,
633             _ => BTreeMap::new(),
634         }
635     }
637 impl<K: std::cmp::Ord, V> From<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
638     fn from(m: BTreeMap<K, V>) -> Self {
639         if m.is_empty() {
640             BTreeMapOrEmptyVec::Empty(vec![])
641         } else {
642             BTreeMapOrEmptyVec::Nonempty(m)
643         }
644     }
646 impl<K: std::cmp::Ord, V> Default for BTreeMapOrEmptyVec<K, V> {
647     fn default() -> Self {
648         BTreeMapOrEmptyVec::Empty(vec![])
649     }
652 #[cfg(test)]
653 mod tests {
654     use super::*;
655     use pretty_assertions::assert_eq; // make assert_eq print huge diffs more human-readable
657     const HHVM_1: &'static str = r#"{
658   "hhvm.aliased_namespaces": {
659     "global_value": {
660       "bar": "baz",
661       "foo": "bar"
662     }
663   },
664   "hhvm.array_provenance": {
665     "global_value": false
666   },
667   "hhvm.emit_class_pointers": {
668     "global_value": "0"
669   },
670   "hhvm.emit_cls_meth_pointers": {
671     "global_value": false
672   },
673   "hhvm.emit_meth_caller_func_pointers": {
674     "global_value": true
675   },
676   "hhvm.enable_implicit_context": {
677     "global_value": false
678   },
679   "hhvm.enable_intrinsics_extension": {
680     "global_value": false
681   },
682   "hhvm.fold_lazy_class_keys": {
683     "global_value": true
684   },
685   "hhvm.hack.lang.abstract_static_props": {
686     "global_value": false
687   },
688   "hhvm.hack.lang.allow_new_attribute_syntax": {
689     "global_value": false
690   },
691   "hhvm.hack.lang.allow_unstable_features": {
692     "global_value": false
693   },
694   "hhvm.hack.lang.check_int_overflow": {
695     "global_value": ""
696   },
697   "hhvm.hack.lang.const_default_func_args": {
698     "global_value": false
699   },
700   "hhvm.hack.lang.const_default_lambda_args": {
701     "global_value": false
702   },
703   "hhvm.hack.lang.const_static_props": {
704     "global_value": false
705   },
706   "hhvm.hack.lang.disable_array": {
707     "global_value": false
708   },
709   "hhvm.hack.lang.disable_array_cast": {
710     "global_value": false
711   },
712   "hhvm.hack.lang.disable_array_typehint": {
713     "global_value": false
714   },
715   "hhvm.hack.lang.disable_legacy_attribute_syntax": {
716     "global_value": false
717   },
718   "hhvm.hack.lang.disable_legacy_soft_typehints": {
719     "global_value": true
720   },
721   "hhvm.hack.lang.disable_lval_as_an_expression": {
722     "global_value": false
723   },
724   "hhvm.hack.lang.disable_unset_class_const": {
725     "global_value": false
726   },
727   "hhvm.hack.lang.disable_xhp_element_mangling": {
728     "global_value": false
729   },
730   "hhvm.hack.lang.disallow_dynamic_meth_caller_args": {
731     "global_value": false
732   },
733   "hhvm.hack.lang.disallow_fun_and_cls_meth_pseudo_funcs": {
734     "global_value": false
735   },
736   "hhvm.hack.lang.disallow_func_ptrs_in_constants": {
737     "global_value": false
738   },
739   "hhvm.hack.lang.disallow_hash_comments": {
740     "global_value": false
741   },
742   "hhvm.hack.lang.disallow_inst_meth": {
743     "global_value": false
744   },
745   "hhvm.hack.lang.enable_class_level_where_clauses": {
746     "global_value": false
747   },
748   "hhvm.hack.lang.enable_enum_classes": {
749     "global_value": true
750   },
751   "hhvm.hack.lang.enable_readonly_enforcement": {
752     "global_value": false
753   },
754   "hhvm.hack.lang.enable_xhp_class_modifier": {
755     "global_value": false
756   },
757   "hhvm.hack.lang.escape_brace": {
758     "global_value": false
759   },
760   "hhvm.hack.lang.rust_emitter": {
761     "global_value": false
762   },
763   "hhvm.include_roots": {
764     "global_value": {}
765   },
766   "hhvm.jit_enable_rename_function": {
767     "global_value": false
768   },
769   "hhvm.log_extern_compiler_perf": {
770     "global_value": false
771   }
772 }"#;
774     #[test]
775     fn test_hhvm_json_ser() {
776         let hhvm = json!(Hhvm {
777             aliased_namespaces: Arg::new({
778                 let mut m = BTreeMap::new();
779                 m.insert("foo".to_owned(), "bar".to_owned());
780                 m.insert("bar".to_owned(), "baz".to_owned());
781                 m.into()
782             }),
783             flags: HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS | HhvmFlags::FOLD_LAZY_CLASS_KEYS,
784             ..Default::default()
785         });
786         assert_eq!(HHVM_1, serde_json::to_string_pretty(&hhvm).unwrap(),);
787     }
789     #[test]
790     fn test_hhvm_json_de() {
791         let j = serde_json::from_str(
792             r#"{
793             "hhvm.aliased_namespaces": { "global_value": {"foo": "bar"} },
794             "hhvm.emit_meth_caller_func_pointers": { "global_value": "true" },
795             "hhvm.jit_enable_rename_function": { "global_value": 1 },
796             "hhvm.log_extern_compiler_perf": { "global_value": false },
797             "hhvm.array_provenance": { "global_value": "1" }
798             }"#,
799         )
800         .unwrap();
801         let hhvm: Hhvm = serde_json::from_value(j).unwrap();
802         assert!(hhvm.flags.contains(
803             HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS
804                 | HhvmFlags::JIT_ENABLE_RENAME_FUNCTION
805                 | HhvmFlags::ARRAY_PROVENANCE
806         ));
807         assert!(!hhvm.flags.contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF));
808     }
810     #[test]
811     fn test_hhvm_json_de_defaults_overrideable() {
812         let hhvm: Hhvm = serde_json::value::from_value(json!({})).unwrap();
813         assert_eq!(hhvm.flags, HhvmFlags::default());
814         assert!(
815             hhvm.flags
816                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
817         );
819         // now override a true-by-default option with a false value
820         let hhvm: Hhvm = serde_json::value::from_value(json!({
821             "hhvm.emit_meth_caller_func_pointers": { "global_value": "false" },
822         }))
823         .unwrap();
824         assert!(
825             !hhvm
826                 .flags
827                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
828         );
829     }
831     #[test]
832     fn test_hhvm_flags_alias_json_de() {
833         // sanity check for defaults (otherwise this test doesn't do much!)
834         assert!(!HhvmFlags::default().contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION));
835         assert!(HhvmFlags::default().contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS));
837         let hhvm: Hhvm = serde_json::from_str(
838             r#"{ "eval.jitenablerenamefunction": { "global_value": "true" },
839                  "hhvm.emit_meth_caller_func_pointers": { "global_value": "false" } }"#,
840         )
841         .unwrap();
842         assert!(
843             hhvm // verify a false-by-default flag was parsed as true
844                 .flags
845                 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
846         );
848         assert!(
849             !hhvm // verify a true-by-default flag was parsed as false
850                 .flags
851                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
852         );
853     }
855     #[test]
856     fn test_empty_flag_treated_as_false_json_de() {
857         // verify a true-by-default flag was parsed as false if ""
858         let hhvm: Hhvm = serde_json::from_str(
859             r#"{ "hhvm.emit_meth_caller_func_pointers": { "global_value": "" } }"#,
860         )
861         .unwrap();
862         assert!(
863             !hhvm
864                 .flags
865                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
866         );
867     }
869     #[test]
870     fn test_options_flat_arg_alias_json_de() {
871         let act: Options = serde_json::value::from_value(json!({
872             "eval.jitenablerenamefunction": {
873                 "global_value": "true",
874             },
875         }))
876         .expect("failed to deserialize");
877         assert!(
878             act.hhvm
879                 .flags
880                 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
881         );
882     }
884     #[test]
885     fn test_options_nonzero_defaults_json_de() {
886         let act: Options = serde_json::value::from_value(json!({})).unwrap();
887         assert_eq!(act, Options::default());
888     }
890     #[test]
891     fn test_options_map_str_str_json_de() {
892         let act: Options = serde_json::value::from_value(json!({
893             "hhvm.aliased_namespaces": { "global_value": {"ns1": "ns2"} }
894         }))
895         .unwrap();
896         assert_eq!(act.hhvm.aliased_namespaces.get().as_map().unwrap(), &{
897             let mut m = BTreeMap::new();
898             m.insert("ns1".to_owned(), "ns2".to_owned());
899             m
900         },);
901     }
903     #[test]
904     fn test_options_map_str_str_as_empty_array_json_de() {
905         let act: Options = serde_json::value::from_value(json!({
906             "hhvm.aliased_namespaces": { "global_value": [] }
907         }))
908         .unwrap();
909         assert_eq!(act.hhvm.aliased_namespaces.get().as_map(), None);
910     }
912     #[test]
913     fn test_options_merge() {
914         let mut dst = json!({
915             "uniqueAtDst": "DST",
916             "person" : { "firstName": "John", "lastName": "Doe" },
917             "flat": [ "will", "be", "overridden" ],
918         });
919         let src = json!({
920             "uniqueAtSrc": "SRC",
921             "person" : { "firstName" : "Jane (not John)" },
922             "flat": "overrides dst's field",
923         });
924         Options::merge(&mut dst, &src);
925         assert_eq!(
926             dst,
927             json!({
928                 "flat": "overrides dst's field",
929                 "person": {
930                     "firstName": "Jane (not John)",
931                     "lastName": "Doe"
932                 },
933                 "uniqueAtDst": "DST",
934                 "uniqueAtSrc": "SRC",
935             })
936         );
937     }
939     const EMPTY_STRS: [&str; 0] = [];
941     #[test]
942     fn test_options_de_multiple_jsons() {
943         let jsons: [String; 2] = [
944             json!({
945                 // override an options from 1 to 0 in first JSON,
946                 "hhvm.hack.lang.enable_enum_classes": { "global_value": false },
947                 // but specify the default (0) on enable_implicit_context)
948                 "hhvm.enable_implicit_context": { "global_value": false }
949             })
950             .to_string(),
951             json!({
952                 // override another option from 0 to 1 in second JSON for the first time
953                 "hhvm.hack.lang.disable_xhp_element_mangling": { "global_value": true },
954                 // and for the second time, respectively *)
955                 "hhvm.enable_implicit_context": { "global_value": true }
956             })
957             .to_string(),
958         ];
959         let act = Options::from_configs_(&jsons, &EMPTY_STRS).unwrap();
960         assert!(
961             act.hhvm
962                 .hack_lang
963                 .flags
964                 .contains(LangFlags::DISABLE_XHP_ELEMENT_MANGLING)
965         );
966         assert!(
967             !act.hhvm
968                 .hack_lang
969                 .flags
970                 .contains(LangFlags::ENABLE_ENUM_CLASSES)
971         );
972         assert!(act.hhvm.flags.contains(HhvmFlags::ENABLE_IMPLICIT_CONTEXT));
973     }
975     #[test]
976     fn test_hhvm_flags_cli_de_missing_equals() {
977         let args = ["eval.jitenablerenamefunction"];
978         let exp = Options::from_cli_args(args.as_ref());
979         assert!(exp.is_err());
980         let err = exp.unwrap_err();
981         assert!(err.starts_with("Missing '='"));
982         assert!(err.ends_with("function"));
983     }
985     #[test]
986     fn test_hhvm_flags_cli_de_to_json() {
987         let args = [
988             "eval.logexterncompilerperf=true",
989             "eval.jitenablerenamefunction=1",
990         ];
991         let act = Options::from_cli_args(&args);
992         assert_eq!(
993             act,
994             Ok(json!({
995                 "hhvm.jit_enable_rename_function": {
996                     "global_value": "1",
997                 },
998                 "hhvm.log_extern_compiler_perf": {
999                     "global_value": "true",
1000                 },
1001             })),
1002         );
1003     }
1005     #[test]
1006     fn test_options_de_from_cli_comma_separated_key_value() {
1007         let mut exp_include_roots = BTreeMap::<String, String>::new();
1008         exp_include_roots.insert("foo".into(), "bar".into());
1009         exp_include_roots.insert("bar".into(), "baz".into());
1010         const CLI_ARG: &str = "hhvm.include_roots=foo:bar,bar:baz";
1011         let act = Options::from_configs_(&EMPTY_STRS, &[CLI_ARG]).unwrap();
1012         assert_eq!(act.hhvm.include_roots.global_value, exp_include_roots,);
1013     }
1015     #[test]
1016     fn test_options_de_regression_boolish_parse_on_unrelated_opt() {
1017         // Note: this fails if bool-looking options are too eagerly parsed
1018         // (i.e., before they're are looked by JSON key/name and match a flag)
1019         let _: Options = serde_json::value::from_value(json!({
1020             "hhvm.only.opt1": { "global_value": "12345678901234567890" },
1021              "hhvm.only.opt2": { "global_value": "" },
1022         }))
1023         .expect("boolish-parsing logic wrongly triggered");
1024     }
1026     #[test]
1027     fn test_options_de_untyped_global_value_no_crash() {
1028         let res: Result<Options, String> = Options::from_configs(
1029             // try parsing an HHVM option whose layout is none of the
1030             // typed variants of GlobalValue, i.e., a string-to-int map
1031             &[r#"{
1032             "hhvm.semr_thread_overrides": {
1033               "global_value": {
1034                 "17":160,
1035                 "14":300,
1036                 "0":320
1037               },
1038               "local_value": { "4":300 },
1039                 "access":4
1040               }
1041                  }"#],
1042             &EMPTY_STRS,
1043         );
1044         assert_eq!(res.err(), None);
1045     }
1047     #[test]
1048     fn test_options_de_empty_configs_skipped_no_crash() {
1049         let res: Result<Options, String> = Options::from_configs_(
1050             // a subset (only 30/5K lines) of the real config passed by HHVM
1051             &[
1052                 "", // this should be skipped (it's an invalid JSON)
1053                 r#"
1054           {
1055             "hhvm.trusted_db_path": {
1056               "access": 4,
1057               "local_value": "",
1058               "global_value": ""
1059             },
1060             "hhvm.query": {
1061               "access": 4,
1062               "local_value": "",
1063               "global_value": ""
1064             },
1065             "hhvm.php7.ltr_assign": {
1066               "access": 4,
1067               "local_value": "0",
1068               "global_value": "0"
1069             },
1070             "hhvm.aliased_namespaces": {
1071               "access": 4,
1072               "local_value": {
1073                 "C": "HH\\Lib\\C"
1074               },
1075               "global_value": {
1076                 "Vec": "HH\\Lib\\Vec"
1077               }
1078             }
1079           }
1080           "#,
1081                 r#"
1082           {
1083             "hhvm.include_roots": {
1084               "global_value": {}
1085             }
1086           }"#,
1087             ],
1088             &EMPTY_STRS,
1089         );
1090         assert_eq!(res.err(), None);
1091     }
1094 // boilerplate code that could eventually be avoided via procedural macros
1096 bitflags! {
1097     struct Flags: u64 {
1098         const CONSTANT_FOLDING = 1 << 0;
1099         const OPTIMIZE_NULL_CHECKS = 1 << 1;
1100         // No longer using bit 2.
1101         const UVS = 1 << 3;
1102         const LTR_ASSIGN = 1 << 4;
1103         /// If true, then renumber labels after generating code for a method
1104         /// body. Semantic diff doesn't care about labels, but for visual diff against
1105         /// HHVM it's helpful to renumber in order that the labels match more closely
1106         const RELABEL = 1 << 5;
1107         // No longer using bit 6.
1108         // No longer using bit 7.
1109         // No longer using bit 8.
1110         const AUTHORITATIVE = 1 << 9;
1111         const JIT_ENABLE_RENAME_FUNCTION = 1 << 10;
1112         // No longer using bits 11-13.
1113         const LOG_EXTERN_COMPILER_PERF = 1 << 14;
1114         const ENABLE_INTRINSICS_EXTENSION = 1 << 15;
1115         // No longer using bits 16-21.
1116         const DISABLE_NONTOPLEVEL_DECLARATIONS = 1 << 22;
1117         // No longer using bits 23-25.
1118         const EMIT_CLS_METH_POINTERS = 1 << 26;
1119         // No longer using bit 27.
1120         const EMIT_METH_CALLER_FUNC_POINTERS = 1 << 28;
1121         const ENABLE_IMPLICIT_CONTEXT = 1 << 29;
1122         const DISABLE_LVAL_AS_AN_EXPRESSION = 1 << 30;
1123         // No longer using bits 31-32.
1124         const ARRAY_PROVENANCE = 1 << 33;
1125         // No longer using bit 34.
1126         const ENABLE_CLASS_LEVEL_WHERE_CLAUSES = 1 << 35;
1127         const DISABLE_LEGACY_SOFT_TYPEHINTS = 1 << 36;
1128         const ALLOW_NEW_ATTRIBUTE_SYNTAX = 1 << 37;
1129         const DISABLE_LEGACY_ATTRIBUTE_SYNTAX = 1 << 38;
1130         const CONST_DEFAULT_FUNC_ARGS = 1 << 39;
1131         const CONST_STATIC_PROPS = 1 << 40;
1132         const ABSTRACT_STATIC_PROPS = 1 << 41;
1133         const DISABLE_UNSET_CLASS_CONST = 1 << 42;
1134         const DISALLOW_FUNC_PTRS_IN_CONSTANTS = 1 << 43;
1135         // No longer using bit 44.
1136         const CONST_DEFAULT_LAMBDA_ARGS = 1 << 45;
1137         const ENABLE_XHP_CLASS_MODIFIER = 1 << 46;
1138         // No longer using bit 47.
1139         const ENABLE_ENUM_CLASSES = 1 << 48;
1140         const DISABLE_XHP_ELEMENT_MANGLING = 1 << 49;
1141         const DISABLE_ARRAY = 1 << 50;
1142         const RUST_EMITTER = 1 << 51;
1143         const DISABLE_ARRAY_CAST = 1 << 52;
1144         const DISABLE_ARRAY_TYPEHINT = 1 << 53;
1145         // No longer using bit 54
1146         const ALLOW_UNSTABLE_FEATURES = 1 << 55;
1147         const DISALLOW_HASH_COMMENTS = 1 << 56;
1148         const DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS = 1 << 57;
1149         const FOLD_LAZY_CLASS_KEYS = 1 << 58;
1150         const DISALLOW_DYNAMIC_METH_CALLER_ARGS = 1 << 59;
1151         const DISALLOW_INST_METH = 1 << 60;
1152         const ENABLE_READONLY_ENFORCEMENT = 1 << 61;
1153         const ESCAPE_BRACE = 1 << 62;
1154     }