Skip to content

Commit db27f0d

Browse files
committed
JS: Better support for contextual types
1 parent 7779e9b commit db27f0d

File tree

2 files changed

+68
-7
lines changed

2 files changed

+68
-7
lines changed

javascript/ql/lib/semmle/javascript/internal/TypeResolution.qll

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ module TypeResolution {
88

99
predicate trackType = TypeFlow::TrackNode<TypeDefinition>::track/1;
1010

11-
predicate trackFunctionType = TypeFlow::TrackNode<Function>::track/1;
11+
Node trackFunctionType(Function fun) {
12+
result = fun
13+
or
14+
exists(Node mid | mid = trackFunctionType(fun) |
15+
TypeFlow::step(mid, result)
16+
or
17+
UnderlyingTypes::underlyingTypeStep(mid, result)
18+
)
19+
}
1220

1321
predicate trackFunctionValue = ValueFlow::TrackNode<Function>::track/1;
1422

@@ -47,6 +55,24 @@ module TypeResolution {
4755
content.isUnknownArrayElement()
4856
)
4957
or
58+
// Ad-hoc support for array types
59+
content.isUnknownArrayElement() and
60+
(
61+
memberType = host.(ArrayTypeExpr).getElementType()
62+
or
63+
exists(GenericTypeExpr type |
64+
host = type and
65+
type.getTypeAccess().(LocalTypeAccess).getName() = ["Array", "ReadonlyArray"] and
66+
memberType = type.getTypeArgument(0)
67+
)
68+
or
69+
exists(JSDocAppliedTypeExpr type |
70+
host = type and
71+
type.getHead().(JSDocLocalTypeAccess).getName() = "Array" and
72+
memberType = type.getArgument(0)
73+
)
74+
)
75+
or
5076
// Inherit members from base types
5177
exists(ClassOrInterface baseType | typeMember(baseType, content, memberType) |
5278
host.(ClassDefinition).getSuperClass() = trackClassValue(baseType)
@@ -115,11 +141,38 @@ module TypeResolution {
115141
)
116142
}
117143

118-
private predicate contextualType(Node value, Node contextualType) {
144+
private predicate contextualType(Node value, Node type) {
119145
exists(InvokeExpr call, Function target, int i |
120146
callTarget(call, target) and
121147
value = call.getArgument(i) and
122-
contextualType = target.getParameter(i).getTypeAnnotation()
148+
type = target.getParameter(i).getTypeAnnotation()
149+
)
150+
or
151+
exists(Function lambda |
152+
not lambda.isAsyncOrGenerator() and
153+
value = lambda.getAReturnedExpr()
154+
|
155+
type = lambda.getReturnTypeAnnotation()
156+
or
157+
not exists(lambda.getReturnTypeAnnotation()) and
158+
exists(Function functionType |
159+
contextualType(lambda, trackFunctionType(functionType)) and
160+
type = functionType.getReturnTypeAnnotation()
161+
)
162+
)
163+
or
164+
exists(ObjectExpr object, Node objectType, Node host, string name |
165+
contextualType(object, objectType) and
166+
typeMemberHostReaches(host, objectType) and
167+
typeMember(host, any(DataFlow::Content c | c.asPropertyName() = name), type) and
168+
value = object.getPropertyByName(name).getInit()
169+
)
170+
or
171+
exists(ArrayExpr array, Node arrayType, Node host |
172+
contextualType(array, arrayType) and
173+
typeMemberHostReaches(host, arrayType) and
174+
typeMember(host, any(DataFlow::Content c | c.isUnknownArrayElement()), type) and
175+
value = array.getAnElement()
123176
)
124177
}
125178

javascript/ql/test/library-tests/UnderlyingTypes/contextualTypes.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,30 @@ declare function doSomething(options: Options);
88

99
function t1() {
1010
doSomething({
11-
handle(req) { // $ MISSING: hasUnderlyingType='express'.Request
11+
handle(req) { // $ hasUnderlyingType='express'.Request
1212
}
1313
});
1414
}
1515

1616
function t2(callback: ((opts: Options) => void) | undefined) {
1717
callback({
18-
handle(req) { } // $ MISSING: hasUnderlyingType='express'.Request
18+
handle(req) { } // $ hasUnderlyingType='express'.Request
1919
})
2020
callback!({
21-
handle(req) { } // $ MISSING: hasUnderlyingType='express'.Request
21+
handle(req) { } // $ hasUnderlyingType='express'.Request
2222
})
2323
}
2424

2525
function t3(): Options {
2626
return {
27-
handle(req) { } // $ MISSING: hasUnderlyingType='express'.Request
27+
handle(req) { } // $ hasUnderlyingType='express'.Request
2828
}
2929
}
30+
31+
function t4(): Options[] {
32+
return [
33+
{
34+
handle(req) { } // $ hasUnderlyingType='express'.Request
35+
}
36+
]
37+
}

0 commit comments

Comments
 (0)