Skip to content

Commit 857889a

Browse files
authored
Fix crash when this is typed as a generic T with no type constraints (#47991)
* fix this type validations for protected fields * update previous baselines * add new tests and baselines * ClassOrInterface can be returned as enclosingClass * inline function code and remove unnecessary blank lines * refactor enclosingClass checks
1 parent 82fc9b1 commit 857889a

7 files changed

+1177
-12
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20644,7 +20644,7 @@ namespace ts {
2064420644

2064520645
// Return true if the given class derives from each of the declaring classes of the protected
2064620646
// constituents of the given property.
20647-
function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol, writing: boolean) {
20647+
function isClassDerivedFromDeclaringClasses<T extends Type>(checkClass: T, prop: Symbol, writing: boolean) {
2064820648
return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ?
2064920649
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
2065020650
}
@@ -28396,14 +28396,15 @@ namespace ts {
2839628396
// of the property as base classes
2839728397
let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => {
2839828398
const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType;
28399-
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined;
28399+
return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing);
2840028400
});
2840128401
// A protected property is accessible if the property is within the declaring class or classes derived from it
2840228402
if (!enclosingClass) {
2840328403
// allow PropertyAccessibility if context is in function with this parameter
28404-
// static member access is disallow
28405-
let thisParameter: ParameterDeclaration | undefined;
28406-
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) {
28404+
// static member access is disallowed
28405+
enclosingClass = getEnclosingClassFromThisParameter(location);
28406+
enclosingClass = enclosingClass && isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing);
28407+
if (flags & ModifierFlags.Static || !enclosingClass) {
2840728408
if (errorNode) {
2840828409
error(errorNode,
2840928410
Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses,
@@ -28412,9 +28413,6 @@ namespace ts {
2841228413
}
2841328414
return false;
2841428415
}
28415-
28416-
const thisType = getTypeFromTypeNode(thisParameter.type);
28417-
enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType as TypeParameter) : thisType) as TypeReference).target;
2841828416
}
2841928417
// No further restrictions for static properties
2842028418
if (flags & ModifierFlags.Static) {
@@ -28435,6 +28433,18 @@ namespace ts {
2843528433
return true;
2843628434
}
2843728435

28436+
function getEnclosingClassFromThisParameter(node: Node): InterfaceType | undefined {
28437+
const thisParameter = getThisParameterFromNodeContext(node);
28438+
let thisType = thisParameter?.type && getTypeFromTypeNode(thisParameter.type);
28439+
if (thisType && thisType.flags & TypeFlags.TypeParameter) {
28440+
thisType = getConstraintOfTypeParameter(thisType as TypeParameter);
28441+
}
28442+
if (thisType && getObjectFlags(thisType) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
28443+
return getTargetType(thisType) as InterfaceType;
28444+
}
28445+
return undefined;
28446+
}
28447+
2843828448
function getThisParameterFromNodeContext(node: Node) {
2843928449
const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false);
2844028450
return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
tests/cases/compiler/protectedMembersThisParameter.ts(9,9): error TS2445: Property 'secret' is protected and only accessible within class 'Message' and its subclasses.
2+
tests/cases/compiler/protectedMembersThisParameter.ts(30,7): error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
3+
tests/cases/compiler/protectedMembersThisParameter.ts(41,7): error TS2446: Property 'a' is protected and only accessible through an instance of class 'C'. This is an instance of class 'B'.
4+
tests/cases/compiler/protectedMembersThisParameter.ts(42,7): error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
5+
tests/cases/compiler/protectedMembersThisParameter.ts(46,7): error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
6+
tests/cases/compiler/protectedMembersThisParameter.ts(47,7): error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
7+
tests/cases/compiler/protectedMembersThisParameter.ts(51,7): error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
8+
tests/cases/compiler/protectedMembersThisParameter.ts(52,7): error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
9+
tests/cases/compiler/protectedMembersThisParameter.ts(55,7): error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
10+
tests/cases/compiler/protectedMembersThisParameter.ts(56,7): error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
11+
tests/cases/compiler/protectedMembersThisParameter.ts(64,9): error TS2445: Property 'd1' is protected and only accessible within class 'D1' and its subclasses.
12+
tests/cases/compiler/protectedMembersThisParameter.ts(68,9): error TS2445: Property 'd1' is protected and only accessible within class 'D1' and its subclasses.
13+
tests/cases/compiler/protectedMembersThisParameter.ts(76,9): error TS2445: Property 'd' is protected and only accessible within class 'D2' and its subclasses.
14+
tests/cases/compiler/protectedMembersThisParameter.ts(77,9): error TS2445: Property 'd2' is protected and only accessible within class 'D2' and its subclasses.
15+
tests/cases/compiler/protectedMembersThisParameter.ts(80,9): error TS2445: Property 'd' is protected and only accessible within class 'D2' and its subclasses.
16+
tests/cases/compiler/protectedMembersThisParameter.ts(81,9): error TS2445: Property 'd2' is protected and only accessible within class 'D2' and its subclasses.
17+
18+
19+
==== tests/cases/compiler/protectedMembersThisParameter.ts (16 errors) ====
20+
class Message {
21+
protected secret(): void {}
22+
}
23+
class MessageWrapper {
24+
message: Message = new Message();
25+
wrap<T>() {
26+
let m = this.message;
27+
let f = function(this: T) {
28+
m.secret(); // should error
29+
~~~~~~
30+
!!! error TS2445: Property 'secret' is protected and only accessible within class 'Message' and its subclasses.
31+
}
32+
}
33+
}
34+
35+
class A {
36+
protected a() {}
37+
}
38+
class B extends A {
39+
protected b() {}
40+
}
41+
class C extends A {
42+
protected c() {}
43+
}
44+
class Z {
45+
protected z() {}
46+
}
47+
48+
function bA<T extends A>(this: T, arg: B) {
49+
this.a();
50+
arg.a();
51+
arg.b(); // should error to avoid cross-hierarchy protected access https://www.typescriptlang.org/docs/handbook/2/classes.html#cross-hierarchy-protected-access
52+
~
53+
!!! error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
54+
}
55+
function bB<T extends B>(this: T, arg: B) {
56+
this.a();
57+
this.b();
58+
arg.a();
59+
arg.b();
60+
}
61+
function bC<T extends C>(this: T, arg: B) {
62+
this.a();
63+
this.c();
64+
arg.a(); // should error
65+
~
66+
!!! error TS2446: Property 'a' is protected and only accessible through an instance of class 'C'. This is an instance of class 'B'.
67+
arg.b(); // should error
68+
~
69+
!!! error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
70+
}
71+
function bZ<T extends Z>(this: T, arg: B) {
72+
this.z();
73+
arg.a(); // should error
74+
~
75+
!!! error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
76+
arg.b(); // should error
77+
~
78+
!!! error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
79+
}
80+
function bString<T extends string>(this: T, arg: B) {
81+
this.toLowerCase();
82+
arg.a(); // should error
83+
~
84+
!!! error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
85+
arg.b(); // should error
86+
~
87+
!!! error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
88+
}
89+
function bAny<T>(this: T, arg: B) {
90+
arg.a(); // should error
91+
~
92+
!!! error TS2445: Property 'a' is protected and only accessible within class 'A' and its subclasses.
93+
arg.b(); // should error
94+
~
95+
!!! error TS2445: Property 'b' is protected and only accessible within class 'B' and its subclasses.
96+
}
97+
98+
class D {
99+
protected d() {}
100+
101+
derived1(arg: D1) {
102+
arg.d();
103+
arg.d1(); // should error
104+
~~
105+
!!! error TS2445: Property 'd1' is protected and only accessible within class 'D1' and its subclasses.
106+
}
107+
derived1ThisD(this: D, arg: D1) {
108+
arg.d();
109+
arg.d1(); // should error
110+
~~
111+
!!! error TS2445: Property 'd1' is protected and only accessible within class 'D1' and its subclasses.
112+
}
113+
derived1ThisD1(this: D1, arg: D1) {
114+
arg.d();
115+
arg.d1();
116+
}
117+
118+
derived2(arg: D2) {
119+
arg.d(); // should error because of overridden method in D2
120+
~
121+
!!! error TS2445: Property 'd' is protected and only accessible within class 'D2' and its subclasses.
122+
arg.d2(); // should error
123+
~~
124+
!!! error TS2445: Property 'd2' is protected and only accessible within class 'D2' and its subclasses.
125+
}
126+
derived2ThisD(this: D, arg: D2) {
127+
arg.d(); // should error because of overridden method in D2
128+
~
129+
!!! error TS2445: Property 'd' is protected and only accessible within class 'D2' and its subclasses.
130+
arg.d2(); // should error
131+
~~
132+
!!! error TS2445: Property 'd2' is protected and only accessible within class 'D2' and its subclasses.
133+
}
134+
derived2ThisD2(this: D2, arg: D2) {
135+
arg.d();
136+
arg.d2();
137+
}
138+
}
139+
class D1 extends D {
140+
protected d1() {}
141+
}
142+
class D2 extends D {
143+
protected d() {}
144+
protected d2() {}
145+
}
146+
147+

0 commit comments

Comments
 (0)