Skip to content

Commit 3c1f37e

Browse files
authored
Use control flow analysis to check 'super(...)' call before 'this' access (#38612)
* Use CFA graph to check this/super accesses are preceded by super() call * Accept cleaned-up API baselines * Accept new baselines * Add tests
1 parent bc0d2ec commit 3c1f37e

13 files changed

+948
-104
lines changed

src/compiler/binder.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ namespace ts {
985985
return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd });
986986
}
987987

988-
function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Node): FlowNode {
988+
function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode {
989989
setFlowNodeReferenced(antecedent);
990990
const result = initFlowNode({ flags, antecedent, node });
991991
if (currentExceptionTarget) {
@@ -1341,7 +1341,7 @@ namespace ts {
13411341
// is potentially an assertion and is therefore included in the control flow.
13421342
if (node.expression.kind === SyntaxKind.CallExpression) {
13431343
const call = <CallExpression>node.expression;
1344-
if (isDottedName(call.expression)) {
1344+
if (isDottedName(call.expression) && call.expression.kind !== SyntaxKind.SuperKeyword) {
13451345
currentFlow = createFlowCall(currentFlow, call);
13461346
}
13471347
}
@@ -1747,6 +1747,9 @@ namespace ts {
17471747
}
17481748
else {
17491749
bindEachChild(node);
1750+
if (node.expression.kind === SyntaxKind.SuperKeyword) {
1751+
currentFlow = createFlowCall(currentFlow, node);
1752+
}
17501753
}
17511754
}
17521755
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
@@ -2464,6 +2467,9 @@ namespace ts {
24642467
node.flowNode = currentFlow;
24652468
}
24662469
return checkStrictModeIdentifier(<Identifier>node);
2470+
case SyntaxKind.SuperKeyword:
2471+
node.flowNode = currentFlow;
2472+
break;
24672473
case SyntaxKind.PrivateIdentifier:
24682474
return checkPrivateIdentifier(node as PrivateIdentifier);
24692475
case SyntaxKind.PropertyAccessExpression:

src/compiler/checker.ts

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,7 @@ namespace ts {
911911
const sharedFlowNodes: FlowNode[] = [];
912912
const sharedFlowTypes: FlowType[] = [];
913913
const flowNodeReachable: (boolean | undefined)[] = [];
914+
const flowNodePostSuper: (boolean | undefined)[] = [];
914915
const potentialThisCollisions: Node[] = [];
915916
const potentialNewTargetCollisions: Node[] = [];
916917
const potentialWeakMapCollisions: Node[] = [];
@@ -20148,7 +20149,7 @@ namespace ts {
2014820149
noCacheCheck = false;
2014920150
}
2015020151
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) {
20151-
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation | PreFinallyFlow>flow).antecedent;
20152+
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation>flow).antecedent;
2015220153
}
2015320154
else if (flags & FlowFlags.Call) {
2015420155
const signature = getEffectsSignature((<FlowCall>flow).node);
@@ -20198,6 +20199,51 @@ namespace ts {
2019820199
}
2019920200
}
2020020201

20202+
// Return true if the given flow node is preceded by a 'super(...)' call in every possible code path
20203+
// leading to the node.
20204+
function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean {
20205+
while (true) {
20206+
const flags = flow.flags;
20207+
if (flags & FlowFlags.Shared) {
20208+
if (!noCacheCheck) {
20209+
const id = getFlowNodeId(flow);
20210+
const postSuper = flowNodePostSuper[id];
20211+
return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true));
20212+
}
20213+
noCacheCheck = false;
20214+
}
20215+
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) {
20216+
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause>flow).antecedent;
20217+
}
20218+
else if (flags & FlowFlags.Call) {
20219+
if ((<FlowCall>flow).node.expression.kind === SyntaxKind.SuperKeyword) {
20220+
return true;
20221+
}
20222+
flow = (<FlowCall>flow).antecedent;
20223+
}
20224+
else if (flags & FlowFlags.BranchLabel) {
20225+
// A branching point is post-super if every branch is post-super.
20226+
return every((<FlowLabel>flow).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false));
20227+
}
20228+
else if (flags & FlowFlags.LoopLabel) {
20229+
// A loop is post-super if the control flow path that leads to the top is post-super.
20230+
flow = (<FlowLabel>flow).antecedents![0];
20231+
}
20232+
else if (flags & FlowFlags.ReduceLabel) {
20233+
const target = (<FlowReduceLabel>flow).target;
20234+
const saveAntecedents = target.antecedents;
20235+
target.antecedents = (<FlowReduceLabel>flow).antecedents;
20236+
const result = isPostSuperFlowNode((<FlowReduceLabel>flow).antecedent, /*noCacheCheck*/ false);
20237+
target.antecedents = saveAntecedents;
20238+
return result;
20239+
}
20240+
else {
20241+
// Unreachable nodes are considered post-super to silence errors
20242+
return !!(flags & FlowFlags.Unreachable);
20243+
}
20244+
}
20245+
}
20246+
2020120247
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
2020220248
let key: string | undefined;
2020320249
let keySet = false;
@@ -21597,31 +21643,10 @@ namespace ts {
2159721643
}
2159821644
}
2159921645

