Skip to content

Commit f39c879

Browse files
dlreevesmeta-codesync[bot]
authored andcommitted
Error on nameof of a strict-isolation package's class
Summary: # Context `nameof Foo` yields the string name of `Foo` without requiring the package of `Foo` to be loaded, so it is normally exempt from the package-boundary check (`inside_nameof` forces the check off in `class_expr`). Strict-isolation packages (`enable_strict_isolation = true`) are the exception: their presence must not be statically referenced from outside, so even a `nameof` of one of their classes is treated as a real cross-package reference. # Solution At the `nameof` typing site, when the target class belongs to a strict-isolation package, run the ordinary package-boundary check (`Typing_visibility.check_package_access`) and report any violation as a hard error, exactly as a normal reference would: a cross-package `nameof` is `Typing[4472]` (cross-package access), and a `nameof` of a class on the package excluded path is `Typing[4518]` (excluded-path access). Same-package references, and targets that are not strict-isolation packages, produce nothing -- matching the other checks in this stack. # Tests New verify tests under `test/package/strict_isolation/`: a cross-package `nameof` into a strict-isolation package errors (`Typing[4472]`); a `nameof` of a class defined on an excluded path of the package errors (`Typing[4518]`, exercising the `ExcludedPathAccess` branch); the same reference within its own package, and a `nameof` of a non-strict-isolation package, produce nothing. Reviewed By: madgen Differential Revision: D112642508 fbshipit-source-id: 3cfd7c7c8f22a88b4ffa3a0774fa4cc863cf651c
1 parent 1d9d645 commit f39c879

13 files changed

Lines changed: 206 additions & 3 deletions

