Skip to content

Commit e2b0c60

Browse files
authored
Merge pull request #4449 from max-schaefer/js/api-graphs-type-handling-improvements
Approved by erik-krogh
2 parents 3b7cf7f + 9ac70e3 commit e2b0c60

File tree

6 files changed

+86
-76
lines changed

6 files changed

+86
-76
lines changed

javascript/ql/src/semmle/javascript/ApiGraphs.qll

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ module API {
286286
/** Gets a node corresponding to an export of module `m`. */
287287
Node moduleExport(string m) { result = Impl::MkModuleDef(m).(Node).getMember("exports") }
288288

289+
/** Provides helper predicates for accessing API-graph nodes. */
290+
module Node {
291+
/** Gets a node whose type has the given qualified name. */
292+
Node ofType(string moduleName, string exportedName) {
293+
exists(TypeName tn |
294+
tn.hasQualifiedName(moduleName, exportedName) and
295+
result = Impl::MkCanonicalNameUse(tn).(Node).getInstance()
296+
)
297+
}
298+
}
299+
289300
/**
290301
* An API entry point.
291302
*
@@ -301,9 +312,6 @@ module API {
301312

302313
/** Gets a data-flow node that defines this entry point. */
303314
abstract DataFlow::Node getARhs();
304-
305-
/** Gets an API-graph node for this entry point. */
306-
API::Node getNode() { result = root().getASuccessor(this) }
307315
}
308316

309317
/**
@@ -346,16 +354,42 @@ module API {
346354
exists(SSA::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()]))
347355
)
348356
)
357+
or
358+
m = any(CanonicalName n | isDefined(n)).getExternalModuleName()
359+
} or
360+
MkModuleImport(string m) {
361+
imports(_, m)
362+
or
363+
m = any(CanonicalName n | isUsed(n)).getExternalModuleName()
349364
} or
350-
MkModuleImport(string m) { imports(_, m) } or
351365
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
352366
MkAsyncFuncResult(DataFlow::FunctionNode f) {
353367
f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f)
354368
} or
355369
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
356370
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
357-
MkCanonicalNameDef(CanonicalName n) { isDefined(n) } or
358-
MkCanonicalNameUse(CanonicalName n) { isUsed(n) }
371+
/**
372+
* A TypeScript canonical name that is defined somewhere, and that isn't a module root.
373+
* (Module roots are represented by `MkModuleExport` nodes instead.)
374+
*
375+
* For most purposes, you probably want to use the `mkCanonicalNameDef` predicate instead of
376+
* this constructor.
377+
*/
378+
MkCanonicalNameDef(CanonicalName n) {
379+
not n.isRoot() and
380+
isDefined(n)
381+
} or
382+
/**
383+
* A TypeScript canonical name that is used somewhere, and that isn't a module root.
384+
* (Module roots are represented by `MkModuleImport` nodes instead.)
385+
*
386+
* For most purposes, you probably want to use the `mkCanonicalNameUse` predicate instead of
387+
* this constructor.
388+
*/
389+
MkCanonicalNameUse(CanonicalName n) {
390+
not n.isRoot() and
391+
isUsed(n)
392+
}
359393

360394
class TDef = MkModuleDef or TNonModuleDef;
361395

@@ -399,6 +433,20 @@ module API {
399433
)
400434
}
401435

