-
Notifications
You must be signed in to change notification settings - Fork 1.9k
JS: Add TypeTracker library and Firebase model #1035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
06b0851
JS: Add Firebase model
asger-semmle e0c06cb
JS: handle Query methods
asger-semmle f554f85
JS: handle 'firebase-admin' package
asger-semmle 49a746b
JS: handle Reference.transaction()
asger-semmle 0401b26
JS: handle CloudFunctions
asger-semmle ad592d7
JS: handle .after and .before
asger-semmle 42c0efd
JS: add test
asger-semmle 99cc09d
JS: use TypeBackTracker where appropriate
asger-semmle c0b58f6
JS: Capitalize Firebase in comments
asger-semmle 28a776a
JS: dataflow -> data flow
asger-semmle 9bbdf84
JS: missing qldoc
asger-semmle 7bfad8c
JS: trailing whitespace
asger-semmle 208bcd4
JS: Make type-tracking predicates private
asger-semmle 0eb9231
JS: Make use of TypeTracker::end()
asger-semmle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
296 changes: 296 additions & 0 deletions
296
javascript/ql/src/semmle/javascript/frameworks/Firebase.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,296 @@ | ||
| /** | ||
| * Provides classes and predicates for reasoning about code using the Firebase API. | ||
| */ | ||
| import javascript | ||
|
|
||
| module Firebase { | ||
| /** Gets a reference to the Firebase API object. */ | ||
| private DataFlow::SourceNode firebase(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| ( | ||
| result = DataFlow::moduleImport("firebase/app") | ||
| or | ||
| result = DataFlow::moduleImport("firebase-admin") | ||
| or | ||
| result = DataFlow::globalVarRef("firebase") | ||
| ) | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = firebase(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a reference to the `firebase/app` or `firebase-admin` API object. */ | ||
| DataFlow::SourceNode firebase() { | ||
| result = firebase(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a reference to a Firebase app created with `initializeApp`. */ | ||
| private DataFlow::SourceNode initApp(DataFlow::TypeTracker t) { | ||
| result = firebase().getAMethodCall("initializeApp") and t.start() | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = initApp(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a reference to a Firebase app, either the `firebase` object or an | ||
| * app created explicitly with `initializeApp()`. | ||
| */ | ||
| DataFlow::SourceNode app() { | ||
| result = firebase(DataFlow::TypeTracker::end()) or result = initApp(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| module Database { | ||
|
|
||
| /** Gets a reference to a Firebase database object, such as `firebase.database()`. */ | ||
| private DataFlow::SourceNode database(DataFlow::TypeTracker t) { | ||
| result = app().getAMethodCall("database") and t.start() | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = database(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a reference to a Firebase database object, such as `firebase.database()`. */ | ||
| DataFlow::SourceNode database() { | ||
| result = database(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */ | ||
| private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| ( | ||
| exists (string name | result = database().getAMethodCall(name) | | ||
| name = "ref" or | ||
| name = "refFromURL" | ||
| ) | ||
| or | ||
| exists (string name | result = ref().getAMethodCall(name) | | ||
| name = "push" or | ||
| name = "child" | ||
| ) | ||
| or | ||
| exists (string name | result = ref().getAPropertyRead(name) | | ||
| name = "parent" or | ||
| name = "root" | ||
| ) | ||
| or | ||
| result = snapshot().getAPropertyRead("ref") | ||
| ) | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = ref(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a node that refers to a `Reference` object, such as `firebase.database().ref()`. */ | ||
| DataFlow::SourceNode ref() { | ||
| result = ref(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a node that refers to a `Query` or `Reference` object. */ | ||
| private DataFlow::SourceNode query(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| ( | ||
| result = ref(t) // a Reference can be used as a Query | ||
| or | ||
| exists (string name | result = query().getAMethodCall(name) | | ||
| name = "endAt" or | ||
| name = "limitTo" + any(string s) or | ||
| name = "orderBy" + any(string s) or | ||
| name = "startAt" | ||
| ) | ||
| ) | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = query(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a node that refers to a `Query` or `Reference` object. */ | ||
| DataFlow::SourceNode query() { | ||
| result = query(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** | ||
| * A call of form `query.on(...)` or `query.once(...)`. | ||
| */ | ||
| class QueryListenCall extends DataFlow::MethodCallNode { | ||
| QueryListenCall() { | ||
| this = query().getAMethodCall() and | ||
| (getMethodName() = "on" or getMethodName() = "once") | ||
| } | ||
|
|
||
| /** | ||
| * Gets the argument in which the callback is passed. | ||
| */ | ||
| DataFlow::Node getCallbackNode() { | ||
| result = getArgument(1) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets a node that is passed as the callback to a `Reference.transaction` call. | ||
| */ | ||
| private DataFlow::SourceNode transactionCallback(DataFlow::TypeBackTracker t) { | ||
| t.start() and | ||
| result = ref().getAMethodCall("transaction").getArgument(0).getALocalSource() | ||
| or | ||
| exists (DataFlow::TypeBackTracker t2 | | ||
| result = transactionCallback(t2).backtrack(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a node that is passed as the callback to a `Reference.transaction` call. | ||
| */ | ||
| DataFlow::SourceNode transactionCallback() { | ||
| result = transactionCallback(DataFlow::TypeBackTracker::end()) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Provides predicates for reasoning about the the Firebase Cloud Functions API, | ||
| * sometimes referred to just as just "Firebase Functions". | ||
| */ | ||
| module CloudFunctions { | ||
| /** Gets a reference to the Cloud Functions namespace. */ | ||
| private DataFlow::SourceNode namespace(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| result = DataFlow::moduleImport("firebase-functions") | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = namespace(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a reference to the Cloud Functions namespace. */ | ||
| DataFlow::SourceNode namespace() { | ||
| result = namespace(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a reference to a Cloud Functions database object. */ | ||
| private DataFlow::SourceNode database(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| result = namespace().getAPropertyRead("database") | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = database(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a reference to a Cloud Functions database object. */ | ||
| DataFlow::SourceNode database() { | ||
| result = database(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a data flow node holding a `RefBuilder` object. */ | ||
| private DataFlow::SourceNode refBuilder(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| result = database().getAMethodCall("ref") | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = refBuilder(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a data flow node holding a `RefBuilder` object. */ | ||
| DataFlow::SourceNode ref() { | ||
| result = refBuilder(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** Gets a call that registers a listener on a `RefBuilder`, such as `ref.onCreate(...)`. */ | ||
| class RefBuilderListenCall extends DataFlow::MethodCallNode { | ||
| RefBuilderListenCall() { | ||
| this = ref().getAMethodCall() and | ||
| getMethodName() = "on" + any(string s) | ||
| } | ||
|
|
||
| /** | ||
| * Gets the data flow node holding the listener callback. | ||
| */ | ||
| DataFlow::Node getCallbackNode() { | ||
| result = getArgument(0) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets a value that will be invoked with a `DataSnapshot` value as its first parameter. | ||
| */ | ||
| private DataFlow::SourceNode snapshotCallback(DataFlow::TypeBackTracker t) { | ||
| t.start() and | ||
| ( | ||
| result = any(Database::QueryListenCall call).getCallbackNode().getALocalSource() | ||
| or | ||
| result = any(CloudFunctions::RefBuilderListenCall call).getCallbackNode().getALocalSource() | ||
| ) | ||
| or | ||
| exists (DataFlow::TypeBackTracker t2 | | ||
| result = snapshotCallback(t2).backtrack(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a value that will be invoked with a `DataSnapshot` value as its first parameter. | ||
| */ | ||
| DataFlow::SourceNode snapshotCallback() { | ||
| result = snapshotCallback(DataFlow::TypeBackTracker::end()) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a node that refers to a `DataSnapshot` value or a promise or `Change` | ||
| * object containing `DataSnapshot`s. | ||
| */ | ||
| private DataFlow::SourceNode snapshot(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| ( | ||
| result = snapshotCallback().(DataFlow::FunctionNode).getParameter(0) | ||
| or | ||
| result instanceof Database::QueryListenCall // returns promise | ||
| or | ||
| result = snapshot().getAMethodCall("child") | ||
| or | ||
| result = snapshot().getAMethodCall("forEach").getCallback(0).getParameter(0) | ||
| or | ||
| exists (string prop | result = snapshot().getAPropertyRead(prop) | | ||
| prop = "before" or // only defined on Change objects | ||
| prop = "after" | ||
| ) | ||
| ) | ||
| or | ||
| promiseTaintStep(snapshot(t), result) | ||
| or | ||
| exists (DataFlow::TypeTracker t2 | | ||
| result = snapshot(t2).track(t2, t) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a node that refers to a `DataSnapshot` value, such as `x` in | ||
| * `firebase.database().ref().on('value', x => {...})`. | ||
| */ | ||
| DataFlow::SourceNode snapshot() { | ||
| result = snapshot(DataFlow::TypeTracker::end()) | ||
| } | ||
|
|
||
| /** | ||
| * A reference to a value obtained from a Firebase database. | ||
| */ | ||
| class FirebaseVal extends RemoteFlowSource { | ||
| FirebaseVal() { | ||
| exists (string name | this = snapshot().getAMethodCall(name) | | ||
| name = "val" or | ||
| name = "exportVal" | ||
| ) | ||
| or | ||
| this = Database::transactionCallback().(DataFlow::FunctionNode).getParameter(0) | ||
| } | ||
|
|
||
| override string getSourceType() { | ||
| result = "Firebase database" | ||
| } | ||
| } | ||
| } | ||
17 changes: 17 additions & 0 deletions
17
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseRef.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| | tst.js:5:1:5:22 | fb.data ... ef('x') | | ||
| | tst.js:7:3:7:7 | x.ref | | ||
| | tst.js:7:3:7:14 | x.ref.parent | | ||
| | tst.js:10:1:10:25 | admin.d ... ef('x') | | ||
| | tst.js:12:3:12:7 | x.ref | | ||
| | tst.js:12:3:12:14 | x.ref.parent | | ||
| | tst.js:17:3:17:7 | x.ref | | ||
| | tst.js:17:3:17:14 | x.ref.parent | | ||
| | tst.js:23:3:23:7 | x.ref | | ||
| | tst.js:23:3:23:14 | x.ref.parent | | ||
| | tst.js:32:12:32:42 | this.fi ... .ref(x) | | ||
| | tst.js:46:12:46:42 | this.fi ... .ref(x) | | ||
| | tst.js:50:12:50:25 | this.getRef(x) | | ||
| | tst.js:50:12:50:34 | this.ge ... hild(x) | | ||
| | tst.js:54:5:54:37 | this.fi ... ef('x') | | ||
| | tst.js:58:1:58:61 | new Fir ... /news') | | ||
| | tst.js:59:1:59:38 | new Fir ... /news') | |
3 changes: 3 additions & 0 deletions
3
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseRef.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import javascript | ||
|
|
||
| select Firebase::Database::ref() |
10 changes: 10 additions & 0 deletions
10
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseSnapshot.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| | tst.js:5:1:8:2 | fb.data ... ent;\\n}) | | ||
| | tst.js:5:38:5:38 | x | | ||
| | tst.js:10:1:13:2 | admin.d ... ent;\\n}) | | ||
| | tst.js:10:41:10:41 | x | | ||
| | tst.js:15:38:15:38 | x | | ||
| | tst.js:20:38:20:38 | x | | ||
| | tst.js:21:3:21:10 | x.before | | ||
| | tst.js:22:3:22:9 | x.after | | ||
| | tst.js:50:12:50:48 | this.ge ... value') | | ||
| | tst.js:60:1:60:39 | new Fir ... em('x') | |
3 changes: 3 additions & 0 deletions
3
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseSnapshot.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import javascript | ||
|
|
||
| select Firebase::snapshot() |
6 changes: 6 additions & 0 deletions
6
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseVal.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| | tst.js:6:3:6:9 | x.val() | | ||
| | tst.js:11:3:11:9 | x.val() | | ||
| | tst.js:16:3:16:9 | x.val() | | ||
| | tst.js:21:3:21:16 | x.before.val() | | ||
| | tst.js:22:3:22:15 | x.after.val() | | ||
| | tst.js:61:36:61:36 | x | |
4 changes: 4 additions & 0 deletions
4
javascript/ql/test/library-tests/frameworks/Firebase/FirebaseVal.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import javascript | ||
|
|
||
| from Firebase::FirebaseVal val | ||
| select val |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
qldoc