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 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_
19 let get_function_has_goto () = !function_has_goto_
21 let set_labels_in_function s
= labels_in_function_ := s
23 let set_function_has_goto f
= function_has_goto_ := f
25 let rec collect_valid_target_labels_aux acc s
=
27 | T.Block block
-> collect_valid_target_labels_for_block_aux acc block
28 (* can jump into the try block but not to finally *)
29 | T.Try
(block
, cl
, _
) ->
30 let acc = collect_valid_target_labels_for_block_aux
acc block
in
31 List.fold_left cl ~init
:acc ~f
:(fun acc (_
, _
, block
) ->
32 collect_valid_target_labels_for_block_aux
acc block
)
33 | T.GotoLabel
(_
, s
) -> SSet.add s
acc
34 | T.If
(_
, then_block
, else_block
) ->
35 let acc = collect_valid_target_labels_for_block_aux
acc then_block
in
36 collect_valid_target_labels_for_block_aux
acc else_block
55 (* jump to while loops/switches/usings are disallowed in php files
56 and permitted in hh - assuming that they can only appear there
57 as a result of source to source transformation that has validated
58 correctness of the target *)
60 | T.Using
{ T.us_block
= block
; _
} ->
61 collect_valid_target_labels_for_block_aux
acc block
62 | T.Switch
(_
, cl
) -> collect_valid_target_labels_for_switch_cases_aux
acc cl
64 and collect_valid_target_labels_for_block_aux
acc block
=
65 List.fold_left block ~init
:acc ~f
:collect_valid_target_labels_aux
67 and collect_valid_target_labels_for_switch_cases_aux
acc cl
=
68 List.fold_left cl ~init
:acc ~f
:(fun acc s
->
70 | T.Default
(_
, block
)
71 | T.Case
(_
, block
) ->
72 collect_valid_target_labels_for_block_aux
acc block
)
74 let rec collect_valid_target_labels_for_def_aux acc def
=
76 | T.Stmt s
-> collect_valid_target_labels_aux acc s
77 | T.Namespace
(_
, defs
) -> collect_valid_target_labels_for_defs_aux
acc defs
80 and collect_valid_target_labels_for_defs_aux
acc defs
=
81 List.fold_left defs ~init
:acc ~f
:collect_valid_target_labels_for_def_aux
83 let collect_valid_target_labels_for_stmt s
=
84 if not
!function_has_goto_ then
87 collect_valid_target_labels_aux SSet.empty s
89 let collect_valid_target_labels_for_defs defs
=
90 if not
!function_has_goto_ then
93 collect_valid_target_labels_for_defs_aux
SSet.empty defs
95 let collect_valid_target_labels_for_switch_cases cl
=
96 if not
!function_has_goto_ then
99 collect_valid_target_labels_for_switch_cases_aux
SSet.empty cl
102 label_break
: Label.t
;
103 label_continue
: Label.t
;
104 iterator
: Iterator.t
option;
108 | Loop
of loop_labels
* SSet.t
109 | Switch
of (*end switch*) Label.t
* SSet.t
110 | TryFinally
of (*finally start*) Label.t
* SSet.t
113 | Using
of (*finally start*) Label.t
* SSet.t
123 module LabelIdMap
: MyMap.S
with type key
= id_key
= MyMap.Make
(struct
126 let compare = Pervasives.compare
129 (* HHVM assigns ids to labels sequentially as break/continue to labels appear
130 in the source code. When emitter is done with scope that contains
131 break/continue - label ids associated with the scope are freed and can be reused.
132 One exception from the rule is return - it is treated as if it is jump to a label that
133 finishes the function so once id for return is assigned - it is never released.
134 Label ids are used in finally epilogue to determine how control flow got into
139 if ($a) { continue; }
140 if ($b) { return 100; }
147 will be emitted roughly as:
153 Int 0 ; push id of the target label "continue label for while loop"
154 SetL _1; save it in dedicated local
161 Int 100 ; push return value
162 Int 1; push id of the 'return' label
163 SetL _1; save id in decicated local
165 SetL _2; save return value in dedicated local
173 IssetL _1; if check if state local is set
176 Switch Unbounded 0 <Case0 Case1>
179 UnsetL _1; new iteration - clean local for state id
182 CGetL _2; load return value
188 let label_to_id = ref LabelIdMap.empty
191 match LabelIdMap.get k
!label_to_id with
195 if LabelIdMap.exists
(fun _ id
-> n
= id
) !label_to_id then
198 label_to_id := LabelIdMap.add k n
!label_to_id;
204 let reset () = label_to_id := LabelIdMap.empty
206 let get_id_for_return () = new_id IdReturn
208 let get_id_for_label l
= new_id (IdLabel l
)
210 let release_id l
= label_to_id := LabelIdMap.remove
(IdLabel l
) !label_to_id
212 (* runs a given function and then released label ids that were possibly assigned
213 to labels at the head of the list *)
214 let run_and_release_ids _labels f s t
=
218 | Loop
({ label_break
; label_continue
; _
}, _
) :: _
->
219 release_id label_break
;
220 release_id label_continue
221 | (Switch
(l
, _
) | TryFinally
(l
, _
) | Using
(l
, _
)) :: _
-> release_id l
222 | (Function _
| Finally _
) :: _
-> ()
223 | [] -> failwith
"impossible"
226 (* CONSIDER: now HHVM does not release state ids for named labels
227 even after leaving the scope where labels are accessible
228 Do the same for now for compatibility reasons *)
229 (* SSet.iter (fun l -> release_id (Label.Named l)) labels; *)
232 let with_loop label_break label_continue iterator t s f
=
233 let labels = collect_valid_target_labels_for_stmt s
in
234 Loop
({ label_break
; label_continue
; iterator
}, labels) :: t
235 |> run_and_release_ids labels f s
237 let with_switch end_label t cl f
=
238 let labels = collect_valid_target_labels_for_switch_cases cl
in
239 (* CONSIDER: now HHVM eagerly reserves state id for the switch end label
240 which does not seem to be necessary - do it for now for HHVM compatibility *)
241 let _ = get_id_for_label end_label
in
242 Switch
(end_label
, labels) :: t
|> run_and_release_ids labels f
()
244 let with_try finally_label t s f
=
245 let labels = collect_valid_target_labels_for_stmt s
in
246 TryFinally
(finally_label
, labels) :: t
|> run_and_release_ids labels f s
248 let with_finally t s f
=
249 let labels = collect_valid_target_labels_for_stmt s
in
250 Finally
labels :: t
|> run_and_release_ids labels f s
252 let with_function t s f
=
253 let labels = collect_valid_target_labels_for_defs s
in
254 Function
labels :: t
|> run_and_release_ids labels f s
256 let with_using finally_label t s f
=
257 let labels = collect_valid_target_labels_for_stmt s
in
258 Using
(finally_label
, labels) :: t
|> run_and_release_ids labels f s
260 type resolved_try_finally
= {
261 target_label
: Label.t
;
262 finally_label
: Label.t
;
264 iterators_to_release
: Iterator.t list
;
267 type resolved_jump_target
=
269 | ResolvedTryFinally
of resolved_try_finally
270 | ResolvedRegular
of Label.t
* Iterator.t list
272 let add_iterator it_opt iters
=
273 Option.value_map it_opt ~default
:iters ~f
:(fun v
-> v
:: iters
)
275 (* Tries to find a target label given a level and a jump kind (break or continue) *)
276 let get_target_for_level ~is_break level t
=
277 (* skip_try_finally is true if we've already determined that we need to jump out of
278 finally and now we are looking for the actual target label (break label of the
279 while loop in the example below: )
290 let rec aux ~skip_try_finally n l iters
=
294 (* looked through the entires list of jump targets and still cannot find a
295 target for a requested level - bad luck *)
297 | (Using
(finally_label
, _) | TryFinally
(finally_label
, _)) :: tl
->
298 if skip_try_finally
then
299 aux ~skip_try_finally n tl iters
301 (* we need to jump out of try body in try/finally - in order to do this
302 we should go through the finally block first.*)
303 (* try to final target label where we would've jumped if there were no finally blocks *)
304 let result = aux ~skip_try_finally
:true n tl iters
in
307 | NotFound
-> NotFound
308 | ResolvedRegular
(target_label
, _) ->
314 iterators_to_release
= iters
;
316 | _ -> failwith
"impossible: TryFinally should be skipped"
318 | Switch
(end_label
, _) :: _ when n
= 1 ->
319 ResolvedRegular
(end_label
, iters
)
320 | Loop
({ label_break
; label_continue
; iterator
}, _) :: _ when n
= 1 ->
323 (label_break
, add_iterator iterator iters
)
325 (label_continue
, iters
)
327 ResolvedRegular
(label
, iters
)
328 | Loop
({ iterator
; _ }, _) :: tl
->
329 aux ~skip_try_finally
(n
- 1) tl
(add_iterator iterator iters
)
330 | Switch
_ :: tl
-> aux ~skip_try_finally
(n
- 1) tl iters
332 (* jumps out of finally body are disallowed *)
335 aux ~skip_try_finally
:false level t
[]
337 let get_closest_enclosing_finally_label t
=
338 let rec aux l iters
=
341 | (Using
(l
, _) | TryFinally
(l
, _)) :: _ -> Some
(l
, iters
)
342 | Loop
({ iterator
; _ }, _) :: tl
-> aux tl
(add_iterator iterator iters
)
343 | _ :: tl
-> aux tl iters
347 let collect_iterators t
=
348 List.filter_map t ~f
:(function
349 | Loop
({ iterator
; _ }, _) -> iterator
352 type resolved_goto_finally
= {
353 rgf_finally_start_label
: Label.t
;
354 rgf_iterators_to_release
: Iterator.t list
;
357 type resolved_goto_target
=
358 | ResolvedGoto_label
of Iterator.t list
359 | ResolvedGoto_finally
of resolved_goto_finally
360 | ResolvedGoto_goto_from_finally
361 | ResolvedGoto_goto_invalid_label
363 let find_goto_target t label
=
364 let rec aux t iters
=
366 | Loop
({ iterator
; _ }, labels) :: tl
->
367 if SSet.mem label
labels then
368 ResolvedGoto_label iters
370 aux tl
(add_iterator iterator iters
)
371 | Switch
(_, labels) :: tl
->
372 if SSet.mem label
labels then
373 ResolvedGoto_label iters
376 | (Using
(finally_start
, labels) | TryFinally
(finally_start
, labels)) :: _
378 if SSet.mem label
labels then
379 ResolvedGoto_label iters
383 rgf_finally_start_label
= finally_start
;
384 rgf_iterators_to_release
= iters
;
386 | Finally
labels :: _ ->
387 if SSet.mem label
labels then
388 ResolvedGoto_label iters
390 ResolvedGoto_goto_from_finally
391 | Function
labels :: _ ->
392 if SSet.mem label
labels then
393 ResolvedGoto_label iters
395 ResolvedGoto_goto_invalid_label
396 | [] -> failwith
"impossible"