21600-
function findFirstSuperCall(n: Node): SuperCall | undefined {
21601-
if (isSuperCall(n)) {
21602-
return n;
21603-
}
21604-
else if (isFunctionLike(n)) {
21605-
return undefined;
21606-
}
21607-
return forEachChild(n, findFirstSuperCall);
21608-
}
21609-
21610-
/**
21611-
* Return a cached result if super-statement is already found.
21612-
* Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor
21613-
*
21614-
* @param constructor constructor-function to look for super statement
21615-
*/
21616-
function getSuperCallInConstructor(constructor: ConstructorDeclaration): SuperCall | undefined {
21617-
const links = getNodeLinks(constructor);
21618-
21619-
// Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result
21620-
if (links.hasSuperCall === undefined) {
21621-
links.superCall = findFirstSuperCall(constructor.body!);
21622-
links.hasSuperCall = links.superCall ? true : false;
21623-
}
21624-
return links.superCall!;
21646+
function findFirstSuperCall(node: Node): SuperCall | undefined {
21647+
return isSuperCall(node) ? node :
21648+
isFunctionLike(node) ? undefined :
21649+
forEachChild(node, findFirstSuperCall);
2162521650
}
2162621651

2162721652
/**
@@ -21644,17 +21669,7 @@ namespace ts {
2164421669
// If a containing class does not have extends clause or the class extends null
2164521670
// skip checking whether super statement is called before "this" accessing.
2164621671
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) {
21647-
const superCall = getSuperCallInConstructor(<ConstructorDeclaration>container);
21648-
21649-
// We should give an error in the following cases:
21650-
// - No super-call
21651-
// - "this" is accessing before super-call.
21652-
// i.e super(this)
21653-
// this.x; super();
21654-
// We want to make sure that super-call is done before accessing "this" so that
21655-
// "this" is not accessed as a parameter of the super-call.
21656-
if (!superCall || superCall.end > node.pos) {
21657-
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
21672+
if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) {
2165821673
error(node, diagnosticMessage);
2165921674
}
2166021675
}
@@ -21879,7 +21894,8 @@ namespace ts {
2187921894
function checkSuperExpression(node: Node): Type {
2188021895
const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
2188121896

21882-
let container = getSuperContainer(node, /*stopOnFunctions*/ true);
21897+
const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true);
21898+
let container = immediateContainer;
2188321899
let needToCaptureLexicalThis = false;
2188421900

2188521901
// adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting
@@ -21915,7 +21931,7 @@ namespace ts {
2191521931
return errorType;
2191621932
}
2191721933

21918-
if (!isCallExpression && container.kind === SyntaxKind.Constructor) {
21934+
if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) {
2191921935
checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class);
2192021936
}
2192121937

