Skip to content

Commit 88d31a4

Browse files
Fix nil pointer dereference in addPropertyToElementList (#3361)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
1 parent 767c22c commit 88d31a4

6 files changed

Lines changed: 318 additions & 2 deletions

internal/checker/nodebuilderimpl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2448,7 +2448,7 @@ func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, t
24482448
writeType := b.ch.getWriteTypeOfSymbol(propertySymbol)
24492449
if !b.ch.isErrorType(propertyType) && !b.ch.isErrorType(writeType) {
24502450
propDeclaration := ast.GetDeclarationOfKind(propertySymbol, ast.KindPropertyDeclaration)
2451-
if propertyType != writeType || propertySymbol.Parent.Flags&ast.SymbolFlagsClass != 0 && propDeclaration == nil {
2451+
if propertyType != writeType || propertySymbol.Parent != nil && propertySymbol.Parent.Flags&ast.SymbolFlagsClass != 0 && propDeclaration == nil {
24522452
symbolMapper := b.ch.valueSymbolLinks.Get(propertySymbol).mapper
24532453
if getterDeclaration := ast.GetDeclarationOfKind(propertySymbol, ast.KindGetAccessor); getterDeclaration != nil {
24542454
getterSignature := b.ch.getSignatureFromDeclaration(getterDeclaration)
@@ -2473,7 +2473,7 @@ func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, t
24732473
typeElements = append(typeElements, setter)
24742474
}
24752475
return typeElements
2476-
} else if propertySymbol.Parent.Flags&ast.SymbolFlagsClass != 0 && propDeclaration != nil && core.Find(propDeclaration.ModifierNodes(), func(m *ast.Node) bool {
2476+
} else if propertySymbol.Parent != nil && propertySymbol.Parent.Flags&ast.SymbolFlagsClass != 0 && propDeclaration != nil && core.Find(propDeclaration.ModifierNodes(), func(m *ast.Node) bool {
24772477
return m.Kind == ast.KindAccessorKeyword
24782478
}) != nil {
24792479
fakeGetterSignature := b.ch.newSignature(SignatureFlagsNone, nil, nil, nil, nil, propertyType, nil, 0)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
declarationEmitForMixinsWithStaticAccessors1.ts(26,11): error TS2417: Class static side 'typeof C' incorrectly extends base class static side 'typeof A & typeof B'.
2+
Types of property 'prototype' are incompatible.
3+
Type 'C' is not assignable to type 'A & B'.
4+
Property 'y' is missing in type 'C' but required in type 'B'.
5+
declarationEmitForMixinsWithStaticAccessors1.ts(26,21): error TS2510: Base constructors must all have the same return type.
6+
7+
8+
==== declarationEmitForMixinsWithStaticAccessors1.ts (2 errors) ====
9+
// Regression test: when a class extends a mixin that returns an intersection,
10+
// the class's anonymous constructor type inherits static accessor properties from the
11+
// intersection. These synthetic properties can have a nil parent symbol when the
12+
// constituent accessor declarations differ. Declaration emit must not crash when
13+
// serializing these properties.
14+
15+
function mix<T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(
16+
base1: T, base2: U
17+
): T & U {
18+
return null as any;
19+
}
20+
21+
class A {
22+
static get shared(): number { return 1; }
23+
static set shared(v: number) { }
24+
x: string = "";
25+
}
26+
27+
class B {
28+
static get shared(): number { return 2; }
29+
static set shared(v: number) { }
30+
y: number = 0;
31+
}
32+
33+
function make() {
34+
class C extends mix(A, B) {
35+
~
36+
!!! error TS2417: Class static side 'typeof C' incorrectly extends base class static side 'typeof A & typeof B'.
37+
!!! error TS2417: Types of property 'prototype' are incompatible.
38+
!!! error TS2417: Type 'C' is not assignable to type 'A & B'.
39+
!!! error TS2417: Property 'y' is missing in type 'C' but required in type 'B'.
40+
!!! related TS2728 declarationEmitForMixinsWithStaticAccessors1.ts:22:5: 'y' is declared here.
41+
~~~~~~~~~
42+
!!! error TS2510: Base constructors must all have the same return type.
43+
z: boolean = true;
44+
}
45+
return C;
46+
}
47+
48+
export const MixedClass = make();
49+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/compiler/declarationEmitForMixinsWithStaticAccessors1.ts] ////
2+
3+
//// [declarationEmitForMixinsWithStaticAccessors1.ts]
4+
// Regression test: when a class extends a mixin that returns an intersection,
5+
// the class's anonymous constructor type inherits static accessor properties from the
6+
// intersection. These synthetic properties can have a nil parent symbol when the
7+
// constituent accessor declarations differ. Declaration emit must not crash when
8+
// serializing these properties.
9+
10+
function mix<T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(
11+
base1: T, base2: U
12+
): T & U {
13+
return null as any;
14+
}
15+
16+
class A {
17+
static get shared(): number { return 1; }
18+
static set shared(v: number) { }
19+
x: string = "";
20+
}
21+
22+
class B {
23+
static get shared(): number { return 2; }
24+
static set shared(v: number) { }
25+
y: number = 0;
26+
}
27+
28+
function make() {
29+
class C extends mix(A, B) {
30+
z: boolean = true;
31+
}
32+
return C;
33+
}
34+
35+
export const MixedClass = make();
36+
37+
38+
//// [declarationEmitForMixinsWithStaticAccessors1.js]
39+
// Regression test: when a class extends a mixin that returns an intersection,
40+
// the class's anonymous constructor type inherits static accessor properties from the
41+
// intersection. These synthetic properties can have a nil parent symbol when the
42+
// constituent accessor declarations differ. Declaration emit must not crash when
43+
// serializing these properties.
44+
function mix(base1, base2) {
45+
return null;
46+
}
47+
class A {
48+
static get shared() { return 1; }
49+
static set shared(v) { }
50+
x = "";
51+
}
52+
class B {
53+
static get shared() { return 2; }
54+
static set shared(v) { }
55+
y = 0;
56+
}
57+
function make() {
58+
class C extends mix(A, B) {
59+
z = true;
60+
}
61+
return C;
62+
}
63+
export const MixedClass = make();
64+
65+
66+
//// [declarationEmitForMixinsWithStaticAccessors1.d.ts]
67+
export declare const MixedClass: {
68+
new (): {
69+
z: boolean;
70+
x: string;
71+
};
72+
new (): {
73+
z: boolean;
74+
x: string;
75+
};
76+
shared: number;
77+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/compiler/declarationEmitForMixinsWithStaticAccessors1.ts] ////
2+
3+
=== declarationEmitForMixinsWithStaticAccessors1.ts ===
4+
// Regression test: when a class extends a mixin that returns an intersection,
5+
// the class's anonymous constructor type inherits static accessor properties from the
6+
// intersection. These synthetic properties can have a nil parent symbol when the
7+
// constituent accessor declarations differ. Declaration emit must not crash when
8+
// serializing these properties.
9+
10+
function mix<T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(
11+
>mix : Symbol(mix, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 0, 0))
12+
>T : Symbol(T, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 13))
13+
>args : Symbol(args, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 28))
14+
>U : Symbol(U, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 51))
15+
>args : Symbol(args, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 67))
16+
17+
base1: T, base2: U
18+
>base1 : Symbol(base1, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 91))
19+
>T : Symbol(T, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 13))
20+
>base2 : Symbol(base2, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 7, 13))
21+
>U : Symbol(U, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 51))
22+
23+
): T & U {
24+
>T : Symbol(T, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 13))
25+
>U : Symbol(U, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 6, 51))
26+
27+
return null as any;
28+
}
29+
30+
class A {
31+
>A : Symbol(A, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 10, 1))
32+
33+
static get shared(): number { return 1; }
34+
>shared : Symbol(A.shared, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 12, 9), Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 13, 45))
35+
36+
static set shared(v: number) { }
37+
>shared : Symbol(A.shared, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 12, 9), Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 13, 45))
38+
>v : Symbol(v, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 14, 22))
39+
40+
x: string = "";
41+
>x : Symbol(A.x, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 14, 36))
42+
}
43+
44+
class B {
45+
>B : Symbol(B, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 16, 1))
46+
47+
static get shared(): number { return 2; }
48+
>shared : Symbol(B.shared, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 18, 9), Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 19, 45))
49+
50+
static set shared(v: number) { }
51+
>shared : Symbol(B.shared, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 18, 9), Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 19, 45))
52+
>v : Symbol(v, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 20, 22))
53+
54+
y: number = 0;
55+
>y : Symbol(B.y, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 20, 36))
56+
}
57+
58+
function make() {
59+
>make : Symbol(make, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 22, 1))
60+
61+
class C extends mix(A, B) {
62+
>C : Symbol(C, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 24, 17))
63+
>mix : Symbol(mix, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 0, 0))
64+
>A : Symbol(A, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 10, 1))
65+
>B : Symbol(B, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 16, 1))
66+
67+
z: boolean = true;
68+
>z : Symbol(C.z, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 25, 31))
69+
}
70+
return C;
71+
>C : Symbol(C, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 24, 17))
72+
}
73+
74+
export const MixedClass = make();
75+
>MixedClass : Symbol(MixedClass, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 31, 12))
76+
>make : Symbol(make, Decl(declarationEmitForMixinsWithStaticAccessors1.ts, 22, 1))
77+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//// [tests/cases/compiler/declarationEmitForMixinsWithStaticAccessors1.ts] ////
2+
3+
=== declarationEmitForMixinsWithStaticAccessors1.ts ===
4+
// Regression test: when a class extends a mixin that returns an intersection,
5+
// the class's anonymous constructor type inherits static accessor properties from the
6+
// intersection. These synthetic properties can have a nil parent symbol when the
7+
// constituent accessor declarations differ. Declaration emit must not crash when
8+
// serializing these properties.
9+
10+
function mix<T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(
11+
>mix : <T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(base1: T, base2: U) => T & U
12+
>args : any[]
13+
>args : any[]
14+
15+
base1: T, base2: U
16+
>base1 : T
17+
>base2 : U
18+
19+
): T & U {
20+
return null as any;
21+
>null as any : any
22+
}
23+
24+
class A {
25+
>A : A
26+
27+
static get shared(): number { return 1; }
28+
>shared : number
29+
>1 : 1
30+
31+
static set shared(v: number) { }
32+
>shared : number
33+
>v : number
34+
35+
x: string = "";
36+
>x : string
37+
>"" : ""
38+
}
39+
40+
class B {
41+
>B : B
42+
43+
static get shared(): number { return 2; }
44+
>shared : number
45+
>2 : 2
46+
47+
static set shared(v: number) { }
48+
>shared : number
49+
>v : number
50+
51+
y: number = 0;
52+
>y : number
53+
>0 : 0
54+
}
55+
56+
function make() {
57+
>make : () => typeof C
58+
59+
class C extends mix(A, B) {
60+
>C : C
61+
>mix(A, B) : A
62+
>mix : <T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(base1: T, base2: U) => T & U
63+
>A : typeof A
64+
>B : typeof B
65+
66+
z: boolean = true;
67+
>z : boolean
68+
>true : true
69+
}
70+
return C;
71+
>C : typeof C
72+
}
73+
74+
export const MixedClass = make();
75+
>MixedClass : typeof C
76+
>make() : typeof C
77+
>make : () => typeof C
78+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @declaration: true
2+
// @target: esnext
3+
4+
// Regression test: when a class extends a mixin that returns an intersection,
5+
// the class's anonymous constructor type inherits static accessor properties from the
6+
// intersection. These synthetic properties can have a nil parent symbol when the
7+
// constituent accessor declarations differ. Declaration emit must not crash when
8+
// serializing these properties.
9+
10+
function mix<T extends new (...args: any[]) => any, U extends new (...args: any[]) => any>(
11+
base1: T, base2: U
12+
): T & U {
13+
return null as any;
14+
}
15+
16+
class A {
17+
static get shared(): number { return 1; }
18+
static set shared(v: number) { }
19+
x: string = "";
20+
}
21+
22+
class B {
23+
static get shared(): number { return 2; }
24+
static set shared(v: number) { }
25+
y: number = 0;
26+
}
27+
28+
function make() {
29+
class C extends mix(A, B) {
30+
z: boolean = true;
31+
}
32+
return C;
33+
}
34+
35+
export const MixedClass = make();

0 commit comments

Comments
 (0)