Change the Aast Shape from shapemap to list
[hiphop-php.git] / hphp / hack / src / typing / nastInitCheck.ml
blob073f9884f1e3f64a332932bb6a96698c0572cc08
1 (**
2 * Copyright (c) 2015, 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 *)
11 (* Module checking that all the class members are properly initialized.
12 * To be more precise, this checks that if the constructor does not throw,
13 * it initializes all members. *)
14 open Core_kernel
15 open Nast
17 module DICheck = Decl_init_check
18 module SN = Naming_special_names
20 module SSetWTop = struct
21 type t =
22 | Top
23 | Set of SSet.t
25 let union s1 s2 =
26 match s1, s2 with
27 | Top, _
28 | _, Top -> Top
29 | Set s1, Set s2 -> Set (SSet.union s1 s2)
31 let inter s1 s2 =
32 match s1, s2 with
33 | Top, s
34 | s, Top -> s
35 | Set s1, Set s2 -> Set (SSet.inter s1 s2)
37 let inter_list (sl: t list) =
38 List.fold_left ~f:inter ~init:Top sl
40 let add x s =
41 match s with
42 | Top -> Top
43 | Set s -> Set (SSet.add x s)
45 let mem x s =
46 match s with
47 | Top -> true
48 | Set s -> SSet.mem x s
50 let empty = Set SSet.empty
51 end
53 module S = SSetWTop
55 (* Exception raised when we hit a return statement and the initialization
56 * is not over.
57 * When that is the case, we bubble up back to the toplevel environment.
58 * An example (right hand side is the set of things initialized):
60 * $this->x = 0; // { x }
61 * if(...) {
62 * $this->y = 1; // { x, y }
63 * if(...) {
64 * $this->z = 2; // { x, y, z }
65 * return; // raise InitReturn with set { x, y, z}
66 * } // exception caught, re-raise with { x, y }
67 * } // exception caught, re-reraise with { x }
69 * What is effectively initialized: { x }
71 exception InitReturn of S.t
73 (* Module initializing the environment
74 Originally, every class member has 2 possible states,
75 Vok ==> when it is declared as optional, it is the job of the
76 typer to make sure it is always check for the null case
77 not our problem here
78 Vnull ==> The value is now null, it MUST be initialized,
79 and cannot be used before it has been initialized.
81 Concerning the methods, basically the information we are
82 interested in is, which class members do they initialize?
83 But we don't want to recompute it every time it is called.
84 So we memoize the result: hence the type method status.
86 module Env = struct
88 type method_status =
89 (* We already computed this method *)
90 | Done
92 (* We have never computed this private method before *)
93 | Todo of func_body
95 type t = {
96 methods : method_status ref SMap.t ;
97 props : SSet.t ;
98 tenv : Typing_env.env ;
101 let parent_id c = match c.c_extends with
102 | [(_, Happly ((_, parent_id), _))] -> Some parent_id
103 | _ -> None
105 let rec make tenv c =
106 let tenv = Typing_env.set_self_id tenv (snd c.c_name) in
107 let tenv = match parent_id c with
108 | None -> tenv
109 | Some parent_id -> Typing_env.set_parent_id tenv parent_id in
110 let methods = List.fold_left ~f:method_ ~init:SMap.empty c.c_methods in
111 let decl_env = tenv.Typing_env.decl_env in
112 let sc = Shallow_decl.class_ decl_env.Decl_env.decl_tcopt c in
113 let props = SSet.empty
114 |> DICheck.own_props sc
115 (* If we define our own constructor, we need to pretend any traits we use
116 * did *not* define a constructor, because they are not reachable through
117 * parent::__construct or similar functions. *)
118 |> DICheck.trait_props decl_env sc
119 |> DICheck.parent decl_env sc in
120 { methods; props; tenv; }
122 and method_ acc m =
123 if m.m_visibility <> Private then acc else
124 let name = snd m.m_name in
125 let acc = SMap.add name (ref (Todo m.m_body)) acc in
128 let get_method env m =
129 SMap.get m env.methods
133 open Env
135 (*****************************************************************************)
136 (* List of functions that can use '$this' before the initialization is
137 * over.
139 (*****************************************************************************)
141 let is_whitelisted = function
142 | x when x = SN.StdlibFunctions.get_class -> true
143 | _ -> false
145 let save_initialized_members_for_suggest cname initialized_props =
146 let props_to_save = match initialized_props with
147 | S.Top -> SSet.empty (* Constructor always throws *)
148 | S.Set s -> s in
149 Typing_suggest.save_initialized_members cname props_to_save
152 let rec class_ tenv c =
153 if c.c_mode = FileInfo.Mdecl then () else
154 match c.c_constructor with
155 | _ when c.c_kind = Ast.Cinterface -> ()
156 | Some { m_body = NamedBody { fnb_unsafe = true; _ }; _ } -> ()
157 | _ -> (
158 let p = match c.c_constructor with
159 | Some m -> fst m.m_name
160 | None -> fst c.c_name
162 let env = Env.make tenv c in
163 let inits = constructor env c.c_constructor in
165 let check_inits inits =
166 let uninit_props = SSet.diff env.props inits in
167 if SSet.empty <> uninit_props then begin
168 if SSet.mem DICheck.parent_init_prop uninit_props then
169 Errors.no_construct_parent p
170 else
171 Errors.not_initialized (p, snd c.c_name) (SSet.elements uninit_props)
172 end in
174 let check_throws_or_init_all inits =
175 match inits with
176 | S.Top ->
177 (* Constructor always throw, so checking that all properties are
178 * initialized is irrelevant. *)
180 | S.Set inits ->
181 check_inits inits in
183 save_initialized_members_for_suggest (snd c.c_name) inits;
184 if c.c_kind = Ast.Ctrait || c.c_kind = Ast.Cabstract
185 then begin
186 let has_constructor = match c.c_constructor with
187 | None -> false
188 | Some m when m.m_abstract -> false
189 | Some _ -> true in
190 if has_constructor then check_throws_or_init_all inits else ()
192 else check_throws_or_init_all inits
196 * Returns the set of properties initialized by the constructor.
197 * More exactly, returns a SSetWTop.t, i.e. either a set, or Top, which is
198 * the top element of the set of sets of properties, i.e. a set containing
199 * all the possible properties.
200 * Top is returned for a block of statements if
201 * this block always throws. It is an abstract construct used to deal
202 * gracefully with control flow.
204 * For example, if we have an `if` statement like:
206 * ```
207 * if (...) {
208 * // This branch initialize a set of properties S1
209 * ...
210 * } else {
211 * // This branch initialize another set of properties S2
212 * ...
214 * ```
216 * then the set `S` of properties initialized by this `if` statement is the
217 * intersection `S1 /\ S2`.
218 * If one of the branches throws, say the first branch, then the set `S`
219 * of properties initialized by the `if` statement is equal to the set of
220 * properties initialized by the branch that does not throw, i.e. `S = S2`.
221 * This amounts to saying that `S1` is some top element of the set of sets
222 * of variables, which we call `Top`, which has the property that for all
223 * set S of properties, S is included in `Top`, such that `S = S1 /\ S2`
224 * still holds.
226 and constructor env cstr =
227 match cstr with
228 | None -> S.empty
229 | Some cstr ->
230 let check_param_initializer = fun e -> ignore (expr env S.empty e) in
231 List.iter cstr.m_params (fun p ->
232 Option.iter p.param_expr check_param_initializer
234 let b = Nast.assert_named_body cstr.m_body in
235 toplevel env S.empty b.fnb_nast
237 and assign _env acc x =
238 S.add x acc
240 and assign_expr env acc e1 =
241 match e1 with
242 | _, Obj_get ((_, This), (_, Id (_, y)), _) ->
243 assign env acc y
244 | _, List el ->
245 List.fold_left ~f:(assign_expr env) ~init:acc el
246 | _ -> acc
248 and stmt env acc st =
249 let expr = expr env in
250 let block = block env in
251 let catch = catch env in
252 let case = case env in
253 match st with
254 | Expr (_, Call (Cnormal, (_, Class_const ((_, CIparent), (_, m))), _, el, _uel))
255 when m = SN.Members.__construct ->
256 let acc = List.fold_left ~f:expr ~init:acc el in
257 assign env acc DICheck.parent_init_prop
258 | Expr e ->
259 if (Typing_func_terminality.expression_exits env.tenv e)
260 then S.Top
261 else expr acc e
262 | GotoLabel _
263 | Goto _
264 | Break _ -> acc
265 | Continue _ -> acc
266 | Throw _ -> S.Top
267 | Return (_, None) ->
268 if are_all_init env acc
269 then acc
270 else raise (InitReturn acc)
271 | Return (_, Some x) ->
272 let acc = expr acc x in
273 if are_all_init env acc
274 then acc
275 else raise (InitReturn acc)
276 | Static_var el
277 | Global_var el
278 -> List.fold_left ~f:expr ~init:acc el
279 | Awaitall (_, el) ->
280 List.fold_left el ~init:acc ~f:(fun acc (e1, e2) ->
281 let acc = expr acc e2 in
282 match e1 with
283 | Some e -> assign_expr env acc e
284 | None -> acc
286 | If (e1, b1, b2) ->
287 let acc = expr acc e1 in
288 let b1 = block acc b1 in
289 let b2 = block acc b2 in
290 S.union acc (S.inter b1 b2)
291 | Do (b, e) ->
292 let acc = block acc b in
293 expr acc e
294 | While (e, _) ->
295 expr acc e
296 | Using us ->
297 let acc = expr acc us.us_expr in
298 block acc us.us_block
299 | For (e1, _, _, _) ->
300 expr acc e1
301 | Switch (e, cl) ->
302 let acc = expr acc e in
303 let cl = List.map cl (case acc) in
304 let c = S.inter_list cl in
305 S.union acc c
306 | Foreach (e, _, _) ->
307 let acc = expr acc e in
309 | Try (b, cl, fb) ->
310 let c = block acc b in
311 let f = block acc fb in
312 let cl = List.map cl (catch acc) in
313 let c = S.inter_list (c :: cl) in
314 (* the finally block executes even if *none* of try and catch do *)
315 let acc = S.union acc f in
316 S.union acc c
317 | Fallthrough -> S.empty
318 | Def_inline _
319 | Unsafe_block _
320 | Noop -> acc
321 | Let (_, _, e) ->
322 (* Scoped local variable cannot escape the block *)
323 expr acc e
324 | Block b -> block acc b
325 | Markup (_, eopt) -> (match eopt with
326 | Some e -> expr acc e
327 | None -> acc
329 | Declare (_, e, b) ->
330 let acc = expr acc e in
331 block acc b
333 and toplevel env acc l =
334 try List.fold_left ~f:(stmt env) ~init:acc l
335 with InitReturn acc -> acc
337 and block env acc l =
338 let acc_before_block = acc in
340 List.fold_left ~f:(stmt env) ~init:acc l
341 with InitReturn _ ->
342 (* The block has a return statement, forget what was initialized in it *)
343 raise (InitReturn acc_before_block)
345 and are_all_init env set =
346 SSet.fold (fun cv acc -> acc && S.mem cv set) env.props true
348 and check_all_init p env acc =
349 SSet.iter begin fun cv ->
350 if not (S.mem cv acc)
351 then Errors.call_before_init p cv
352 end env.props
354 and exprl env acc l = List.fold_left ~f:(expr env) ~init:acc l
355 and expr env acc (p, e) = expr_ env acc p e
356 and expr_ env acc p e =
357 let expr = expr env in
358 let exprl = exprl env in
359 let field = field env in
360 let afield = afield env in
361 let fun_paraml = fun_paraml env in
362 match e with
363 | Any -> acc
364 | Array fdl -> List.fold_left ~f:afield ~init:acc fdl
365 | Darray fdl -> List.fold_left ~f:field ~init:acc fdl
366 | Varray fdl -> List.fold_left ~f:expr ~init:acc fdl
367 | ValCollection (_, el) -> exprl acc el
368 | KeyValCollection (_, fdl) -> List.fold_left ~f:field ~init:acc fdl
369 | This -> check_all_init p env acc; acc
370 | Fun_id _
371 | Method_id _
372 | Smethod_id _
373 | Method_caller _
374 | Typename _
375 | Id _ -> acc
376 | Lvar _
377 | ImmutableVar _
378 | Lplaceholder _ | Dollardollar _ -> acc
379 | Obj_get ((_, This), (_, Id (_, vx as v)), _) ->
380 if SSet.mem vx env.props && not (S.mem vx acc)
381 then (Errors.read_before_write v; acc)
382 else acc
383 | Clone e -> expr acc e
384 | Obj_get (e1, e2, _) ->
385 let acc = expr acc e1 in
386 expr acc e2
387 | Array_get (e, eo) ->
388 let acc = expr acc e in
389 (match eo with
390 | None -> acc
391 | Some e -> expr acc e)
392 | Class_const _
393 | Class_get _ -> acc
394 | Call (Cnormal, (p, Obj_get ((_, This), (_, Id (_, f)), _)), _, _, _) ->
395 let method_ = Env.get_method env f in
396 (match method_ with
397 | None ->
398 check_all_init p env acc;
400 | Some method_ ->
401 (match !method_ with
402 | Done -> acc
403 | Todo b ->
404 method_ := Done;
405 let fb = Nast.assert_named_body b in
406 toplevel env acc fb.fnb_nast
409 | Call (_, e, _, el, uel) ->
410 let el = el @ uel in
411 let el =
412 match e with
413 | _, Id (_, fun_name) when is_whitelisted fun_name ->
414 List.filter el begin function
415 | _, This -> false
416 | _ -> true
418 | _ -> el
420 let acc = List.fold_left ~f:expr ~init:acc el in
421 expr acc e
422 | True
423 | False
424 | Int _
425 | Float _
426 | Null
427 | String _
428 | String2 _
429 | PrefixedString _
430 | Execution_operator _
431 | Unsafe_expr _ -> acc
432 | Assert (AE_assert e) -> expr acc e
433 | Yield e -> afield acc e
434 | Yield_from e -> expr acc e
435 | Yield_break -> acc
436 | Dollar e -> expr acc e
437 | Await e -> expr acc e
438 | Suspend e -> expr acc e
439 | List _ ->
440 (* List is always an lvalue *)
442 | Expr_list el ->
443 exprl acc el
444 | Special_func (Gena e)
445 | Special_func (Gen_array_rec e) ->
446 expr acc e
447 | Special_func (Genva el) ->
448 exprl acc el
449 | New (_, el, uel, _) ->
450 exprl acc (el @ uel)
451 | Pair (e1, e2) ->
452 let acc = expr acc e1 in
453 expr acc e2
454 | Cast (_, e)
455 | Unop (_, e) -> expr acc e
456 | Binop (Ast.Eq None, e1, e2) ->
457 let acc = expr acc e2 in
458 assign_expr env acc e1
459 | Binop (Ast.Ampamp, e, _)
460 | Binop (Ast.Barbar, e, _) ->
461 expr acc e
462 | Binop (_, e1, e2) ->
463 let acc = expr acc e1 in
464 expr acc e2
465 | Pipe (_, e1, e2) ->
466 let acc = expr acc e1 in
467 expr acc e2
468 | Eif (e1, None, e3) ->
469 let acc = expr acc e1 in
470 expr acc e3
471 | Eif (e1, Some e2, e3) ->
472 let acc = expr acc e1 in
473 let acc = expr acc e2 in
474 expr acc e3
475 | InstanceOf (e, _) -> expr acc e
476 | Is (e, _) -> expr acc e
477 | As (e, _, _) -> expr acc e
478 | Efun (f, _) ->
479 let acc = fun_paraml acc f.f_params in
480 (* We don't need to analyze the body of closures *)
482 | Xml (_, l, el) ->
483 let l = List.map l get_xhp_attr_expr in
484 let acc = exprl acc l in
485 exprl acc el
486 | Callconv (_, e) -> expr acc e
487 | Shape fdm ->
488 List.fold_left
489 ~f:begin fun acc (_, v) ->
490 expr acc v
492 ~init:acc
494 | Omitted -> acc
495 | NewAnonClass _ -> acc
497 and case env acc = function
498 | Default b
499 | Case (_, b) -> block env acc b
501 and catch env acc (_, _, b) = block env acc b
503 and field env acc (e1, e2) =
504 let acc = expr env acc e1 in
505 let acc = expr env acc e2 in
508 and afield env acc = function
509 | AFvalue e ->
510 expr env acc e
511 | AFkvalue (e1, e2) ->
512 let acc = expr env acc e1 in
513 let acc = expr env acc e2 in
516 and fun_param env acc param =
517 match param.param_expr with
518 | None -> acc
519 | Some x -> expr env acc x
521 and fun_paraml env acc l = List.fold_left ~f:(fun_param env) ~init:acc l