@@ -29912,7 +29928,7 @@ namespace ts {
2991229928
if (getClassExtendsHeritageElement(containingClassDecl)) {
2991329929
captureLexicalThis(node.parent, containingClassDecl);
2991429930
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
29915-
const superCall = getSuperCallInConstructor(node);
29931+
const superCall = findFirstSuperCall(node.body!);
2991629932
if (superCall) {
2991729933
if (classExtendsNull) {
2991829934
error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ namespace ts {
150150
* returns a falsey value, then returns false.
151151
* If no such value is found, the callback is applied to each element of array and `true` is returned.
152152
*/
153-
export function every<T>(array: readonly T[], callback: (element: T, index: number) => boolean): boolean {
153+
export function every<T>(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean {
154154
if (array) {
155155
for (let i = 0; i < array.length; i++) {
156156
if (!callback(array[i], i)) {

src/compiler/types.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,34 +2791,21 @@ namespace ts {
27912791
}
27922792

27932793
export type FlowNode =
2794-
| AfterFinallyFlow
2795-
| PreFinallyFlow
27962794
| FlowStart
27972795
| FlowLabel
27982796
| FlowAssignment
27992797
| FlowCall
28002798
| FlowCondition
28012799
| FlowSwitchClause
2802-
| FlowArrayMutation;
2800+
| FlowArrayMutation
2801+
| FlowCall
2802+
| FlowReduceLabel;
28032803

28042804
export interface FlowNodeBase {
28052805
flags: FlowFlags;
28062806
id?: number; // Node id used by flow type cache in checker
28072807
}
28082808

2809-
export interface FlowLock {
2810-
locked?: boolean;
2811-
}
2812-
2813-
export interface AfterFinallyFlow extends FlowNodeBase, FlowLock {
2814-
antecedent: FlowNode;
2815-
}
2816-
2817-
export interface PreFinallyFlow extends FlowNodeBase {
2818-
antecedent: FlowNode;
2819-
lock: FlowLock;
2820-
}
2821-
28222809
// FlowStart represents the start of a control flow. For a function expression or arrow
28232810
// function, the node property references the function (which in turn has a flowNode
28242811
// property for the containing control flow).
@@ -4316,8 +4303,6 @@ namespace ts {
43164303
resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element
43174304
resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element
43184305
resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference
4319-
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
4320-
superCall?: SuperCall; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
43214306
switchTypes?: Type[]; // Cached array of switch case expression types
43224307
jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node
43234308
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,21 +1743,11 @@ declare namespace ts {
17431743
Label = 12,
17441744
Condition = 96
17451745
}
1746-
export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation;
1746+
export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel;
17471747
export interface FlowNodeBase {
17481748
flags: FlowFlags;
17491749
id?: number;
17501750
}
1751-
export interface FlowLock {
1752-
locked?: boolean;
1753-
}
1754-
export interface AfterFinallyFlow extends FlowNodeBase, FlowLock {
1755-
antecedent: FlowNode;
1756-
}
1757-
export interface PreFinallyFlow extends FlowNodeBase {
1758-
antecedent: FlowNode;
1759-
lock: FlowLock;
1760-
}
17611751
export interface FlowStart extends FlowNodeBase {
17621752
node?: FunctionExpression | ArrowFunction | MethodDeclaration;
17631753
}

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,21 +1743,11 @@ declare namespace ts {
17431743
Label = 12,
17441744
Condition = 96
17451745
}
1746-
export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation;
1746+
export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel;
17471747
export interface FlowNodeBase {
17481748
flags: FlowFlags;
17491749
id?: number;
17501750
}
1751-
export interface FlowLock {
1752-
locked?: boolean;
1753-
}
1754-
export interface AfterFinallyFlow extends FlowNodeBase, FlowLock {
1755-
antecedent: FlowNode;
1756-
}
1757-
export interface PreFinallyFlow extends FlowNodeBase {
1758-
antecedent: FlowNode;
1759-
lock: FlowLock;
1760-
}
17611751
export interface FlowStart extends FlowNodeBase {
17621752
node?: FunctionExpression | ArrowFunction | MethodDeclaration;
17631753
}

tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.errors.txt

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)