2 * Copyright (c) 2015, 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.
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. *)
17 module DICheck
= Decl_init_check
18 module SN
= Naming_special_names
20 module SSetWTop
= struct
29 | Set s1
, Set s2
-> Set
(SSet.union s1 s2
)
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
43 | Set s
-> Set
(SSet.add x s
)
48 | Set s
-> SSet.mem x s
50 let empty = Set
SSet.empty
55 (* Exception raised when we hit a return statement and the initialization
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 }
62 * $this->y = 1; // { x, y }
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
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.
89 (* We already computed this method *)
92 (* We have never computed this private method before *)
96 methods
: method_status
ref SMap.t
;
98 tenv
: Typing_env.env
;
101 let parent_id c
= match c
.c_extends
with
102 | [(_
, Happly
((_
, parent_id), _
))] -> Some
parent_id
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
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; }
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
135 (*****************************************************************************)
136 (* List of functions that can use '$this' before the initialization is
139 (*****************************************************************************)
141 let is_whitelisted = function
142 | x
when x
= SN.StdlibFunctions.get_class
-> true
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 *)
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; _
}; _
} -> ()
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
171 Errors.not_initialized
(p, snd c
.c_name
) (SSet.elements
uninit_props)
174 let check_throws_or_init_all inits =
177 (* Constructor always throw, so checking that all properties are
178 * initialized is irrelevant. *)
183 save_initialized_members_for_suggest (snd c
.c_name
) inits;
184 if c
.c_kind
= Ast.Ctrait
|| c
.c_kind
= Ast.Cabstract
186 let has_constructor = match c
.c_constructor
with
188 | Some m
when m
.m_abstract
-> false
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:
208 * // This branch initialize a set of properties S1
211 * // This branch initialize another set of properties S2
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`
226 and constructor
env 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
=
240 and assign_expr
env acc e1
=
242 | _
, Obj_get
((_
, This
), (_
, Id
(_
, y
)), _
) ->
245 List.fold_left ~f
:(assign_expr
env) ~init
:acc el
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
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
259 if (Typing_func_terminality.expression_exits
env.tenv e
)
267 | Return
(_
, None
) ->
268 if are_all_init
env acc
270 else raise
(InitReturn
acc)
271 | Return
(_
, Some x
) ->
272 let acc = expr acc x
in
273 if are_all_init
env acc
275 else raise
(InitReturn
acc)
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
283 | Some e
-> assign_expr
env acc e
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)
292 let acc = block acc b in
297 let acc = expr acc us
.us_expr
in
298 block acc us
.us_block
299 | For
(e1
, _
, _
, _
) ->
302 let acc = expr acc e
in
303 let cl = List.map
cl (case acc) in
304 let c = S.inter_list cl in
306 | Foreach
(e
, _
, _
) ->
307 let acc = expr acc e
in
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
317 | Fallthrough
-> S.empty
322 (* Scoped local variable cannot escape the block *)
324 | Block
b -> block acc b
325 | Markup
(_
, eopt
) -> (match eopt
with
326 | Some e
-> expr acc e
329 | Declare
(_
, e
, b) ->
330 let acc = expr acc e
in
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
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
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
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
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)
383 | Clone e
-> expr acc e
384 | Obj_get
(e1
, e2
, _
) ->
385 let acc = expr acc e1
in
387 | Array_get
(e
, eo
) ->
388 let acc = expr acc e
in
391 | Some e
-> expr acc e
)
394 | Call
(Cnormal
, (p, Obj_get
((_
, This
), (_
, Id
(_
, f)), _
)), _
, _
, _
) ->
395 let method_ = Env.get_method env f in
398 check_all_init
p env acc;
405 let fb = Nast.assert_named_body
b in
406 toplevel
env acc fb.fnb_nast
409 | Call
(_
, e
, _
, el
, uel
) ->
413 | _
, Id
(_
, fun_name
) when is_whitelisted fun_name
->
414 List.filter
el begin function
420 let acc = List.fold_left ~
f:expr ~init
:acc el in
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
436 | Dollar e
-> expr acc e
437 | Await e
-> expr acc e
438 | Suspend e
-> expr acc e
440 (* List is always an lvalue *)
444 | Special_func
(Gena e
)
445 | Special_func
(Gen_array_rec e
) ->
447 | Special_func
(Genva
el) ->
449 | New
(_
, el, uel
, _
) ->
452 let acc = expr acc e1
in
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
, _
) ->
462 | Binop
(_
, e1
, e2
) ->
463 let acc = expr acc e1
in
465 | Pipe
(_
, e1
, e2
) ->
466 let acc = expr acc e1
in
468 | Eif
(e1
, None
, e3
) ->
469 let acc = expr acc e1
in
471 | Eif
(e1
, Some e2
, e3
) ->
472 let acc = expr acc e1
in
473 let acc = expr acc e2
in
475 | InstanceOf
(e
, _
) -> expr acc e
476 | Is
(e
, _
) -> expr acc e
477 | As
(e
, _
, _
) -> expr acc e
479 let acc = fun_paraml acc f.f_params
in
480 (* We don't need to analyze the body of closures *)
483 let l = List.map
l get_xhp_attr_expr
in
484 let acc = exprl acc l in
486 | Callconv
(_
, e
) -> expr acc e
489 ~
f:begin fun acc (_
, v
) ->
495 | NewAnonClass _
-> acc
497 and case env acc = function
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
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
519 | Some x
-> expr env acc x
521 and fun_paraml env acc l = List.fold_left ~
f:(fun_param
env) ~init
:acc l