Check argument to 'unset' in reactive mode
[hiphop-php.git] / hphp / hack / src / hhbc / emit_inout_function.ml
blob5f14e499dc6c4bc610b626bff58f6c0661155994
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 *)
10 open Instruction_sequence
11 open Hh_core
12 open Hhbc_ast
14 module H = Hhbc_ast
15 module SU = Hhbc_string_utils
17 let is_last_param_variadic param_count params =
18 let last_p = List.nth_exn params (param_count - 1) in
19 Hhas_param.is_variadic last_p
21 let emit_body_instrs_inout params call_instrs =
22 let param_count = List.length params in
23 let param_instrs = gather @@
24 List.map params ~f:(fun p ->
25 if Hhas_param.is_inout p
26 then gather [
27 instr_cgetquietl (Local.Named (Hhas_param.name p));
28 instr_null;
29 instr_popl (Local.Named (Hhas_param.name p))
31 else instr_pushl (Local.Named (Hhas_param.name p))
32 ) in
33 let inout_params = List.filter_map params ~f:(fun p ->
34 if not @@ Hhas_param.is_inout p then None else
35 Some (instr_setl @@ Local.Named (Hhas_param.name p))) in
36 let msrv = Hhbc_options.use_msrv_for_inout !Hhbc_options.compiler_options in
37 let local = Local.get_unnamed_local () in
38 let has_variadic = is_last_param_variadic param_count params in
39 let num_inout = List.length inout_params in
40 let num_uninit = if msrv then num_inout else 0 in
41 gather [
42 gather @@ List.init num_uninit ~f:(fun _ -> instr_nulluninit);
43 call_instrs;
44 param_instrs;
45 begin match (msrv, has_variadic) with
46 | (false, false) -> instr (ICall (FCall param_count))
47 | (false, true) -> instr (ICall (FCallUnpack param_count))
48 | (true, false) -> instr (ICall (FCallM (param_count, num_inout + 1)))
49 | (true, true) -> instr (ICall (FCallUnpackM (param_count, num_inout + 1)))
50 end;
51 begin if msrv then empty else instr_unboxr_nop end;
52 Emit_inout_helpers.emit_list_set_for_inout_call local inout_params;
53 instr_retc
56 let emit_body_instrs_ref params call_instrs =
57 let param_count = List.length params in
58 let msrv = Hhbc_options.use_msrv_for_inout !Hhbc_options.compiler_options in
59 let param_instrs = gather @@
60 List.map params ~f:(fun p ->
61 let local = Local.Named (Hhas_param.name p) in
62 if Hhas_param.is_reference p then
63 instr_vgetl local
64 else
65 instr_pushl local
66 ) in
67 let param_get_instrs =
68 List.filter_map params ~f:(fun p ->
69 if Hhas_param.is_reference p
70 then Some (instr_cgetl (Local.Named (Hhas_param.name p))) else None) in
71 let fcall_instr =
72 if is_last_param_variadic param_count params
73 then instr_fcallunpack param_count
74 else instr_fcall param_count
76 gather [
77 call_instrs;
78 param_instrs;
79 fcall_instr;
80 instr_unboxr_nop;
81 gather param_get_instrs;
82 if msrv then
83 instr_retm (List.length param_get_instrs + 1)
84 else
85 gather [
86 instr_new_vec_array (List.length param_get_instrs + 1);
87 instr_retc;
91 let emit_body_instrs ~wrapper_type env pos params call_instrs =
92 let body =
93 match wrapper_type with
94 | Emit_inout_helpers.InoutWrapper ->
95 emit_body_instrs_inout params call_instrs
96 | Emit_inout_helpers.RefWrapper ->
97 emit_body_instrs_ref params call_instrs
99 let begin_label, default_value_setters =
100 Emit_param.emit_param_default_value_setter env pos params in
101 gather [
102 begin_label;
103 Emit_pos.emit_pos pos;
104 body;
105 default_value_setters;
108 (* Construct the wrapper function body *)
109 let make_wrapper_body env doc decl_vars return_type params instrs =
110 let params = List.map params ~f:Hhas_param.without_type in
111 let return_user_type = Hhas_type_info.user_type return_type in
112 let return_tc = Hhas_type_constraint.make None [] in
113 let return_type = Hhas_type_info.make return_user_type return_tc in
114 Emit_body.make_body
115 instrs
116 decl_vars
117 false (* is_memoize_wrapper *)
118 params
119 (Some return_type)
120 [] (* static_inits: this is intentionally empty *)
122 (Some env)
124 let emit_wrapper_function
125 ~decl_vars ~is_top ~wrapper_type ~original_id ~renamed_id ast_fun =
126 (* Wrapper methods may not have iterators *)
127 Iterator.reset_iterator ();
128 let scope = [Ast_scope.ScopeItem.Function ast_fun] in
129 let namespace = ast_fun.Ast.f_namespace in
130 let env = Emit_env.(
131 empty |> with_namespace namespace |> with_scope scope
132 ) in
133 let tparams =
134 List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _) -> s) in
135 let params = Emit_param.from_asts ~namespace ~tparams ~generate_defaults:true
136 ~scope ast_fun.Ast.f_params in
137 let function_attributes =
138 Emit_attribute.from_asts namespace ast_fun.Ast.f_user_attributes
139 |> Core_list.filter ~f:(fun attr -> not (Hhas_attribute.is_native attr)) in
140 let scope = [Ast_scope.ScopeItem.Function ast_fun] in
141 let return_type_info =
142 Emit_body.emit_return_type_info
143 ~scope ~skipawaitable:false ~namespace ast_fun.Ast.f_ret in
144 let param_count = List.length params in
145 let modified_params, name, call_instrs =
146 if wrapper_type = Emit_inout_helpers.InoutWrapper then
147 List.map ~f:Hhas_param.switch_inout_to_reference params,
148 original_id,
149 instr_fpushfuncd param_count renamed_id
150 else
151 List.map ~f:Hhas_param.switch_reference_to_inout params,
152 renamed_id,
153 instr_fpushfuncd param_count original_id
155 Local.reset_local @@ List.length decl_vars + List.length params;
156 let body_instrs =
157 emit_body_instrs ~wrapper_type env ast_fun.Ast.f_span params call_instrs in
158 let fault_instrs = extract_fault_funclets body_instrs in
159 let body_instrs = gather [body_instrs; fault_instrs] in
160 let doc = ast_fun.A.f_doc_comment in
161 let body =
162 make_wrapper_body env doc decl_vars return_type_info modified_params body_instrs
164 let return_by_ref = ast_fun.Ast.f_ret_by_ref in
165 let is_interceptable =
166 Interceptable.is_function_interceptable namespace ast_fun function_attributes in
167 Hhas_function.make
168 function_attributes
169 name
170 body
171 (Hhas_pos.pos_to_span ast_fun.Ast.f_span)
172 false (* is_async *)
173 false (* is_generator *)
174 false (* is_pair_generator *)
175 is_top
176 true (* no_injection *)
177 true (* inout_wrapper *)
178 return_by_ref
179 is_interceptable
180 false (* is_memoize_impl *)
182 let emit_wrapper_method
183 ~is_closure ~decl_vars ~original_id ~renamed_id ast_class ast_method =
184 (* Wrapper methods may not have iterators *)
185 Iterator.reset_iterator ();
186 let decl_vars =
187 if is_closure then List.filter decl_vars ((<>) "$0Closure") else decl_vars in
188 let scope =
189 [Ast_scope.ScopeItem.Method ast_method;
190 Ast_scope.ScopeItem.Class ast_class] in
191 let namespace = ast_class.Ast.c_namespace in
192 let env = Emit_env.(
193 empty |> with_namespace namespace |> with_scope scope
194 ) in
195 let tparams =
196 List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _) -> s) in
197 let params = Emit_param.from_asts ~namespace ~tparams ~generate_defaults:true
198 ~scope ast_method.Ast.m_params in
199 let has_ref_params = List.exists params ~f:Hhas_param.is_reference in
200 let method_is_abstract =
201 List.mem ast_method.Ast.m_kind Ast.Abstract ||
202 ast_class.Ast.c_kind = Ast.Cinterface in
203 let method_is_final = List.mem ast_method.Ast.m_kind Ast.Final in
204 let method_is_static = not is_closure &&
205 List.mem ast_method.Ast.m_kind Ast.Static in
206 let method_attributes = Emit_attribute.from_asts
207 (Emit_env.get_namespace env) ast_method.Ast.m_user_attributes in
208 let method_is_private =
209 List.mem ast_method.Ast.m_kind Ast.Private in
210 let method_is_protected =
211 List.mem ast_method.Ast.m_kind Ast.Protected in
212 let method_is_public =
213 List.mem ast_method.Ast.m_kind Ast.Public ||
214 (not method_is_private && not method_is_protected) in
215 let is_in_trait = ast_class.Ast.c_kind = Ast.Ctrait in
216 let return_type_info =
217 Emit_body.emit_return_type_info
218 ~scope ~skipawaitable:false ~namespace ast_method.Ast.m_ret in
219 let method_is_return_by_ref = ast_method.Ast.m_ret_by_ref in
220 let param_count = List.length params in
221 let class_name =
222 Hhbc_id.Class.from_ast_name @@ SU.Xhp.mangle @@ snd ast_class.A.c_name in
223 let wrapper_type, original_id, renamed_id, params =
224 if is_closure || has_ref_params then
225 Emit_inout_helpers.RefWrapper, renamed_id, original_id,
226 List.map ~f:Hhas_param.switch_inout_to_reference params
227 else
228 Emit_inout_helpers.InoutWrapper, original_id, renamed_id, params
230 let call_instrs =
231 if is_closure then
232 gather [
233 instr_this;
234 instr_fpushfunc param_count []
236 else if method_is_static then
237 if is_in_trait then
238 instr_fpushclsmethodsd param_count H.SpecialClsRef.Self renamed_id
239 else
240 instr_fpushclsmethodd param_count renamed_id class_name
241 else
242 gather [
243 instr_this;
244 instr_fpushobjmethodd param_count renamed_id Ast.OG_nullsafe
247 Local.reset_local @@ List.length decl_vars + List.length params;
248 let body_instrs =
249 emit_body_instrs ~wrapper_type env ast_method.Ast.m_span params call_instrs in
250 let fault_instrs = extract_fault_funclets body_instrs in
251 let body_instrs = gather [body_instrs; fault_instrs] in
252 let params =
253 if wrapper_type = Emit_inout_helpers.InoutWrapper then
254 List.map ~f:Hhas_param.switch_inout_to_reference params
255 else
256 List.map ~f:Hhas_param.switch_reference_to_inout params
258 let doc = ast_method.A.m_doc_comment in
259 let body =
260 make_wrapper_body env doc decl_vars return_type_info params body_instrs
262 let method_is_interceptable =
263 Interceptable.is_method_interceptable namespace ast_class original_id method_attributes in
264 Hhas_method.make
265 method_attributes
266 method_is_protected
267 method_is_public
268 method_is_private
269 method_is_static
270 method_is_final
271 method_is_abstract
272 true (*method_no_injection*)
273 true (*method_inout_wrapper*)
274 original_id
275 body
276 (Hhas_pos.pos_to_span ast_method.Ast.m_span)
277 false (*method_is_async*)
278 false (*method_is_generator*)
279 false (*method_is_pair_generator*)
280 false (*method_is_closure_body*)
281 method_is_return_by_ref
282 method_is_interceptable
283 false (*method_is_memoize_impl*)