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::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
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_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
176 #[prefix_all("hhvm.")]
177 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
180 pub aliased_namespaces: Arg<BTreeMapOrEmptyVec<String, String>>,
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>,
190 serialize_with = "serialize_flags",
191 deserialize_with = "deserialize_flags"
193 pub flags: HhvmFlags,
195 #[serde(flatten, default)]
196 pub hack_lang: HackLang,
199 impl Default for Hhvm {
200 fn default() -> 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(),
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())))
217 _ => Either::Left(empty()),
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())))
226 _ => Either::Left(empty()),
231 #[prefix_all("hhvm.hack.lang.")]
232 #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)]
233 pub struct HackLang {
236 serialize_with = "serialize_flags",
237 deserialize_with = "deserialize_flags"
239 pub flags: LangFlags,
242 pub check_int_overflow: Arg<String>,
248 ABSTRACT_STATIC_PROPS,
249 ALLOW_NEW_ATTRIBUTE_SYNTAX,
250 ALLOW_UNSTABLE_FEATURES,
251 CONST_DEFAULT_FUNC_ARGS,
252 CONST_DEFAULT_LAMBDA_ARGS,
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,
261 DISALLOW_FUNC_PTRS_IN_CONSTANTS,
262 DISALLOW_HASH_COMMENTS,
263 DISALLOW_DYNAMIC_METH_CALLER_ARGS,
264 ENABLE_CLASS_LEVEL_WHERE_CLAUSES,
266 ENABLE_READONLY_ENFORCEMENT,
267 ENABLE_XHP_CLASS_MODIFIER,
270 DISABLE_ARRAY_TYPEHINT,
274 impl Default for LangFlags {
275 fn default() -> LangFlags {
276 LangFlags::DISABLE_LEGACY_SOFT_TYPEHINTS | LangFlags::ENABLE_ENUM_CLASSES
282 "hhvm.hack.lang.phpism.",
283 DISABLE_NONTOPLEVEL_DECLARATIONS,
285 impl Default for PhpismFlags {
286 fn default() -> PhpismFlags {
297 impl Default for Php7Flags {
298 fn default() -> Php7Flags {
308 impl Default for RepoFlags {
309 fn default() -> RepoFlags {
314 #[prefix_all("hhvm.server.")]
315 #[derive(Clone, Serialize, Deserialize, Default, PartialEq, Debug)]
318 pub include_search_paths: Arg<Vec<String>>,
321 #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
324 pub doc_root: Arg<String>,
328 serialize_with = "serialize_flags",
329 deserialize_with = "deserialize_flags"
331 pub hack_compiler_flags: CompilerFlags,
333 #[serde(flatten, default)]
336 #[serde(default = "defaults::max_array_elem_size_on_the_stack")]
337 pub max_array_elem_size_on_the_stack: Arg<isize>,
341 serialize_with = "serialize_flags",
342 deserialize_with = "deserialize_flags"
344 pub phpism_flags: PhpismFlags,
348 serialize_with = "serialize_flags",
349 deserialize_with = "deserialize_flags"
351 pub php7_flags: Php7Flags,
355 serialize_with = "serialize_flags",
356 deserialize_with = "deserialize_flags"
358 pub repo_flags: RepoFlags,
360 #[serde(flatten, default)]
365 pub fn log_extern_compiler_perf(&self) -> bool {
368 .contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF)
372 impl Default for Options {
373 fn default() -> 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()),
388 /// Non-zero argument defaults for use in both Default::default & SerDe framework
392 pub fn max_array_elem_size_on_the_stack() -> Arg<isize> {
396 pub fn emit_class_pointers() -> Arg<String> {
397 Arg::new("0".to_string())
402 static CACHE: RefCell<Cache> = {
403 let hasher = fnv::FnvBuildHasher::default();
404 RefCell::new(LruCache::with_hasher(100, hasher))
408 pub(crate) type Cache = LruCache<(Vec<String>, Vec<String>), Options, fnv::FnvBuildHasher>;
411 pub fn to_string(&self) -> String {
412 serde_json::to_string_pretty(&self).expect("failed to parse JSON")
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))
420 fn from_cli_args(args: &[impl AsRef<str>]) -> Result<Json, String> {
421 let mut json = json!({});
423 let arg = arg.as_ref();
424 match arg.find('=') {
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| {
441 return Err(format!("Invalid format for CLI arg key: {}", key));
444 None => return Err(format!("Missing '=' key-value separator in: {}", arg)),
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) {
455 (&mut Json::Object(ref mut dst), &Json::Object(ref src)) => {
457 Self::merge(dst.entry(k.clone()).or_insert(Json::Null), v);
466 pub fn from_configs<S: AsRef<str>>(jsons: &[S], clis: &[S]) -> Result<Self, String> {
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(),
472 let mut cache = cache.borrow_mut();
473 if let Some(o) = cache.get_mut(&key) {
476 let o = Options::from_configs_(&key.0, &key.1)?;
477 cache.put(key, o.clone());
483 fn from_configs_<S1, S2>(jsons: &[S1], cli_args: &[S2]) -> Result<Self, String>
488 let mut merged = json!({});
490 let json: &str = json.as_ref();
496 &serde_json::from_str(json).map_err(|e| e.to_string())?,
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())
505 pub fn array_provenance(&self) -> bool {
506 self.hhvm.flags.contains(HhvmFlags::ARRAY_PROVENANCE)
509 pub fn check_int_overflow(&self) -> bool {
515 .map_or(false, |x| x.is_positive())
518 pub fn emit_class_pointers(&self) -> i32 {
519 self.hhvm.emit_class_pointers.get().parse::<i32>().unwrap()
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))?;
536 /// Expected JSON layouts for each field that is a valid Hack option
537 #[derive(Deserialize)]
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>(
552 ) -> Result<P, D::Error> {
554 use std::marker::PhantomData;
555 struct Phantom<P>(PhantomData<P>);
557 impl<'de, P: PrefixedFlags> Visitor<'de> for Phantom<P> {
560 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
561 formatter.write_str("flag with string global_value")
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 {
571 "false" => Ok(false),
575 .map_err(de::Error::custom),
577 while let Some((ref k, ref v)) = map.next_entry::<String, Arg<GlobalValue>>()? {
578 let mut found = None;
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();
584 if let Some(flag) = found {
585 let truish = match v.get() {
586 GlobalValue::String(s) => {
593 GlobalValue::Bool(b) => *b,
594 GlobalValue::Int(n) => *n == 1,
595 _ => continue, // types such as VecStr aren't parsable as flags
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)]
617 pub enum BTreeMapOrEmptyVec<K: std::cmp::Ord, V> {
618 Nonempty(BTreeMap<K, V>),
621 impl<K: std::cmp::Ord, V> BTreeMapOrEmptyVec<K, V> {
622 pub fn as_map(&self) -> Option<&BTreeMap<K, V>> {
624 BTreeMapOrEmptyVec::Nonempty(m) => Some(m),
629 impl<K: std::cmp::Ord, V> Into<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
630 fn into(self) -> BTreeMap<K, V> {
632 BTreeMapOrEmptyVec::Nonempty(m) => m,
633 _ => BTreeMap::new(),
637 impl<K: std::cmp::Ord, V> From<BTreeMap<K, V>> for BTreeMapOrEmptyVec<K, V> {
638 fn from(m: BTreeMap<K, V>) -> Self {
640 BTreeMapOrEmptyVec::Empty(vec![])
642 BTreeMapOrEmptyVec::Nonempty(m)
646 impl<K: std::cmp::Ord, V> Default for BTreeMapOrEmptyVec<K, V> {
647 fn default() -> Self {
648 BTreeMapOrEmptyVec::Empty(vec![])
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": {
664 "hhvm.array_provenance": {
665 "global_value": false
667 "hhvm.emit_class_pointers": {
670 "hhvm.emit_cls_meth_pointers": {
671 "global_value": false
673 "hhvm.emit_meth_caller_func_pointers": {
676 "hhvm.enable_implicit_context": {
677 "global_value": false
679 "hhvm.enable_intrinsics_extension": {
680 "global_value": false
682 "hhvm.fold_lazy_class_keys": {
685 "hhvm.hack.lang.abstract_static_props": {
686 "global_value": false
688 "hhvm.hack.lang.allow_new_attribute_syntax": {
689 "global_value": false
691 "hhvm.hack.lang.allow_unstable_features": {
692 "global_value": false
694 "hhvm.hack.lang.check_int_overflow": {
697 "hhvm.hack.lang.const_default_func_args": {
698 "global_value": false
700 "hhvm.hack.lang.const_default_lambda_args": {
701 "global_value": false
703 "hhvm.hack.lang.const_static_props": {
704 "global_value": false
706 "hhvm.hack.lang.disable_array": {
707 "global_value": false
709 "hhvm.hack.lang.disable_array_cast": {
710 "global_value": false
712 "hhvm.hack.lang.disable_array_typehint": {
713 "global_value": false
715 "hhvm.hack.lang.disable_legacy_attribute_syntax": {
716 "global_value": false
718 "hhvm.hack.lang.disable_legacy_soft_typehints": {
721 "hhvm.hack.lang.disable_lval_as_an_expression": {
722 "global_value": false
724 "hhvm.hack.lang.disable_unset_class_const": {
725 "global_value": false
727 "hhvm.hack.lang.disable_xhp_element_mangling": {
728 "global_value": false
730 "hhvm.hack.lang.disallow_dynamic_meth_caller_args": {
731 "global_value": false
733 "hhvm.hack.lang.disallow_fun_and_cls_meth_pseudo_funcs": {
734 "global_value": false
736 "hhvm.hack.lang.disallow_func_ptrs_in_constants": {
737 "global_value": false
739 "hhvm.hack.lang.disallow_hash_comments": {
740 "global_value": false
742 "hhvm.hack.lang.disallow_inst_meth": {
743 "global_value": false
745 "hhvm.hack.lang.enable_class_level_where_clauses": {
746 "global_value": false
748 "hhvm.hack.lang.enable_enum_classes": {
751 "hhvm.hack.lang.enable_readonly_enforcement": {
752 "global_value": false
754 "hhvm.hack.lang.enable_xhp_class_modifier": {
755 "global_value": false
757 "hhvm.hack.lang.escape_brace": {
758 "global_value": false
760 "hhvm.hack.lang.rust_emitter": {
761 "global_value": false
763 "hhvm.include_roots": {
766 "hhvm.jit_enable_rename_function": {
767 "global_value": false
769 "hhvm.log_extern_compiler_perf": {
770 "global_value": false
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());
783 flags: HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS | HhvmFlags::FOLD_LAZY_CLASS_KEYS,
786 assert_eq!(HHVM_1, serde_json::to_string_pretty(&hhvm).unwrap(),);
790 fn test_hhvm_json_de() {
791 let j = serde_json::from_str(
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" }
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
807 assert!(!hhvm.flags.contains(HhvmFlags::LOG_EXTERN_COMPILER_PERF));
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());
816 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
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" },
827 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
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" } }"#,
843 hhvm // verify a false-by-default flag was parsed as true
845 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
849 !hhvm // verify a true-by-default flag was parsed as false
851 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
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": "" } }"#,
865 .contains(HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS)
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",
876 .expect("failed to deserialize");
880 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
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());
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"} }
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());
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": [] }
909 assert_eq!(act.hhvm.aliased_namespaces.get().as_map(), None);
913 fn test_options_merge() {
914 let mut dst = json!({
915 "uniqueAtDst": "DST",
916 "person" : { "firstName": "John", "lastName": "Doe" },
917 "flat": [ "will", "be", "overridden" ],
920 "uniqueAtSrc": "SRC",
921 "person" : { "firstName" : "Jane (not John)" },
922 "flat": "overrides dst's field",
924 Options::merge(&mut dst, &src);
928 "flat": "overrides dst's field",
930 "firstName": "Jane (not John)",
933 "uniqueAtDst": "DST",
934 "uniqueAtSrc": "SRC",
939 const EMPTY_STRS: [&str; 0] = [];
942 fn test_options_de_multiple_jsons() {
943 let jsons: [String; 2] = [
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 }
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 }
959 let act = Options::from_configs_(&jsons, &EMPTY_STRS).unwrap();
964 .contains(LangFlags::DISABLE_XHP_ELEMENT_MANGLING)
970 .contains(LangFlags::ENABLE_ENUM_CLASSES)
972 assert!(act.hhvm.flags.contains(HhvmFlags::ENABLE_IMPLICIT_CONTEXT));
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"));
986 fn test_hhvm_flags_cli_de_to_json() {
988 "eval.logexterncompilerperf=true",
989 "eval.jitenablerenamefunction=1",
991 let act = Options::from_cli_args(&args);
995 "hhvm.jit_enable_rename_function": {
998 "hhvm.log_extern_compiler_perf": {
999 "global_value": "true",
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,);
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": "" },
1023 .expect("boolish-parsing logic wrongly triggered");
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
1032 "hhvm.semr_thread_overrides": {
1038 "local_value": { "4":300 },
1044 assert_eq!(res.err(), None);
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
1052 "", // this should be skipped (it's an invalid JSON)
1055 "hhvm.trusted_db_path": {
1065 "hhvm.php7.ltr_assign": {
1070 "hhvm.aliased_namespaces": {
1076 "Vec": "HH\\Lib\\Vec"
1083 "hhvm.include_roots": {
1090 assert_eq!(res.err(), None);
1094 // boilerplate code that could eventually be avoided via procedural macros
1098 const CONSTANT_FOLDING = 1 << 0;
1099 const OPTIMIZE_NULL_CHECKS = 1 << 1;
1100 // No longer using bit 2.
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;