Skip to content

Commit 651f07d

Browse files
committed
JS: Add initial type resolution
1 parent 56d7e10 commit 651f07d

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
/**
2+
* Provides name resolution and propagates type information.
3+
*/
4+
5+
private import javascript
6+
7+
/**
8+
* Provides name resolution and propagates type information.
9+
*/
10+
module TypeResolution {
11+
private class NodeBase =
12+
@expr or @typeexpr or @lexical_name or @toplevel or @function_decl_stmt or @class_decl_stmt or
13+
@namespace_declaration or @enum_declaration;
14+
15+
/**
16+
* A node in a graph which we use to perform name and type resolution.
17+
*/
18+
class Node extends NodeBase {
19+
string toString() {
20+
result = this.(AstNode).toString()
21+
or
22+
result = this.(LexicalName).toString()
23+
}
24+
25+
Location getLocation() {
26+
result = this.(AstNode).getLocation()
27+
or
28+
result = this.(LocalVariable).getLocation()
29+
}
30+
}
31+
32+
private signature predicate nodeSig(Node node);
33+
34+
/**
35+
* A module top-level, or a `module {}` or `enum {}` statement.
36+
*/
37+
private class ModuleLike extends AstNode {
38+
ModuleLike() {
39+
this instanceof Module
40+
or
41+
this instanceof NamespaceDefinition // `module {}` or `enum {}` statement
42+
}
43+
}
44+
45+
/**
46+
* Holds if values/namespaces/types in `node1` can flow to values/namespaces/types in `node2`.
47+
*/
48+
private predicate commonStep(Node node1, Node node2) {
49+
// Import paths are part of the graph and has an incoming edge from the imported module, if found.
50+
// This ensures we can also use the PathExpr as a source when working with external (unresolved) modules.
51+
exists(Import imprt |
52+
node1 = imprt.getImportedModule() and
53+
node2 = imprt.getImportedPath()
54+
)
55+
or
56+
exists(ImportNamespaceSpecifier spec |
57+
node1 = spec.getImportDeclaration().getImportedPath() and
58+
node2 = spec.getLocal()
59+
)
60+
or
61+
exists(ExportNamespaceSpecifier spec |
62+
node1 = spec.getExportDeclaration().(ReExportDeclaration).getImportedPath() and
63+
node2 = spec
64+
)
65+
or
66+
exists(ExportAssignDeclaration assign |
67+
node1 = assign.getExpression() and
68+
node2 = assign.getContainer()
69+
)
70+
or
71+
exists(ImportEqualsDeclaration imprt |
72+
node1 = imprt.getImportedEntity() and
73+
node2 = imprt.getIdentifier()
74+
)
75+
or
76+
exists(ExternalModuleReference ref |
77+
node1 = ref.getImportedPath() and
78+
node2 = ref
79+
)
80+
or
81+
exists(ImportTypeExpr imprt |
82+
node1 = imprt.getPathExpr() and // TODO: ImportTypeExpr does not seem to be resolved to a Module
83+
node2 = imprt
84+
)
85+
or
86+
exists(ClassOrInterface cls |
87+
node1 = cls and
88+
node2 = cls.getIdentifier()
89+
)
90+
or
91+
exists(NamespaceDefinition def |
92+
node1 = def and
93+
node2 = def.getIdentifier()
94+
)
95+
or
96+
exists(EnumMember def |
97+
node1 = def.getInitializer() and
98+
node2 = def.getIdentifier()
99+
)
100+
or
101+
exists(TypeAliasDeclaration alias |
102+
node1 = alias.getDefinition() and
103+
node2 = alias.getIdentifier()
104+
)
105+
or
106+
exists(VariableDeclarator decl |
107+
node1 = decl.getInit() and
108+
node2 = decl.getBindingPattern()
109+
)
110+
or
111+
exists(ParenthesizedTypeExpr type |
112+
node1 = type.getElementType() and
113+
node2 = type
114+
)
115+
or
116+
exists(ParenthesisExpr expr |
117+
node1 = expr.getExpression() and
118+
node2 = expr
119+
)
120+
or
121+
exists(FunctionTypeExpr fun |
122+
node1 = fun.getFunction() and
123+
node2 = fun
124+
)
125+
}
126+
127+
/**
128+
* Holds if there is a read from `node1` to `node2` that accesses the member `name`.
129+
*/
130+
predicate readStep(Node node1, string name, Node node2) {
131+
exists(QualifiedTypeAccess access |
132+
node1 = access.getQualifier() and
133+
name = access.getIdentifier().getName() and
134+
node2 = access
135+
)
136+
or
137+
exists(QualifiedNamespaceAccess access |
138+
node1 = access.getQualifier() and
139+
name = access.getIdentifier().getName() and
140+
node2 = access
141+
)
142+
or
143+
exists(PropAccess access |
144+
node1 = access.getBase() and
145+
name = access.getPropertyName() and
146+
node2 = access
147+
)
148+
or
149+
exists(ImportSpecifier spec |
150+
node1 = spec.getImportDeclaration().getImportedPath() and
151+
name = spec.getImportedName() and
152+
node2 = spec.getLocal()
153+
)
154+
}
155+
156+
private signature module TypeResolutionInputSig {
157+
/**
158+
* Holds if flow is permitted through the given variable.
159+
*/
160+
predicate isRelevantVariable(LexicalName var);
161+
}
162+
163+
/**
164+
* A local variable with exactly one definition, not counting implicit initialization.
165+
*/
166+
private class EffectivelyConstantVariable extends LocalVariable {
167+
EffectivelyConstantVariable() {
168+
count(SsaExplicitDefinition ssa | ssa.getSourceVariable() = this) <= 1 // count may be zero if ambient
169+
}
170+
}
171+
172+
/** Configuration for propagating values and namespaces */
173+
private module ValueConfig implements TypeResolutionInputSig {
174+
predicate isRelevantVariable(LexicalName var) {
175+
var instanceof EffectivelyConstantVariable
176+
or
177+
// We merge the namespace and value declaration spaces as it seems there is
178+
// no need to distinguish them in practice.
179+
var instanceof LocalNamespaceName
180+
}
181+
}
182+
183+
/**
184+
* Associates information about values, such as references to a class, module, or namespace.
185+
*/
186+
module ValueFlow = FlowImpl<ValueConfig>;
187+
188+
private module TypeConfig implements TypeResolutionInputSig {
189+
predicate isRelevantVariable(LexicalName var) { var instanceof LocalTypeName }
190+
}
191+
192+
/**
193+
* Associates nodes with information about types.
194+
*/
195+
module TypeFlow = FlowImpl<TypeConfig>;
196+
197+
private module FlowImpl<TypeResolutionInputSig S> {
198+
/**
199+
* Gets the exported member of `mod` named `name`.
200+
*/
201+
Node getModuleExport(ModuleLike mod, string name) {
202+
exists(ExportDeclaration exprt |
203+
mod = exprt.getContainer() and
204+
exprt.exportsAs(result, name) and
205+
S::isRelevantVariable(result)
206+
)
207+
or
208+
exists(ExportNamespaceSpecifier spec |
209+
result = spec and
210+
mod = spec.getContainer() and
211+
name = spec.getExportedName()
212+
)
213+
or
214+
exists(EnumDeclaration enum |
215+
mod = enum and
216+
result = enum.getMemberByName(name).getIdentifier()
217+
)
218+
}
219+
220+
/** Steps that only apply for this configuration. */
221+
private predicate specificStep(Node node1, Node node2) {
222+
exists(LexicalName var | S::isRelevantVariable(var) |
223+
node1.(LexicalDecl).getALexicalName() = var and
224+
node2 = var
225+
or
226+
node1 = var and
227+
node2.(LexicalAccess).getALexicalName() = var
228+
)
229+
or
230+
exists(Node base, string name, ModuleLike mod |
231+
readStep(base, name, node2) and
232+
base = trackModule(mod) and
233+
node1 = getModuleExport(mod, name)
234+
)
235+
}
236+
237+
/**
238+
* Holds if data should propagate from `node1` to `node2`.
239+
*/
240+
pragma[inline]
241+
predicate step(Node node1, Node node2) {
242+
commonStep(node1, node2)
243+
or
244+
specificStep(node1, node2)
245+
}
246+
247+
/** Helps track flow from a particular set of source nodes. */
248+
module Track<nodeSig/1 isSource> {
249+
/** Gets the set of nodes reachable from `source`. */
250+
Node track(Node source) {
251+
isSource(source) and
252+
result = source
253+
or
254+
step(track(source), result)
255+
}
256+
}
257+
258+
signature class AstNodeSig extends AstNode;
259+
260+
/** Helps track flow from a particular set of source nodes. */
261+
module TrackNode<AstNodeSig Source> {
262+
/** Gets the set of nodes reachable from `source`. */
263+
Node track(Source source) {
264+
result = source
265+
or
266+
step(track(source), result)
267+
}
268+
}
269+
}
270+
271+
/**
272+
* Gets a node to which the given module flows.
273+
*/
274+
predicate trackModule = ValueFlow::TrackNode<ModuleLike>::track/1;
275+
276+
/**
277+
* Gets a node to which the given class flows.
278+
*/
279+
predicate trackClassValue = ValueFlow::TrackNode<ClassDefinition>::track/1;
280+
281+
/**
282+
* Gets the string constant referred to by `node`.
283+
*/
284+
cached
285+
string getStringValue(Node node) {
286+
result = node.(Expr).getStringValue()
287+
or
288+
result = node.(Label).getName()
289+
or
290+
exists(Node mid |
291+
result = getStringValue(mid) and
292+
ValueFlow::step(mid, node) and
293+
// Exclude steps where we use the import path as representative for the imported module
294+
not mid = any(Import imprt).getImportedPath()
295+
)
296+
}
297+
298+
/**
299+
* Gets the int constant referred to by `node`.
300+
*/
301+
cached
302+
int getIntValue(Node node) {
303+
result = node.(Expr).getIntValue()
304+
or
305+
exists(Node mid |
306+
result = getIntValue(mid) and
307+
ValueFlow::step(mid, node)
308+
)
309+
}
310+
311+
/**
312+
* Tracks types that have a certain property, in the sense that:
313+
* - an intersection type has the property if any operand has the property
314+
* - a union type has the property if all its operands have the property
315+
*/
316+
module TrackMustProp<nodeSig/1 directlyHasProperty> {
317+
predicate hasProperty(Node node) {
318+
directlyHasProperty(node)
319+
or
320+
exists(Node mid |
321+
hasProperty(mid) and
322+
TypeFlow::step(mid, node)
323+
)
324+
or
325+
unionHasProp(node)
326+
or
327+
hasProperty(node.(IntersectionTypeExpr).getAnElementType())
328+
or
329+
exists(ConditionalTypeExpr cond |
330+
hasProperty(cond.getTrueType()) and
331+
hasProperty(cond.getFalseType())
332+
)
333+
}
334+
335+
private predicate unionHasProp(UnionTypeExpr node, int n) {
336+
hasProperty(node.getElementType(0)) and n = 1
337+
or
338+
unionHasProp(node, n - 1) and
339+
hasProperty(node.getElementType(n - 1))
340+
}
341+
342+
private predicate unionHasProp(UnionTypeExpr node) {
343+
unionHasProp(node, node.getNumElementType())
344+
}
345+
}
346+
347+
private predicate isSanitizingPrimitiveTypeBase(Node node) {
348+
node.(TypeExpr).isNumbery()
349+
or
350+
node.(TypeExpr).isBooleany()
351+
or
352+
node.(TypeExpr).isNull()
353+
or
354+
node.(TypeExpr).isUndefined()
355+
or
356+
node.(TypeExpr).isVoid()
357+
or
358+
node.(TypeExpr).isNever()
359+
or
360+
node instanceof LiteralTypeExpr
361+
or
362+
node = any(EnumMember m).getIdentifier() // enum members are constant
363+
or
364+
node instanceof EnumDeclaration // enums are unions of constants
365+
}
366+
367+
/**
368+
* Holds if `node` refers to a type that is considered untaintable (if actually enforced at runtime).
369+
*
370+
* Specifically, the types `number`, `boolean`, `null`, `undefined`, `void`, `never`, as well as literal types (`"foo"`)
371+
* and enums and enum members have this property.
372+
*/
373+
predicate isSanitizingPrimitiveType =
374+
TrackMustProp<isSanitizingPrimitiveTypeBase/1>::hasProperty/1;
375+
}

0 commit comments

Comments
 (0)