mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Dynamic/JS: Add support for re-exporting type models
This commit is contained in:
@@ -72,6 +72,13 @@ signature module ModelExportSig {
|
||||
* Holds if a named must be generated for `node` if it is to be included in the exported graph.
|
||||
*/
|
||||
default predicate mustBeNamed(API::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the exported model should preserve all paths leading to an instance of `type`,
|
||||
* including partial ones. It does not need to be closed transitively, `ModelExport` will
|
||||
* extend this to include type models from which `type` can be derived.
|
||||
*/
|
||||
default predicate shouldContainType(string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +86,7 @@ signature module ModelExportSig {
|
||||
*/
|
||||
module ModelExport<ModelExportSig S> {
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import internal.ApiGraphModelsExport
|
||||
|
||||
private module GraphExportConfig implements GraphExportSig<API::Node> {
|
||||
predicate edge = Specific::apiGraphHasEdge/3;
|
||||
@@ -147,7 +155,7 @@ module ModelExport<ModelExportSig S> {
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = GraphExport<API::Node, GraphExportConfig>;
|
||||
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
|
||||
|
||||
import ExportedGraph
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ private predicate summaryModel(string type, string path, string input, string ou
|
||||
}
|
||||
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(string type1, string type2, string path) {
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = type1 and
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Contains an extension of `GraphExport` that relies on API graph specific functionality.
|
||||
*/
|
||||
|
||||
private import ApiGraphModels as Shared
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import ApiGraphModelsSpecific as Specific
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private import Shared
|
||||
|
||||
/**
|
||||
* Holds if some proper prefix of `(type, path)` evaluated to `node`, where `remainingPath`
|
||||
* is bound to the suffix of `path` that was not evaluated yet.
|
||||
*/
|
||||
bindingset[type, path]
|
||||
predicate partiallyEvaluatedModel(string type, string path, API::Node node, string remainingPath) {
|
||||
exists(int n, AccessPath accessPath |
|
||||
accessPath = path and
|
||||
getNodeFromPath(type, accessPath, n) = node and
|
||||
n > 0 and
|
||||
// Note that `n < accessPath.getNumToken()` is implied by the use of strictconcat()
|
||||
remainingPath =
|
||||
strictconcat(int k |
|
||||
k = [n .. accessPath.getNumToken() - 1]
|
||||
|
|
||||
accessPath.getToken(k), "." order by k
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` and all types leading to `type` should be re-exported.
|
||||
*/
|
||||
signature predicate shouldContainTypeSig(string type);
|
||||
|
||||
/**
|
||||
* Wrapper around `GraphExport` that also exports information about re-exported types.
|
||||
*
|
||||
* ### JavaScript example 1
|
||||
* For example, suppose `shouldContainType("foo")` holds, and the following is the entry point for a package `bar`:
|
||||
* ```js
|
||||
* // bar.js
|
||||
* module.exports.xxx = require('foo');
|
||||
* ```
|
||||
* then this would generate the following type model:
|
||||
* ```
|
||||
* foo; bar; Member[xxx]
|
||||
* ```
|
||||
*
|
||||
* ### JavaScript example 2
|
||||
* For a more complex case, suppose the following type model exists:
|
||||
* ```
|
||||
* foo.XYZ; foo; Member[x].Member[y].Member[z]
|
||||
* ```
|
||||
* And the package exports something that matches a prefix of the access path above:
|
||||
* ```js
|
||||
* module.exports.blah = require('foo').x.y;
|
||||
* ```
|
||||
* This would result in the following type model:
|
||||
* ```
|
||||
* foo.XYZ; bar; Member[blah].Member[z]
|
||||
* ```
|
||||
* Notice that the access path `Member[blah].Member[z]` consists of an access path generated from the API
|
||||
* graph, with pieces of the access path from the original type model appended to it.
|
||||
*/
|
||||
module TypeGraphExport<GraphExportSig<API::Node> S, shouldContainTypeSig/1 shouldContainType> {
|
||||
/** Like `shouldContainType` but includes types that lead to `type` via type models. */
|
||||
private predicate shouldContainTypeEx(string type) {
|
||||
shouldContainType(type)
|
||||
or
|
||||
exists(string prevType |
|
||||
shouldContainType(prevType) and
|
||||
Shared::typeModel(prevType, type, _)
|
||||
)
|
||||
}
|
||||
|
||||
private module Config implements GraphExportSig<API::Node> {
|
||||
import S
|
||||
|
||||
predicate shouldContain(API::Node node) {
|
||||
S::shouldContain(node)
|
||||
or
|
||||
exists(string type1 | shouldContainTypeEx(type1) |
|
||||
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink()
|
||||
or
|
||||
exists(string type2, string path |
|
||||
Shared::typeModel(type1, type2, path) and
|
||||
getNodeFromPath(type2, path, _).getAValueReachableFromSource() = node.asSink()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = GraphExport<API::Node, Config>;
|
||||
|
||||
import ExportedGraph
|
||||
|
||||
/**
|
||||
* Holds if `type1, type2, path` should be emitted as a type model, that is `(type2, path)` leads to an instance of `type1`.
|
||||
*/
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
ExportedGraph::typeModel(type1, type2, path)
|
||||
or
|
||||
shouldContainTypeEx(type1) and
|
||||
exists(API::Node node |
|
||||
// A relevant type is exported directly
|
||||
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink() and
|
||||
ExportedGraph::pathToNode(type2, path, node)
|
||||
or
|
||||
// Something that leads to a relevant type, but didn't finish its access path, is exported
|
||||
exists(string midType, string midPath, string remainingPath, string prefix, API::Node source |
|
||||
Shared::typeModel(type1, midType, midPath) and
|
||||
partiallyEvaluatedModel(midType, midPath, source, remainingPath) and
|
||||
source.getAValueReachableFromSource() = node.asSink() and
|
||||
ExportedGraph::pathToNode(type2, prefix, node) and
|
||||
path = join(prefix, remainingPath)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
typeModel
|
||||
| (reexport).func | reexport | Member[func] |
|
||||
| upstream-lib | (reexport).func | ReturnValue |
|
||||
| upstream-lib | reexport | Member[lib] |
|
||||
| upstream-lib.XYZ | reexport | Member[x].Member[y].Member[z] |
|
||||
| upstream-lib.XYZ | reexport | Member[xy].Member[z] |
|
||||
summaryModel
|
||||
|
||||
@@ -8,6 +8,8 @@ module ModelExportConfig implements ModelExportSig {
|
||||
}
|
||||
|
||||
predicate mustBeNamed(API::Node node) { shouldContain(node) }
|
||||
|
||||
predicate shouldContainType(string type) { Shared::isRelevantType(type) }
|
||||
}
|
||||
|
||||
module Exported = ModelExport<ModelExportConfig>;
|
||||
|
||||
Reference in New Issue
Block a user