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.
14 let labels_in_function_: (bool SMap.t
) ref = ref SMap.empty
15 let function_has_goto_ = ref false
17 let get_labels_in_function () = !labels_in_function_
18 let get_function_has_goto () = !function_has_goto_
20 let set_labels_in_function s
= labels_in_function_ := s
21 let set_function_has_goto f
= function_has_goto_ := f
23 let rec collect_valid_target_labels_aux is_hh_file acc s
=
25 | A.Declare
(_
, _
, block
)
27 collect_valid_target_labels_for_block_aux is_hh_file acc block
28 (* can jump into the try block but not to finally *)
29 | A.Try
(block
, cl
, _
) ->
30 let acc = collect_valid_target_labels_for_block_aux is_hh_file
acc block
in
33 ~f
:(fun acc (_
, _
, block
) ->
34 collect_valid_target_labels_for_block_aux is_hh_file
acc block
)
35 | A.GotoLabel
(_
, s
) ->
37 | A.If
(_
, then_block
, else_block
) ->
38 let acc = collect_valid_target_labels_for_block_aux is_hh_file
acc then_block
in
39 collect_valid_target_labels_for_block_aux is_hh_file
acc else_block
56 | A.Def_inline _
-> acc
57 (* jump to while loops/switches/usings are disallowed in php files
58 and permitted in hh - assuming that they can only appear there
59 as a result of source to source transformation that has validated
60 correctness of the target *)
62 | A.Using
{ A.us_block
= block
; _
} ->
64 then collect_valid_target_labels_for_block_aux is_hh_file
acc block
68 then collect_valid_target_labels_for_switch_cases_aux is_hh_file
acc cl
71 and collect_valid_target_labels_for_block_aux is_hh_file
acc block
=
72 List.fold_left block ~init
:acc ~f
:(collect_valid_target_labels_aux is_hh_file
)
74 and collect_valid_target_labels_for_switch_cases_aux is_hh_file
acc cl
=
75 List.fold_left cl ~init
:acc ~f
:(fun acc s
->
78 | A.Case
(_
, block
) -> collect_valid_target_labels_for_block_aux is_hh_file
acc block
)
80 let rec collect_valid_target_labels_for_def_aux is_hh_file
acc def
=
82 | A.Stmt s
-> collect_valid_target_labels_aux is_hh_file
acc s
83 | A.Namespace
(_
, defs
) -> collect_valid_target_labels_for_defs_aux is_hh_file
acc defs
85 and collect_valid_target_labels_for_defs_aux is_hh_file
acc defs
=
86 List.fold_left defs ~init
:acc ~f
:(collect_valid_target_labels_for_def_aux is_hh_file
)
88 let collect_valid_target_labels_for_stmt is_hh_file s
=
89 if not
(!function_has_goto_) then SSet.empty
90 else collect_valid_target_labels_aux is_hh_file
SSet.empty s
92 let collect_valid_target_labels_for_defs is_hh_file defs
=
93 if not
(!function_has_goto_) then SSet.empty
94 else collect_valid_target_labels_for_defs_aux is_hh_file
SSet.empty defs
96 let collect_valid_target_labels_for_switch_cases is_hh_file cl
=
97 if not
(!function_has_goto_) then SSet.empty
98 else collect_valid_target_labels_for_switch_cases_aux is_hh_file
SSet.empty cl
100 type iterator
= (*is mutable*) bool * Iterator.t
103 { label_break
: Label.t
104 ; label_continue
: Label.t
105 ; iterator
: iterator
option
109 | Loop
of loop_labels
* SSet.t
110 | Switch
of (*end switch*) Label.t
* SSet.t
111 | TryFinally
of (*finally start*) Label.t
* SSet.t
114 | Using
of (*finally start*) Label.t
* SSet.t
120 type id_key
= IdReturn
| IdLabel
of Label.t
121 module LabelIdMap
: MyMap.S
with type key
= id_key
= MyMap.Make
(struct
123 let compare = Pervasives.compare
126 (* HHVM assigns ids to labels sequentially as break/continue to labels appear
127 in the source code. When emitter is done with scope that contains
128 break/continue - label ids associated with the scope are freed and can be reused.
129 One exception from the rule is return - it is treated as if it is jump to a label that
130 finishes the function so once id for return is assigned - it is never released.
131 Label ids are used in finally epilogue to determine how control flow got into
136 if ($a) { continue; }
137 if ($b) { return 100; }
144 will be emitted roughly as:
150 Int 0 ; push id of the target label "continue label for while loop"
151 SetL _1; save it in dedicated local
158 Int 100 ; push return value
159 Int 1; push id of the 'return' label
160 SetL _1; save id in decicated local
162 SetL _2; save return value in dedicated local
170 IssetL _1; if check if state local is set
173 Switch Unbounded 0 <Case0 Case1>
176 UnsetL _1; new iteration - clean local for state id
179 CGetL _2; load return value
185 let label_to_id = ref LabelIdMap.empty
188 match LabelIdMap.get k
!label_to_id with
192 if LabelIdMap.exists
(fun _ id
-> n
= id
) !label_to_id then aux (n
+ 1)
193 else (label_to_id := LabelIdMap.add k n
!label_to_id; n
)
197 label_to_id := LabelIdMap.empty
199 let get_id_for_return () = new_id IdReturn
200 let get_id_for_label l
= new_id (IdLabel l
)
203 label_to_id := LabelIdMap.remove
(IdLabel l
) !label_to_id
205 (* runs a given function and then released label ids that were possibly assigned
206 to labels at the head of the list *)
207 let run_and_release_ids _labels f s t
=
210 | Loop
({ label_break
; label_continue
; _
}, _
) ::_
->
211 release_id label_break
;
212 release_id label_continue
;
213 | (Switch
(l
, _
) | TryFinally
(l
, _
) | Using
(l
, _
)) :: _
->
215 | (Function _
| Finally _
) :: _
-> ()
216 | [] -> failwith
"impossible"
218 (* CONSIDER: now HHVM does not release state ids for named labels
219 even after leaving the scope where labels are accessible
220 Do the same for now for compatibility reasons *)
221 (* SSet.iter (fun l -> release_id (Label.Named l)) labels; *)
224 let with_loop is_hh_file label_break label_continue iterator t s f
=
225 let labels = collect_valid_target_labels_for_stmt is_hh_file s
in
226 Loop
({ label_break
; label_continue
; iterator
}, labels) :: t
227 |> run_and_release_ids labels f s
229 let with_switch is_hh_file end_label t cl f
=
230 let labels = collect_valid_target_labels_for_switch_cases is_hh_file cl
in
231 (* CONSIDER: now HHVM eagerly reserves state id for the switch end label
232 which does not seem to be necessary - do it for now for HHVM compatibility *)
233 let _ = get_id_for_label end_label
in
234 Switch
(end_label
, labels) :: t
235 |> run_and_release_ids labels f
()
237 let with_try is_hh_file finally_label t s f
=
238 let labels = collect_valid_target_labels_for_stmt is_hh_file s
in
239 TryFinally
(finally_label
, labels) :: t
240 |> run_and_release_ids labels f s
242 let with_finally is_hh_file t s f
=
243 let labels = collect_valid_target_labels_for_stmt is_hh_file s
in
245 |> run_and_release_ids labels f s
247 let with_function is_hh_file t s f
=
248 let labels = collect_valid_target_labels_for_defs is_hh_file s
in
250 |> run_and_release_ids labels f s
252 let with_using is_hh_file finally_label t s f
=
253 let labels = collect_valid_target_labels_for_stmt is_hh_file s
in
254 Using
(finally_label
, labels) :: t
255 |> run_and_release_ids labels f s
257 type resolved_try_finally
=
258 { target_label
: Label.t
259 ; finally_label
: Label.t
260 ; adjusted_level
: int
261 ; iterators_to_release
: iterator list
264 type resolved_jump_target
=
266 | ResolvedTryFinally
of resolved_try_finally
267 | ResolvedRegular
of Label.t
* iterator list
269 let add_iterator it_opt iters
=
270 Option.value_map it_opt ~default
:iters ~f
:(fun v
-> v
:: iters
)
272 (* Tries to find a target label given a level and a jump kind (break or continue) *)
273 let get_target_for_level ~is_break level t
=
274 (* skip_try_finally is true if we've already determined that we need to jump out of
275 finally and now we are looking for the actual target label (break label of the
276 while loop in the example below: )
287 let rec aux ~skip_try_finally n l iters
=
289 | [] | Function
_ :: _ ->
290 (* looked through the entires list of jump targets and still cannot find a
291 target for a requested level - bad luck *)
293 | (Using
(finally_label
, _) | TryFinally
(finally_label
, _)) :: tl
->
294 if skip_try_finally
then aux ~skip_try_finally n tl iters
296 (* we need to jump out of try body in try/finally - in order to do this
297 we should go through the finally block first.*)
298 (* try to final target label where we would've jumped if there were no finally blocks *)
299 let result = aux ~skip_try_finally
:true n tl iters
in
300 begin match result with
301 | NotFound
-> NotFound
302 | ResolvedRegular
(target_label
, _) ->
304 { target_label
= target_label
307 ; iterators_to_release
= iters
}
308 | _ -> failwith
"impossible: TryFinally should be skipped"
310 | Switch
(end_label
, _) :: _ when n
= 1 -> ResolvedRegular
(end_label
, iters
)
311 | Loop
({ label_break
; label_continue
; iterator
}, _) :: _ when n
= 1 ->
313 if is_break
then label_break
, add_iterator iterator iters
314 else label_continue
, iters
316 ResolvedRegular
(label, iters
)
317 | Loop
({ iterator
; _ }, _) :: tl
->
318 aux ~skip_try_finally
(n
- 1) tl
(add_iterator iterator iters
)
320 aux ~skip_try_finally
(n
- 1) tl iters
322 (* jumps out of finally body are disallowed *)
325 aux ~skip_try_finally
:false level t
[]
327 let get_closest_enclosing_finally_label t
=
328 let rec aux l iters
=
331 | (Using
(l
, _) | TryFinally
(l
, _)) :: _ -> Some
(l
, iters
)
332 | Loop
({ iterator
; _ }, _) :: tl
-> aux tl
(add_iterator iterator iters
)
333 | _ :: tl
-> aux tl iters
336 let collect_iterators t
=
337 List.filter_map t ~f
:(function | Loop
({ iterator
; _ }, _) -> iterator
| _ -> None
)
339 type resolved_goto_finally
= {
340 rgf_finally_start_label
: Label.t
;
341 rgf_iterators_to_release
: iterator list
344 type resolved_goto_target
=
345 | ResolvedGoto_label
of iterator list
346 | ResolvedGoto_finally
of resolved_goto_finally
347 | ResolvedGoto_goto_from_finally
348 | ResolvedGoto_goto_invalid_label
350 let find_goto_target t
label =
351 let rec aux t iters
=
353 | Loop
({ iterator
; _ }, labels) :: tl
->
354 if SSet.mem
label labels then ResolvedGoto_label iters
355 else aux tl
(add_iterator iterator iters
)
356 | Switch
(_, labels) :: tl
->
357 if SSet.mem
label labels then ResolvedGoto_label iters
359 | (Using
(finally_start
, labels) | TryFinally
(finally_start
, labels)) :: _ ->
360 if SSet.mem
label labels then ResolvedGoto_label iters
361 else ResolvedGoto_finally
{
362 rgf_finally_start_label
= finally_start
;
363 rgf_iterators_to_release
= iters
}
364 | Finally
labels :: _ ->
365 if SSet.mem
label labels then ResolvedGoto_label iters
366 else ResolvedGoto_goto_from_finally
367 | Function
labels :: _ ->
368 if SSet.mem
label labels then ResolvedGoto_label iters
369 else ResolvedGoto_goto_invalid_label
370 | [] -> failwith
"impossible" in