Skip to content

Commit 6c95bdb

Browse files
Vincent Silesfacebook-github-bot
authored andcommitted
Cyclic base types for enums are not checked
Summary: We currently only perform cycle detection on enum constants, but the following code would make the runtime fatal ``` enum E : E {} ``` See T128241203 for the full report. This diff implements this detection for enum (and enum class while we're at it, even if the runtime would not fatal in that case). Reviewed By: chenmela Differential Revision: D39129381 fbshipit-source-id: 9b586e1f5b942d514ddd3a3bd1f07428616413b7
1 parent 8201ae8 commit 6c95bdb

File tree

5 files changed

+88
-1
lines changed

5 files changed

+88
-1
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
(*
2+
* Copyright (c) 2021, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the "hack" directory of this source tree.
7+
*
8+
*)
9+
10+
open Hh_prelude
11+
open Aast
12+
open Typing_defs
13+
module Env = Tast_env
14+
module Cls = Decl_provider.Class
15+
16+
let get_name dty =
17+
match get_node dty with
18+
| Tapply ((_, name), []) -> Some name
19+
| _ -> None
20+
21+
(* Check that the base type of an enum or (enum class) is not an alias
22+
* to the enum being defined:
23+
*
24+
* enum Foo : Foo {}
25+
* enum Bar0 : Bar1 {}
26+
* enum Bar1 : Bar0 {}
27+
*
28+
* Such code would make HHVM fatal.
29+
*
30+
* Note that we have a similar check for enum/class constants themselves in
31+
* Cyclic_class_constant but it doesn't take into account the empty enums.
32+
*)
33+
let find_cycle env class_name =
34+
(* Note w.r.t. Cyclic_class_constant:
35+
* Since `self` is not allowed (parsing error) in this position, we just
36+
* keep track of the hints we see.
37+
*)
38+
let rec spot_target seen current =
39+
let open Option in
40+
let enum_def = Env.get_enum env current in
41+
let enum_info = enum_def >>= Cls.enum_type in
42+
let te_base = enum_info >>= fun info -> get_name info.te_base in
43+
match te_base with
44+
| None -> None
45+
| Some base ->
46+
if SSet.mem base seen then
47+
Some seen
48+
else
49+
spot_target (SSet.add base seen) base
50+
in
51+
spot_target SSet.empty class_name
52+
53+
let handler =
54+
object
55+
inherit Tast_visitor.handler_base
56+
57+
method! at_class_ env c =
58+
let (pos, c_name) = c.c_name in
59+
match find_cycle env c_name with
60+
| Some stack ->
61+
Errors.add_typing_error
62+
Typing_error.(primary @@ Primary.Cyclic_class_def { pos; stack })
63+
| None -> ()
64+
end

hphp/hack/src/typing/tast_check/tast_check.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ let visitor ctx =
9999
Some Global_write_check.handler
100100
else
101101
None);
102+
(if skip_hierarchy_checks then
103+
None
104+
else
105+
Some Enum_check.handler);
102106
]
103107
in
104108
let handlers =
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?hh
2+
3+
enum E : E {}
4+
5+
enum E0 : E1 {}
6+
7+
enum E1 : E2 {}
8+
9+
enum E2 : E0 {}
10+
11+
/* enum XXX : self {} // not allowed */
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
File "enum_base_cycle.php", line 3, characters 6-6:
2+
Cyclic class definition : `E` (Typing[4013])
3+
File "enum_base_cycle.php", line 5, characters 6-7:
4+
Cyclic class definition : `E2` `E1` `E0` (Typing[4013])
5+
File "enum_base_cycle.php", line 7, characters 6-7:
6+
Cyclic class definition : `E2` `E1` `E0` (Typing[4013])
7+
File "enum_base_cycle.php", line 9, characters 6-7:
8+
Cyclic class definition : `E2` `E1` `E0` (Typing[4013])

hphp/hack/test/unit/server_tests.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ let test_process_file_deferring () =
129129

130130
true
131131

132-
let expected_decling_count = 65
132+
let expected_decling_count = 66
133133

134134
(* This test verifies that the deferral/counting machinery works for
135135
ProviderUtils.compute_tast_and_errors_unquarantined. *)

0 commit comments

Comments
 (0)