enables arena based instruction sequence in hackc
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / options.rs
blob22130a9ec895044ddd009fd95163a0cba8f4f154
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::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
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_INST_METH_POINTERS,
162     EMIT_METH_CALLER_FUNC_POINTERS,
163     ENABLE_INTRINSICS_EXTENSION,
164     FOLD_LAZY_CLASS_KEYS,
165     HACK_ARR_COMPAT_NOTICES,
166     HACK_ARR_DV_ARRS,
167     JIT_ENABLE_RENAME_FUNCTION,
168     LOG_EXTERN_COMPILER_PERF,
169     RX_IS_ENABLED,
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
177     }
180 #[prefix_all("hhvm.")]
181 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
182 pub struct Hhvm {
183     #[serde(default)]
184     pub aliased_namespaces: Arg<BTreeMapOrEmptyVec<String, String>>,
186     #[serde(default)]
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>,
192     #[serde(
193         flatten,
194         serialize_with = "serialize_flags",
195         deserialize_with = "deserialize_flags"
196     )]
197     pub flags: HhvmFlags,
199     #[serde(flatten, default)]
200     pub hack_lang: HackLang,
203 impl Default for Hhvm {
204     fn default() -> Self {
205         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(),
211         }
212     }
215 impl Hhvm {
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())))
220             }
221             _ => Either::Left(empty()),
222         }
223     }
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())))
229             }
230             _ => Either::Left(empty()),
231         }
232     }
235 #[prefix_all("hhvm.hack.lang.")]
236 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
237 pub struct HackLang {
238     #[serde(
239         flatten,
240         serialize_with = "serialize_flags",
241         deserialize_with = "deserialize_flags"
242     )]
243     pub flags: LangFlags,
245     #[serde(default)]
246     pub check_int_overflow: Arg<String>,
249 prefixed_flags!(
250     LangFlags,
251     "hhvm.hack.lang.",
252     ABSTRACT_STATIC_PROPS,
253     ALLOW_NEW_ATTRIBUTE_SYNTAX,
254     ALLOW_UNSTABLE_FEATURES,
255     CONST_DEFAULT_FUNC_ARGS,
256     CONST_DEFAULT_LAMBDA_ARGS,
257     CONST_STATIC_PROPS,
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,
264     DISALLOW_INST_METH,
265     DISALLOW_FUNC_PTRS_IN_CONSTANTS,
266     DISALLOW_HASH_COMMENTS,
267     DISALLOW_DYNAMIC_METH_CALLER_ARGS,
268     ENABLE_CLASS_LEVEL_WHERE_CLAUSES,
269     ENABLE_ENUM_CLASSES,
270     ENABLE_XHP_CLASS_MODIFIER,
271     DISABLE_ARRAY_CAST,
272     DISABLE_ARRAY_TYPEHINT,
273     DISABLE_ARRAY,
274     RUST_EMITTER,
276 impl Default for LangFlags {
277     fn default() -> LangFlags {
278         LangFlags::DISABLE_LEGACY_SOFT_TYPEHINTS | LangFlags::ENABLE_ENUM_CLASSES
279     }
282 prefixed_flags!(
283     PhpismFlags,
284     "hhvm.hack.lang.phpism.",
285     DISABLE_NONTOPLEVEL_DECLARATIONS,
287 impl Default for PhpismFlags {
288     fn default() -> PhpismFlags {
289         PhpismFlags::empty()
290     }
293 prefixed_flags!(
294     Php7Flags,
295     "hhvm.php7.",
296     LTR_ASSIGN, //
297     UVS,        //
299 impl Default for Php7Flags {
300     fn default() -> Php7Flags {
301         Php7Flags::empty()
302     }
305 prefixed_flags!(
306     RepoFlags,
307     "hhvm.repo.",
308     AUTHORITATIVE, //
310 impl Default for RepoFlags {
311     fn default() -> RepoFlags {
312         RepoFlags::empty()
313     }
316 #[prefix_all("hhvm.server.")]
317 #[derive(Clone, Serialize, Deserialize, Default, PartialEq, Debug)]
318 pub struct Server {
319     #[serde(default)]
320     pub include_search_paths: Arg<Vec<String>>,
323 #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
324 pub struct Options {
325     #[serde(default)]
326     pub doc_root: Arg<String>,
328     #[serde(
329         flatten,
330         serialize_with = "serialize_flags",
331         deserialize_with = "deserialize_flags"
332     )]
333     pub hack_compiler_flags: CompilerFlags,
335     #[serde(flatten, default)]
336     pub hhvm: Hhvm,
338     #[serde(default = "defaults::max_array_elem_size_on_the_stack")]
339     pub max_array_elem_size_on_the_stack: Arg<isize>,
341     #[serde(
342         flatten,
343         serialize_with = "serialize_flags",
344         deserialize_with = "deserialize_flags"
345     )]
346     pub phpism_flags: PhpismFlags,
348     #[serde(
349         flatten,
350         serialize_with = "serialize_flags",
351         deserialize_with = "deserialize_flags"
352     )]
353     pub php7_flags: Php7Flags,
355     #[serde(
356         flatten,
357         serialize_with = "serialize_flags",
358         deserialize_with = "deserialize_flags"
359     )]
360     pub repo_flags: RepoFlags,
362     #[serde(flatten, default)]
363     pub server: Server,
366 impl Options {
367     pub fn log_extern_compiler_perf(&self) -> bool {
368         self.hhvm
369             .flags
370             .contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF)
371     }
374 impl Default for Options {
375     fn default() -> Options {
376         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()),
386         }
387     }
390 /// Non-zero argument defaults for use in both Default::default & SerDe framework
391 mod defaults {
392     use super::*;
394     pub fn max_array_elem_size_on_the_stack() -> Arg<isize> {
395         Arg::new(64)
396     }
398     pub fn emit_class_pointers() -> Arg<String> {
399         Arg::new("0".to_string())
400     }
403 thread_local! {
404     static CACHE: RefCell<Cache> = {
405         let hasher = fnv::FnvBuildHasher::default();
406         RefCell::new(LruCache::with_hasher(100, hasher))
407     }
410 pub(crate) type Cache = LruCache<(Vec<String>, Vec<String>), Options, fnv::FnvBuildHasher>;
412 impl Options {
413     pub fn to_string(&self) -> String {
414         serde_json::to_string_pretty(&self).expect("failed to parse JSON")
415     }
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))
420     }
422     fn from_cli_args(args: &[impl AsRef<str>]) -> Result<Json, String> {
423         let mut json = json!({});
424         for arg in args {
425             let arg = arg.as_ref();
426             match arg.find('=') {
427                 Some(pos) => {
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| {
435                             m.insert(
436                                 key.to_owned(),
437                                 json!({
438                                     "global_value": val,
439                                 }),
440                             )
441                         });
442                     } else {
443                         return Err(format!("Invalid format for CLI arg key: {}", key));
444                     }
445                 }
446                 None => return Err(format!("Missing '=' key-value separator in: {}", arg)),
447             }
448         }
449         Ok(json)
450     }
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) {
456         match (dst, src) {
457             (&mut Json::Object(ref mut dst), &Json::Object(ref src)) => {
458                 for (k, v) in src {
459                     Self::merge(dst.entry(k.clone()).or_insert(Json::Null), v);
460                 }
461             }
462             (dst, src) => {
463                 *dst = src.clone();
464             }
465         }
466     }
468     pub fn from_configs<S: AsRef<str>>(jsons: &[S], clis: &[S]) -> Result<Self, String> {
469         CACHE.with(|cache| {
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(),
473             );
474             let mut cache = cache.borrow_mut();
475             if let Some(o) = cache.get_mut(&key) {
476                 Ok(o.clone())
477             } else {
478                 let o = Options::from_configs_(&key.0, &key.1)?;
479                 cache.put(key, o.clone());
480                 Ok(o)
481             }
482         })
483     }
485     fn from_configs_<S1, S2>(jsons: &[S1], cli_args: &[S2]) -> Result<Self, String>
486     where
487         S1: AsRef<str>,
488         S2: AsRef<str>,
489     {
490         let mut merged = json!({});
491         for json in jsons {
492             let json: &str = json.as_ref();
493             if json.is_empty() {
494                 continue;
495             }
496             Self::merge(
497                 &mut merged,
498                 &serde_json::from_str(json).map_err(|e| e.to_string())?,
499             );
500         }
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())
505     }
507     pub fn array_provenance(&self) -> bool {
508         self.hhvm.flags.contains(HhvmFlags::ARRAY_PROVENANCE)
509     }
511     pub fn check_int_overflow(&self) -> bool {
512         self.hhvm
513             .hack_lang
514             .check_int_overflow
515             .get()
516             .parse::<i32>()
517             .map_or(false, |x| x.is_positive())
518     }
520     pub fn emit_class_pointers(&self) -> i32 {
521         self.hhvm.emit_class_pointers.get().parse::<i32>().unwrap()
522     }
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))?;
534     }
535     map.end()
538 /// Expected JSON layouts for each field that is a valid Hack option
539 #[derive(Deserialize)]
540 #[serde(untagged)]
541 enum GlobalValue {
542     String(String),
543     Bool(bool),
544     Int(isize),
545     VecStr(Vec<String>),
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>(
553     deserializer: D,
554 ) -> Result<P, D::Error> {
555     use std::fmt;
556     use std::marker::PhantomData;
557     struct Phantom<P>(PhantomData<P>);
559     impl<'de, P: PrefixedFlags> Visitor<'de> for Phantom<P> {
560         type Value = P;
562         fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
563             formatter.write_str("flag with string global_value")
564         }
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 {
572                 "true" => Ok(true),
573                 "false" => Ok(false),
574                 num => num
575                     .parse::<i32>()
576                     .map(|v| v == 1)
577                     .map_err(de::Error::custom),
578             };
579             while let Some((ref k, ref v)) = map.next_entry::<String, Arg<GlobalValue>>()? {
580                 let mut found = None;
581                 let k: &str = &k;
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();
585                 }
586                 if let Some(flag) = found {
587                     let truish = match v.get() {
588                         GlobalValue::String(s) => {
589                             if s.is_empty() {
590                                 false
591                             } else {
592                                 from_str(s)?
593                             }
594                         }
595                         GlobalValue::Bool(b) => *b,
596                         GlobalValue::Int(n) => *n == 1,
597                         _ => continue, // types such as VecStr aren't parsable as flags
598                     };
599                     if truish {
600                         flags |= flag
601                     } else {
602                         flags &= !flag
603                     }
604                 }
605             }
606             Ok(flags)
607         }
608     }
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)]
618 #[serde(untagged)]
619 pub enum BTreeMapOrEmptyVec<K: std::cmp::Ord, V> {
620     Nonempty(BTreeMap<K, V>),
621     Empty(Vec<V>),
623 impl<K: std::cmp::Ord, V> BTreeMapOrEmptyVec<K, V> {
624     pub fn as_map(&self) -> Option<&BTreeMap<K, V>> {
625         match self {
626             BTreeMapOrEmptyVec::Nonempty(m) => Some(m),
627             _ => None,
628         }
629     }
631 impl<K: std::cmp::Ord, V> Into<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
632     fn into(self) -> BTreeMap<K, V> {
633         match self {
634             BTreeMapOrEmptyVec::Nonempty(m) => m,
635             _ => BTreeMap::new(),
636         }
637     }
639 impl<K: std::cmp::Ord, V> From<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
640     fn from(m: BTreeMap<K, V>) -> Self {
641         if m.is_empty() {
642             BTreeMapOrEmptyVec::Empty(vec![])
643         } else {
644             BTreeMapOrEmptyVec::Nonempty(m)
645         }
646     }
648 impl<K: std::cmp::Ord, V> Default for BTreeMapOrEmptyVec<K, V> {
649     fn default() -> Self {
650         BTreeMapOrEmptyVec::Empty(vec![])
651     }
654 #[cfg(test)]
655 mod tests {
656     use super::*;
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": {
661     "global_value": {
662       "bar": "baz",
663       "foo": "bar"
664     }
665   },
666   "hhvm.array_provenance": {
667     "global_value": false
668   },
669   "hhvm.emit_class_pointers": {
670     "global_value": "0"
671   },
672   "hhvm.emit_cls_meth_pointers": {
673     "global_value": false
674   },
675   "hhvm.emit_inst_meth_pointers": {
676     "global_value": false
677   },
678   "hhvm.emit_meth_caller_func_pointers": {
679     "global_value": true
680   },
681   "hhvm.enable_intrinsics_extension": {
682     "global_value": false
683   },
684   "hhvm.fold_lazy_class_keys": {
685     "global_value": true
686   },
687   "hhvm.hack.lang.abstract_static_props": {
688     "global_value": false
689   },
690   "hhvm.hack.lang.allow_new_attribute_syntax": {
691     "global_value": false
692   },
693   "hhvm.hack.lang.allow_unstable_features": {
694     "global_value": false
695   },
696   "hhvm.hack.lang.check_int_overflow": {
697     "global_value": ""
698   },
699   "hhvm.hack.lang.const_default_func_args": {
700     "global_value": false
701   },
702   "hhvm.hack.lang.const_default_lambda_args": {
703     "global_value": false
704   },
705   "hhvm.hack.lang.const_static_props": {
706     "global_value": false
707   },
708   "hhvm.hack.lang.disable_array": {
709     "global_value": false
710   },
711   "hhvm.hack.lang.disable_array_cast": {
712     "global_value": false
713   },
714   "hhvm.hack.lang.disable_array_typehint": {
715     "global_value": false
716   },
717   "hhvm.hack.lang.disable_legacy_attribute_syntax": {
718     "global_value": false
719   },
720   "hhvm.hack.lang.disable_legacy_soft_typehints": {
721     "global_value": true
722   },
723   "hhvm.hack.lang.disable_lval_as_an_expression": {
724     "global_value": false
725   },
726   "hhvm.hack.lang.disable_unset_class_const": {
727     "global_value": false
728   },
729   "hhvm.hack.lang.disable_xhp_element_mangling": {
730     "global_value": false
731   },
732   "hhvm.hack.lang.disallow_dynamic_meth_caller_args": {
733     "global_value": false
734   },
735   "hhvm.hack.lang.disallow_fun_and_cls_meth_pseudo_funcs": {
736     "global_value": false
737   },
738   "hhvm.hack.lang.disallow_func_ptrs_in_constants": {
739     "global_value": false
740   },
741   "hhvm.hack.lang.disallow_hash_comments": {
742     "global_value": false
743   },
744   "hhvm.hack.lang.disallow_inst_meth": {
745     "global_value": false
746   },
747   "hhvm.hack.lang.enable_class_level_where_clauses": {
748     "global_value": false
749   },
750   "hhvm.hack.lang.enable_enum_classes": {
751     "global_value": true
752   },
753   "hhvm.hack.lang.enable_xhp_class_modifier": {
754     "global_value": false
755   },
756   "hhvm.hack.lang.rust_emitter": {
757     "global_value": false
758   },
759   "hhvm.hack_arr_compat_notices": {
760     "global_value": false
761   },
762   "hhvm.hack_arr_dv_arrs": {
763     "global_value": false
764   },
765   "hhvm.include_roots": {
766     "global_value": {}
767   },
768   "hhvm.jit_enable_rename_function": {
769     "global_value": false
770   },
771   "hhvm.log_extern_compiler_perf": {
772     "global_value": false
773   },
774   "hhvm.rx_is_enabled": {
775     "global_value": false
776   }
777 }"#;
779     #[test]
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());
786                 m.into()
787             }),
788             flags: HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS | HhvmFlags::FOLD_LAZY_CLASS_KEYS,
789             ..Default::default()
790         });
791         assert_eq!(HHVM_1, serde_json::to_string_pretty(&hhvm).unwrap(),);
792     }
794     #[test]
795     fn test_hhvm_json_de() {
796         let j = serde_json::from_str(
797             r#"{
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" }
803             }"#,
804         )
805         .unwrap();
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
811         ));
812         assert!(!hhvm.flags.contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF));
813     }
815     #[test]
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());
819         assert!(
820             hhvm.flags
821                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
822         );
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" },
827         }))
828         .unwrap();
829         assert!(
830             !hhvm
831                 .flags
832                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
833         );
834     }
836     #[test]
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" } }"#,
845         )
846         .unwrap();
847         assert!(
848             hhvm // verify a false-by-default flag was parsed as true
849                 .flags
850                 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
851         );
853         assert!(
854             !hhvm // verify a true-by-default flag was parsed as false
855                 .flags
856                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
857         );
858     }
860     #[test]
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": "" } }"#,
865         )
866         .unwrap();
867         assert!(
868             !hhvm
869                 .flags
870                 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
871         );
872     }
874     #[test]
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",
879             },
880         }))
881         .expect("failed to deserialize");
882         assert!(
883             act.hhvm
884                 .flags
885                 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
886         );
887     }
889     #[test]
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());
893     }
895     #[test]
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"} }
899         }))
900         .unwrap();
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());
904             m
905         },);
906     }
908     #[test]
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": [] }
912         }))
913         .unwrap();
914         assert_eq!(act.hhvm.aliased_namespaces.get().as_map(), None);
915     }
917     #[test]
918     fn test_options_merge() {
919         let mut dst = json!({
920             "uniqueAtDst": "DST",
921             "person" : { "firstName": "John", "lastName": "Doe" },
922             "flat": [ "will", "be", "overridden" ],
923         });
924         let src = json!({
925             "uniqueAtSrc": "SRC",
926             "person" : { "firstName" : "Jane (not John)" },
927             "flat": "overrides dst's field",
928         });
929         Options::merge(&mut dst, &src);
930         assert_eq!(
931             dst,
932             json!({
933                 "flat": "overrides dst's field",
934                 "person": {
935                     "firstName": "Jane (not John)",
936                     "lastName": "Doe"
937                 },
938                 "uniqueAtDst": "DST",
939                 "uniqueAtSrc": "SRC",
940             })
941         );
942     }
944     const EMPTY_STRS: [&str; 0] = [];
946     #[test]
947     fn test_options_de_multiple_jsons() {
948         let jsons: [String; 2] = [
949             json!({
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 }
954             })
955             .to_string(),
956             json!({
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 }
961             })
962             .to_string(),
963         ];
964         let act = Options::from_configs_(&jsons, &EMPTY_STRS).unwrap();
965         assert!(
966             act.hhvm
967                 .hack_lang
968                 .flags
969                 .contains(LangFlags::DISABLE_XHP_ELEMENT_MANGLING)
970         );
971         assert!(
972             !act.hhvm
973                 .hack_lang
974                 .flags
975                 .contains(LangFlags::ENABLE_ENUM_CLASSES)
976         );
977         assert!(act.hhvm.flags.contains(HhvmFlags::RX_IS_ENABLED));
978     }
980     #[test]
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"));
988     }
990     #[test]
991     fn test_hhvm_flags_cli_de_to_json() {
992         let args = [
993             "eval.logexterncompilerperf=true",
994             "eval.jitenablerenamefunction=1",
995         ];
996         let act = Options::from_cli_args(&args);
997         assert_eq!(
998             act,
999             Ok(json!({
1000                 "hhvm.jit_enable_rename_function": {
1001                     "global_value": "1",
1002                 },
1003                 "hhvm.log_extern_compiler_perf": {
1004                     "global_value": "true",
1005                 },
1006             })),
1007         );
1008     }
1010     #[test]
1011     fn test_options_de_from_cli_override_json() {
1012         let cli_args = [
1013             "eval.jitenablerenamefunction=1",
1014             "eval.hackarrcompatnotices=true",
1015         ];
1016         let json = json!({
1017             "hhvm.hack_arr_compat_notices": {
1018                 "global_value": "0",
1019             },
1020             "hhvm.log_extern_compiler_perf": {
1021                 "global_value": "true",
1022             },
1023         });
1024         let act = Options::from_configs_(&[json.to_string()], &cli_args).unwrap();
1025         assert!(act.hhvm.flags.contains(HhvmFlags::HACK_ARR_COMPAT_NOTICES));
1026     }
1028     #[test]
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,);
1036     }
1038     #[test]
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": "" },
1045         }))
1046         .expect("boolish-parsing logic wrongly triggered");
1047     }
1049     #[test]
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
1054             &[r#"{
1055             "hhvm.semr_thread_overrides": {
1056               "global_value": {
1057                 "17":160,
1058                 "14":300,
1059                 "0":320
1060               },
1061               "local_value": { "4":300 },
1062                 "access":4
1063               }
1064                  }"#],
1065             &EMPTY_STRS,
1066         );
1067         assert_eq!(res.err(), None);
1068     }
1070     #[test]
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
1074             &[
1075                 "", // this should be skipped (it's an invalid JSON)
1076                 r#"
1077           {
1078             "hhvm.trusted_db_path": {
1079               "access": 4,
1080               "local_value": "",
1081               "global_value": ""
1082             },
1083             "hhvm.query": {
1084               "access": 4,
1085               "local_value": "",
1086               "global_value": ""
1087             },
1088             "hhvm.php7.ltr_assign": {
1089               "access": 4,
1090               "local_value": "0",
1091               "global_value": "0"
1092             },
1093             "hhvm.aliased_namespaces": {
1094               "access": 4,
1095               "local_value": {
1096                 "C": "HH\\Lib\\C"
1097               },
1098               "global_value": {
1099                 "Vec": "HH\\Lib\\Vec"
1100               }
1101             }
1102           }
1103           "#,
1104                 r#"
1105           {
1106             "hhvm.include_roots": {
1107               "global_value": {}
1108             }
1109           }"#,
1110             ],
1111             &EMPTY_STRS,
1112         );
1113         assert_eq!(res.err(), None);
1114     }
1117 // boilerplate code that could eventually be avoided via procedural macros
1119 bitflags! {
1120     struct Flags: u64 {
1121         const CONSTANT_FOLDING = 1 << 0;
1122         const OPTIMIZE_NULL_CHECKS = 1 << 1;
1123         // No longer using bit 2.
1124         const UVS = 1 << 3;
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;
1175     }