436+
/** An API-graph node representing definitions of the canonical name `cn`. */
437+
private TApiNode mkCanonicalNameDef(CanonicalName cn) {
438+
if cn.isModuleRoot()
439+
then result = MkModuleExport(cn.getExternalModuleName())
440+
else result = MkCanonicalNameDef(cn)
441+
}
442+
443+
/** An API-graph node representing uses of the canonical name `cn`. */
444+
private TApiNode mkCanonicalNameUse(CanonicalName cn) {
445+
if cn.isModuleRoot()
446+
then result = MkModuleImport(cn.getExternalModuleName())
447+
else result = MkCanonicalNameUse(cn)
448+
}
449+
402450
/**
403451
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
404452
* incoming edge from `base` labeled `lbl` in the API graph.
@@ -713,20 +761,11 @@ module API {
713761
succ = MkClassInstance(trackDefNode(def))
714762
)
715763
or
716-
exists(CanonicalName cn |
717-
pred = MkRoot() and
718-
lbl = Label::mod(cn.getExternalModuleName())
719-
|
720-
succ = MkCanonicalNameUse(cn) or
721-
succ = MkCanonicalNameDef(cn)
722-
)
723-
or
724-
exists(CanonicalName cn1, CanonicalName cn2 |
725-
cn2 = cn1.getAChild() and
726-
lbl = Label::member(cn2.getName())
727-
|
728-
(pred = MkCanonicalNameDef(cn1) or pred = MkCanonicalNameUse(cn1)) and
729-
(succ = MkCanonicalNameDef(cn2) or succ = MkCanonicalNameUse(cn2))
764+
exists(CanonicalName cn1, string n, CanonicalName cn2 |
765+
pred in [mkCanonicalNameDef(cn1), mkCanonicalNameUse(cn1)] and
766+
cn2 = cn1.getChild(n) and
767+
lbl = Label::member(n) and
768+
succ in [mkCanonicalNameDef(cn2), mkCanonicalNameUse(cn2)]
730769
)
731770
or
732771
exists(DataFlow::Node nd, DataFlow::FunctionNode f |

javascript/ql/src/semmle/javascript/CanonicalNames.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ class CanonicalName extends @symbol {
2525
CanonicalName getParent() { symbol_parent(this, result) }
2626

2727
/**
28-
* Gets a child of this canonical name, i.e. an extension of its qualified name.
28+
* Gets a child of this canonical name, that is, an extension of its qualified name.
2929
*/
3030
CanonicalName getAChild() { result.getParent() = this }
3131

32+
/**
33+
* Gets the child of this canonical name that has the given `name`, if any.
34+
*/
35+
CanonicalName getChild(string name) {
36+
result = getAChild() and
37+
result.getName() = name
38+
}
39+
3240
/**
3341
* Gets the name without prefix.
3442
*/

javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,6 @@ private module MongoDB {
4848
not result.getAnImmediateUse().(DataFlow::ParameterNode).getName() = "client" // mongodb v3 provides a `Mongoclient` here
4949
}
5050

51-
/**
52-
* A collection based on the type `mongodb.Collection`.
53-
*
54-
* Note that this also covers `mongoose` models since they are subtypes
55-
* of `mongodb.Collection`.
56-
*/
57-
private class TypedMongoCollection extends API::EntryPoint {
58-
TypedMongoCollection() { this = "TypedMongoCollection" }
59-
60-
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongodb", "Collection") }
61-
62-
override DataFlow::Node getARhs() { none() }
63-
}
64-
6551
/** Gets a data flow node referring to a MongoDB collection. */
6652
private API::Node getACollection() {
6753
// A collection resulting from calling `Db.collection(...)`.
@@ -71,7 +57,8 @@ private module MongoDB {
7157
result = collection.getParameter(1).getParameter(0)
7258
)
7359
or
74-
result = any(TypedMongoCollection c).getNode()
60+
// note that this also covers `mongoose` models since they are subtypes of `mongodb.Collection`
61+
result = API::Node::ofType("mongodb", "Collection")
7562
}
7663

7764
/** A call to a MongoDB query method. */
@@ -225,17 +212,6 @@ private module Mongoose {
225212
}
226213
}
227214

