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.
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. *)
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
33 | (Set s1
, Set s2
) -> Set
(SSet.union s1 s2
)
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
47 | Set s
-> Set
(SSet.add x s
)
52 | Set s
-> SSet.mem x s
54 let empty = Set
SSet.empty
57 let parent_init_prop = "parent::" ^
SN.Members.__construct
59 let lookup_props env class_name props
=
64 if String.equal name
parent_init_prop then
65 Some
(Typing_make_type.nonnull
Typing_reason.Rnone
)
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
77 (* If a type is missing, nullable, or dynamic, initialization is not required *)
78 let type_does_not_require_init env
ty_opt =
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
92 (* Exception raised when we hit a return statement and the initialization
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 }
99 * $this->y = 1; // { x, y }
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
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.
125 (* We already computed this method *)
127 (* We have never computed this private method before *)
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
146 match sc.sc_constructor
with
147 | Some s
-> not
(sm_abstract s
)
150 let (private_props
, _
) =
151 if shallow_decl_enabled ctx then
152 DeferredMembers.class_ tenv
sc
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
160 (fun _
ty_opt -> not
(type_does_not_require_init tenv
ty_opt))
163 if not
@@ SMap.is_empty
uninit then
166 |> Errors.constructor_required c
.c_name
);
168 let ( add_init_not_required_props
,
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
)
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
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
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 }
201 if not
(Aast.equal_visibility m
.m_visibility Private
) then
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
213 (*****************************************************************************)
214 (* List of functions that can use '$this' before the initialization is
217 (*****************************************************************************)
219 let is_whitelisted = function
220 | x
when String.equal x
SN.StdlibFunctions.get_class
-> true
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
230 (match Decl_provider.Class.get_prop decl prop_name
with
231 | None
-> Pos_or_decl.none
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
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
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
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:
271 * // This branch initialize a set of properties S1
274 * // This branch initialize another set of properties S2
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`
289 let rec constructor env 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
=
303 | (_
, _
, Obj_get
((_
, _
, This
), (_
, _
, Id
(_
, y
)), _
, false)) ->
305 | (_
, _
, List el
) -> List.fold_left ~f
:(assign_expr env
) ~init
:acc el
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
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
320 if Typing_func_terminality.expression_exits env
.tenv e
then
328 if are_all_init env
acc then
331 raise
(InitReturn
acc)
332 | Yield_break
-> S.Top
334 let acc = expr acc x
in
335 if are_all_init env
acc then
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
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)
349 let acc = block acc b in
351 | While
(e
, _
) -> expr acc e
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
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
363 | Foreach
(e
, _
, _
) ->
364 let acc = expr acc e
in
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
374 | Fallthrough
-> S.empty
376 | Block
b -> block acc b
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
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 =
398 if not
(S.mem cv acc) then Errors.call_before_init p
cv
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
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
418 check_all_init p env
acc;
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
;
437 | Clone e
-> expr acc e
438 | Obj_get
(e1
, e2
, _
, false) ->
439 let acc = expr acc e1
in
442 | Array_get
(e
, eo
) ->
443 let acc = expr acc e
in
446 | Some e
-> expr acc e
)
451 ( (_
, p
, Obj_get
((_
, _
, This
), (_
, _
, Id
(_
, f)), _
, false)),
454 unpacked_element
) ->
455 let method_ = Env.get_method env
f in
458 check_all_init p env
acc;
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
469 Option.value_map ~
f:(expr acc) ~default
:acc unpacked_element
472 toplevel env
acc b.fb_ast
))
473 | Call
(e
, _
, el
, unpacked_element
) ->
476 | (_
, _
, Id
(_
, fun_name
)) when is_whitelisted fun_name
->
477 List.filter
el ~
f:(function
478 | (_
, _
, This
) -> false
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
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
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
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
, _
) ->
517 | Binop
(_
, e1
, e2
) ->
518 let acc = expr acc e1
in
520 | Pipe
(_
, e1
, e2
) ->
521 let acc = expr acc e1
in
523 | Eif
(e1
, None
, e3
) ->
524 let acc = expr acc e1
in
526 | Eif
(e1
, Some e2
, e3
) ->
527 let acc = expr acc e1
in
528 let acc = expr acc e2
in
530 | Is
(e
, _
) -> expr acc e
531 | As
(e
, _
, _
) -> expr acc e
534 let acc = fun_paraml acc f.f_params
in
535 (* We don't need to analyze the body of closures *)
538 let l = List.map
l ~
f:get_xhp_attr_expr
in
539 let acc = exprl acc l in
541 | Callconv
(_
, e
) -> expr acc e
551 | ExpressionTree _
-> 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
565 and case_has_body
= function
567 | Case
(_
, []) -> false
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
587 | Some x
-> expr env
acc x
589 and fun_paraml env
acc l = List.fold_left ~
f:(fun_param env
) ~init
:acc l
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
->
600 ~
f:(Decl_hint.hint tenv
.Typing_env_types.decl_env)
601 (hint_of_type_hint
cv.cv_type
)
606 || type_does_not_require_init tenv
ty_opt
610 Errors.missing_assign
(fst
cv.cv_id
)
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
; _
}; _
} -> ()
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 =
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
632 let class_uninit_props =
634 (fun prop _
-> not
(SSet.mem prop env.init_not_required_props))
637 if not
(SMap.is_empty
class_uninit_props) then
638 Errors.not_initialized
640 (SMap.bindings
class_uninit_props
641 |> List.map ~
f:(fun (name, _
) ->
646 (Typing_env.get_ctx tenv
)
650 let check_throws_or_init_all inits =
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
662 | Some m
when m
.m_abstract
-> false
665 if has_constructor then
666 check_throws_or_init_all inits
670 check_throws_or_init_all inits