Skip to content

Commit 3cd2f91

Browse files
committed
JS: Support MaD targeting specific files
For codebase-specific models it's useful to be able to write models for specific files, without an NPM package boundary around it. But previously it was only possible to use NPM package exports as the starting point of a model. This adds the type `file:<path>` which uses imports of the given file as the starting point, exactly as it if had been importing aname NPM package.
1 parent 7bb0034 commit 3cd2f91

5 files changed

Lines changed: 57 additions & 0 deletions

File tree

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ predicate parseTypeString(string rawType, string package, string qualifiedName)
5353
qualifiedName = ""
5454
}
5555

56+
/** If `type` has the form `file:<path>` gets the path to the file. */
57+
bindingset[type]
58+
overlay[caller]
59+
private string getRawFilePathFromTypeName(string type) {
60+
result = type.regexpCapture("file:(.*)", 1)
61+
}
62+
5663
/**
5764
* Holds if models describing `package` may be relevant for the analysis of this database.
5865
*/
@@ -76,6 +83,8 @@ predicate isTypeUsed(string type) {
7683
parseTypeString(type, package, _) and
7784
isPackageUsed(package)
7885
)
86+
or
87+
exists(getRawFilePathFromTypeName(type)) // No need to prune repository-specific models
7988
}
8089

8190
/**
@@ -126,6 +135,41 @@ private API::Node getGlobalNode(string globalName) {
126135
result = any(GlobalApiEntryPoint e | e.getGlobal() = globalName).getANode()
127136
}
128137

138+
/** Holds if `type` is used as a type string in a model, and has the form `file:<filePath>` */
139+
overlay[local]
140+
private predicate relevantRawFilePath(string type, string filePath) {
141+
isRelevantType(type) and
142+
filePath = getRawFilePathFromTypeName(type)
143+
}
144+
145+
/** An API graph entry point for package specifiers of form `file:<path>`. */
146+
overlay[local?]
147+
private class RawFilePathEntryPoint extends API::EntryPoint {
148+
string path;
149+
150+
RawFilePathEntryPoint() {
151+
relevantRawFilePath(_, path) and
152+
this = "RawFilePathEntryPoint:" + path
153+
}
154+
155+
override DataFlow::SourceNode getASource() {
156+
exists(JS::Import imprt |
157+
imprt.getImportedFile().getRelativePath() = path and
158+
result = imprt.getImportedModuleNode()
159+
)
160+
}
161+
162+
/** Gets the name of the path variable. */
163+
string getPath() { result = path }
164+
}
165+
166+
/**
167+
* Gets an API node referring to the given global variable (if relevant).
168+
*/
169+
private API::Node getRawFilePathNode(string rawFilePathNode) {
170+
result = any(RawFilePathEntryPoint e | e.getPath() = rawFilePathNode).getANode()
171+
}
172+
129173
/** Gets a JavaScript-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */
130174
bindingset[type, path]
131175
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
@@ -150,6 +194,11 @@ API::Node getExtraNodeFromType(string type) {
150194
// Access instance of a type based on type annotations
151195
result = API::Internal::getANodeOfTypeRaw(package, qualifiedName)
152196
)
197+
or
198+
exists(string filePath |
199+
relevantRawFilePath(type, filePath) and
200+
result = getRawFilePathNode(filePath)
201+
)
153202
}
154203

155204
/**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as bar from './foo/bar/baz';
2+
3+
function t1() {
4+
sink(bar.customSource()); // NOT OK
5+
}

javascript/ql/test/library-tests/frameworks/data/test.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ taintFlow
55
| guardedRouteHandler.js:10:10:10:28 | res.injectedResData | guardedRouteHandler.js:10:10:10:28 | res.injectedResData |
66
| guardedRouteHandler.js:16:10:16:28 | req.injectedReqData | guardedRouteHandler.js:16:10:16:28 | req.injectedReqData |
77
| guardedRouteHandler.js:20:10:20:28 | res.injectedResData | guardedRouteHandler.js:20:10:20:28 | res.injectedResData |
8+
| importFileBasedModel.js:4:10:4:27 | bar.customSource() | importFileBasedModel.js:4:10:4:27 | bar.customSource() |
89
| paramDecorator.ts:6:54:6:54 | x | paramDecorator.ts:7:10:7:10 | x |
910
| test.js:5:30:5:37 | source() | test.js:5:8:5:38 | testlib ... urce()) |
1011
| test.js:6:22:6:29 | source() | test.js:6:8:6:30 | preserv ... urce()) |

javascript/ql/test/library-tests/frameworks/data/test.ext.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extensions:
1515
- ['danger-constant', 'Member[danger]', 'test-source']
1616
- ['testlib', 'Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[0].Member[injectedReqData]', 'test-source']
1717
- ['testlib', 'Member[middleware].ReturnValue.GuardedRouteHandler.Parameter[1].Member[injectedResData]', 'test-source']
18+
- ['file:foo/bar/baz.js', 'Member[customSource].ReturnValue', 'test-source']
1819

1920
- addsTo:
2021
pack: codeql/javascript-all

0 commit comments

Comments
 (0)