228-
/**
229-
* A Mongoose collection based on the type `mongoose.Model`.
230-
*/
231-
private class TypedMongooseModel extends API::EntryPoint {
232-
TypedMongooseModel() { this = "TypedMongooseModel" }
233-
234-
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Model") }
235-
236-
override DataFlow::Node getARhs() { none() }
237-
}
238-
239215
/**
240216
* Gets a API-graph node referring to a Mongoose Model object.
241217
*/
@@ -247,7 +223,7 @@ private module Mongoose {
247223
result = conn.getMember("models").getAMember()
248224
)
249225
or
250-
result = any(TypedMongooseModel c).getNode()
226+
result = API::Node::ofType("mongoose", "Model")
251227
}
252228

253229
/**
@@ -341,24 +317,13 @@ private module Mongoose {
341317
override API::Node getQueryArgument() { result = this.getParameter(2) }
342318
}
343319

344-
/**
345-
* A Mongoose query.
346-
*/
347-
private class TypedMongooseQuery extends API::EntryPoint {
348-
TypedMongooseQuery() { this = "TypedMongooseQuery" }
349-
350-
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Query") }
351-
352-
override DataFlow::Node getARhs() { none() }
353-
}
354-
355320
/**
356321
* Gets a data flow node referring to a Mongoose query object.
357322
*/
358323
API::Node getAMongooseQuery() {
359324
result = any(MongooseFunction f).getQueryReturn()
360325
or
361-
result = any(TypedMongooseQuery c).getNode()
326+
result = API::Node::ofType("mongoose", "Query")
362327
or
363328
result =
364329
getAMongooseQuery()
@@ -560,23 +525,14 @@ private module Mongoose {
560525
}
561526
}
562527

563-
/**
564-
* A Mongoose document.
565-
*/
566-
private class TypedMongooseDocument extends API::EntryPoint {
567-
TypedMongooseDocument() { this = "TypedMongooseDocument" }
568-
569-
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Document") }
570-
571-
override DataFlow::Node getARhs() { none() }
572-
}
573-
574528
/**
575529
* Gets a data flow node referring to a Mongoose Document object.
576530
*/
577531
private API::Node getAMongooseDocument() {
578-
result instanceof RetrievedDocument or
579-
result = any(TypedMongooseDocument c).getNode() or
532+
result instanceof RetrievedDocument
533+
or
534+
result = API::Node::ofType("mongoose", "Document")
535+
or
580536
result =
581537
getAMongooseDocument()
582538
.getMember(any(string name | MethodSignatures::returnsDocument(name)))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| mongodb | Collection | index.ts:14:3:14:17 | getCollection() |
2+
| mongoose | Model | index.ts:22:3:22:20 | getMongooseModel() |
3+
| mongoose | Query | index.ts:23:3:23:20 | getMongooseQuery() |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from string mod, string tp
4+
select mod, tp, API::Node::ofType(mod, tp).getAnImmediateUse()

javascript/ql/test/ApiGraphs/typed/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ app.use(bodyParser.json());
1111

1212
app.post("/find", (req, res) => {
1313
let v = JSON.parse(req.body.x);
14-
getCollection().find({ id: v }); /* use (member find (instance (member Collection (module mongodb)))) */
14+
getCollection().find({ id: v }); /* use (member find (instance (member Collection (member exports (module mongodb))))) */
1515
});
1616

1717
import * as mongoose from "mongoose";
1818
declare function getMongooseModel(): mongoose.Model;
1919
declare function getMongooseQuery(): mongoose.Query;
2020
app.post("/find", (req, res) => {
2121
let v = JSON.parse(req.body.x);
22-
getMongooseModel().find({ id: v }); /* def (parameter 0 (member find (instance (member Model (module mongoose))))) */
23-
getMongooseQuery().find({ id: v }); /* def (parameter 0 (member find (instance (member Query (module mongoose))))) */
22+
getMongooseModel().find({ id: v }); /* def (parameter 0 (member find (instance (member Model (member exports (module mongoose)))))) */
23+
getMongooseQuery().find({ id: v }); /* def (parameter 0 (member find (instance (member Query (member exports (module mongoose)))))) */
2424
});

0 commit comments

Comments
 (0)