Check argument to 'unset' in reactive mode
[hiphop-php.git] / hphp / hack / src / hhbc / emit_body.ml
blob03aa3f41bac2918c074564a390b87fa8f5e55f12
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
9 open Hh_core
10 open Hhbc_ast
11 open Instruction_sequence
12 module SU = Hhbc_string_utils
14 let has_type_constraint ti =
15 match ti with
16 | Some ti when (Hhas_type_info.has_type_constraint ti) -> true
17 | _ -> false
19 let emit_method_prolog ~pos ~params ~should_emit_init_this =
20 let instr_list =
21 (if should_emit_init_this
22 then instr (IMisc (InitThisLoc (Local.Named "$this")))
23 else empty)
25 List.filter_map params (fun p ->
26 if Hhas_param.is_variadic p
27 then None else
28 let param_type_info = Hhas_param.type_info p in
29 let param_name = Hhas_param.name p in
30 if has_type_constraint param_type_info
31 then Some (instr (IMisc (VerifyParamType (Param_named param_name))))
32 else None) in
33 if List.is_empty instr_list
34 then empty
35 else gather (Emit_pos.emit_pos pos :: instr_list)
38 let tparams_to_strings tparams =
39 List.map tparams (fun (_, (_, s), _) -> s)
41 let rec emit_def env def =
42 match def with
43 | Ast.Stmt s -> Emit_statement.emit_stmt env s
44 | Ast.Constant c ->
45 let cns_name = snd c.Ast.cst_name in
46 if c.Ast.cst_kind = Ast.Cst_define &&
47 not (Namespace_env.is_global_namespace c.Ast.cst_namespace)
48 then
49 (* replace 'define' in namespace with call to 'define' function *)
50 let env =
51 Emit_env.with_namespace c.Ast.cst_namespace env in
52 let define_call =
53 let p0 = Pos.none in
54 let p_name = fst c.Ast.cst_name in
55 let args = [
56 p_name, Ast.String (SU.strip_global_ns cns_name);
57 c.Ast.cst_value] in
58 p0, Ast.Expr(p0, Ast.Call ((p0, Ast.Id (p0, "define")), [], args, [])) in
59 Emit_statement.emit_stmt env define_call
60 else
61 let cns_id =
62 if c.Ast.cst_kind = Ast.Cst_define
63 then
64 (* names of constants declared using 'define function' are always
65 prefixed with '\\', see 'def' function in 'namespaces.ml' *)
66 Hhbc_id.Const.from_raw_string (SU.strip_global_ns cns_name)
67 else Hhbc_id.Const.from_ast_name cns_name in
68 gather [
69 Emit_expression.emit_expr ~need_ref:false env c.Ast.cst_value;
70 Emit_pos.emit_pos_then c.Ast.cst_span
71 @@ instr (IIncludeEvalDefine (DefCns cns_id));
72 instr_popc;
74 (* We assume that SetNamespaceEnv does namespace setting *)
75 | Ast.Namespace(_, defs) ->
76 emit_defs env defs
77 | _ ->
78 empty
80 and check_namespace_update env ns =
81 let p = Pos.none in
82 let prev_ns = Emit_env.get_namespace env in
83 let check_map2 prev curr =
84 (* Check whether any definition updates an earlier definition *)
85 SMap.iter (fun key value ->
86 match SMap.get key prev with
87 | None -> () (* new element, it is fine *)
88 | Some v ->
89 if v = value then () (* same element, it is fine *)
90 else Emit_fatal.raise_fatal_parse p @@ Printf.sprintf
91 "Cannot use namespace %s as %s because the name is already in use"
92 (SU.strip_global_ns value) key
93 ) curr
95 check_map2 prev_ns.Namespace_env.ns_ns_uses ns.Namespace_env.ns_ns_uses;
96 check_map2 prev_ns.Namespace_env.ns_class_uses ns.Namespace_env.ns_class_uses;
97 check_map2 prev_ns.Namespace_env.ns_fun_uses ns.Namespace_env.ns_fun_uses;
98 check_map2 prev_ns.Namespace_env.ns_const_uses ns.Namespace_env.ns_const_uses
100 and emit_defs env defs =
101 let rec aux env defs =
102 match defs with
103 | Ast.SetNamespaceEnv ns :: defs ->
104 check_namespace_update env ns;
105 let env = Emit_env.with_namespace ns env in
106 aux env defs
107 | [] -> Emit_statement.emit_dropthrough_return env
108 (* emit last statement in the list as final statement *)
109 | [Ast.Stmt s]
110 (* emit statement as final if it is one before the last and last statement is
111 empty markup (which will be no-op) *)
112 | [Ast.Stmt s; Ast.Stmt (_, Ast.Markup ((_, ""), None))] ->
113 Emit_statement.emit_final_statement env s
114 | [d] ->
115 gather [emit_def env d; Emit_statement.emit_dropthrough_return env]
116 | d::defs ->
117 let i1 = emit_def env d in
118 let i2 = aux env defs in
119 gather [i1; i2]
121 let rec emit_markup env defs =
122 match defs with
123 | Ast.Stmt (_, Ast.Markup ((_, s), echo_expr_opt))::defs ->
124 let i1 =
125 Emit_statement.emit_markup env s echo_expr_opt ~check_for_hashbang:true
127 let i2 = aux env defs in
128 gather [i1; i2]
129 | [_] | [] -> aux env defs
130 | d :: defs ->
131 let i1 = emit_def env d in
132 if is_empty i1 then emit_markup env defs
133 else gather [i1; aux env defs]
135 emit_markup env defs
137 let check_redefinition v1 v2 text =
138 if v1 > 0 && v2 > 0
139 then Emit_fatal.raise_fatal_runtime Pos.none ("Multiple " ^ text ^ " directives")
140 else max v1 v2
142 let check_num_iters n1 n2 = check_redefinition n1 n2 ".numiters"
143 let check_num_cls_ref_slots n1 n2 = check_redefinition n1 n2 ".numclsrefslots"
144 let deduplicate l =
146 |> List.fold_left ~init:Unique_list_string.empty ~f:Unique_list_string.add
147 |> Unique_list_string.items
149 let make_body_ function_directives_opt body_instrs decl_vars is_memoize_wrapper
150 params return_type_info static_inits doc_comment
151 env =
152 let body_instrs = rewrite_user_labels body_instrs in
153 let body_instrs = rewrite_class_refs body_instrs in
154 let params, body_instrs =
155 if Hhbc_options.relabel !Hhbc_options.compiler_options
156 then Label_rewriter.relabel_function params body_instrs
157 else params, body_instrs in
158 let num_iters =
159 if is_memoize_wrapper then 0 else !Iterator.num_iterators in
160 let num_cls_ref_slots = get_num_cls_ref_slots body_instrs in
161 let num_iters, num_cls_ref_slots, is_memoize_wrapper, static_inits =
162 match function_directives_opt with
163 | Some (iters_hhas, cls_ref_slots_hhas, is_memoize_wrapper_hhas, static_inits_hhas) ->
164 check_num_iters num_iters iters_hhas,
165 check_num_cls_ref_slots num_cls_ref_slots cls_ref_slots_hhas,
166 is_memoize_wrapper || is_memoize_wrapper_hhas,
167 deduplicate @@ Core_list.append static_inits static_inits_hhas
168 | None -> num_iters, num_cls_ref_slots, is_memoize_wrapper, static_inits in
169 Hhas_body.make
170 body_instrs
171 decl_vars
172 num_iters
173 num_cls_ref_slots
174 is_memoize_wrapper
175 params
176 return_type_info
177 static_inits
178 doc_comment
181 let make_body body_instrs decl_vars is_memoize_wrapper params
182 return_type_info static_inits doc_comment env =
183 make_body_ None body_instrs decl_vars is_memoize_wrapper params
184 return_type_info static_inits doc_comment env
186 let prepare_inline_hhas_blocks decl_vars params hhas_blocks =
187 assert (not @@ List.is_empty hhas_blocks);
188 let param_names =
189 List.fold params
190 ~init:SSet.empty
191 ~f:(fun acc v -> SSet.add (Hhas_param.name v) acc) in
192 let is_param v = SSet.mem v param_names in
193 let parse_hhas s =
194 let lexer = Lexing.from_string (s ^ "\n") in
195 try s, Hhas_parser.functionbodywithdirectives Hhas_lexer.read lexer
196 with Parsing.Parse_error ->
197 Emit_fatal.raise_fatal_parse Pos.none @@
198 "Error parsing inline hhas:\n" ^ s in
199 let hhas_blocks = List.map hhas_blocks ~f:parse_hhas in
200 let text_to_block =
201 List.fold_left hhas_blocks
202 ~init:SMap.empty
203 ~f:(fun acc (k, v) -> SMap.add k v acc) in
204 let combine (used_vars, num_iters, num_cls_ref_slots, is_memoize, static_init) (_, b) =
205 let can_use v = not @@ is_param v in
206 let locals =
207 Instruction_sequence.collect_locals can_use (Hhas_asm.instrs b) in
208 Core_list.append used_vars (Unique_list_string.items locals),
209 check_num_iters num_iters (Hhas_asm.num_iters b),
210 check_num_cls_ref_slots num_cls_ref_slots (Hhas_asm.num_cls_ref_slots b),
211 is_memoize || (Hhas_asm.is_memoize_wrapper b),
212 Core_list.append static_init (Hhas_asm.static_inits b) in
213 let decl_vars, num_iters, num_cls_ref_slots, is_memoize, static_init =
214 List.fold_left hhas_blocks ~init:(decl_vars, 0, 0, false, []) ~f:combine in
215 deduplicate decl_vars,
216 text_to_block,
217 Some (num_iters, num_cls_ref_slots, is_memoize, deduplicate static_init)
219 let emit_return_type_info ~scope ~skipawaitable ~namespace ret =
220 let tparams =
221 List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _) -> s) in
222 match ret with
223 | None ->
224 Hhas_type_info.make (Some "") (Hhas_type_constraint.make None [])
225 | Some h ->
226 Emit_type_hint.(hint_to_type_info
227 ~kind:Return ~nullable:false ~skipawaitable ~tparams ~namespace h)
229 let emit_deprecation_warning scope = function
230 | None -> empty
231 | Some args ->
232 (* Drop the indexes *)
233 let args = List.filteri ~f:(fun i _ -> i mod 2 <> 0) args in
234 let strip_id id = SU.strip_global_ns (snd id) in
235 let class_name, trait_instrs, concat_instruction =
236 match Ast_scope.Scope.get_class scope with
237 | None -> "", empty, empty
238 | Some c when c.Ast.c_kind = Ast.Ctrait ->
239 "::", gather [instr_self; instr_clsrefname;], instr_concat
240 | Some c -> strip_id c.Ast.c_name ^ "::", empty, empty
242 let fn_name =
243 match scope with
244 | Ast_scope.ScopeItem.Function fd :: _ -> strip_id fd.Ast.f_name
245 | Ast_scope.ScopeItem.Method md :: _ -> strip_id md.Ast.m_name
246 | _ -> failwith "deprecated functions must have names"
248 let deprecation_string = class_name ^ fn_name ^ ": " ^
249 match args with
250 | [] -> "deprecated function"
251 | Typed_value.String s :: _ -> s
252 | _ -> failwith "deprecated attribute first argument is not a string"
254 let sampling_rate =
255 match args with
256 | [] | [_] -> Int64.one
257 | _ :: Typed_value.Int i :: _ -> i
258 | _ -> failwith "deprecated attribute second argument is not an integer"
260 let error_code = if Emit_env.is_systemlib () then
261 8192 (* E_DEPRECATED *) else 16384 (* E_USER_DEPRECATED *)
263 if Int64.to_int sampling_rate <= 0 then empty else
264 gather [
265 trait_instrs;
266 instr_string deprecation_string;
267 concat_instruction;
268 instr_int64 sampling_rate;
269 instr_int error_code;
270 instr_trigger_sampled_error;
271 instr_popr;
274 let rec is_awaitable h =
275 match h with
276 | _, A.Happly ((_, "Awaitable"), ([] | [_])) -> true
277 | _, (A.Hsoft h | A.Hoption h) -> is_awaitable h
278 | _ -> false
280 let is_mixed_or_dynamic t =
281 String_utils.string_ends_with t "HH\\mixed" ||
282 String_utils.string_ends_with t "HH\\dynamic"
284 let emit_verify_out params =
285 let msrv = Hhbc_options.use_msrv_for_inout !Hhbc_options.compiler_options in
286 let param_instrs = List.filter_mapi params ~f:(fun i p ->
287 if not @@ Hhas_param.is_inout p then None else
288 let b = match Hhas_param.type_info p with
289 | None -> false
290 | Some { Hhas_type_info.type_info_user_type = Some t; _ } ->
291 not @@ is_mixed_or_dynamic t
292 | _ -> true in
293 Some (
294 gather [
295 instr_cgetl (Local.Named (Hhas_param.name p));
296 if b then instr_verifyOutType (Param_unnamed i) else empty
298 )) in
299 let param_instrs = if msrv then List.rev param_instrs else param_instrs in
300 let len = List.length param_instrs in
301 if len = 0 then (0, empty) else (len, gather param_instrs)
303 let emit_body
304 ~pos
305 ~scope
306 ~is_closure_body
307 ~is_memoize
308 ~is_native
309 ~is_async
310 ~deprecation_info
311 ~skipawaitable
312 ~is_return_by_ref
313 ~default_dropthrough
314 ~return_value
315 ~namespace
316 ~doc_comment params ret body =
317 if is_return_by_ref && Hhbc_options.disable_return_by_reference !Hhbc_options.compiler_options
318 then begin
319 Emit_fatal.raise_fatal_runtime pos (
320 "Return by reference is disabled by the " ^
321 "compiler through the option hhvm.disable_return_by_reference " ^
322 "or Eval.DisableReturnByReference")
323 end;
324 if is_async && skipawaitable
325 then begin
326 let report_error =
327 not (Option.value_map ret ~default:true ~f:is_awaitable) in
328 if report_error
329 then begin
330 let message =
331 if is_closure_body
332 then "Return type hint for async closure must be awaitable"
333 else
334 let kind, name =
335 match scope with
336 | Ast_scope.ScopeItem.Function fd :: _ ->
337 "function", Utils.strip_ns (snd fd.A.f_name)
338 | Ast_scope.ScopeItem.Method md :: Ast_scope.ScopeItem.Class cd :: _ ->
339 "method", Utils.strip_ns (snd cd.A.c_name) ^ "::" ^ (snd md.A.m_name)
340 | _ -> assert false in
341 Printf.sprintf "Return type hint for async %s %s() must be awaitable"
342 kind name in
343 Emit_fatal.raise_fatal_runtime pos message
344 end;
345 end;
346 let tparams =
347 List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _) -> s) in
348 Label.reset_label ();
349 Iterator.reset_iterator ();
350 let return_type_info =
351 emit_return_type_info ~scope ~skipawaitable ~namespace ret in
352 let return_type_info = if not is_native then return_type_info else
353 Emit_type_hint.emit_type_constraint_for_native_function
354 tparams ret return_type_info
356 let is_generator, is_pair_generator = Generator.is_function_generator body in
357 let verify_return =
358 return_type_info.Hhas_type_info.type_info_user_type <> Some "" &&
359 Hhas_type_info.has_type_constraint return_type_info && not is_generator in
360 let default_dropthrough =
361 if default_dropthrough <> None then default_dropthrough else begin
362 if is_async && verify_return
363 then Some (gather [instr_null; instr_verifyRetTypeC; instr_retc])
364 else None
367 let params =
368 Emit_param.from_asts
369 ~namespace ~tparams ~generate_defaults:(not is_memoize) ~scope params
371 let params = if is_closure_body
372 then List.map ~f:Hhas_param.switch_inout_to_reference params else params in
373 let num_out, verify_out = if is_closure_body then 0, empty else emit_verify_out params in
374 Emit_statement.set_verify_return verify_return;
375 Emit_statement.set_verify_out verify_out;
376 Emit_statement.set_num_out num_out;
377 Emit_statement.set_default_dropthrough default_dropthrough;
378 Emit_statement.set_default_return_value return_value;
379 Emit_statement.set_return_by_ref is_return_by_ref;
380 Emit_statement.set_function_pos pos;
381 Jump_targets.reset ();
383 let remove_this vars =
384 List.filter vars (fun s -> s <> "$this") in
386 let move_this vars =
387 if List.mem vars "$this"
388 then remove_this vars @ ["$this"]
389 else vars in
391 let starts_with s prefix =
392 String.length s >= String.length prefix &&
393 String.sub s 0 (String.length prefix) = prefix in
395 let has_this = Ast_scope.Scope.has_this scope in
396 let is_toplevel = Ast_scope.Scope.is_toplevel scope in
397 (* see comment in decl_vars.ml, method on_efun of declvar_visitor
398 why Decl_vars needs 'explicit_use_set' *)
399 let explicit_use_set = Emit_env.get_explicit_use_set () in
400 let is_in_static_method = Ast_scope.Scope.is_in_static_method scope in
401 let needs_local_this, decl_vars =
402 Decl_vars.from_ast
403 ~is_closure_body
404 ~has_this
405 ~params
406 ~is_toplevel
407 ~is_in_static_method
408 ~explicit_use_set
409 body in
410 let decl_vars =
411 if is_closure_body
412 then
413 let ast_class =
414 match scope with
415 | _ :: _:: Ast_scope.ScopeItem.Class ast_class :: _ -> ast_class
416 | _ -> failwith "impossible, closure scope should be lambda->method->class" in
417 (* reorder decl_vars to match HHVM *)
418 let captured_vars =
419 ast_class.Ast.c_body
420 |> List.concat_map ~f:(fun item ->
421 match item with
422 | Ast.ClassVars { Ast.cv_names = cvl; _ } ->
423 List.filter_map cvl ~f:(fun (_, (_, id), _) ->
424 if not (starts_with id "86static_")
425 then Some ("$" ^ id) else None)
426 | _ -> []) in
427 "$0Closure" ::
428 captured_vars @
429 List.filter (move_this decl_vars) (fun v -> not (List.mem captured_vars v))
430 else
431 match scope with
432 | _ :: Ast_scope.ScopeItem.Class _ :: _ -> move_this decl_vars
433 | _ when Ast_scope.Scope.is_toplevel scope -> move_this decl_vars
434 | _ -> decl_vars in
436 let function_state_key =
437 let open Ast_scope in
438 match scope with
439 | [] -> Emit_env.get_unique_id_for_main ()
440 | ScopeItem.Method md :: ScopeItem.Class cls :: _
441 | ScopeItem.Lambda :: ScopeItem.Method md :: ScopeItem.Class cls :: _ ->
442 Emit_env.get_unique_id_for_method cls md
443 | ScopeItem.Function fd :: _ ->
444 Emit_env.get_unique_id_for_function fd
445 | _ -> failwith @@ "unexpected scope shape:" ^
446 "expected either " ^
447 "'ScopeItem.Method md :: ScopeItem.Class cls' for method or " ^
448 "'ScopeItem.Function fd' for function or " ^
449 "empty scope for top level" in
451 let inline_hhas_blocks =
452 Emit_env.get_inline_hhas_blocks function_state_key in
454 let decl_vars, inline_hhas_blocks, function_directives_opt =
455 if List.is_empty inline_hhas_blocks
456 then decl_vars, SMap.empty, None
457 else prepare_inline_hhas_blocks decl_vars params inline_hhas_blocks in
458 Emit_expression.set_inline_hhas_blocks inline_hhas_blocks;
460 let should_reserve_locals =
461 SSet.mem function_state_key @@ Emit_env.get_functions_with_finally () in
463 begin match SMap.get function_state_key @@ Emit_env.get_function_to_labels_map () with
464 | Some s ->
465 Jump_targets.set_function_has_goto true;
466 Jump_targets.set_labels_in_function s;
467 | None ->
468 Jump_targets.set_function_has_goto false;
469 Jump_targets.set_labels_in_function SMap.empty;
470 end;
472 Local.reset_local (List.length params + List.length decl_vars);
473 if should_reserve_locals then Local.reserve_retval_and_label_id_locals ();
475 let env = Emit_env.(
476 empty |>
477 with_namespace namespace |>
478 with_needs_local_this needs_local_this |>
479 with_scope scope) in
480 let stmt_instrs = if is_native then instr_nativeimpl else
481 Emit_env.do_function env body emit_defs in
482 let begin_label, default_value_setters =
483 Emit_param.emit_param_default_value_setter ~is_native env pos params in
484 let generator_instr =
485 if is_generator then gather [instr_createcont; instr_popc] else empty
487 let svar_map = Static_var.make_static_map body in
488 let emit_expr env e =
489 gather [
490 Emit_expression.emit_expr env ~need_ref:false e;
491 Emit_pos.emit_pos (fst e)
492 ] in
493 let stmt_instrs =
494 rewrite_static_instrseq svar_map emit_expr env stmt_instrs
496 let first_instruction_is_label =
497 match Instruction_sequence.first stmt_instrs with
498 | Some (ILabel _) -> true
499 | _ -> false
501 let should_emit_init_this = not is_in_static_method && (needs_local_this ||
502 (is_toplevel && List.exists ~f:(fun x -> x = "$this") decl_vars))
504 let header =
505 (* per comment in emitter.cpp there should be no
506 * jumps to the base of the function - inject EntryNop
507 * if first instruction in the statement list is label
508 * and header content is empty *)
509 let header_content = gather [
510 if is_native then empty else
511 emit_method_prolog ~pos ~params ~should_emit_init_this;
512 emit_deprecation_warning scope deprecation_info;
513 generator_instr;
514 ] in
515 if first_instruction_is_label &&
516 Instruction_sequence.is_empty header_content
517 then gather [
518 begin_label;
519 instr_entrynop;
521 else gather [
522 begin_label;
523 header_content;
524 ] in
525 let svar_instrs = SMap.ordered_keys svar_map in
526 let body_instrs = gather [
527 header;
528 stmt_instrs;
529 default_value_setters;
530 ] in
531 let fault_instrs = extract_fault_funclets body_instrs in
532 let body_instrs = gather [body_instrs; fault_instrs] in
533 make_body_
534 function_directives_opt
535 body_instrs
536 decl_vars
537 false (*is_memoize_wrapper*)
538 params
539 (Some return_type_info)
540 svar_instrs
541 doc_comment
542 (Some env),
543 is_generator,
544 is_pair_generator