JS: More re-export logic to handle subclass export

This commit is contained in:
Asger F
2024-04-05 16:01:03 +02:00
parent f2ea88aa4c
commit 56ebe6c727
4 changed files with 28 additions and 4 deletions

View File

@@ -106,14 +106,14 @@ module TypeGraphExport<GraphExportSig<API::Node> S, shouldContainTypeSig/1 shoul
shouldContainTypeEx(type1) and
exists(API::Node node |
// A relevant type is exported directly
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink() and
Specific::sourceFlowsToSink(ModelOutput::getATypeNode(type1), node) 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
Specific::sourceFlowsToSink(source, node) and
ExportedGraph::pathToNode(type2, prefix, node) and
path = join(prefix, remainingPath)
)

View File

@@ -379,3 +379,28 @@ predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
path = ""
)
}
/**
* Holds if the value of `source` is exposed at `sink`.
*/
bindingset[source]
predicate sourceFlowsToSink(API::Node source, API::Node sink) {
source.getAValueReachableFromSource() = sink.asSink()
or
// Handle the case of an upstream class being the base class of an exposed own class
//
// class Foo extends external.BaseClass {}
//
// Here we want to ensure that `Instance(Foo)` is seen as subtype of `Instance(external.BaseClass)`.
//
// Although we have a dedicated sink node for `Instance(Foo)` we don't have dedicate source node for `Instance(external.BaseClass)`.
//
// However, there is always an `Instance` edge from the base class expression (`external.BaseClass`)
// to the receiver node in subclass constructor (the implicit constructor of `Foo`), which always exists.
// So we use the constructor receiver as the representative for `Instance(external.BaseClass)`.
// (This will get simplified when migrating to Ruby-style API graphs, as both sides will have explicit API nodes).
exists(DataFlow::ClassNode cls |
source.asSource() = cls.getConstructor().getReceiver() and
sink = API::Internal::getClassInstance(cls)
)
}

View File

@@ -50,6 +50,7 @@ typeModel
| (subclass).D.prototype.d | (subclass).D.prototype | Member[d] |
| upstream-lib | (reexport).func | ReturnValue |
| upstream-lib | reexport | Member[lib] |
| upstream-lib.Type | (subclass).D.prototype | |
| upstream-lib.XYZ | reexport | Member[x].Member[y].Member[z] |
| upstream-lib.XYZ | reexport | Member[xy].Member[z] |
summaryModel

View File

@@ -12,8 +12,6 @@ export class C extends B {
import * as upstream from "upstream-lib";
// TODO: needs to emit type model: [upstream.Type; (subclass).D.prototype; ""]
// The getAValueReachableFromSource() logic does not handle the base class -> instance step
export class D extends upstream.Type {
d() {}
}