Skip to content

Commit 251cd56

Browse files
committed
JS: Use for determining underlying types
1 parent 651f07d commit 251cd56

File tree

3 files changed

+152
-27
lines changed

3 files changed

+152
-27
lines changed

javascript/ql/lib/semmle/javascript/TypeScript.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import javascript
2+
private import semmle.javascript.internal.UnderlyingTypes
23

34
/**
45
* A statement that defines a namespace, that is, a namespace declaration or enum declaration.
@@ -577,7 +578,7 @@ class TypeExpr extends ExprOrType, @typeexpr, TypeAnnotation {
577578
override TopLevel getTopLevel() { result = ExprOrType.super.getTopLevel() }
578579

579580
override DataFlow::ClassNode getClass() {
580-
result.getAstNode() = this.getType().(ClassType).getClass()
581+
UnderlyingTypes::nodeHasUnderlyingClassType(this, result.getAstNode())
581582
}
582583
}
583584

javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ private import internal.PreCallGraphStep
2727
private import semmle.javascript.internal.CachedStages
2828
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
2929
private import semmle.javascript.dataflow.internal.VariableOrThis
30+
private import semmle.javascript.internal.UnderlyingTypes
3031

3132
module DataFlow {
3233
/**
@@ -189,26 +190,6 @@ module DataFlow {
189190
FlowSteps::identityFunctionStep(result, this)
190191
}
191192

192-
/**
193-
* Gets the static type of this node as determined by the TypeScript type system.
194-
*/
195-
private Type getType() {
196-
exists(AST::ValueNode node |
197-
this = TValueNode(node) and
198-
ast_node_type(node, result)
199-
)
200-
or
201-
exists(BindingPattern pattern |
202-
this = lvalueNode(pattern) and
203-
ast_node_type(pattern, result)
204-
)
205-
or
206-
exists(MethodDefinition def |
207-
this = TThisNode(def.getInit()) and
208-
ast_node_type(def.getDeclaringClass(), result)
209-
)
210-
}
211-
212193
/**
213194
* Gets the type annotation describing the type of this node,
214195
* provided that a static type could not be found.
@@ -236,9 +217,7 @@ module DataFlow {
236217
cached
237218
predicate hasUnderlyingType(string globalName) {
238219
Stages::TypeTracking::ref() and
239-
this.getType().hasUnderlyingType(globalName)
240-
or
241-
this.getFallbackTypeAnnotation().getAnUnderlyingType().hasQualifiedName(globalName)
220+
UnderlyingTypes::nodeHasUnderlyingType(this.getFallbackTypeAnnotation(), "global", globalName)
242221
}
243222

244223
/**
@@ -248,9 +227,8 @@ module DataFlow {
248227
cached
249228
predicate hasUnderlyingType(string moduleName, string typeName) {
250229
Stages::TypeTracking::ref() and
251-
this.getType().hasUnderlyingType(moduleName, typeName)
252-
or
253-
this.getFallbackTypeAnnotation().getAnUnderlyingType().hasQualifiedName(moduleName, typeName)
230+
moduleName != "global" and
231+
UnderlyingTypes::nodeHasUnderlyingType(this.getFallbackTypeAnnotation(), moduleName, typeName)
254232
}
255233

256234
/**
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Provides name resolution and propagates type information.
3+
*/
4+
5+
private import javascript
6+
private import semmle.javascript.internal.TypeResolution::TypeResolution
7+
8+
/**
9+
* Provides name resolution and propagates type information.
10+
*/
11+
module UnderlyingTypes {
12+
/**
13+
* Holds if `moduleName` appears to start with a package name, as opposed to a relative file import.
14+
*/
15+
bindingset[moduleName]
16+
private predicate isExternalModuleName(string moduleName) {
17+
not moduleName.regexpMatch("^(\\.|/).*")
18+
}
19+
20+
bindingset[name]
21+
private string normalizeModuleName(string name) {
22+
result =
23+
name.regexpReplaceAll("^node:", "")
24+
.regexpReplaceAll("\\.[jt]sx?$", "")
25+
.regexpReplaceAll("/(index)?$", "")
26+
}
27+
28+
/**
29+
* Holds if `node` is a reference to the given module, or a qualified name rooted in that module.
30+
*
31+
* If `qualifiedName` is empty, `node` refers to the module itself.
32+
*
33+
* If `mod` is the string `"global"`, `node` refers to a global access path.
34+
*/
35+
predicate nodeRefersToModule(Node node, string mod, string qualifiedName) {
36+
exists(Import imprt |
37+
node = imprt.getImportedPath() and
38+
mod = normalizeModuleName(imprt.getImportedPath().getValue()) and
39+
isExternalModuleName(mod) and
40+
qualifiedName = ""
41+
)
42+
or
43+
mod = "global" and
44+
exists(LocalNamespaceAccess access |
45+
node = access and
46+
not exists(access.getLocalNamespaceName()) and
47+
access.getName() = qualifiedName
48+
)
49+
or
50+
// Additionally track through bulk re-exports (`export * from 'mod`).
51+
// These are normally handled by 'exportAs' which supports various shadowing rules,
52+
// but has no effect when the ultimate re-exported module is not resolved to a Module.
53+
// We propagate external module refs through bulk re-exports and ignore shadowing rules.
54+
exists(BulkReExportDeclaration reExport |
55+
nodeRefersToModule(reExport.getImportedPath(), mod, qualifiedName) and
56+
node = reExport.getContainer()
57+
)
58+
or
59+
exists(Node mid |
60+
nodeRefersToModule(mid, mod, qualifiedName) and
61+
ValueFlow::step(mid, node) and
62+
not node instanceof Variable // avoid a lot of unnecessary tuples
63+
)
64+
or
65+
exists(Node mid, string prefix, string step |
66+
nodeRefersToModule(mid, mod, prefix) and
67+
readStep(mid, step, node) and
68+
qualifiedName = append(prefix, step)
69+
)
70+
}
71+
72+
private predicate subtypeStep(Node node1, Node node2) {
73+
exists(ClassOrInterface cls |
74+
(
75+
node1 = cls.getSuperClass() or // TODO: test that type flow actually reaches the super class
76+
node1 = cls.getASuperInterface()
77+
) and
78+
node2 = cls
79+
)
80+
}
81+
82+
private predicate underlyingTypeStep(Node node1, Node node2) {
83+
exists(UnionOrIntersectionTypeExpr type |
84+
node1 = type.getAnElementType() and
85+
node2 = type
86+
)
87+
or
88+
exists(ReadonlyTypeExpr type |
89+
node1 = type.getElementType() and
90+
node2 = type
91+
)
92+
or
93+
exists(OptionalTypeExpr type |
94+
node1 = type.getElementType() and
95+
node2 = type
96+
)
97+
or
98+
exists(GenericTypeExpr type |
99+
node1 = type.getTypeAccess() and
100+
node2 = type
101+
)
102+
or
103+
exists(ExpressionWithTypeArguments e |
104+
node1 = e.getExpression() and
105+
node2 = e
106+
)
107+
}
108+
109+
bindingset[a, b]
110+
private string append(string a, string b) {
111+
if b = "default"
112+
then result = a
113+
else (
114+
(if a = "" or b = "" then result = a + b else result = a + "." + b) and
115+
result.length() < 100
116+
)
117+
}
118+
119+
predicate nodeHasUnderlyingType(Node node, string mod, string name) {
120+
exists(Node mid, string prefix, string step |
121+
nodeRefersToModule(mid, mod, prefix) and
122+
readStep(mid, step, node) and
123+
name = append(prefix, step)
124+
)
125+
or
126+
exists(Node mid | nodeHasUnderlyingType(mid, mod, name) |
127+
TypeFlow::step(mid, node)
128+
or
129+
underlyingTypeStep(mid, node)
130+
or
131+
subtypeStep(mid, node)
132+
)
133+
}
134+
135+
predicate nodeHasUnderlyingClassType(Node node, ClassDefinition cls) {
136+
node = cls
137+
or
138+
exists(Node mid | nodeHasUnderlyingClassType(mid, cls) |
139+
TypeFlow::step(mid, node)
140+
or
141+
underlyingTypeStep(mid, node)
142+
// Note: unlike for external types, we do not use subtype steps here.
143+
// The caller is responsible for handling the class hierarchy.
144+
)
145+
}
146+
}

0 commit comments

Comments
 (0)