Add .mli file for nastInitCheck
[hiphop-php.git] / hphp / hack / src / typing / nastInitCheck.ml
blobbe76d87f803da7ba2fef65f92c068e85da83bf58
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 *)
10 (* Module checking that all the class members are properly initialized.
11 * To be more precise, this checks that if the constructor does not throw,
12 * it initializes all members. *)
13 open Hh_prelude
14 open Aast
15 open Nast
16 module DICheck = Decl_init_check
17 module DeferredMembers = Typing_deferred_members
18 module SN = Naming_special_names
20 let shallow_decl_enabled (ctx : Provider_context.t) : bool =
21 TypecheckerOptions.shallow_class_decl (Provider_context.get_tcopt ctx)
23 module SSetWTop = struct
24 type t =
25 | Top
26 | Set of SSet.t
28 let union s1 s2 =
29 match (s1, s2) with
30 | (Top, _)
31 | (_, Top) ->
32 Top
33 | (Set s1, Set s2) -> Set (SSet.union s1 s2)
35 let inter s1 s2 =
36 match (s1, s2) with
37 | (Top, s)
38 | (s, Top) ->
40 | (Set s1, Set s2) -> Set (SSet.inter s1 s2)
42 let inter_list (sl : t list) = List.fold_left ~f:inter ~init:Top sl
44 let add x s =
45 match s with
46 | Top -> Top
47 | Set s -> Set (SSet.add x s)
49 let mem x s =
50 match s with
51 | Top -> true
52 | Set s -> SSet.mem x s
54 let empty = Set SSet.empty
55 end
57 let parent_init_prop = "parent::" ^ SN.Members.__construct
59 let lookup_props env class_name props =
60 SSet.fold
61 begin
62 fun name map ->
63 let ty_opt =
64 if String.equal name parent_init_prop then
65 Some (Typing_make_type.nonnull Typing_reason.Rnone)
66 else
67 Typing_env.get_class env class_name
68 |> Option.bind ~f:(fun cls ->
69 Typing_env.get_member false env cls name)
70 |> Option.bind ~f:(fun ce -> Some (Lazy.force ce.Typing_defs.ce_type))
72 SMap.add name ty_opt map
73 end
74 props
75 SMap.empty
77 (* If a type is missing, nullable, or dynamic, initialization is not required *)
78 let type_does_not_require_init env ty_opt =
79 match ty_opt with
80 | None -> true
81 | Some ty ->
82 let (env, ty) = Typing_phase.localize_no_subst env ~ignore_errors:true ty in
83 let null = Typing_make_type.null Typing_reason.Rnone in
84 Typing_subtype.is_sub_type env null ty
86 let dynamic = Typing_make_type.dynamic Typing_reason.Rnone in
87 Typing_subtype.is_sub_type env dynamic ty
88 && Typing_subtype.is_sub_type env ty dynamic
90 module S = SSetWTop
92 (* Exception raised when we hit a return statement and the initialization
93 * is not over.
94 * When that is the case, we bubble up back to the toplevel environment.
95 * An example (right hand side is the set of things initialized):
97 * $this->x = 0; // { x }
98 * if(...) {
99 * $this->y = 1; // { x, y }
100 * if(...) {
101 * $this->z = 2; // { x, y, z }
102 * return; // raise InitReturn with set { x, y, z}
103 * } // exception caught, re-raise with { x, y }
104 * } // exception caught, re-reraise with { x }
106 * What is effectively initialized: { x }
108 exception InitReturn of S.t
110 (* Module initializing the environment
111 Originally, every class member has 2 possible states,
112 Vok ==> when it is declared as optional, it is the job of the
113 typer to make sure it is always check for the null case
114 not our problem here
115 Vnull ==> The value is now null, it MUST be initialized,
116 and cannot be used before it has been initialized.
118 Concerning the methods, basically the information we are
119 interested in is, which class members do they initialize?
120 But we don't want to recompute it every time it is called.
121 So we memoize the result: hence the type method status.
123 module Env = struct
124 type method_status =
125 (* We already computed this method *)
126 | Done
127 (* We have never computed this private method before *)
128 | Todo of func_body
130 type t = {
131 methods: method_status ref SMap.t;
132 props: Typing_defs.decl_ty option SMap.t;
133 tenv: Typing_env_types.env;
134 init_not_required_props: SSet.t;
137 let rec make tenv c =
138 let ctx = Typing_env.get_ctx tenv in
139 let (_, _, methods) = split_methods c.c_methods in
140 let methods = List.fold_left ~f:method_ ~init:SMap.empty methods in
141 let sc = Shallow_decl.class_ ctx c in
143 (* Error when an abstract class has private properties but lacks a constructor *)
144 (let open Shallow_decl_defs in
145 let has_own_cstr =
146 match sc.sc_constructor with
147 | Some s -> not (sm_abstract s)
148 | None -> false
150 let (private_props, _) =
151 if shallow_decl_enabled ctx then
152 DeferredMembers.class_ tenv sc
153 else
154 DICheck.class_ ~has_own_cstr tenv.Typing_env_types.decl_env sc
156 let private_props = lookup_props tenv (snd c.c_name) private_props in
157 if Ast_defs.is_c_abstract sc.sc_kind && not has_own_cstr then
158 let uninit =
159 SMap.filter
160 (fun _ ty_opt -> not (type_does_not_require_init tenv ty_opt))
161 private_props
163 if not @@ SMap.is_empty uninit then
164 SMap.bindings uninit
165 |> List.map ~f:fst
166 |> Errors.constructor_required c.c_name);
168 let ( add_init_not_required_props,
169 add_trait_props,
170 add_parent_props,
171 add_parent ) =
172 if shallow_decl_enabled ctx then
173 ( DeferredMembers.init_not_required_props,
174 DeferredMembers.trait_props tenv,
175 DeferredMembers.parent_props tenv,
176 DeferredMembers.parent tenv )
177 else
178 let decl_env = tenv.Typing_env_types.decl_env in
179 ( DICheck.init_not_required_props,
180 DICheck.trait_props decl_env,
181 DICheck.parent_props decl_env,
182 DICheck.parent decl_env )
184 let init_not_required_props = add_init_not_required_props sc SSet.empty in
185 let props =
186 SSet.empty
187 |> DeferredMembers.own_props sc
188 (* If we define our own constructor, we need to pretend any traits we use
189 * did *not* define a constructor, because they are not reachable through
190 * parent::__construct or similar functions. *)
191 |> add_trait_props sc
192 |> add_parent_props sc
193 |> add_parent sc
194 |> lookup_props tenv (snd c.c_name)
195 |> SMap.filter (fun _ ty_opt ->
196 not (type_does_not_require_init tenv ty_opt))
198 { methods; props; tenv; init_not_required_props }
200 and method_ acc m =
201 if not (Aast.equal_visibility m.m_visibility Private) then
203 else
204 let name = snd m.m_name in
205 let acc = SMap.add name (ref (Todo m.m_body)) acc in
208 let get_method env m = SMap.find_opt m env.methods
211 open Env
213 (*****************************************************************************)
214 (* List of functions that can use '$this' before the initialization is
215 * over.
217 (*****************************************************************************)
219 let is_whitelisted = function
220 | x when String.equal x SN.StdlibFunctions.get_class -> true
221 | _ -> false
223 let is_lateinit cv =
224 Naming_attributes.mem SN.UserAttributes.uaLateInit cv.cv_user_attributes
226 let class_prop_pos class_name prop_name ctx : Pos_or_decl.t =
227 match Decl_provider.get_class ctx class_name with
228 | None -> Pos_or_decl.none
229 | Some decl ->
230 (match Decl_provider.Class.get_prop decl prop_name with
231 | None -> Pos_or_decl.none
232 | Some elt ->
233 let member_origin = elt.Typing_defs.ce_origin in
234 if shallow_decl_enabled ctx then
235 match Shallow_classes_provider.get ctx member_origin with
236 | None -> Pos_or_decl.none
237 | Some sc ->
238 let prop =
239 List.find_exn sc.Shallow_decl_defs.sc_props ~f:(fun prop ->
240 String.equal (snd prop.Shallow_decl_defs.sp_name) prop_name)
242 fst prop.Shallow_decl_defs.sp_name
243 else
244 let get_class_by_name ctx x =
245 let open Option.Monad_infix in
246 Naming_provider.get_type_path ctx x >>= fun fn ->
247 Ast_provider.find_class_in_file ctx fn x
249 (match get_class_by_name ctx member_origin with
250 | None -> Pos_or_decl.none
251 | Some cls ->
252 let cv =
253 List.find_exn cls.Aast.c_vars ~f:(fun cv ->
254 String.equal (snd cv.Aast.cv_id) prop_name)
256 Pos_or_decl.of_raw_pos @@ fst cv.Aast.cv_id))
259 * Returns the set of properties initialized by the constructor.
260 * More exactly, returns a SSetWTop.t, i.e. either a set, or Top, which is
261 * the top element of the set of sets of properties, i.e. a set containing
262 * all the possible properties.
263 * Top is returned for a block of statements if
264 * this block always throws. It is an abstract construct used to deal
265 * gracefully with control flow.
267 * For example, if we have an `if` statement like:
269 * ```
270 * if (...) {
271 * // This branch initialize a set of properties S1
272 * ...
273 * } else {
274 * // This branch initialize another set of properties S2
275 * ...
277 * ```
279 * then the set `S` of properties initialized by this `if` statement is the
280 * intersection `S1 /\ S2`.
281 * If one of the branches throws, say the first branch, then the set `S`
282 * of properties initialized by the `if` statement is equal to the set of
283 * properties initialized by the branch that does not throw, i.e. `S = S2`.
284 * This amounts to saying that `S1` is some top element of the set of sets
285 * of variables, which we call `Top`, which has the property that for all
286 * set S of properties, S is included in `Top`, such that `S = S1 /\ S2`
287 * still holds.
289 let rec constructor env cstr =
290 match cstr with
291 | None -> S.empty
292 | Some cstr ->
293 let check_param_initializer e = ignore (expr env S.empty e) in
294 List.iter cstr.m_params ~f:(fun p ->
295 Option.iter p.param_expr ~f:check_param_initializer);
296 let b = cstr.m_body in
297 toplevel env S.empty b.fb_ast
299 and assign _env acc x = S.add x acc
301 and assign_expr env acc e1 =
302 match e1 with
303 | (_, _, Obj_get ((_, _, This), (_, _, Id (_, y)), _, false)) ->
304 assign env acc y
305 | (_, _, List el) -> List.fold_left ~f:(assign_expr env) ~init:acc el
306 | _ -> acc
308 and stmt env acc st =
309 let expr = expr env in
310 let block = block env in
311 let catch = catch env in
312 let case = case env in
313 match snd st with
314 | Expr
315 (_, _, Call ((_, _, Class_const ((_, _, CIparent), (_, m))), _, el, _uel))
316 when String.equal m SN.Members.__construct ->
317 let acc = List.fold_left ~f:expr ~init:acc el in
318 assign env acc DeferredMembers.parent_init_prop
319 | Expr e ->
320 if Typing_func_terminality.expression_exits env.tenv e then
321 S.Top
322 else
323 expr acc e
324 | Break -> acc
325 | Continue -> acc
326 | Throw _ -> S.Top
327 | Return None ->
328 if are_all_init env acc then
330 else
331 raise (InitReturn acc)
332 | Yield_break -> S.Top
333 | Return (Some x) ->
334 let acc = expr acc x in
335 if are_all_init env acc then
337 else
338 raise (InitReturn acc)
339 | Awaitall (el, b) ->
340 let acc = List.fold_left el ~init:acc ~f:(fun acc (_, e2) -> expr acc e2) in
341 let acc = block acc b in
343 | If (e1, b1, b2) ->
344 let acc = expr acc e1 in
345 let b1 = block acc b1 in
346 let b2 = block acc b2 in
347 S.union acc (S.inter b1 b2)
348 | Do (b, e) ->
349 let acc = block acc b in
350 expr acc e
351 | While (e, _) -> expr acc e
352 | Using us ->
353 let acc = List.fold_left (snd us.us_exprs) ~f:expr ~init:acc in
354 block acc us.us_block
355 | For (e1, _, _, _) -> exprl env acc e1
356 | Switch (e, cl) ->
357 let acc = expr acc e in
358 (* Filter out cases that fallthrough *)
359 let cl_body = List.filter cl ~f:case_has_body in
360 let cl = List.map cl_body ~f:(case acc) in
361 let c = S.inter_list cl in
362 S.union acc c
363 | Foreach (e, _, _) ->
364 let acc = expr acc e in
366 | Try (b, cl, fb) ->
367 let c = block acc b in
368 let f = block acc fb in
369 let cl = List.map cl ~f:(catch acc) in
370 let c = S.inter_list (c :: cl) in
371 (* the finally block executes even if *none* of try and catch do *)
372 let acc = S.union acc f in
373 S.union acc c
374 | Fallthrough -> S.empty
375 | Noop -> acc
376 | Block b -> block acc b
377 | Markup _ -> acc
378 | AssertEnv _ -> acc
380 and toplevel env acc l =
381 try List.fold_left ~f:(stmt env) ~init:acc l with
382 | InitReturn acc -> acc
384 and block env acc l =
385 let acc_before_block = acc in
386 try List.fold_left ~f:(stmt env) ~init:acc l with
387 | InitReturn _ ->
388 (* The block has a return statement, forget what was initialized in it *)
389 raise (InitReturn acc_before_block)
391 and are_all_init env set =
392 SMap.fold (fun cv _ acc -> acc && S.mem cv set) env.props true
394 and check_all_init p env acc =
395 SMap.iter
396 begin
397 fun cv _ ->
398 if not (S.mem cv acc) then Errors.call_before_init p cv
400 env.props
402 and exprl env acc l = List.fold_left ~f:(expr env) ~init:acc l
404 and expr env acc (_, p, e) = expr_ env acc p e
406 and expr_ env acc p e =
407 let expr = expr env in
408 let exprl = exprl env in
409 let field = field env in
410 let afield = afield env in
411 let fun_paraml = fun_paraml env in
412 match e with
413 | Darray (_, fdl) -> List.fold_left ~f:field ~init:acc fdl
414 | Varray (_, fdl) -> List.fold_left ~f:expr ~init:acc fdl
415 | ValCollection (_, _, el) -> exprl acc el
416 | KeyValCollection (_, _, fdl) -> List.fold_left ~f:field ~init:acc fdl
417 | This ->
418 check_all_init p env acc;
420 | Fun_id _
421 | Method_id _
422 | Smethod_id _
423 | Method_caller _
424 | EnumClassLabel _
425 | Id _ ->
427 | Lvar _
428 | Lplaceholder _
429 | Dollardollar _ ->
431 | Obj_get ((_, _, This), (_, _, Id ((_, vx) as v)), _, false) ->
432 if SMap.mem vx env.props && not (S.mem vx acc) then (
433 Errors.read_before_write v;
435 ) else
437 | Clone e -> expr acc e
438 | Obj_get (e1, e2, _, false) ->
439 let acc = expr acc e1 in
440 expr acc e2
441 | Obj_get _ -> acc
442 | Array_get (e, eo) ->
443 let acc = expr acc e in
444 (match eo with
445 | None -> acc
446 | Some e -> expr acc e)
447 | Class_const _
448 | Class_get _ ->
450 | Call
451 ( (_, p, Obj_get ((_, _, This), (_, _, Id (_, f)), _, false)),
454 unpacked_element ) ->
455 let method_ = Env.get_method env f in
456 (match method_ with
457 | None ->
458 check_all_init p env acc;
460 | Some method_ ->
461 (match !method_ with
462 | Done -> acc
463 | Todo b ->
464 (* First time we encounter this private method. Let's check its
465 * arguments first, and then recurse into the method body.
467 let acc = List.fold_left ~f:expr ~init:acc el in
468 let acc =
469 Option.value_map ~f:(expr acc) ~default:acc unpacked_element
471 method_ := Done;
472 toplevel env acc b.fb_ast))
473 | Call (e, _, el, unpacked_element) ->
474 let el =
475 match e with
476 | (_, _, Id (_, fun_name)) when is_whitelisted fun_name ->
477 List.filter el ~f:(function
478 | (_, _, This) -> false
479 | _ -> true)
480 | _ -> el
482 let acc = List.fold_left ~f:expr ~init:acc el in
483 let acc = Option.value_map ~f:(expr acc) ~default:acc unpacked_element in
484 expr acc e
485 | True
486 | False
487 | Int _
488 | Float _
489 | Null
490 | String _
491 | String2 _
492 | PrefixedString _ ->
494 | Yield e -> afield acc e
495 | Await e -> expr acc e
496 | Tuple el -> List.fold_left ~f:expr ~init:acc el
497 | List _ ->
498 (* List is always an lvalue *)
500 | New (_, _, el, unpacked_element, _) ->
501 let acc = exprl acc el in
502 let acc = Option.value_map ~default:acc ~f:(expr acc) unpacked_element in
504 | Record (_, fdl) -> List.fold_left ~f:field ~init:acc fdl
505 | Pair (_, e1, e2) ->
506 let acc = expr acc e1 in
507 expr acc e2
508 | Cast (_, e)
509 | Unop (_, e) ->
510 expr acc e
511 | Binop (Ast_defs.Eq None, e1, e2) ->
512 let acc = expr acc e2 in
513 assign_expr env acc e1
514 | Binop (Ast_defs.Ampamp, e, _)
515 | Binop (Ast_defs.Barbar, e, _) ->
516 expr acc e
517 | Binop (_, e1, e2) ->
518 let acc = expr acc e1 in
519 expr acc e2
520 | Pipe (_, e1, e2) ->
521 let acc = expr acc e1 in
522 expr acc e2
523 | Eif (e1, None, e3) ->
524 let acc = expr acc e1 in
525 expr acc e3
526 | Eif (e1, Some e2, e3) ->
527 let acc = expr acc e1 in
528 let acc = expr acc e2 in
529 expr acc e3
530 | Is (e, _) -> expr acc e
531 | As (e, _, _) -> expr acc e
532 | Efun (f, _)
533 | Lfun (f, _) ->
534 let acc = fun_paraml acc f.f_params in
535 (* We don't need to analyze the body of closures *)
537 | Xml (_, l, el) ->
538 let l = List.map l ~f:get_xhp_attr_expr in
539 let acc = exprl acc l in
540 exprl acc el
541 | Callconv (_, e) -> expr acc e
542 | Shape fdm ->
543 List.fold_left
545 begin
546 fun acc (_, v) ->
547 expr acc v
549 ~init:acc
551 | ExpressionTree _ -> acc
552 | Omitted -> acc
553 | Import _ -> acc
554 | Collection _ -> acc
555 | FunctionPointer _ -> acc
556 | ET_Splice e -> expr acc e
557 | ReadonlyExpr e -> expr acc e
558 | Hole (e, _, _, _) -> expr acc e
560 and case env acc = function
561 | Default (_, b)
562 | Case (_, b) ->
563 block env acc b
565 and case_has_body = function
566 | Default _ -> true
567 | Case (_, []) -> false
568 | Case _ -> true
570 and catch env acc (_, _, b) = block env acc b
572 and field env acc (e1, e2) =
573 let acc = expr env acc e1 in
574 let acc = expr env acc e2 in
577 and afield env acc = function
578 | AFvalue e -> expr env acc e
579 | AFkvalue (e1, e2) ->
580 let acc = expr env acc e1 in
581 let acc = expr env acc e2 in
584 and fun_param env acc param =
585 match param.param_expr with
586 | None -> acc
587 | Some x -> expr env acc x
589 and fun_paraml env acc l = List.fold_left ~f:(fun_param env) ~init:acc l
591 let class_ tenv c =
592 if not FileInfo.(equal_mode c.c_mode Mhhi) then
593 List.iter c.c_vars ~f:(fun cv ->
594 match cv.cv_expr with
595 | Some _ when is_lateinit cv ->
596 Errors.lateinit_with_default (fst cv.cv_id)
597 | None when cv.cv_is_static ->
598 let ty_opt =
599 Option.map
600 ~f:(Decl_hint.hint tenv.Typing_env_types.decl_env)
601 (hint_of_type_hint cv.cv_type)
604 is_lateinit cv
605 || cv.cv_abstract
606 || type_does_not_require_init tenv ty_opt
607 then
609 else
610 Errors.missing_assign (fst cv.cv_id)
611 | _ -> ());
612 let (c_constructor, _, _) = split_methods c.c_methods in
613 match c_constructor with
614 | _ when Ast_defs.is_c_interface c.c_kind -> ()
615 | Some { m_body = { fb_annotation = Nast.NamedWithUnsafeBlocks; _ }; _ } -> ()
616 | _ ->
617 let p =
618 match c_constructor with
619 | Some m -> fst m.m_name
620 | None -> fst c.c_name
622 let env = Env.make tenv c in
623 let inits = constructor env c_constructor in
624 let check_inits inits =
625 let uninit_props =
626 SMap.filter (fun k _ -> not (SSet.mem k inits)) env.props
628 if not (SMap.is_empty uninit_props) then
629 if SMap.mem DeferredMembers.parent_init_prop uninit_props then
630 Errors.no_construct_parent p
631 else
632 let class_uninit_props =
633 SMap.filter
634 (fun prop _ -> not (SSet.mem prop env.init_not_required_props))
635 uninit_props
637 if not (SMap.is_empty class_uninit_props) then
638 Errors.not_initialized
639 (p, snd c.c_name)
640 (SMap.bindings class_uninit_props
641 |> List.map ~f:(fun (name, _) ->
642 let pos =
643 class_prop_pos
644 (snd c.c_name)
645 name
646 (Typing_env.get_ctx tenv)
648 (pos, name)))
650 let check_throws_or_init_all inits =
651 match inits with
652 | S.Top ->
653 (* Constructor always throw, so checking that all properties are
654 * initialized is irrelevant. *)
656 | S.Set inits -> check_inits inits
658 if Ast_defs.is_c_trait c.c_kind || Ast_defs.is_c_abstract c.c_kind then
659 let has_constructor =
660 match c_constructor with
661 | None -> false
662 | Some m when m.m_abstract -> false
663 | Some _ -> true
665 if has_constructor then
666 check_throws_or_init_all inits
667 else
669 else
670 check_throws_or_init_all inits