Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c55e03c

Browse files
committed
Dynamic/JS: Add support for re-exporting type models
1 parent acef9b7 commit c55e03c

5 files changed

Lines changed: 138 additions & 2 deletions

File tree

javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,21 @@ signature module ModelExportSig {
7272
* Holds if a named must be generated for `node` if it is to be included in the exported graph.
7373
*/
7474
default predicate mustBeNamed(API::Node node) { none() }
75+
76+
/**
77+
* Holds if the exported model should preserve all paths leading to an instance of `type`,
78+
* including partial ones. It does not need to be closed transitively, `ModelExport` will
79+
* extend this to include type models from which `type` can be derived.
80+
*/
81+
default predicate shouldContainType(string type) { none() }
7582
}
7683

7784
/**
7885
* Module for exporting type models for a given set of nodes in the API graph.
7986
*/
8087
module ModelExport<ModelExportSig S> {
8188
private import codeql.mad.dynamic.GraphExport
89+
private import internal.ApiGraphModelsExport
8290

8391
private module GraphExportConfig implements GraphExportSig<API::Node> {
8492
predicate edge = Specific::apiGraphHasEdge/3;
@@ -147,7 +155,7 @@ module ModelExport<ModelExportSig S> {
147155
}
148156
}
149157

150-
private module ExportedGraph = GraphExport<API::Node, GraphExportConfig>;
158+
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
151159

152160
import ExportedGraph
153161
}

javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ private predicate summaryModel(string type, string path, string input, string ou
267267
}
268268

269269
/** Holds if a type model exists for the given parameters. */
270-
private predicate typeModel(string type1, string type2, string path) {
270+
predicate typeModel(string type1, string type2, string path) {
271271
exists(string row |
272272
typeModel(row) and
273273
row.splitAt(";", 0) = type1 and
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* Contains an extension of `GraphExport` that relies on API graph specific functionality.
3+
*/
4+
5+
private import ApiGraphModels as Shared
6+
private import codeql.mad.dynamic.GraphExport
7+
private import ApiGraphModelsSpecific as Specific
8+
9+
private module API = Specific::API;
10+
11+
private import Shared
12+
13+
/**
14+
* Holds if some proper prefix of `(type, path)` evaluated to `node`, where `remainingPath`
15+
* is bound to the suffix of `path` that was not evaluated yet.
16+
*/
17+
bindingset[type, path]
18+
predicate partiallyEvaluatedModel(string type, string path, API::Node node, string remainingPath) {
19+
exists(int n, AccessPath accessPath |
20+
accessPath = path and
21+
getNodeFromPath(type, accessPath, n) = node and
22+
n > 0 and
23+
// Note that `n < accessPath.getNumToken()` is implied by the use of strictconcat()
24+
remainingPath =
25+
strictconcat(int k |
26+
k = [n .. accessPath.getNumToken() - 1]
27+
|
28+
accessPath.getToken(k), "." order by k
29+
)
30+
)
31+
}
32+
33+
/**
34+
* Holds if `type` and all types leading to `type` should be re-exported.
35+
*/
36+
signature predicate shouldContainTypeSig(string type);
37+
38+
/**
39+
* Wrapper around `GraphExport` that also exports information about re-exported types.
40+
*
41+
* ### JavaScript example 1
42+
* For example, suppose `shouldContainType("foo")` holds, and the following is the entry point for a package `bar`:
43+
* ```js
44+
* // bar.js
45+
* module.exports.xxx = require('foo');
46+
* ```
47+
* then this would generate the following type model:
48+
* ```
49+
* foo; bar; Member[xxx]
50+
* ```
51+
*
52+
* ### JavaScript example 2
53+
* For a more complex case, suppose the following type model exists:
54+
* ```
55+
* foo.XYZ; foo; Member[x].Member[y].Member[z]
56+
* ```
57+
* And the package exports something that matches a prefix of the access path above:
58+
* ```js
59+
* module.exports.blah = require('foo').x.y;
60+
* ```
61+
* This would result in the following type model:
62+
* ```
63+
* foo.XYZ; bar; Member[blah].Member[z]
64+
* ```
65+
* Notice that the access path `Member[blah].Member[z]` consists of an access path generated from the API
66+
* graph, with pieces of the access path from the original type model appended to it.
67+
*/
68+
module TypeGraphExport<GraphExportSig<API::Node> S, shouldContainTypeSig/1 shouldContainType> {
69+
/** Like `shouldContainType` but includes types that lead to `type` via type models. */
70+
private predicate shouldContainTypeEx(string type) {
71+
shouldContainType(type)
72+
or
73+
exists(string prevType |
74+
shouldContainType(prevType) and
75+
Shared::typeModel(prevType, type, _)
76+
)
77+
}
78+
79+
private module Config implements GraphExportSig<API::Node> {
80+
import S
81+
82+
predicate shouldContain(API::Node node) {
83+
S::shouldContain(node)
84+
or
85+
exists(string type1 | shouldContainTypeEx(type1) |
86+
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink()
87+
or
88+
exists(string type2, string path |
89+
Shared::typeModel(type1, type2, path) and
90+
getNodeFromPath(type2, path, _).getAValueReachableFromSource() = node.asSink()
91+
)
92+
)
93+
}
94+
}
95+
96+
private module ExportedGraph = GraphExport<API::Node, Config>;
97+
98+
import ExportedGraph
99+
100+
/**
101+
* Holds if `type1, type2, path` should be emitted as a type model, that is `(type2, path)` leads to an instance of `type1`.
102+
*/
103+
predicate typeModel(string type1, string type2, string path) {
104+
ExportedGraph::typeModel(type1, type2, path)
105+
or
106+
shouldContainTypeEx(type1) and
107+
exists(API::Node node |
108+
// A relevant type is exported directly
109+
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink() and
110+
ExportedGraph::pathToNode(type2, path, node)
111+
or
112+
// Something that leads to a relevant type, but didn't finish its access path, is exported
113+
exists(string midType, string midPath, string remainingPath, string prefix, API::Node source |
114+
Shared::typeModel(type1, midType, midPath) and
115+
partiallyEvaluatedModel(midType, midPath, source, remainingPath) and
116+
source.getAValueReachableFromSource() = node.asSink() and
117+
ExportedGraph::pathToNode(type2, prefix, node) and
118+
path = join(prefix, remainingPath)
119+
)
120+
)
121+
}
122+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
typeModel
22
| (reexport).func | reexport | Member[func] |
3+
| upstream-lib | (reexport).func | ReturnValue |
4+
| upstream-lib | reexport | Member[lib] |
5+
| upstream-lib.XYZ | reexport | Member[x].Member[y].Member[z] |
6+
| upstream-lib.XYZ | reexport | Member[xy].Member[z] |
37
summaryModel

javascript/ql/test/library-tests/ModelGeneration/ModelGeneration.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module ModelExportConfig implements ModelExportSig {
88
}
99

1010
predicate mustBeNamed(API::Node node) { shouldContain(node) }
11+
12+
predicate shouldContainType(string type) { Shared::isRelevantType(type) }
1113
}
1214

1315
module Exported = ModelExport<ModelExportConfig>;

0 commit comments

Comments
 (0)