Skip to content
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

JS: Add library for exporting graphs as type models #15386

Merged
merged 27 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
acef9b7
Dynamic/JS: Add library for exporting models
asgerf Apr 4, 2024
c55e03c
Dynamic/JS: Add support for re-exporting type models
asgerf Apr 4, 2024
348c95e
JS: Add a test case with fluent flow
asgerf Apr 5, 2024
946f0b4
JS: Add test for class with aliases
asgerf Apr 5, 2024
f4e05cc
JS: Add tests with semi-internal class problem
asgerf Apr 5, 2024
ab3c03d
JS: Add test where root export object is a function
asgerf Apr 5, 2024
3022c59
JS: Add access path alias test
asgerf Apr 5, 2024
ef7767b
JS: Add partial test for subclassing
asgerf Apr 5, 2024
9313564
JS: Add subclassing test and fix lack of subclassing handling
asgerf Apr 5, 2024
f2ea88a
JS: Add test showing missing re-export of base class relationship
asgerf Apr 5, 2024
56ebe6c
JS: More re-export logic to handle subclass export
asgerf Apr 5, 2024
29a6145
JS: Add test case showing problem with chains going through internal …
asgerf Apr 5, 2024
81b96a8
JS: Ensure MkClassInstance exists for base classes
asgerf Apr 5, 2024
8cb80d6
JS: Switch from hasLocationInfo to Location
asgerf Apr 8, 2024
8210143
Dynamic: Add hasPrettyName()
asgerf Apr 8, 2024
f5355cf
Dynamic: Sync ApiGraphModels.qll
asgerf Apr 9, 2024
15eabb4
JS: Address review comments
asgerf Apr 12, 2024
330229c
Update javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsDa…
asgerf Apr 12, 2024
3949ae4
Update shared/mad/codeql/mad/dynamic/GraphExport.qll
asgerf Apr 12, 2024
844b29b
Update shared/mad/codeql/mad/dynamic/GraphExport.qll
asgerf Apr 16, 2024
ee5cb6f
Update shared/mad/codeql/mad/dynamic/GraphExport.qll
asgerf Apr 16, 2024
be64daf
Merge branch 'main' into js/graph-export
asgerf Apr 16, 2024
c0db40d
Merge branch 'js/graph-export' of github.com:asgerf/codeql into js/gr…
asgerf Apr 16, 2024
3335d48
Sync files
asgerf Apr 16, 2024
93a9c62
Merge branch 'main' into js/graph-export
asgerf Apr 17, 2024
5e7026c
JS: Use AccessPath as parameter type
asgerf Apr 17, 2024
3c885f3
JS: Fix bug in MkClassInstance use-nodes
asgerf Apr 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,21 @@ signature module ModelExportSig {
* Holds if a named must be generated for `node` if it is to be included in the exported graph.
asgerf marked this conversation as resolved.
Show resolved Hide resolved
*/
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() }
}

/**
* Module for exporting type models for a given set of nodes in the API graph.
*/
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;
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/
asgerf marked this conversation as resolved.
Show resolved Hide resolved
bindingset[type, path]
predicate partiallyEvaluatedModel(string type, string path, API::Node node, string remainingPath) {
asgerf marked this conversation as resolved.
Show resolved Hide resolved
exists(int n, AccessPath accessPath |
accessPath = path and
asgerf marked this conversation as resolved.
Show resolved Hide resolved
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)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
typeModel
| (reexport).func | reexport | Member[func] |
asgerf marked this conversation as resolved.
Show resolved Hide resolved
| 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
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down