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

Conversation

asgerf
Copy link
Contributor

@asgerf asgerf commented Jan 19, 2024

Adds a parameterised module GraphExport which converts an arbitrary graph labelled with access paths, into a set of typeModel rows. This module doesn't really know about API graphs, it just sees its input as a directed graph, which simplified the logic a lot IMO.

In ApiGraphModelsExport.qll we defined a wrapper around GraphExport which additionally re-exported typeModels from upstream packages (see example below). This is complicated enough that it's nice to factor it out, and it also know a bit more about API nodes and currently needs access to the other ApiGraphModels*.qll files.

Lastly, we expose a JS-specific module ModelExport which exports the paths leading to a specific set of API nodes. The idea is that each dynamic language eventually adds its own version of ModelExport which uses the above shared components under the hood. I've prototyped one for Ruby in this draft PR, but I'd like to move ahead with just JS for now.

Re-exporting type information

This also supports re-exporting information about types from upstream libraries.

This part is contained in the second commit, and to manage complexity, it is implemented as a wrapper module around GraphExport.

Below are a few examples to demonstrate what it means to re-export type information and some of the complexity involved.

JavaScript example 1

For example, if the following is the entry point for a package bar:

// bar.js
module.exports.xxx = require('foo');

then this would generate the following type model:

foo; bar; Member[xxx]

That is, we export the fact that require('bar').xxx is an alias for the foo package.

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:

module.exports.blah = require('foo').x.y;

This would result in the following type model:

foo.XYZ; bar; Member[blah].Member[z]

That is, we export the fact that require('bar').blah.z is an instance of foo.XYZ.

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.

@github-actions github-actions bot added the JS label Jan 19, 2024
@asgerf asgerf force-pushed the js/graph-export branch 2 times, most recently from 4751f68 to 2f8bbb0 Compare April 4, 2024 13:41
shared/mad/codeql/mad/dynamic/GraphExport.qll Dismissed Show dismissed Hide dismissed
@asgerf asgerf force-pushed the js/graph-export branch 2 times, most recently from 9042409 to d2a3093 Compare April 8, 2024 08:16
@asgerf asgerf force-pushed the js/graph-export branch from d2a3093 to 8210143 Compare April 9, 2024 12:34
@asgerf asgerf added the no-change-note-required This PR does not need a change note label Apr 9, 2024
@asgerf asgerf marked this pull request as ready for review April 9, 2024 13:29
@asgerf asgerf requested review from a team as code owners April 9, 2024 13:29
@asgerf asgerf requested a review from a team as a code owner April 9, 2024 13:29
erik-krogh
erik-krogh previously approved these changes Apr 10, 2024
Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 👍
I only have a few minor questions/comments.

Copy link
Member

@RasmusWL RasmusWL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this work LGTM 👍 There are some points I would like to understand better though (see inline discussions).

While reading through the commits I noticed that javascript/ql/test/library-tests/ModelGeneration/ModelGeneration.expected is nearing a level of complexity where I'm starting to think inline-expectation-tests would pay off

* Holds if the edge `pred -> succ` labelled with `path` exists in the API graph.
*/
bindingset[pred]
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have any guarantees (or tests) to show that the edge names we generate here are valid MaD access-paths?

Say we changed Member[xxx] to be Property[xxx] in the rest of our MaD modeling, would the whole type-model export just silently produce models that couldn't be parsed until a human realized there was a problem? 🤔

I can imagine scenarios where we try to parse the access-path generated as if it had been in a YAML file, maybe even checking that if we start from a node that we generate an MaD access-path for, we will reach the same node from parsing and following that access-path

(I realize it's not so much a question about this specific predicate, as a thing in whole... and the question might be answered from reading some of the next commits)

Copy link
Contributor Author

@asgerf asgerf Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No guarantees and no currently no tests. I spent a bit of time refactoring the ModelOutput::getAWarning into a parameterised module so we could check these as well, but it gets a bit fiddly. I backed out of it for now.

Some tests are going to exist in an internal repo where we use model generation, in the sense that those tests would fail if there was a mismatch here.

Copy link
Member

@RasmusWL RasmusWL Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice with these kinds of tests, however I'm not going to block this PR to get them in NOW.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

however I'm going to block this PR to get them in NOW.

I assume a "not" was accidentally left out of that sentence 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed 🙈 (fixed by edit)

* Holds if `name` is a good name for `node` that should be used in case the node needs
* to be named with a type name.
*
* Should not hold for nodes that are named via `exposedName`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like we could make some consistency check for this 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that would be more work 😱

shared/mad/codeql/mad/dynamic/GraphExport.qll Outdated Show resolved Hide resolved
shared/mad/codeql/mad/dynamic/GraphExport.qll Show resolved Hide resolved
Comment on lines +5 to +6
| (return-this).FluentInterface.prototype | (return-this).FluentInterface.prototype.bar | ReturnValue |
| (return-this).FluentInterface.prototype | (return-this).FluentInterface.prototype.baz | ReturnValue |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there not this row?

| (return-this).FluentInterface.prototype | (return-this).FluentInterface.prototype.foo | ReturnValue |

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, good question. The flow is captured by the summary model so it shouldn't be a problem, but it is a bit strange that it's generated for bar,baz but not foo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite know what to say around this. I think it would be nice if we could at least explain why it's happening, but I might be a little too cautious around this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it out, turns out this was due to a bug in API graphs. See 3c885f3.

Nice catch, btw!

erik-krogh
erik-krogh previously approved these changes Apr 12, 2024
Copy link
Member

@RasmusWL RasmusWL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few qldoc suggestions

shared/mad/codeql/mad/dynamic/GraphExport.qll Outdated Show resolved Hide resolved
}

/**
* Holds if a named type exists or will be generated for `node`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Holds if a named type exists or will be generated for `node`.
* Holds if a named type exists or will be generated for `node`, and `node` doesn't have a pretty name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Holds if a named type exists or will be generated for `node`.
* Holds if a synthetic name must be generated for `node`.

Actually the existing parts of the qldoc was wrong, let me just fix that.

asgerf added 2 commits April 17, 2024 13:31
This only worked when the RHS was a SourceNode, which is not generally the case
@asgerf
Copy link
Contributor Author

asgerf commented Apr 18, 2024

Thanks for the reviews everyone!

@asgerf asgerf merged commit decd576 into github:main Apr 18, 2024
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS no-change-note-required This PR does not need a change note Python Ruby
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants