Skip to content

Commit 9585481

Browse files
committed
add support for static initializer blocks in TypeScript
1 parent 59f15eb commit 9585481

File tree

10 files changed

+228
-42
lines changed

10 files changed

+228
-42
lines changed

javascript/extractor/src/com/semmle/jcorn/Parser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3219,7 +3219,7 @@ protected Node parseClass(Position startLoc, boolean isStatement) {
32193219
Expression superClass = this.parseClassSuper();
32203220
Position bodyStartLoc = this.startLoc;
32213221
boolean hadConstructor = false;
3222-
List<MemberDefinition<?>> body = new ArrayList<MemberDefinition<?>>();
3222+
List<Node> body = new ArrayList<>(); // TODO: The JS parser doesn't support static initializer blocks.
32233223
this.expect(TokenType.braceL);
32243224
while (!this.eat(TokenType.braceR)) {
32253225
if (this.eat(TokenType.semi)) continue;

javascript/extractor/src/com/semmle/js/ast/ClassBody.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44

55
/** The body of a {@linkplain ClassDeclaration} or {@linkplain ClassExpression}. */
66
public class ClassBody extends Node {
7-
private final List<MemberDefinition<?>> body;
7+
private final List<Node> body; // either MemberDefintion or BlockStatement (static initialization blocks)
88

9-
public ClassBody(SourceLocation loc, List<MemberDefinition<?>> body) {
9+
public ClassBody(SourceLocation loc, List<Node> body) {
1010
super("ClassBody", loc);
1111
this.body = body;
1212
}
1313

14-
public List<MemberDefinition<?>> getBody() {
14+
public List<Node> getBody() {
1515
return body;
1616
}
1717

18-
public void addMember(MemberDefinition<?> md) {
18+
public void addMember(Node md) {
1919
body.add(md);
2020
}
2121

2222
public MethodDefinition getConstructor() {
23-
for (MemberDefinition<?> md : body) if (md.isConstructor()) return (MethodDefinition) md;
23+
for (Node md : body) if (md instanceof MethodDefinition && ((MethodDefinition)md).isConstructor()) return (MethodDefinition) md;
2424
return null;
2525
}
2626

javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ public Node visit(ClassExpression nd, Void v) {
454454

455455
@Override
456456
public Node visit(ClassBody nd, Void v) {
457-
for (MemberDefinition<?> m : nd.getBody()) {
457+
for (Node m : nd.getBody()) {
458458
if (m instanceof MethodDefinition) {
459459
Node first = m.accept(this, v);
460460
if (first != null) return first;
@@ -1163,10 +1163,14 @@ public Void visit(ClassExpression nd, SuccessorInfo i) {
11631163
private Map<Expression, AClass> constructor2Class = new LinkedHashMap<>();
11641164

11651165
private Void visit(Node nd, AClass ac, SuccessorInfo i) {
1166-
for (MemberDefinition<?> m : ac.getBody().getBody())
1167-
if (m.isConstructor() && m.isConcrete()) constructor2Class.put(m.getValue(), ac);
1166+
for (Node m : ac.getBody().getBody()) {
1167+
if (m instanceof MemberDefinition) {
1168+
MemberDefinition md = (MemberDefinition) m;
1169+
if (md.isConstructor() && md.isConcrete()) constructor2Class.put(md.getValue(), ac);
1170+
}
1171+
}
11681172
visitSequence(ac.getId(), ac.getSuperClass(), ac.getBody(), nd);
1169-
writeSuccessors(nd, visitSequence(getStaticFields(ac.getBody()), getDecoratorsOfClass(ac), i.getAllSuccessors()));
1173+
writeSuccessors(nd, visitSequence(getStaticInitializers(ac.getBody()), getDecoratorsOfClass(ac), i.getAllSuccessors()));
11701174
return null;
11711175
}
11721176

@@ -1203,15 +1207,18 @@ private List<Node> getDecoratorsOfClass(AClass ac) {
12031207
List<Node> staticDecorators = new ArrayList<>();
12041208
List<Node> constructorParameterDecorators = new ArrayList<>();
12051209
List<Node> classDecorators = (List<Node>)(List<?>)ac.getDecorators();
1206-
for (MemberDefinition<?> member : ac.getBody().getBody()) {
1207-
if (!member.isConcrete()) continue;
1208-
List<Node> decorators = getMemberDecorators(member);
1209-
if (member.isConstructor()) {
1210-
constructorParameterDecorators.addAll(decorators);
1211-
} else if (member.isStatic()) {
1212-
staticDecorators.addAll(decorators);
1213-
} else {
1214-
instanceDecorators.addAll(decorators);
1210+
for (Node memberNode : ac.getBody().getBody()) {
1211+
if (memberNode instanceof MemberDefinition) {
1212+
MemberDefinition<?> member = (MemberDefinition<?>) memberNode;
1213+
if (!member.isConcrete()) continue;
1214+
List<Node> decorators = getMemberDecorators(member);
1215+
if (member.isConstructor()) {
1216+
constructorParameterDecorators.addAll(decorators);
1217+
} else if (member.isStatic()) {
1218+
staticDecorators.addAll(decorators);
1219+
} else {
1220+
instanceDecorators.addAll(decorators);
1221+
}
12151222
}
12161223
}
12171224
List<Node> result = new ArrayList<>();
@@ -1612,25 +1619,32 @@ public Void visit(ClassBody nd, SuccessorInfo i) {
16121619

16131620
private List<MemberDefinition<?>> getMethods(ClassBody nd) {
16141621
List<MemberDefinition<?>> mds = new ArrayList<>();
1615-
for (MemberDefinition<?> md : nd.getBody()) {
1616-
if (md instanceof MethodDefinition) mds.add(md);
1622+
for (Node md : nd.getBody()) {
1623+
if (md instanceof MethodDefinition) mds.add((MemberDefinition<?>)md);
16171624
}
16181625
return mds;
16191626
}
16201627

1621-
private List<MemberDefinition<?>> getStaticFields(ClassBody nd) {
1622-
List<MemberDefinition<?>> mds = new ArrayList<>();
1623-
for (MemberDefinition<?> md : nd.getBody()) {
1624-
if (md instanceof FieldDefinition && md.isStatic()) mds.add(md);
1628+
/**
1629+
* Gets the static fields, and static initializer blocks, from `nd`.
1630+
*/
1631+
private List<Node> getStaticInitializers(ClassBody nd) {
1632+
List<Node> nodes = new ArrayList<>();
1633+
for (Node node : nd.getBody()) {
1634+
if (node instanceof FieldDefinition && ((FieldDefinition)node).isStatic()) nodes.add(node);
1635+
if (node instanceof BlockStatement) nodes.add(node);
16251636
}
1626-
return mds;
1637+
return nodes;
16271638
}
16281639

16291640
private List<FieldDefinition> getConcreteInstanceFields(ClassBody nd) {
16301641
List<FieldDefinition> fds = new ArrayList<>();
1631-
for (MemberDefinition<?> md : nd.getBody())
1642+
for (Node node : nd.getBody()) {
1643+
if (!(node instanceof MemberDefinition)) continue;
1644+
MemberDefinition<?> md = (MemberDefinition<?>)node;
16321645
if (md instanceof FieldDefinition && !md.isStatic() && md.isConcrete())
16331646
fds.add((FieldDefinition) md);
1647+
}
16341648
return fds;
16351649
}
16361650

javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,8 @@ private Node convertNodeUntyped(JsonObject node, String defaultKind) throws Pars
632632
return convertWithStatement(node, loc);
633633
case "YieldExpression":
634634
return convertYieldExpression(node, loc);
635+
case "ClassStaticBlockDeclaration":
636+
return convertStaticInitializerBlock(node, loc);
635637
default:
636638
throw new ParseError(
637639
"Unsupported TypeScript syntax " + kind, getSourceLocation(node).getStart());
@@ -866,6 +868,10 @@ private Node convertBinaryExpression(JsonObject node, SourceLocation loc) throws
866868
}
867869
}
868870

871+
private Node convertStaticInitializerBlock(JsonObject node, SourceLocation loc) throws ParseError {
872+
return new BlockStatement(loc, convertChildren(node.get("body").getAsJsonObject(), "statements"));
873+
}
874+
869875
private Node convertBlock(JsonObject node, SourceLocation loc) throws ParseError {
870876
return new BlockStatement(loc, convertChildren(node, "statements"));
871877
}

javascript/ql/test/library-tests/CFG/CFG.expected

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,37 @@
770770
| staticFieldsTS | 6 | D | 6 | new D() |
771771
| staticFieldsTS | 6 | instance | 6 | D |
772772
| staticFieldsTS | 6 | new D() | 6 | static ... ew D(); |
773-
| staticFieldsTS | 6 | static ... ew D(); | 8 | exit node of <toplevel> |
773+
| staticFieldsTS | 6 | static ... ew D(); | 9 | export ... ;\\n }\\n} |
774+
| staticFieldsTS | 9 | E | 9 | constructor |
775+
| staticFieldsTS | 9 | class E ... ;\\n }\\n} | 10 | f |
776+
| staticFieldsTS | 9 | constructor | 9 | function in constructor() {} |
777+
| staticFieldsTS | 9 | constructor() {} | 9 | class E ... ;\\n }\\n} |
778+
| staticFieldsTS | 9 | entry node of () {} | 9 | {} |
779+
| staticFieldsTS | 9 | export ... ;\\n }\\n} | 9 | E |
780+
| staticFieldsTS | 9 | function in constructor() {} | 9 | constructor() {} |
781+
| staticFieldsTS | 9 | {} | 9 | exit node of () {} |
782+
| staticFieldsTS | 10 | f | 10 | false |
783+
| staticFieldsTS | 10 | false | 10 | static ... false; |
784+
| staticFieldsTS | 10 | static ... false; | 11 | static ... ();\\n } |
785+
| staticFieldsTS | 11 | static ... ();\\n } | 12 | E.f = new C(); |
786+
| staticFieldsTS | 12 | C | 12 | new C() |
787+
| staticFieldsTS | 12 | E | 12 | f |
788+
| staticFieldsTS | 12 | E.f | 12 | C |
789+
| staticFieldsTS | 12 | E.f = new C() | 14 | g |
790+
| staticFieldsTS | 12 | E.f = new C(); | 12 | E |
791+
| staticFieldsTS | 12 | f | 12 | E.f |
792+
| staticFieldsTS | 12 | new C() | 12 | E.f = new C() |
793+
| staticFieldsTS | 14 | 1337 | 14 | static ... = 1337; |
794+
| staticFieldsTS | 14 | g | 14 | 1337 |
795+
| staticFieldsTS | 14 | static ... = 1337; | 15 | static ... ();\\n } |
796+
| staticFieldsTS | 15 | static ... ();\\n } | 16 | E.g = new D(); |
797+
| staticFieldsTS | 16 | D | 16 | new D() |
798+
| staticFieldsTS | 16 | E | 16 | g |
799+
| staticFieldsTS | 16 | E.g | 16 | D |
800+
| staticFieldsTS | 16 | E.g = new D() | 18 | exit node of <toplevel> |
801+
| staticFieldsTS | 16 | E.g = new D(); | 16 | E |
802+
| staticFieldsTS | 16 | g | 16 | E.g |
803+
| staticFieldsTS | 16 | new D() | 16 | E.g = new D() |
774804
| switch | 1 | entry node of <toplevel> | 14 | f |
775805
| switch | 1 | switch ... 19;\\n} | 2 | x |
776806
| switch | 2 | x | 6 | case\\n ... 19; |

javascript/ql/test/library-tests/CFG/StaticInit.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@
1414
| staticFields.js:2:3:2:28 | static ... ew C(); | Field initializer occurs after its class is created |
1515
| staticFieldsTS.ts:2:3:2:31 | static ... ew C(); | Field initializer occurs after its class is created |
1616
| staticFieldsTS.ts:6:3:6:31 | static ... ew D(); | Field initializer occurs after its class is created |
17+
| staticFieldsTS.ts:10:3:10:32 | static ... false; | Field initializer occurs after its class is created |
18+
| staticFieldsTS.ts:14:3:14:30 | static ... = 1337; | Field initializer occurs after its class is created |

javascript/ql/test/library-tests/CFG/staticFieldsTS.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@ class C {
55
export class D {
66
static instance: D = new D();
77
}
8+
9+
export class E {
10+
static f: C | boolean = false;
11+
static {
12+
E.f = new C();
13+
}
14+
static g: D | number = 1337;
15+
static {
16+
E.g = new D();
17+
}
18+
}

0 commit comments

Comments
 (0)