2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
13 module SyntaxError
= Full_fidelity_syntax_error
14 module SourceText
= Full_fidelity_source_text
16 Full_fidelity_syntax_tree.WithSyntax
(Full_fidelity_positioned_syntax
)
17 module Logger
= HackcEventLogger
19 (*****************************************************************************)
20 (* Types, constants *)
21 (*****************************************************************************)
29 config_list
: string list
;
31 output_file
: string option;
32 config_file
: string option;
35 input_file_list
: string option;
36 dump_symbol_refs
: bool;
41 for_debugger_eval
: bool;
44 type message_handler
= Hh_json.json
-> string -> unit
46 type message_handlers
= {
47 set_config
: message_handler
;
48 compile
: message_handler
;
49 facts
: message_handler
;
50 parse
: message_handler
;
51 error
: message_handler
;
54 (*****************************************************************************)
56 (*****************************************************************************)
61 printing_t
: float ref;
64 let new_debug_time () =
65 { parsing_t
= ref 0.0; codegen_t
= ref 0.0; printing_t
= ref 0.0 }
67 (*****************************************************************************)
69 (*****************************************************************************)
75 let is_file_path_for_evaled_code s
=
76 let s = Relative_path.to_absolute
s in
77 String_utils.string_ends_with
s ") : eval()'d code"
79 let print_compiler_version () =
81 let compiler_version_msg =
85 ("type", JSON_String
"compiler_version");
86 ("version", JSON_String
(Compiler_id.get_compiler_id
()));
89 P.printf
"%s\n%!" compiler_version_msg)
91 let assert_regular_file filename
=
93 (not
(Sys.file_exists filename
))
94 || (Unix.stat filename
).Unix.st_kind
<> Unix.S_REG
96 raise
(Arg.Bad
(filename ^
" not a valid file"))
98 let parse_options () =
99 let fn_ref = ref None
in
100 let want_version = ref false in
101 let fallback = ref false in
102 let debug_time = ref false in
103 let config_list = ref [] in
104 let mode = ref CLI
in
105 let output_file = ref None
in
106 let config_file = ref None
in
107 let quiet_mode = ref false in
108 let input_file_list = ref None
in
109 let dump_symbol_refs = ref false in
110 let extract_facts = ref false in
111 let dump_stats = ref false in
112 let dump_config = ref false in
113 let log_stats = ref false in
114 let for_debugger_eval = ref false in
116 P.sprintf
"Usage: hh_single_compile (%s) filename\n" Sys.argv
.(0)
120 ("--version", Arg.Set
want_version, " print the version and do nothing");
121 ("--fallback", Arg.Set
fallback, " Enables fallback compilation");
124 " Enables debugging logging for elapsed time" );
127 " Runs very quietly, and ignore any result if invoked without -o "
128 ^
"(lower priority than the debug-time option)" );
129 ("--facts", Arg.Set
extract_facts, "Extract facts from the source code.");
131 Arg.String
(fun str
-> config_list := str
:: !config_list),
132 " Configuration: Server.Port=<value> "
134 ^
"\t\tAllows overriding config options passed on a file" );
138 assert_regular_file str
;
139 config_file := Some str
),
140 " Config file in JSON format" );
142 Arg.String
(fun str
-> output_file := Some str
),
143 " Output file. Creates it if necessary" );
145 Arg.Unit
(fun () -> mode := DAEMON
),
146 " Run a daemon which processes Hack source from standard input" );
147 ( "--input-file-list",
148 Arg.String
(fun str
-> input_file_list := Some str
),
149 " read a list of files (one per line) from the file `input-file-list'"
151 ( "--dump-symbol-refs",
152 Arg.Set
dump_symbol_refs,
153 " Dump symbol ref sections of HHAS" );
154 ("--dump-stats", Arg.Set
dump_stats, " Dump timing stats for functions");
155 ("--dump-config", Arg.Set
dump_config, " Dump configuration settings");
156 ( "--enable-logging-stats",
157 Arg.Unit
(fun () -> log_stats := true),
158 " Starts logging stats" );
159 ( "--stop-logging-stats",
160 Arg.Unit
(fun () -> log_stats := false),
161 " Stop logging stats" );
162 ( "--for-debugger-eval",
163 Arg.Unit
(fun () -> for_debugger_eval := true),
164 " Mutate the program as if we're in the debugger repl" );
167 let options = Arg.align ~limit
:25 options in
168 Arg.parse
options (fun fn
-> fn_ref := Some fn
) usage;
169 if !want_version then (
170 print_compiler_version ();
173 if !mode = DAEMON
then print_compiler_version ();
174 let needs_file = Option.is_none
!input_file_list in
193 fallback = !fallback;
194 config_list = !config_list;
195 debug_time = !debug_time;
196 output_file = !output_file;
197 config_file = !config_file;
198 quiet_mode = !quiet_mode;
200 input_file_list = !input_file_list;
201 dump_symbol_refs = !dump_symbol_refs;
202 dump_stats = !dump_stats;
203 dump_config = !dump_config;
204 log_stats = !log_stats;
205 extract_facts = !extract_facts;
206 for_debugger_eval = !for_debugger_eval;
209 let fail_daemon file error
=
211 let file = Option.value ~default
:"[unknown]" file in
216 ("type", JSON_String
"error");
217 ("file", JSON_String
file);
218 ("error", JSON_String error
);
221 P.printf
"%s\n%!" msg;
224 let rec dispatch_loop handlers
=
227 let read_message () =
228 let line = Caml.read_line
() in
229 let header = json_of_string
line in
230 let file = get_field_opt
(get_string
"file") header in
231 let bytes = get_field
(get_number_int
"bytes") (fun _af
-> 0) header in
232 let is_systemlib = get_field_opt
(get_bool
"is_systemlib") header in
233 Emit_env.set_is_systemlib
@@ Option.value ~default
:false is_systemlib;
234 let body = Bytes.create
bytes in
236 Caml.really_input
Caml.stdin
body 0 bytes;
241 ("Exception reading message body: " ^
Caml.Printexc.to_string exc
)
243 let (header, body) = read_message () in
248 fail_daemon None
("Cannot determine type of message: " ^ af
))
252 | "code" -> handlers
.compile
header body
253 | "error" -> handlers
.error
header body
254 | "config" -> handlers
.set_config
header body
255 | "facts" -> handlers
.facts
header body
256 | "parse" -> handlers
.parse
header body
257 | _
-> fail_daemon None
("Unhandled message type '" ^
msg_type ^
"'"));
258 dispatch_loop handlers
))
260 let set_stats_if_enabled ~compiler_options
=
261 if compiler_options
.dump_stats then
262 Stats_container.set_instance
(Some
(Stats_container.new_container
()))
264 let write_stats_if_enabled ~compiler_options
=
265 if compiler_options
.dump_stats then
266 match Stats_container.get_instance
() with
267 | Some
s -> Stats_container.write_out ~out
:Caml.stderr
s
270 let parse_text compiler_options popt
fn text
=
271 let () = set_stats_if_enabled ~compiler_options
in
273 not
(Hhbc_options.source_mapping
!Hhbc_options.compiler_options
)
275 let php5_compat_mode =
277 (Hhbc_options.enable_uniform_variable_syntax
278 !Hhbc_options.compiler_options
)
280 let hacksperimental =
281 Hhbc_options.hacksperimental !Hhbc_options.compiler_options
283 let lower_coroutines =
284 Hhbc_options.enable_coroutines
!Hhbc_options.compiler_options
287 Full_fidelity_ast.make_env
298 let source_text = SourceText.make
fn text
in
299 let { Full_fidelity_ast.ast
; Full_fidelity_ast.is_hh_file
; _
} =
300 Full_fidelity_ast.from_text
env source_text
302 let () = write_stats_if_enabled ~compiler_options
in
305 let parse_file compiler_options popt filename text
=
308 (Errors.do_
(fun () -> parse_text compiler_options popt filename text
))
310 (* FFP failed to parse *)
311 | Failure
s -> `ParseFailure
(SyntaxError.make
0 0 s, Pos.none
)
312 (* FFP generated an error *)
313 | SyntaxError.ParserFatal
(e
, p
) -> `ParseFailure
(e
, p
)
315 let add_to_time_ref r t0
=
316 let t = Unix.gettimeofday
() in
317 r
:= !r
+. (t -. t0
);
320 let print_debug_time_info filename
debug_time =
321 let stat = Caml.Gc.stat () in
322 P.eprintf
"File %s:\n" (Relative_path.to_absolute filename
);
323 P.eprintf
"Parsing: %0.3f s\n" !(debug_time.parsing_t
);
324 P.eprintf
"Codegen: %0.3f s\n" !(debug_time.codegen_t
);
325 P.eprintf
"Printing: %0.3f s\n" !(debug_time.printing_t
);
326 P.eprintf
"MinorWords: %0.3f\n" stat.Caml.Gc.minor_words
;
327 P.eprintf
"PromotedWords: %0.3f\n" stat.Caml.Gc.promoted_words
329 let mode_to_string = function
333 let log_success compiler_options filename
debug_time =
335 ~filename
:(Relative_path.to_absolute filename
)
336 ~parsing_t
:!(debug_time.parsing_t
)
337 ~codegen_t
:!(debug_time.codegen_t
)
338 ~printing_t
:!(debug_time.printing_t
)
339 ~
mode:(mode_to_string compiler_options
.mode)
341 let log_fail compiler_options filename exc ~stack
=
343 ~filename
:(Relative_path.to_absolute filename
)
344 ~
mode:(mode_to_string compiler_options
.mode)
345 ~exc
:(Caml.Printexc.to_string exc ^
"\n" ^ stack
)
347 (* Maps an Ast to a Tast where every type is Tany
348 * Used to produce a Tast for unsafe code without inferring types for it. *)
349 let ast_to_tast_tany =
350 let tany = (Typing_reason.Rnone
, Typing_defs.make_tany
()) in
351 let get_expr_annotation (p
: Ast_defs.pos
) = (p
, tany) in
352 Ast_to_aast.convert_program
359 * Converts a legacy ast (ast.ml) into a typed ast (tast.ml / aast.ml)
360 * so that codegen and typing both operate on the same ast structure.
361 * There are some errors that are not valid hack but are still expected
362 * to produce valid bytecode. These errors are caught during the conversion
363 * so as to match legacy behavior.
365 let convert_to_tast ast
=
368 let ast = ast_to_tast_tany ast in
369 if Hhbc_options.enable_pocket_universes
!Hhbc_options.compiler_options
371 Pocket_universes.translate
ast
377 let handle_error _path error acc
=
378 match Errors.get_code error
with
379 (* Ignore these errors to match legacy AST behavior *)
381 (* Naming.MethodNeedsVisibility *)
384 (* Naming.UnsupportedTraitUseAs *)
386 | 2103 (* Naming.UnsupportedInsteadOf *) ->
388 | _
(* Emit fatal parse otherwise *) ->
390 let msg = snd
(List.hd_exn
(Errors.to_list error
)) in
391 Some
(Errors.get_pos error
, msg)
395 let result = Errors.fold_errors ~init
:None ~f
:handle_error errors
in
397 | Some error
-> Error error
400 let do_compile filename compiler_options popt fail_or_ast
debug_time =
401 let t = Unix.gettimeofday
() in
402 let t = add_to_time_ref debug_time.parsing_t
t in
404 match fail_or_ast
with
405 | `ParseFailure
(e
, pos
) ->
407 match SyntaxError.error_type e
with
408 | SyntaxError.ParseError
-> Hhbc_ast.FatalOp.Parse
409 | SyntaxError.RuntimeError
-> Hhbc_ast.FatalOp.Runtime
411 let s = SyntaxError.message e
in
412 Emit_program.emit_fatal_program ~ignore_message
:false error_t pos
s
413 | `ParseResult
(errors
, (ast, is_hh_file
)) ->
414 List.iter
(Errors.get_error_list errors
) (fun e
->
415 P.eprintf
"%s\n%!" (Errors.to_string
(Errors.to_absolute e
)));
416 if Errors.is_empty errors
then
417 match convert_to_tast ast with
419 Emit_program.from_ast
421 ~is_evaled
:(is_file_path_for_evaled_code filename
)
422 ~
for_debugger_eval:compiler_options
.for_debugger_eval
425 | Error
(pos
, msg) ->
426 Emit_program.emit_fatal_program
427 ~ignore_message
:false
428 Hhbc_ast.FatalOp.Parse
432 Emit_program.emit_fatal_program
434 Hhbc_ast.FatalOp.Parse
438 let t = add_to_time_ref debug_time.codegen_t
t in
440 Hhbc_hhas.to_segments
442 ~
dump_symbol_refs:compiler_options
.dump_symbol_refs
445 ignore
@@ add_to_time_ref debug_time.printing_t
t;
446 if compiler_options
.debug_time then print_debug_time_info filename
debug_time;
447 if compiler_options
.log_stats then
448 log_success compiler_options filename
debug_time;
451 let extract_facts ~filename text
=
454 let co = !compiler_options
in
455 Facts_parser.extract_as_json_string
456 ~
php5_compat_mode:true
457 ~hhvm_compat_mode
:true
458 ~disable_nontoplevel_declarations
:
459 (phpism_disable_nontoplevel_declarations
co)
460 ~disable_legacy_soft_typehints
:(disable_legacy_soft_typehints
co)
461 ~allow_new_attribute_syntax
:(allow_new_attribute_syntax
co)
462 ~disable_legacy_attribute_syntax
:(disable_legacy_attribute_syntax
co)
465 |> Option.value ~default
:"");
468 let parse_hh_file filename
body =
470 let co = !compiler_options
in
471 let file = Relative_path.create
Relative_path.Dummy filename
in
472 let source_text = SourceText.make
file body in
473 let mode = Full_fidelity_parser.parse_mode
source_text in
475 Full_fidelity_parser_env.make
477 ~
php5_compat_mode:true
478 ~hhvm_compat_mode
:true
479 ~disable_nontoplevel_declarations
:
480 (phpism_disable_nontoplevel_declarations
co)
481 ~disable_legacy_soft_typehints
:(disable_legacy_soft_typehints
co)
482 ~allow_new_attribute_syntax
:(allow_new_attribute_syntax
co)
483 ~disable_legacy_attribute_syntax
:(disable_legacy_attribute_syntax
co)
487 let syntax_tree = SyntaxTree.make ~
env source_text in
488 let json = SyntaxTree.to_json
syntax_tree in
489 [Hh_json.json_to_string
json])
491 (*****************************************************************************)
492 (* Main entry point *)
493 (*****************************************************************************)
497 let co = !compiler_options
in
499 ~auto_namespace_map
:(aliased_namespaces
co)
501 ~disallow_execution_operator
:(phpism_disallow_execution_operator
co)
502 ~disable_nontoplevel_declarations
:
503 (phpism_disable_nontoplevel_declarations
co)
504 ~disable_static_closures
:(phpism_disable_static_closures
co)
505 ~disable_halt_compiler
:(phpism_disable_halt_compiler
co)
506 ~disable_lval_as_an_expression
:(disable_lval_as_an_expression
co)
507 ~enable_class_level_where_clauses
:(enable_class_level_where_clauses
co)
508 ~disable_legacy_soft_typehints
:(disable_legacy_soft_typehints
co)
509 ~allow_new_attribute_syntax
:(allow_new_attribute_syntax
co)
510 ~disable_legacy_attribute_syntax
:(disable_legacy_attribute_syntax
co)
511 ~const_default_func_args
:(const_default_func_args
co)
512 ~disallow_silence
:false
513 ~const_static_props
:(const_static_props
co)
514 ~abstract_static_props
:(abstract_static_props
co)
515 ~disable_unset_class_const
:(disable_unset_class_const
co)
516 ~disallow_func_ptrs_in_constants
:(disallow_func_ptrs_in_constants
co))
518 let process_single_source_unit
519 compiler_options handle_output handle_exception filename
source_text =
521 let popt = make_popt () in
522 let debug_time = new_debug_time () in
523 let t = Unix.gettimeofday
() in
525 if compiler_options
.extract_facts then
526 extract_facts ~filename
source_text
529 parse_file compiler_options
popt filename
source_text
531 ignore
@@ add_to_time_ref debug_time.parsing_t
t;
532 do_compile filename compiler_options
popt fail_or_ast debug_time
534 handle_output filename
output debug_time
536 let stack = Caml.Printexc.get_backtrace
() in
537 if compiler_options
.log_stats then
538 log_fail compiler_options filename exc ~
stack;
539 handle_exception filename exc
541 let decl_and_run_mode compiler_options
=
544 let print_and_flush_strings strings
=
545 List.iter ~f
:(P.printf
"%s") strings
;
548 let set_compiler_options config_json
=
550 Hhbc_options.get_options_from_config
552 ~init
:!Hhbc_options.compiler_options
553 ~
config_list:compiler_options
.config_list
555 Hhbc_options.set_compiler_options options
557 let ini_config_json =
558 Option.map ~f
:json_of_file compiler_options
.config_file
560 set_compiler_options ini_config_json;
562 lazy (Hhbc_options.to_string
!Hhbc_options.compiler_options
)
564 Ident.track_names
:= true;
566 match compiler_options
.mode with
568 let handle_output filename
output debug_time =
569 let abs_path = Relative_path.to_absolute filename
in
571 List.fold ~f
:(fun len
s -> len
+ String.length
s) ~init
:0 output
575 ("type", JSON_String
"success");
576 ("file", JSON_String
abs_path);
577 ("bytes", int_
bytes);
582 Hhbc_options.log_extern_compiler_perf
583 !Hhbc_options.compiler_options
585 let json_microsec t = int_
@@ int_of_float
@@ (t *. 1000000.0) in
586 ("parsing_time", json_microsec !(debug_time.parsing_t
))
587 :: ("codegen_time", json_microsec !(debug_time.codegen_t
))
588 :: ("printing_time", json_microsec !(debug_time.printing_t
))
593 P.printf
"%s\n" (json_to_string
@@ JSON_Object
msg);
594 print_and_flush_strings output
596 let handle_exception filename exc
=
597 let abs_path = Relative_path.to_absolute filename
in
602 ("type", JSON_String
"error");
603 ("file", JSON_String
abs_path);
604 ("error", JSON_String
(Caml.Printexc.to_string exc
));
607 P.printf
"%s\n%!" msg
617 Some
(json_of_string
body)
619 set_compiler_options config_json);
628 ("Cannot determine file name of source unit: " ^ af
))
637 "No 'error' field in error message")
642 ("Error processing " ^
filename ^
": " ^
error));
651 ("Cannot determine file name of source unit: " ^ af
))
654 let for_debugger_eval =
656 (get_bool
"for_debugger_eval")
658 fail_daemon None
("for_debugger_eval flag missing: " ^ af
))
661 let old_config = !Hhbc_options.compiler_options
in
662 let config_overrides =
664 (get_obj
"config_overrides")
665 (fun _af
-> JSON_Object
[])
668 set_compiler_options (Some
config_overrides);
669 let compiler_options =
670 { compiler_options with for_debugger_eval }
673 process_single_source_unit
677 (Relative_path.create
Relative_path.Dummy
filename)
680 Hhbc_options.set_compiler_options old_config;
684 (* if body is empty - read file from disk *)
691 ("Cannot determine file name of source unit: " ^ af
))
695 if String.length
body = 0 then
696 Sys_utils.cat
filename
701 Relative_path.create
Relative_path.Dummy
filename
703 let old_config = !Hhbc_options.compiler_options in
704 let config_overrides =
706 (get_obj
"config_overrides")
707 (fun _af
-> JSON_Object
[])
710 set_compiler_options (Some
config_overrides);
712 handle_output path (extract_facts ~
filename:path body)
714 Hhbc_options.set_compiler_options old_config;
716 (new_debug_time ()));
725 ("Cannot determine file name of source unit: " ^ af
))
729 if String.length
body = 0 then
730 Sys_utils.cat
filename
734 let old_config = !Hhbc_options.compiler_options in
735 let config_overrides =
737 (get_obj
"config_overrides")
738 (fun _af
-> JSON_Object
[])
741 set_compiler_options (Some
config_overrides);
744 (Relative_path.create
Relative_path.Dummy
filename)
745 (parse_hh_file filename body)
747 Hhbc_options.set_compiler_options old_config;
749 (new_debug_time ()));
752 dispatch_loop handlers
754 let handle_exception filename exc
=
755 if not
compiler_options.quiet_mode then (
756 let stack = Caml.Printexc.get_backtrace
() in
759 "Error in file %s: %s\n"
760 (Relative_path.to_absolute
filename)
761 (Caml.Printexc.to_string exc
)
764 let process_single_file handle_output filename =
765 let filename = Relative_path.create
Relative_path.Dummy
filename in
766 let abs_path = Relative_path.to_absolute
filename in
767 process_single_source_unit
774 let (filenames
, handle_output) =
775 match compiler_options.input_file_list with
776 (* List of source files explicitly given *)
777 | Some
input_file_list ->
778 let get_lines_in_file filename =
779 let inch = Caml.open_in
filename in
781 match Caml.input_line
inch with
782 | line -> go (Caml.String.trim
line :: lines
)
783 | exception End_of_file
-> lines
787 let handle_output _filename
output _debug_time
=
788 if compiler_options.dump_config then
790 "===CONFIG===\n%s\n\n%!"
791 (Lazy.force
dumped_options);
792 if not
compiler_options.quiet_mode then
793 print_and_flush_strings output
795 (get_lines_in_file input_file_list, handle_output)
798 Sys.is_directory
compiler_options.filename
799 (* Compile every file under directory *)
808 |> Array.map ~f
:(Filename.concat dir
)
810 |> List.partition_tf ~f
:Sys.is_directory
814 go [compiler_options.filename]
816 let handle_output filename output _debug_time
=
817 let abs_path = Relative_path.to_absolute
filename in
818 if Filename.check_suffix
abs_path ".php" then
820 Filename.chop_suffix
abs_path ".php" ^
".hhas"
822 if Sys.file_exists
output_file then (
823 if not
compiler_options.quiet_mode then
826 "Output file %s already exists\n"
829 Sys_utils.write_strings_to_file ~
file:output_file output
831 (files_in_dir, handle_output)
832 (* Compile a single file *)
834 let handle_output _filename
output _debug_time
=
835 match compiler_options.output_file with
836 | Some
output_file ->
837 Sys_utils.write_strings_to_file ~
file:output_file output
839 if compiler_options.dump_config then
841 "===CONFIG===\n%s\n\n%!"
842 (Lazy.force
dumped_options);
843 if not
compiler_options.quiet_mode then
844 print_and_flush_strings output
846 ([compiler_options.filename], handle_output)
847 (* Actually execute the compilation(s) *)
849 List.iter filenames
(process_single_file handle_output)))
852 let start_time = Unix.gettimeofday
() in
853 if opts
.log_stats then Logger.init
start_time;
854 decl_and_run_mode opts
856 (* command line driver *)
858 Printexc.record_backtrace
true;
860 if !Sys.interactive
then
863 (* On windows, setting 'binary mode' avoids to output CRLF on
864 stdout. The 'text mode' would not hurt the user in general, but
865 it breaks the testsuite where the output is compared to the
866 expected one (i.e. in given file without CRLF). *)
867 Caml.set_binary_mode_out
Caml.stdout
true;
869 SharedMem.init ~num_workers
:0 GlobalConfig.empty_sharedmem_config
871 ignore
(handle : SharedMem.handle);
872 let options = parse_options () in
875 let stack = Caml.Printexc.get_backtrace
() in
877 die (Caml.Printexc.to_string exc
)