hphp/hack/src/typing/typing.ml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11952,16 +11952,35 @@ end = struct
1195211952
| Expr.Check_all
1195311953
| Expr.Skip_package ->
1195411954
let should_check_package_boundary =
11955+
(* Some reference contexts are exempt from the package boundary check
11956+
by default (nameof, catch, ::class), but a strict-isolation target
11957+
opts them back in: its classes must not be statically referenced
11958+
from outside its package. Reuses the already-fetched decl, so no
11959+
extra heap access. *)
11960+
let exempt_unless_strict_isolation spec =
11961+
if
11962+
Typing_packages.is_strict_isolation_target
11963+
env
11964+
(Cls.get_package class_)
11965+
then
11966+
`Yes spec
11967+
else
11968+
`No
11969+
in
1195511970
if
11956-
inside_nameof
11957-
|| is_attribute
11958-
|| is_catch
11971+
is_attribute
1195911972
||
1196011973
match attribute_check_policy with
1196111974
| Expr.Skip_package -> true
1196211975
| _ -> false
1196311976
then
1196411977
`No
11978+
else if is_catch then
11979+
(* `catch (C $e)` references the exception class C. *)
11980+
exempt_unless_strict_isolation Typing_error.Primary.Package.Class
11981+
else if inside_nameof then
11982+
(* `nameof C` yields C's name without loading its package. *)
11983+
exempt_unless_strict_isolation Typing_error.Primary.Package.Class
1196511984
else if is_const then begin
1196611985
if Env.package_allow_classconst_violations env then
1196711986
if is_classptr then
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// isolated/exn.php
2+
<?hh
3+
// An exception class in the strict-isolation package `isolated`.
4+
class IsolatedException extends Exception {}
5+
6+
//// intern/use.php
7+
<?hh
8+
// `intern` does not include `isolated`; catching its exception statically
9+
// references the strict-isolation package, which is a hard package violation.
10+
function use_isolated_catch(): void {
11+
try {
12+
} catch (IsolatedException $_e) {
13+
}
14+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error: Typing[4472] Cannot access class `IsolatedException` defined in package `isolated` which may not be available [1]
2+
-> `IsolatedException` is defined in catch_cross_package_forbidden.php--isolated/exn.php [2]
3+
-> `IsolatedException` belongs to package `isolated` by this package definition [3]
4+
-> package `intern` is available because the current file belongs to it by this package definition [4]
5+
-> `intern` includes `shared` via its definition [5]
6+
7+
catch_cross_package_forbidden.php--intern/use.php:6:12
8+
4 | function use_isolated_catch(): void {
9+
5 | try {
10+
6 | } catch (IsolatedException $_e) {
11+
| ^^^^^^^^^^^^^^^^^ [1]
12+
7 | }
13+
8 | }
14+
15+
catch_cross_package_forbidden.php--isolated/exn.php:3:7
16+
1 | <?hh
17+
2 | // An exception class in the strict-isolation package `isolated`.
18+
3 | class IsolatedException extends Exception {}
19+
| ^^^^^^^^^^^^^^^^^ [2]
20+
21+
PACKAGES.toml:14:11
22+
4 | include_paths = ["//shared/"]
23+
5 |
24+
6 | [packages.intern]
25+
| ^^^^^^ [4]
26+
7 | include_paths = ["//intern/"]
27+
8 | includes = ["shared"]
28+
| ^^^^^^^^ [5]
29+
9 |
30+
10 | # A package that opts into strict isolation. Its presence may not be dynamically
31+
11 | # observed (no `package` expression / `__RequirePackage`), and
32+
12 | # package_exclude_patterns (e.g. __tests__) do not grant a typecheck exemption
33+
13 | # for references into it.
34+
14 | [packages.isolated]
35+
| ^^^^^^^^ [3]
36+
15 | include_paths = ["//isolated/"]
37+
16 | includes = ["intern", "shared"]
38+
39+
1 error found
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// isolated/exn.php
2+
<?hh
3+
// An exception class in the strict-isolation package `isolated`.
4+
class IsolatedException2 extends Exception {}
5+
6+
//// isolated/use.php
7+
<?hh
8+
// Same package (`isolated`) may catch its own exception: no violation.
9+
function use_within_isolated(): void {
10+
try {
11+
} catch (IsolatedException2 $_e) {
12+
}
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No errors
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// isolated/c.php
2+
<?hh
3+
// A class in the strict-isolation package `isolated`.
4+
class IsolatedNameof {}
5+
6+
//// intern/use.php
7+
<?hh
8+
// `intern` does not include `isolated`. Even though `nameof` does not require the
9+
// package to be loaded, a strict-isolation package may not be statically
10+
// referenced from outside, so this is a hard package-boundary error.
11+
function use_isolated_nameof(): void {
12+
$_ = nameof IsolatedNameof;
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: Typing[4472] Cannot access class `IsolatedNameof` defined in package `isolated` which may not be available [1]
2+
-> `IsolatedNameof` is defined in nameof_cross_package_error.php--isolated/c.php [2]
3+
-> `IsolatedNameof` belongs to package `isolated` by this package definition [3]
4+
-> package `intern` is available because the current file belongs to it by this package definition [4]
5+
-> `intern` includes `shared` via its definition [5]
6+
7+
nameof_cross_package_error.php--intern/use.php:6:15
8+
4 | // referenced from outside, so this is a hard package-boundary error.
9+
5 | function use_isolated_nameof(): void {
10+
6 | $_ = nameof IsolatedNameof;
11+
| ^^^^^^^^^^^^^^ [1]
12+
7 | }
13+
14+
nameof_cross_package_error.php--isolated/c.php:3:7
15+
1 | <?hh
16+
2 | // A class in the strict-isolation package `isolated`.
17+
3 | class IsolatedNameof {}
18+
| ^^^^^^^^^^^^^^ [2]
19+
20+
PACKAGES.toml:14:11
21+
4 | include_paths = ["//shared/"]
22+
5 |
23+
6 | [packages.intern]
24+
| ^^^^^^ [4]
25+
7 | include_paths = ["//intern/"]
26+
8 | includes = ["shared"]
27+
| ^^^^^^^^ [5]
28+
9 |
29+
10 | # A package that opts into strict isolation. Its presence may not be dynamically
30+
11 | # observed (no `package` expression / `__RequirePackage`), and
31+
12 | # package_exclude_patterns (e.g. __tests__) do not grant a typecheck exemption
32+
13 | # for references into it.
33+
14 | [packages.isolated]
34+
| ^^^^^^^^ [3]
35+
15 | include_paths = ["//isolated/"]
36+
16 | includes = ["intern", "shared"]
37+
38+
1 error found
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// isolated/__tests__/helper.php
2+
<?hh
3+
// A class on an excluded path (__tests__) within the strict-isolation package
4+
// `isolated`.
5+
class IsolatedTestOnlyNameof {}
6+
7+
//// isolated/use.php
8+
<?hh
9+
// Non-excluded code in `isolated` referencing its own excluded-path class via
10+
// `nameof` is an error too: the class is deployment-only (an excluded path), so
11+
// the reference is forbidden even within the same package. This exercises the
12+
// `ExcludedPathAccess` branch of the nameof check.
13+
function use_isolated_excluded_nameof(): void {
14+
$_ = nameof IsolatedTestOnlyNameof;
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: Typing[4518] Cannot access class `IsolatedTestOnlyNameof`: it is defined on an excluded path of the strict-isolation package `isolated`, and code outside the excluded path may not reference it [1]
2+
-> `IsolatedTestOnlyNameof` is defined here, in nameof_excluded_path_error.php--isolated/__tests__/helper.php -- an excluded path (e.g. matching `package_exclude_patterns`) [2]
3+
-> package `isolated` is a strict-isolation package (declared here); code on an excluded path is deployment-only and may not be referenced from outside that path [3]
4+
5+
nameof_excluded_path_error.php--isolated/use.php:7:15
6+
5 | // `ExcludedPathAccess` branch of the nameof check.
7+
6 | function use_isolated_excluded_nameof(): void {
8+
7 | $_ = nameof IsolatedTestOnlyNameof;
9+
| ^^^^^^^^^^^^^^^^^^^^^^ [1]
10+
8 | }
11+
12+
nameof_excluded_path_error.php--isolated/__tests__/helper.php:4:7
13+
2 | // A class on an excluded path (__tests__) within the strict-isolation package
14+
3 | // `isolated`.
15+
4 | class IsolatedTestOnlyNameof {}
16+
| ^^^^^^^^^^^^^^^^^^^^^^ [2]
17+
18+
PACKAGES.toml:14:11
19+
12 | # package_exclude_patterns (e.g. __tests__) do not grant a typecheck exemption
20+
13 | # for references into it.
21+
14 | [packages.isolated]
22+
| ^^^^^^^^ [3]
23+
15 | include_paths = ["//isolated/"]
24+
16 | includes = ["intern", "shared"]
25+
26+
1 error found
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//// standalone/c.php
2+
<?hh
3+
// A class in `standalone`, which does NOT opt into strict isolation.
4+
class StandaloneNameof {}
5+
6+
//// intern/use.php
7+
<?hh
8+
// `intern` does not include `standalone`, but `standalone` is not a
9+
// strict-isolation package, so `nameof` on it produces no warning.
10+
function use_standalone_nameof(): void {
11+
$_ = nameof StandaloneNameof;
12+
}

0 commit comments

Comments
 (0)