1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 pub mod dump_expr_tree;
9 rust_aast_parser_types::{Env as AastEnv, Result as AastResult},
10 AastParser, Error as AastError,
13 use bitflags::bitflags;
14 use bytecode_printer::{print_unit, Context};
15 use decl_provider::{DeclProvider, NoDeclProvider};
16 use emit_unit::{emit_unit, FromAstFlags};
17 use env::emitter::Emitter;
18 use hackc_unit::HackCUnit;
19 use hhbc_ast::FatalOp;
20 use instruction_sequence::Error;
21 use ocamlrep::{rc::RcOc, FromError, FromOcamlRep, Value};
22 use ocamlrep_derive::{FromOcamlRep, ToOcamlRep};
23 use options::{Arg, HackLang, Hhvm, HhvmFlags, LangFlags, Options, Php7Flags, RepoFlags};
25 ast, namespace_env::Env as NamespaceEnv, parser_options::ParserOptions, pos::Pos,
26 relative_path::RelativePath,
28 use parser_core_types::{
29 indexed_source_text::IndexedSourceText, source_text::SourceText, syntax_error::ErrorType,
31 use rewrite_program::rewrite_program;
32 use stack_limit::StackLimit;
35 /// Common input needed for compilation. Extra care is taken
36 /// so that everything is easily serializable at the FFI boundary
37 /// until the migration from OCaml is fully complete
38 #[derive(Debug, FromOcamlRep)]
40 pub filepath: RelativePath,
41 pub config_jsons: Vec<S>,
42 pub config_list: Vec<S>,
47 pub struct NativeEnv<S> {
48 pub filepath: RelativePath,
49 pub aliased_namespaces: S,
51 pub emit_class_pointers: i32,
52 pub check_int_overflow: i32,
53 pub hhbc_flags: HHBCFlags,
54 pub parser_flags: ParserFlags,
59 // Note: these flags are intentionally packed into bits to overcome
60 // the limitation of to-OCaml FFI functions having at most 5 parameters
61 pub struct EnvFlags: u8 {
62 const IS_SYSTEMLIB = 1 << 0;
63 const IS_EVALED = 1 << 1;
64 const FOR_DEBUGGER_EVAL = 1 << 2;
65 const DUMP_SYMBOL_REFS = 1 << 3;
66 const DISABLE_TOPLEVEL_ELABORATION = 1 << 4;
67 const ENABLE_DECL = 1 << 5;
71 // Keep in sync with compiler_ffi.rs
73 pub struct HHBCFlags: u32 {
74 const LTR_ASSIGN=1 << 0;
76 // No longer using bit 3.
77 const AUTHORITATIVE=1 << 4;
78 const JIT_ENABLE_RENAME_FUNCTION=1 << 5;
79 const LOG_EXTERN_COMPILER_PERF=1 << 6;
80 const ENABLE_INTRINSICS_EXTENSION=1 << 7;
81 // No longer using bit 8.
82 // No longer using bit 9.
83 const EMIT_CLS_METH_POINTERS=1 << 10;
84 const EMIT_METH_CALLER_FUNC_POINTERS=1 << 11;
85 const ENABLE_IMPLICIT_CONTEXT=1 << 12;
86 const ARRAY_PROVENANCE=1 << 13;
87 // No longer using bit 14.
88 const FOLD_LAZY_CLASS_KEYS=1 << 15;
89 // No longer using bit 16.
93 // Mapping must match getParserFlags() in runtime-option.cpp and compiler_ffi.rs
95 pub struct ParserFlags: u32 {
96 const ABSTRACT_STATIC_PROPS=1 << 0;
97 const ALLOW_NEW_ATTRIBUTE_SYNTAX=1 << 1;
98 const ALLOW_UNSTABLE_FEATURES=1 << 2;
99 const CONST_DEFAULT_FUNC_ARGS=1 << 3;
100 const CONST_STATIC_PROPS=1 << 4;
101 const DISABLE_ARRAY=1 << 5;
102 // No longer using bit 6
103 const DISABLE_ARRAY_TYPEHINT=1 << 7;
104 const DISABLE_LVAL_AS_AN_EXPRESSION=1 << 8;
105 // No longer using bit 9
106 const DISALLOW_INST_METH=1 << 10;
107 const DISABLE_XHP_ELEMENT_MANGLING=1 << 11;
108 const DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS=1 << 12;
109 const DISALLOW_FUNC_PTRS_IN_CONSTANTS=1 << 13;
110 // No longer using bit 14.
111 // No longer using bit 15.
112 const ENABLE_ENUM_CLASSES=1 << 16;
113 const ENABLE_XHP_CLASS_MODIFIER=1 << 17;
114 // No longer using bits 18-19.
115 const ENABLE_CLASS_LEVEL_WHERE_CLAUSES=1 << 20;
119 impl FromOcamlRep for EnvFlags {
120 fn from_ocamlrep(value: Value<'_>) -> Result<Self, FromError> {
121 Ok(EnvFlags::from_bits(value.as_int().unwrap() as u8).unwrap())
126 fn to_php7_flags(self) -> Php7Flags {
127 let mut f = Php7Flags::empty();
128 if self.contains(HHBCFlags::UVS) {
131 if self.contains(HHBCFlags::LTR_ASSIGN) {
132 f |= Php7Flags::LTR_ASSIGN;
137 fn to_hhvm_flags(self) -> HhvmFlags {
138 let mut f = HhvmFlags::empty();
139 if self.contains(HHBCFlags::ARRAY_PROVENANCE) {
140 f |= HhvmFlags::ARRAY_PROVENANCE;
142 if self.contains(HHBCFlags::EMIT_CLS_METH_POINTERS) {
143 f |= HhvmFlags::EMIT_CLS_METH_POINTERS;
145 if self.contains(HHBCFlags::EMIT_METH_CALLER_FUNC_POINTERS) {
146 f |= HhvmFlags::EMIT_METH_CALLER_FUNC_POINTERS;
148 if self.contains(HHBCFlags::ENABLE_INTRINSICS_EXTENSION) {
149 f |= HhvmFlags::ENABLE_INTRINSICS_EXTENSION;
151 if self.contains(HHBCFlags::FOLD_LAZY_CLASS_KEYS) {
152 f |= HhvmFlags::FOLD_LAZY_CLASS_KEYS;
154 if self.contains(HHBCFlags::JIT_ENABLE_RENAME_FUNCTION) {
155 f |= HhvmFlags::JIT_ENABLE_RENAME_FUNCTION;
157 if self.contains(HHBCFlags::LOG_EXTERN_COMPILER_PERF) {
158 f |= HhvmFlags::LOG_EXTERN_COMPILER_PERF;
160 if self.contains(HHBCFlags::ENABLE_IMPLICIT_CONTEXT) {
161 f |= HhvmFlags::ENABLE_IMPLICIT_CONTEXT;
166 fn to_repo_flags(self) -> RepoFlags {
167 let mut f = RepoFlags::empty();
168 if self.contains(HHBCFlags::AUTHORITATIVE) {
169 f |= RepoFlags::AUTHORITATIVE;
176 fn to_lang_flags(self) -> LangFlags {
177 let mut f = LangFlags::empty();
178 if self.contains(ParserFlags::ABSTRACT_STATIC_PROPS) {
179 f |= LangFlags::ABSTRACT_STATIC_PROPS;
181 if self.contains(ParserFlags::ALLOW_NEW_ATTRIBUTE_SYNTAX) {
182 f |= LangFlags::ALLOW_NEW_ATTRIBUTE_SYNTAX;
184 if self.contains(ParserFlags::ALLOW_UNSTABLE_FEATURES) {
185 f |= LangFlags::ALLOW_UNSTABLE_FEATURES;
187 if self.contains(ParserFlags::CONST_DEFAULT_FUNC_ARGS) {
188 f |= LangFlags::CONST_DEFAULT_FUNC_ARGS;
190 if self.contains(ParserFlags::CONST_STATIC_PROPS) {
191 f |= LangFlags::CONST_STATIC_PROPS;
193 if self.contains(ParserFlags::DISABLE_ARRAY) {
194 f |= LangFlags::DISABLE_ARRAY;
196 if self.contains(ParserFlags::DISABLE_ARRAY_TYPEHINT) {
197 f |= LangFlags::DISABLE_ARRAY_TYPEHINT;
199 if self.contains(ParserFlags::DISABLE_LVAL_AS_AN_EXPRESSION) {
200 f |= LangFlags::DISABLE_LVAL_AS_AN_EXPRESSION;
202 if self.contains(ParserFlags::DISALLOW_INST_METH) {
203 f |= LangFlags::DISALLOW_INST_METH;
205 if self.contains(ParserFlags::DISABLE_XHP_ELEMENT_MANGLING) {
206 f |= LangFlags::DISABLE_XHP_ELEMENT_MANGLING;
208 if self.contains(ParserFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS) {
209 f |= LangFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS;
211 if self.contains(ParserFlags::DISALLOW_FUNC_PTRS_IN_CONSTANTS) {
212 f |= LangFlags::DISALLOW_FUNC_PTRS_IN_CONSTANTS;
214 if self.contains(ParserFlags::ENABLE_ENUM_CLASSES) {
215 f |= LangFlags::ENABLE_ENUM_CLASSES;
217 if self.contains(ParserFlags::ENABLE_XHP_CLASS_MODIFIER) {
218 f |= LangFlags::ENABLE_XHP_CLASS_MODIFIER;
220 if self.contains(ParserFlags::ENABLE_CLASS_LEVEL_WHERE_CLAUSES) {
221 f |= LangFlags::ENABLE_CLASS_LEVEL_WHERE_CLAUSES;
227 impl<S: AsRef<str>> NativeEnv<S> {
228 pub fn to_options(native_env: &NativeEnv<S>) -> Options {
229 let hhbc_flags = native_env.hhbc_flags;
230 let config = [&native_env.aliased_namespaces, &native_env.include_roots];
231 let opts = Options::from_configs(&config, &[]).unwrap();
233 aliased_namespaces: opts.hhvm.aliased_namespaces,
234 include_roots: opts.hhvm.include_roots,
235 flags: hhbc_flags.to_hhvm_flags(),
236 emit_class_pointers: Arg::new(native_env.emit_class_pointers.to_string()),
237 hack_lang: HackLang {
238 flags: native_env.parser_flags.to_lang_flags(),
239 check_int_overflow: Arg::new(native_env.check_int_overflow.to_string()),
244 php7_flags: hhbc_flags.to_php7_flags(),
245 repo_flags: hhbc_flags.to_repo_flags(),
251 /// Compilation profile. All times are in seconds,
252 /// except when they are ignored and should not be reported,
253 /// such as in the case hhvm.log_extern_compiler_perf is false
254 /// (this avoids the need to read Options from OCaml, as
255 /// they can be simply returned as NaNs to signal that
256 /// they should _not_ be passed back as JSON to HHVM process)
257 #[derive(Debug, Default, ToOcamlRep)]
264 pub fn emit_fatal_unit<S: AsRef<str>>(
266 writer: &mut dyn std::io::Write,
268 ) -> anyhow::Result<()> {
269 let is_systemlib = env.flags.contains(EnvFlags::IS_SYSTEMLIB);
271 Options::from_configs(&env.config_jsons, &env.config_list).map_err(anyhow::Error::msg)?;
272 let alloc = bumpalo::Bump::new();
273 let emitter = Emitter::new(
276 env.flags.contains(EnvFlags::FOR_DEBUGGER_EVAL),
277 env.flags.contains(EnvFlags::ENABLE_DECL),
282 let prog = emit_unit::emit_fatal_unit(&alloc, FatalOp::Parse, &Pos::make_none(), err_msg);
283 let prog = prog.map_err(|e| anyhow!("Unhandled Emitter error: {}", e))?;
288 env.flags.contains(EnvFlags::DUMP_SYMBOL_REFS),
296 pub fn from_text<'arena, 'decl, S: AsRef<str>>(
297 alloc: &'arena bumpalo::Bump,
299 stack_limit: &StackLimit,
300 writer: &mut dyn std::io::Write,
301 source_text: SourceText<'_>,
302 native_env: Option<&NativeEnv<S>>,
303 decl_provider: &'decl dyn DeclProvider<'decl>,
304 ) -> anyhow::Result<Option<Profile>> {
305 let mut emitter = create_emitter(env, native_env, decl_provider, alloc)?;
306 let (unit, profile) = emit_unit_from_text(&mut emitter, env, stack_limit, source_text)?;
308 let (print_result, printing_t) = time(|| {
313 env.flags.contains(EnvFlags::DUMP_SYMBOL_REFS),
321 Ok(profile.map(|mut prof| {
322 prof.printing_t = printing_t;
327 fn rewrite_and_emit<'p, 'arena, 'decl, S: AsRef<str>>(
328 emitter: &mut Emitter<'arena, 'decl>,
330 namespace_env: RcOc<NamespaceEnv>,
331 ast: &'p mut ast::Program,
332 ) -> Result<HackCUnit<'arena>, Error> {
334 let result = rewrite(emitter, ast, RcOc::clone(&namespace_env)); // Modifies `ast` in place.
337 // Rewrite ok, now emit.
338 emit_unit_from_ast(emitter, env, namespace_env, ast)
340 Err(Error::IncludeTimeFatalException(op, pos, msg)) => {
341 emit_unit::emit_fatal_unit(emitter.alloc, op, &pos, msg)
347 fn rewrite<'p, 'arena, 'decl>(
348 emitter: &mut Emitter<'arena, 'decl>,
349 ast: &'p mut ast::Program,
350 namespace_env: RcOc<NamespaceEnv>,
351 ) -> Result<(), Error> {
352 rewrite_program(emitter, ast, namespace_env)
355 pub fn unit_from_text<'arena, 'decl, S: AsRef<str>>(
356 alloc: &'arena bumpalo::Bump,
358 stack_limit: &StackLimit,
359 source_text: SourceText<'_>,
360 native_env: Option<&NativeEnv<S>>,
361 decl_provider: &'decl dyn DeclProvider<'decl>,
362 ) -> anyhow::Result<(HackCUnit<'arena>, Option<Profile>)> {
363 let mut emitter = create_emitter(env, native_env, decl_provider, alloc)?;
364 emit_unit_from_text(&mut emitter, env, stack_limit, source_text)
367 pub fn unit_to_string<W: std::io::Write, S: AsRef<str>>(
369 native_env: Option<&NativeEnv<S>>,
371 program: &HackCUnit<'_>,
372 ) -> anyhow::Result<()> {
373 let alloc = bumpalo::Bump::new();
374 let emitter = create_emitter(env, native_env, &NoDeclProvider, &alloc)?;
375 let (print_result, _) = time(|| {
380 env.flags.contains(EnvFlags::DUMP_SYMBOL_REFS),
386 print_result.map_err(|e| anyhow!("{}", e))
389 fn emit_unit_from_ast<'p, 'arena, 'decl, S: AsRef<str>>(
390 emitter: &mut Emitter<'arena, 'decl>,
392 namespace: RcOc<NamespaceEnv>,
393 ast: &'p mut ast::Program,
394 ) -> Result<HackCUnit<'arena>, Error> {
395 let mut flags = FromAstFlags::empty();
396 if env.flags.contains(EnvFlags::IS_EVALED) {
397 flags |= FromAstFlags::IS_EVALED;
399 if env.flags.contains(EnvFlags::FOR_DEBUGGER_EVAL) {
400 flags |= FromAstFlags::FOR_DEBUGGER_EVAL;
402 if env.flags.contains(EnvFlags::IS_SYSTEMLIB) {
403 flags |= FromAstFlags::IS_SYSTEMLIB;
406 emit_unit(emitter, flags, namespace, ast)
409 fn emit_unit_from_text<'arena, 'decl, S: AsRef<str>>(
410 emitter: &mut Emitter<'arena, 'decl>,
412 stack_limit: &StackLimit,
413 source_text: SourceText<'_>,
414 ) -> anyhow::Result<(HackCUnit<'arena>, Option<Profile>)> {
415 let log_extern_compiler_perf = emitter.options().log_extern_compiler_perf();
417 let namespace_env = RcOc::new(NamespaceEnv::empty(
418 emitter.options().hhvm.aliased_namespaces_cloned().collect(),
419 true, /* is_codegen */
425 .contains(LangFlags::DISABLE_XHP_ELEMENT_MANGLING),
428 let (parse_result, parsing_t) = time(|| {
433 !env.flags.contains(EnvFlags::DISABLE_TOPLEVEL_ELABORATION),
434 RcOc::clone(&namespace_env),
435 env.flags.contains(EnvFlags::IS_SYSTEMLIB),
439 let (program, codegen_t) = match parse_result {
441 elaborate_namespaces_visitor::elaborate_program(RcOc::clone(&namespace_env), &mut ast);
442 time(move || rewrite_and_emit(emitter, env, namespace_env, &mut ast))
444 Err(ParseError(pos, msg, is_runtime_error)) => {
445 time(|| emit_fatal(emitter.alloc, is_runtime_error, &pos, msg))
448 let profile = if log_extern_compiler_perf {
458 Ok(prog) => Ok((prog, profile)),
459 Err(e) => Err(anyhow!("Unhandled Emitter error: {}", e)),
463 fn emit_fatal<'arena>(
464 alloc: &'arena bumpalo::Bump,
465 is_runtime_error: bool,
467 msg: impl AsRef<str> + 'arena,
468 ) -> Result<HackCUnit<'arena>, Error> {
469 let op = if is_runtime_error {
474 emit_unit::emit_fatal_unit(alloc, op, pos, msg)
477 fn create_emitter<'arena, 'decl, S: AsRef<str>>(
479 native_env: Option<&NativeEnv<S>>,
480 decl_provider: &'decl dyn DeclProvider<'decl>,
481 alloc: &'arena bumpalo::Bump,
482 ) -> anyhow::Result<Emitter<'arena, 'decl>> {
483 let opts = match native_env {
484 None => Options::from_configs(&env.config_jsons, &env.config_list)
485 .map_err(anyhow::Error::msg)?,
486 Some(native_env) => NativeEnv::to_options(native_env),
490 env.flags.contains(EnvFlags::IS_SYSTEMLIB),
491 env.flags.contains(EnvFlags::FOR_DEBUGGER_EVAL),
492 env.flags.contains(EnvFlags::ENABLE_DECL),
498 fn create_parser_options(opts: &Options) -> ParserOptions {
499 let hack_lang_flags = |flag| opts.hhvm.hack_lang.flags.contains(flag);
501 po_auto_namespace_map: opts.hhvm.aliased_namespaces_cloned().collect(),
503 po_disallow_silence: false,
504 po_disable_lval_as_an_expression: hack_lang_flags(LangFlags::DISABLE_LVAL_AS_AN_EXPRESSION),
505 po_enable_class_level_where_clauses: hack_lang_flags(
506 LangFlags::ENABLE_CLASS_LEVEL_WHERE_CLAUSES,
508 po_disable_legacy_soft_typehints: hack_lang_flags(LangFlags::DISABLE_LEGACY_SOFT_TYPEHINTS),
509 po_allow_new_attribute_syntax: hack_lang_flags(LangFlags::ALLOW_NEW_ATTRIBUTE_SYNTAX),
510 po_disable_legacy_attribute_syntax: hack_lang_flags(
511 LangFlags::DISABLE_LEGACY_ATTRIBUTE_SYNTAX,
513 po_const_default_func_args: hack_lang_flags(LangFlags::CONST_DEFAULT_FUNC_ARGS),
514 po_const_default_lambda_args: hack_lang_flags(LangFlags::CONST_DEFAULT_LAMBDA_ARGS),
515 tco_const_static_props: hack_lang_flags(LangFlags::CONST_STATIC_PROPS),
516 po_abstract_static_props: hack_lang_flags(LangFlags::ABSTRACT_STATIC_PROPS),
517 po_disallow_func_ptrs_in_constants: hack_lang_flags(
518 LangFlags::DISALLOW_FUNC_PTRS_IN_CONSTANTS,
520 po_enable_xhp_class_modifier: hack_lang_flags(LangFlags::ENABLE_XHP_CLASS_MODIFIER),
521 po_disable_xhp_element_mangling: hack_lang_flags(LangFlags::DISABLE_XHP_ELEMENT_MANGLING),
522 po_enable_enum_classes: hack_lang_flags(LangFlags::ENABLE_ENUM_CLASSES),
523 po_disable_array: hack_lang_flags(LangFlags::DISABLE_ARRAY),
524 po_disable_array_typehint: hack_lang_flags(LangFlags::DISABLE_ARRAY_TYPEHINT),
525 po_allow_unstable_features: hack_lang_flags(LangFlags::ALLOW_UNSTABLE_FEATURES),
526 po_disallow_fun_and_cls_meth_pseudo_funcs: hack_lang_flags(
527 LangFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS,
529 po_disallow_inst_meth: hack_lang_flags(LangFlags::DISALLOW_INST_METH),
534 #[derive(Error, Debug)]
536 pub(crate) struct ParseError(Pos, String, bool);
538 /// parse_file returns either error(Left) or ast(Right)
539 /// - Left((Position, message, is_runtime_error))
543 stack_limit: &StackLimit,
544 source_text: SourceText<'_>,
545 elaborate_namespaces: bool,
546 namespace_env: RcOc<NamespaceEnv>,
548 ) -> Result<ast::Program, ParseError> {
549 let aast_env = AastEnv {
552 // Ocaml's implementation
553 // let enable_uniform_variable_syntax o = o.option_php7_uvs in
555 // (not (Hhbc_options.enable_uniform_variable_syntax hhbc_options))
556 php5_compat_mode: !opts.php7_flags.contains(Php7Flags::UVS),
559 elaborate_namespaces,
560 parser_options: create_parser_options(opts),
564 let indexed_source_text = IndexedSourceText::new(source_text);
565 let ast_result = AastParser::from_text_with_namespace_env(
568 &indexed_source_text,
572 Err(AastError::Other(msg)) => Err(ParseError(Pos::make_none(), msg, false)),
573 Err(AastError::NotAHackFile()) => Err(ParseError(
575 "Not a Hack file".to_string(),
578 Err(AastError::ParserFatal(syntax_error, pos)) => {
579 Err(ParseError(pos, syntax_error.message.to_string(), false))
581 Ok(ast) => match ast {
582 AastResult { syntax_errors, .. } if !syntax_errors.is_empty() => {
583 let error = syntax_errors
585 .find(|e| e.error_type == ErrorType::RuntimeError)
586 .unwrap_or(&syntax_errors[0]);
587 let pos = indexed_source_text.relative_pos(error.start_offset, error.end_offset);
590 error.message.to_string(),
591 error.error_type == ErrorType::RuntimeError,
594 AastResult { lowpri_errors, .. } if !lowpri_errors.is_empty() => {
595 let (pos, msg) = lowpri_errors.into_iter().next().unwrap();
596 Err(ParseError(pos, msg, false))
604 let mut errors = errors.iter().filter(|e| {
605 scoured_comments.get_fixme(e.pos(), e.code()).is_none()
606 /* Ignore these errors to match legacy AST behavior */
608 /* Naming.MethodNeedsVisibility */
610 /* Naming.UnsupportedTraitUseAs */
613 if errors.next().is_some() {
614 Err(ParseError(Pos::make_none(), String::new(), false))
617 Ok(aast) => Ok(aast),
618 Err(msg) => Err(ParseError(Pos::make_none(), msg, false)),
626 fn time<T>(f: impl FnOnce() -> T) -> (T, f64) {
627 let (r, t) = profile_rust::time(f);
631 pub fn expr_to_string_lossy<S: AsRef<str>>(env: &Env<S>, expr: &ast::Expr) -> String {
633 Options::from_configs(&env.config_jsons, &env.config_list).expect("Malformed options");
635 let alloc = bumpalo::Bump::new();
636 let emitter = Emitter::new(
638 env.flags.contains(EnvFlags::IS_SYSTEMLIB),
639 env.flags.contains(EnvFlags::FOR_DEBUGGER_EVAL),
640 env.flags.contains(EnvFlags::ENABLE_DECL),
644 let ctx = Context::new(
647 env.flags.contains(EnvFlags::DUMP_SYMBOL_REFS),
650 bytecode_printer::expr_to_string_lossy(ctx, expr)